mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-20 15:41:40 -04:00
Refactor tests (#541)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { IdentityGeneratorEn } from '../IdentityGeneratorEn';
|
||||
import { IdentityGeneratorNl } from '../IdentityGeneratorNl';
|
||||
import { IdentityGeneratorEn } from '../implementations/IdentityGeneratorEn';
|
||||
import { IdentityGeneratorNl } from '../implementations/IdentityGeneratorNl';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { IIdentityGenerator } from '../../types/IIdentityGenerator';
|
||||
import { IIdentityGenerator } from '../interfaces/IIdentityGenerator';
|
||||
|
||||
// Test factory function to run tests for each language implementation
|
||||
const testIdentityGenerator = (
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UsernameEmailGenerator } from '../../UsernameEmailGenerator';
|
||||
import { Gender } from '../../types/Gender';
|
||||
import { IIdentityGenerator } from '../../types/IIdentityGenerator';
|
||||
import { IIdentityGenerator } from '../../interfaces/IIdentityGenerator';
|
||||
import { Identity } from '../../types/Identity';
|
||||
import * as fs from 'fs';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Identity } from "./Identity";
|
||||
import { Identity } from "../types/Identity";
|
||||
|
||||
export interface IIdentityGenerator {
|
||||
generateRandomIdentity(): Promise<Identity>;
|
||||
@@ -0,0 +1,102 @@
|
||||
export class PasswordGenerator {
|
||||
private readonly lowercaseChars = 'abcdefghijklmnopqrstuvwxyz';
|
||||
private readonly uppercaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
private readonly numberChars = '0123456789';
|
||||
private readonly specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?';
|
||||
|
||||
private length: number = 18;
|
||||
private useLowercase: boolean = true;
|
||||
private useUppercase: boolean = true;
|
||||
private useNumbers: boolean = true;
|
||||
private useSpecial: boolean = true;
|
||||
|
||||
public setLength(length: number): PasswordGenerator {
|
||||
this.length = length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public useLowercaseLetters(use: boolean): PasswordGenerator {
|
||||
this.useLowercase = use;
|
||||
return this;
|
||||
}
|
||||
|
||||
public useUppercaseLetters(use: boolean): PasswordGenerator {
|
||||
this.useUppercase = use;
|
||||
return this;
|
||||
}
|
||||
|
||||
public useNumericCharacters(use: boolean): PasswordGenerator {
|
||||
this.useNumbers = use;
|
||||
return this;
|
||||
}
|
||||
|
||||
public useSpecialCharacters(use: boolean): PasswordGenerator {
|
||||
this.useSpecial = use;
|
||||
return this;
|
||||
}
|
||||
|
||||
private getUnbiasedRandomIndex(max: number): number {
|
||||
// Calculate the largest multiple of max that fits within Uint32
|
||||
const limit = Math.floor((2 ** 32) / max) * max;
|
||||
|
||||
while (true) {
|
||||
const array = new Uint32Array(1);
|
||||
crypto.getRandomValues(array);
|
||||
const value = array[0];
|
||||
|
||||
// Reject values that would introduce bias
|
||||
if (value < limit) {
|
||||
return value % max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public generateRandomPassword(): string {
|
||||
let chars = '';
|
||||
let password = '';
|
||||
|
||||
// Build character set based on options
|
||||
if (this.useLowercase) chars += this.lowercaseChars;
|
||||
if (this.useUppercase) chars += this.uppercaseChars;
|
||||
if (this.useNumbers) chars += this.numberChars;
|
||||
if (this.useSpecial) chars += this.specialChars;
|
||||
|
||||
// Ensure at least one character set is selected
|
||||
if (chars.length === 0) {
|
||||
chars = this.lowercaseChars;
|
||||
}
|
||||
|
||||
// Generate password
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
password += chars[this.getUnbiasedRandomIndex(chars.length)];
|
||||
}
|
||||
|
||||
// Ensure password contains at least one character from each selected set
|
||||
if (this.useLowercase && !password.match(/[a-z]/)) {
|
||||
const pos = this.getUnbiasedRandomIndex(this.length);
|
||||
password = password.substring(0, pos) +
|
||||
this.lowercaseChars[this.getUnbiasedRandomIndex(this.lowercaseChars.length)] +
|
||||
password.substring(pos + 1);
|
||||
}
|
||||
if (this.useUppercase && !password.match(/[A-Z]/)) {
|
||||
const pos = this.getUnbiasedRandomIndex(this.length);
|
||||
password = password.substring(0, pos) +
|
||||
this.uppercaseChars[this.getUnbiasedRandomIndex(this.uppercaseChars.length)] +
|
||||
password.substring(pos + 1);
|
||||
}
|
||||
if (this.useNumbers && !password.match(/[0-9]/)) {
|
||||
const pos = this.getUnbiasedRandomIndex(this.length);
|
||||
password = password.substring(0, pos) +
|
||||
this.numberChars[this.getUnbiasedRandomIndex(this.numberChars.length)] +
|
||||
password.substring(pos + 1);
|
||||
}
|
||||
if (this.useSpecial && !password.match(/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/)) {
|
||||
const pos = this.getUnbiasedRandomIndex(this.length);
|
||||
password = password.substring(0, pos) +
|
||||
this.specialChars[this.getUnbiasedRandomIndex(this.specialChars.length)] +
|
||||
password.substring(pos + 1);
|
||||
}
|
||||
|
||||
return password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { PasswordGenerator } from '../PasswordGenerator';
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
|
||||
describe('PasswordGenerator', () => {
|
||||
let generator: PasswordGenerator;
|
||||
|
||||
beforeEach(() => {
|
||||
generator = new PasswordGenerator();
|
||||
});
|
||||
|
||||
it('generates password with default settings', () => {
|
||||
const password = generator.generateRandomPassword();
|
||||
|
||||
// Default length is 18
|
||||
expect(password.length).toBe(18);
|
||||
|
||||
// Should contain at least one of each character type by default
|
||||
expect(password).toMatch(/[a-z]/); // lowercase
|
||||
expect(password).toMatch(/[A-Z]/); // uppercase
|
||||
expect(password).toMatch(/[0-9]/); // numbers
|
||||
expect(password).toMatch(/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/); // special
|
||||
});
|
||||
|
||||
it('respects custom length setting', () => {
|
||||
const customLength = 24;
|
||||
const password = generator.setLength(customLength).generateRandomPassword();
|
||||
expect(password.length).toBe(customLength);
|
||||
});
|
||||
|
||||
it('respects lowercase setting', () => {
|
||||
const password = generator.useLowercaseLetters(false).generateRandomPassword();
|
||||
expect(password).not.toMatch(/[a-z]/);
|
||||
});
|
||||
|
||||
it('respects uppercase setting', () => {
|
||||
const password = generator.useUppercaseLetters(false).generateRandomPassword();
|
||||
expect(password).not.toMatch(/[A-Z]/);
|
||||
});
|
||||
|
||||
it('respects numbers setting', () => {
|
||||
const password = generator.useNumericCharacters(false).generateRandomPassword();
|
||||
expect(password).not.toMatch(/[0-9]/);
|
||||
});
|
||||
|
||||
it('respects special characters setting', () => {
|
||||
const password = generator.useSpecialCharacters(false).generateRandomPassword();
|
||||
expect(password).not.toMatch(/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/);
|
||||
});
|
||||
|
||||
it('generates different passwords on subsequent calls', () => {
|
||||
const password1 = generator.generateRandomPassword();
|
||||
const password2 = generator.generateRandomPassword();
|
||||
expect(password1).not.toBe(password2);
|
||||
});
|
||||
|
||||
it('handles minimum character requirements', () => {
|
||||
// Generate multiple passwords to ensure consistency
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const password = generator.generateRandomPassword();
|
||||
|
||||
// Each password should contain at least one character from each enabled set
|
||||
expect(password).toMatch(/[a-z]/);
|
||||
expect(password).toMatch(/[A-Z]/);
|
||||
expect(password).toMatch(/[0-9]/);
|
||||
expect(password).toMatch(/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/);
|
||||
}
|
||||
});
|
||||
|
||||
it('falls back to lowercase when all options disabled', () => {
|
||||
const password = generator
|
||||
.useLowercaseLetters(false)
|
||||
.useUppercaseLetters(false)
|
||||
.useNumericCharacters(false)
|
||||
.useSpecialCharacters(false)
|
||||
.generateRandomPassword();
|
||||
|
||||
// Should fall back to lowercase
|
||||
expect(password).toMatch(/^[a-z]+$/);
|
||||
});
|
||||
|
||||
it('maintains method chaining', () => {
|
||||
const result = generator
|
||||
.setLength(20)
|
||||
.useLowercaseLetters(true)
|
||||
.useUppercaseLetters(true)
|
||||
.useNumericCharacters(true)
|
||||
.useSpecialCharacters(true);
|
||||
|
||||
expect(result).toBe(generator);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user