added unit tests

This commit is contained in:
yousef-alkhrissat
2024-07-31 22:26:51 +03:00
parent f919b6696c
commit 26cba4d05b
8 changed files with 497 additions and 19 deletions

View File

@ -1,24 +1,15 @@
module.exports = {
moduleFileExtensions: [
"js",
"json",
"ts"
],
rootDir: ".",
testRegex: ".*\\.spec\\.ts$",
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: '.',
testRegex: '.*\\.spec\\.ts$',
transform: {
"^.+\\.(t|j)s$": "ts-jest"
'^.+\\.(t|j)s$': 'ts-jest',
},
collectCoverageFrom: [
"**/*.(t|j)s"
],
coverageDirectory: "./coverage",
testEnvironment: "node",
roots: [
"<rootDir>/src/",
"<rootDir>/libs/"
],
collectCoverageFrom: ['**/*.(t|j)s'],
coverageDirectory: './coverage',
testEnvironment: 'node',
roots: ['<rootDir>/src/', '<rootDir>/libs/'],
moduleNameMapper: {
"^@app/common(|/.*)$": "<rootDir>/libs/common/src/$1"
}
'^@app/common(|/.*)$': '<rootDir>/libs/common/src/$1',
},
};

View File

@ -0,0 +1,114 @@
import { SnakeNamingStrategy } from './snack-naming.strategy';
import { snakeCase } from 'typeorm/util/StringUtils';
describe('SnakeNamingStrategy', () => {
let strategy: SnakeNamingStrategy;
beforeEach(() => {
strategy = new SnakeNamingStrategy();
});
it('should be defined', () => {
expect(strategy).toBeDefined();
});
describe('tableName', () => {
it('should use customName if provided', () => {
const className = 'User';
const customName = 'users_table';
expect(strategy.tableName(className, customName)).toBe(customName);
});
it('should convert className to snake_case if customName is not provided', () => {
const className = 'User';
expect(strategy.tableName(className, '')).toBe(snakeCase(className));
});
});
describe('columnName', () => {
it('should use customName if provided', () => {
const propertyName = 'firstName';
const customName = 'first_name';
const embeddedPrefixes = ['user'];
expect(
strategy.columnName(propertyName, customName, embeddedPrefixes),
).toBe(snakeCase(embeddedPrefixes.join('_')) + customName);
});
it('should convert propertyName to snake_case with embeddedPrefixes if customName is not provided', () => {
const propertyName = 'firstName';
const embeddedPrefixes = ['user'];
expect(strategy.columnName(propertyName, '', embeddedPrefixes)).toBe(
snakeCase(embeddedPrefixes.join('_')) + snakeCase(propertyName),
);
});
});
describe('relationName', () => {
it('should convert propertyName to snake_case', () => {
const propertyName = 'profilePicture';
expect(strategy.relationName(propertyName)).toBe(snakeCase(propertyName));
});
});
describe('joinColumnName', () => {
it('should convert relationName and referencedColumnName to snake_case', () => {
const relationName = 'user';
const referencedColumnName = 'id';
expect(strategy.joinColumnName(relationName, referencedColumnName)).toBe(
snakeCase(`${relationName}_${referencedColumnName}`),
);
});
});
describe('joinTableName', () => {
it('should convert table names and property name to snake_case', () => {
const firstTableName = 'users';
const secondTableName = 'roles';
const firstPropertyName = 'userRoles';
expect(
strategy.joinTableName(
firstTableName,
secondTableName,
firstPropertyName,
),
).toBe(
snakeCase(
`${firstTableName}_${firstPropertyName.replaceAll(/\./gi, '_')}_${secondTableName}`,
),
);
});
});
describe('joinTableColumnName', () => {
it('should use columnName if provided', () => {
const tableName = 'user_roles';
const propertyName = 'user';
const columnName = 'role';
expect(
strategy.joinTableColumnName(tableName, propertyName, columnName),
).toBe(snakeCase(`${tableName}_${columnName}`));
});
it('should convert propertyName to snake_case if columnName is not provided', () => {
const tableName = 'user_roles';
const propertyName = 'role';
expect(strategy.joinTableColumnName(tableName, propertyName)).toBe(
snakeCase(`${tableName}_${propertyName}`),
);
});
});
describe('classTableInheritanceParentColumnName', () => {
it('should convert parentTableName and parentTableIdPropertyName to snake_case', () => {
const parentTableName = 'users';
const parentTableIdPropertyName = 'id';
expect(
strategy.classTableInheritanceParentColumnName(
parentTableName,
parentTableIdPropertyName,
),
).toBe(snakeCase(`${parentTableName}_${parentTableIdPropertyName}`));
});
});
});

View File

@ -0,0 +1,86 @@
import { convertKeysToCamelCase } from './camelCaseConverter';
describe('convertKeysToCamelCase', () => {
it('should return the same value if not an object or array', () => {
expect(convertKeysToCamelCase(null)).toBeNull();
expect(convertKeysToCamelCase(undefined)).toBeUndefined();
expect(convertKeysToCamelCase(123)).toBe(123);
expect(convertKeysToCamelCase('string')).toBe('string');
expect(convertKeysToCamelCase(true)).toBe(true);
expect(convertKeysToCamelCase(false)).toBe(false);
});
it('should convert object keys from snake_case to camelCase', () => {
const obj = {
first_name: 'John',
last_name: 'Doe',
address_details: {
street_name: 'Main St',
postal_code: '12345',
},
};
const expected = {
firstName: 'John',
lastName: 'Doe',
addressDetails: {
streetName: 'Main St',
postalCode: '12345',
},
};
expect(convertKeysToCamelCase(obj)).toEqual(expected);
});
it('should convert array of objects with snake_case keys to camelCase', () => {
const arr = [
{ first_name: 'Jane', last_name: 'Doe' },
{ first_name: 'John', last_name: 'Smith' },
];
const expected = [
{ firstName: 'Jane', lastName: 'Doe' },
{ firstName: 'John', lastName: 'Smith' },
];
expect(convertKeysToCamelCase(arr)).toEqual(expected);
});
it('should handle nested arrays and objects', () => {
const nestedObj = {
user_info: {
user_name: 'Alice',
contact_details: [
{ email_address: 'alice@example.com' },
{ phone_number: '123-456-7890' },
],
},
};
const expected = {
userInfo: {
userName: 'Alice',
contactDetails: [
{ emailAddress: 'alice@example.com' },
{ phoneNumber: '123-456-7890' },
],
},
};
expect(convertKeysToCamelCase(nestedObj)).toEqual(expected);
});
it('should handle objects with no snake_case keys', () => {
const obj = {
firstName: 'Alice',
lastName: 'Johnson',
};
const expected = {
firstName: 'Alice',
lastName: 'Johnson',
};
expect(convertKeysToCamelCase(obj)).toEqual(expected);
});
});

View File

@ -0,0 +1,99 @@
import { HelperHashService } from './helper.hash.service';
import { enc, SHA256 } from 'crypto-js';
describe('HelperHashService', () => {
let service: HelperHashService;
const secretKey = '12345678901234567890123456789012';
const iv = '1234567890123456';
const password = 'password123';
let salt: string;
let hashedPassword: string;
beforeEach(() => {
service = new HelperHashService();
salt = service.randomSalt(10);
hashedPassword = service.bcrypt(password, salt);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('randomSalt', () => {
it('should generate a salt of the specified length', () => {
expect(service.randomSalt(10)).toHaveLength(29);
});
});
describe('bcrypt', () => {
it('should hash the password with the given salt', () => {
expect(service.bcrypt(password, salt)).toBe(hashedPassword);
});
});
describe('bcryptCompare', () => {
it('should return true for correct password comparison', () => {
expect(service.bcryptCompare(password, hashedPassword)).toBe(true);
});
it('should return false for incorrect password comparison', () => {
expect(service.bcryptCompare('wrongpassword', hashedPassword)).toBe(
false,
);
});
});
describe('sha256', () => {
it('should hash a string using SHA256', () => {
const hash = SHA256(password).toString(enc.Hex);
expect(service.sha256(password)).toBe(hash);
});
});
describe('sha256Compare', () => {
it('should return true for identical SHA256 hashes', () => {
const hash = SHA256(password).toString(enc.Hex);
expect(service.sha256Compare(hash, hash)).toBe(true);
});
it('should return false for different SHA256 hashes', () => {
const hash1 = SHA256(password).toString(enc.Hex);
const hash2 = SHA256('anotherpassword').toString(enc.Hex);
expect(service.sha256Compare(hash1, hash2)).toBe(false);
});
});
describe('encryptPassword', () => {
it('should encrypt a password with the given secret key', () => {
const encrypted = service.encryptPassword(password, secretKey);
const decrypted = service.decryptPassword(encrypted, secretKey);
expect(decrypted).toBe('trx8g6gi');
});
});
describe('decryptPassword', () => {
it('should decrypt an encrypted password with the given secret key', () => {
const encrypted = service.encryptPassword(password, secretKey);
const decrypted = service.decryptPassword(encrypted, secretKey);
expect(decrypted).toBe('trx8g6gi');
});
});
describe('aes256Encrypt', () => {
it('should encrypt data with AES-256 and return the ciphertext', () => {
const data = { key: 'value' };
const encrypted = service.aes256Encrypt(data, secretKey, iv);
expect(encrypted).toBeDefined();
});
});
describe('aes256Decrypt', () => {
it('should decrypt data with AES-256 and return the plaintext', async () => {
const data = { key: 'value' };
const encrypted = service.aes256Encrypt(data, secretKey, iv);
const decrypted = service.aes256Decrypt(encrypted, secretKey, iv);
expect(decrypted).toBeDefined();
expect(() => JSON.parse(decrypted)).not.toThrow();
expect(JSON.parse(decrypted)).toEqual(data);
});
});
});

View File

@ -0,0 +1,54 @@
import { convertKeysToSnakeCase } from './snakeCaseConverter';
describe('convertKeysToSnakeCase', () => {
it('should convert single level object keys to snake case', () => {
const input = { camelCase: 'value', anotherKey: 'anotherValue' };
const expected = { camel_case: 'value', another_key: 'anotherValue' };
expect(convertKeysToSnakeCase(input)).toEqual(expected);
});
it('should convert nested object keys to snake case', () => {
const input = {
camelCaseKey: 'value',
nestedObject: {
nestedCamelCase: 'nestedValue',
arrayOfObjects: [
{ arrayCamelCase: 'arrayValue' },
{ anotherCamelCase: 'anotherValue' },
],
},
};
const expected = {
camel_case_key: 'value',
nested_object: {
nested_camel_case: 'nestedValue',
array_of_objects: [
{ array_camel_case: 'arrayValue' },
{ another_camel_case: 'anotherValue' },
],
},
};
expect(convertKeysToSnakeCase(input)).toEqual(expected);
});
it('should handle arrays of objects', () => {
const input = [{ camelCaseItem: 'value' }, { anotherItem: 'anotherValue' }];
const expected = [
{ camel_case_item: 'value' },
{ another_item: 'anotherValue' },
];
expect(convertKeysToSnakeCase(input)).toEqual(expected);
});
it('should handle empty objects and arrays', () => {
expect(convertKeysToSnakeCase({})).toEqual({});
expect(convertKeysToSnakeCase([])).toEqual([]);
});
it('should handle primitive values without modification', () => {
expect(convertKeysToSnakeCase('string')).toEqual('string');
expect(convertKeysToSnakeCase(123)).toEqual(123);
expect(convertKeysToSnakeCase(null)).toEqual(null);
expect(convertKeysToSnakeCase(undefined)).toEqual(undefined);
});
});

View File

@ -0,0 +1,118 @@
import {
Constructor,
Plain,
Optional,
Nullable,
Path,
PathValue,
KeyOfType,
} from './types';
interface TestInterface {
user: {
profile: {
name: string;
age: number;
};
settings: {
theme: string;
};
};
}
interface SampleEntity {
id: number;
name: string;
tags: string[];
}
class TestClass {
constructor(
public name: string,
public age: number,
) {}
}
describe('TypeScript Utility Types', () => {
it('should validate Constructor type', () => {
type ValidConstructorTest = Constructor<TestClass, [string, number]>;
const instance: ValidConstructorTest = TestClass;
expect(instance).toBeDefined();
});
it('should validate Plain type', () => {
type PlainNumberTest = Plain<number>;
type PlainStringTest = Plain<string>;
type PlainObjectTest = Plain<{ name: string; age: number }>;
const num: PlainNumberTest = 42;
const str: PlainStringTest = 'hello';
const obj: PlainObjectTest = { name: 'John', age: 30 };
expect(num).toBe(42);
expect(str).toBe('hello');
expect(obj).toEqual({ name: 'John', age: 30 });
});
it('should validate Optional type', () => {
type OptionalNumberTest = Optional<number>;
type OptionalObjectTest = Optional<{ name: string }>;
const num: OptionalNumberTest = undefined;
const obj: OptionalObjectTest = { name: 'Jane' };
const objUndefined: OptionalObjectTest = undefined;
expect(num).toBeUndefined();
expect(obj).toEqual({ name: 'Jane' });
expect(objUndefined).toBeUndefined();
});
it('should validate Nullable type', () => {
type NullableNumberTest = Nullable<number>;
type NullableObjectTest = Nullable<{ name: string }>;
const num: NullableNumberTest = null;
const obj: NullableObjectTest = { name: 'Jack' };
const objNull: NullableObjectTest = null;
expect(num).toBeNull();
expect(obj).toEqual({ name: 'Jack' });
expect(objNull).toBeNull();
});
it('should validate Path type', () => {
type PathTest = Path<TestInterface>;
const path1: PathTest = 'user.profile.name';
const path2: PathTest = 'user.settings.theme';
expect(path1).toBe('user.profile.name');
expect(path2).toBe('user.settings.theme');
});
it('should validate PathValue type', () => {
type NameTypeTest = PathValue<TestInterface, 'user.profile.name'>;
type AgeTypeTest = PathValue<TestInterface, 'user.profile.age'>;
type ThemeTypeTest = PathValue<TestInterface, 'user.settings.theme'>;
const name: NameTypeTest = 'Alice';
const age: AgeTypeTest = 25;
const theme: ThemeTypeTest = 'dark';
expect(name).toBe('Alice');
expect(age).toBe(25);
expect(theme).toBe('dark');
});
it('should validate KeyOfType type', () => {
type StringKeysTest = KeyOfType<SampleEntity, string>;
type NumberKeysTest = KeyOfType<SampleEntity, number>;
type ArrayKeysTest = KeyOfType<SampleEntity, string[]>;
const stringKey: StringKeysTest = 'name';
const numberKey: NumberKeysTest = 'id';
const arrayKey: ArrayKeysTest = 'tags';
expect(stringKey).toBe('name');
expect(numberKey).toBe('id');
expect(arrayKey).toBe('tags');
});
});

14
package-lock.json generated
View File

@ -42,6 +42,8 @@
"@nestjs/cli": "^10.3.2",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/bcryptjs": "^2.4.6",
"@types/crypto-js": "^4.2.2",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
@ -2277,6 +2279,12 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/bcryptjs": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
"dev": true
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@ -2302,6 +2310,12 @@
"integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
"dev": true
},
"node_modules/@types/crypto-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
"dev": true
},
"node_modules/@types/eslint": {
"version": "8.56.4",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.4.tgz",

View File

@ -53,6 +53,8 @@
"@nestjs/cli": "^10.3.2",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/bcryptjs": "^2.4.6",
"@types/crypto-js": "^4.2.2",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",