wsp-ms-core 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +639 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +265 -0
- package/dist/index.d.ts +265 -0
- package/dist/index.js +598 -0
- package/dist/index.js.map +1 -0
- package/package.json +8 -22
- package/jest.config.cjs +0 -9
- package/src/application/contracts/EventBus.ts +0 -7
- package/src/application/contracts/EventBusRepository.ts +0 -10
- package/src/domain/contracts/DomainEntity.ts +0 -58
- package/src/domain/contracts/DomainError.ts +0 -9
- package/src/domain/contracts/DomainEvent.ts +0 -22
- package/src/domain/contracts/ValueObject.ts +0 -26
- package/src/domain/errors/FatalError.ts +0 -9
- package/src/domain/errors/InternalError.ts +0 -9
- package/src/domain/errors/UsageError.ts +0 -12
- package/src/domain/value-objects/Currency.ts +0 -68
- package/src/domain/value-objects/DateTime.ts +0 -132
- package/src/domain/value-objects/Email.ts +0 -24
- package/src/domain/value-objects/Language.ts +0 -94
- package/src/domain/value-objects/Price.ts +0 -54
- package/src/domain/value-objects/UUID.ts +0 -23
- package/src/index.ts +0 -43
- package/src/infrastructure/contracts/DatabaseConnection.ts +0 -11
- package/src/infrastructure/contracts/DatabaseConnector.ts +0 -8
- package/src/infrastructure/contracts/Logger.ts +0 -9
- package/src/infrastructure/errors/ErrorManager.ts +0 -93
- package/src/infrastructure/mysql/MysqlConnection.ts +0 -45
- package/src/infrastructure/mysql/MysqlConnector.ts +0 -51
- package/src/utils/StringVars.ts +0 -14
- package/test/domain/value-objects/Currency.test.ts +0 -48
- package/test/domain/value-objects/DateTime.test.ts +0 -32
- package/test/domain/value-objects/Email.test.ts +0 -38
- package/test/domain/value-objects/Language.test.ts +0 -76
- package/test/domain/value-objects/Price.test.ts +0 -96
- package/test/domain/value-objects/UUID.test.ts +0 -18
- package/test/infrastructure/errors/ErrorManager.test.ts +0 -125
- package/test/infrastructure/mysql/MysqlConnection.test.ts +0 -45
- package/tsconfig.json +0 -14
- package/tsup.config.ts +0 -18
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
export interface DatabaseConnection<Q = string, Params = unknown[], Row = Record<string, unknown>> {
|
|
3
|
-
|
|
4
|
-
query<T = Row>(statement: Q, params?: Params): Promise<T[]>;
|
|
5
|
-
begin(): Promise<void>;
|
|
6
|
-
commit(): Promise<void>;
|
|
7
|
-
rollback(toSavepoint?: string): Promise<void>;
|
|
8
|
-
transaction<T>(fn: (conn: this) => Promise<T>): Promise<T>;
|
|
9
|
-
close(): Promise<void>;
|
|
10
|
-
|
|
11
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import {DatabaseConnection} from "@infrastructure/contracts/DatabaseConnection";
|
|
2
|
-
|
|
3
|
-
export interface DatabaseConnector<C extends DatabaseConnection = DatabaseConnection> {
|
|
4
|
-
|
|
5
|
-
getConnection(options?: { readonly?: boolean }): Promise<C>;
|
|
6
|
-
closePool(): Promise<void>;
|
|
7
|
-
|
|
8
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export interface Logger {
|
|
2
|
-
|
|
3
|
-
debug(type: string, message: string, meta?: Record<string, any>): void;
|
|
4
|
-
info(type: string, message: string, meta?: Record<string, any>): void;
|
|
5
|
-
warn(type: string, message: string, meta?: Record<string, any>): void;
|
|
6
|
-
error(type: string, message: string, meta?: Record<string, any>): void;
|
|
7
|
-
fatal(type: string, message: string, meta?: Record<string, any>): void;
|
|
8
|
-
|
|
9
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import {Language} from "@domain/value-objects/Language";
|
|
2
|
-
import {FatalError} from "@domain/errors/FatalError";
|
|
3
|
-
import {InternalError} from "@domain/errors/InternalError";
|
|
4
|
-
import {UsageError} from "@domain/errors/UsageError";
|
|
5
|
-
import {StringVars} from "@utils/StringVars";
|
|
6
|
-
import {Logger} from "@infrastructure/contracts/Logger";
|
|
7
|
-
|
|
8
|
-
export interface ErrorTemplate {
|
|
9
|
-
type: string;
|
|
10
|
-
languages: Record<string, string>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type ErrorManagerHandleResult = {
|
|
14
|
-
status: number | string;
|
|
15
|
-
message: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export class ErrorManager {
|
|
19
|
-
|
|
20
|
-
private static readonly DEFAULT_MESSAGES: Record<string, string> = {
|
|
21
|
-
'es': 'Ups, hemos encontrado un error. Nuestro equipo ya está trabajando para solucionarlo',
|
|
22
|
-
'en': 'Ups, we found an error. Our team is working on it.',
|
|
23
|
-
'pt': 'Ops, encontramos um bug. Nossa equipe já está trabalhando para resolver isso.',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
public static readonly APP_ERRORS = {
|
|
27
|
-
UNDEFINED: 'UNDEFINED_ERROR',
|
|
28
|
-
PROCESS: 'PROCESS_ERROR',
|
|
29
|
-
DATABASE: 'DATABASE_ERROR'
|
|
30
|
-
} as const;
|
|
31
|
-
|
|
32
|
-
private static readonly TEMPLATES = new Map<string, ErrorTemplate>();
|
|
33
|
-
|
|
34
|
-
public constructor(private readonly logger: Logger | null = null) {}
|
|
35
|
-
|
|
36
|
-
private getDefaultMessage(lang: Language): string {
|
|
37
|
-
return (ErrorManager.DEFAULT_MESSAGES[lang.value] || ErrorManager.DEFAULT_MESSAGES[lang.base()] || 'error');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private onFatal(err: FatalError, lang: Language): ErrorManagerHandleResult {
|
|
41
|
-
this.logger?.fatal(err.type, err.message);
|
|
42
|
-
return { status: 'ERROR', message: this.getDefaultMessage(lang) };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private onInternal(err: InternalError, lang: Language): ErrorManagerHandleResult {
|
|
46
|
-
this.logger?.error(err.type, err.message);
|
|
47
|
-
return { status: 'ERROR', message: this.getDefaultMessage(lang) };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private onUsage(err: UsageError, lang: Language): ErrorManagerHandleResult {
|
|
51
|
-
const tmpl = ErrorManager.TEMPLATES.get(err.type);
|
|
52
|
-
if (!tmpl) {
|
|
53
|
-
this.logger?.error('TEMPLATE_NOT_FOUND', `${err.type}`);
|
|
54
|
-
return { status: 'ERROR', message: this.getDefaultMessage(lang) };
|
|
55
|
-
}
|
|
56
|
-
const code = lang.value;
|
|
57
|
-
const base = lang.base();
|
|
58
|
-
const rawMsg =
|
|
59
|
-
tmpl.languages[code] ??
|
|
60
|
-
tmpl.languages[base] ??
|
|
61
|
-
this.getDefaultMessage(lang);
|
|
62
|
-
return {
|
|
63
|
-
status: 'ERROR',
|
|
64
|
-
message: StringVars.parse(rawMsg, err.vars),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private onUnknown(err: Error, lang: Language): ErrorManagerHandleResult {
|
|
69
|
-
this.logger?.error('UNKNOWN_ERROR', err.message);
|
|
70
|
-
return { status: 'ERROR', message: this.getDefaultMessage(lang) };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
public handle(err: Error, lang: Language): ErrorManagerHandleResult {
|
|
74
|
-
if (['local','dev'].includes(process.env.ENVIRONMENT ?? '')) {
|
|
75
|
-
console.log(err);
|
|
76
|
-
}
|
|
77
|
-
if (err instanceof FatalError) {
|
|
78
|
-
return this.onFatal(err, lang);
|
|
79
|
-
}
|
|
80
|
-
if (err instanceof InternalError) {
|
|
81
|
-
return this.onInternal(err, lang);
|
|
82
|
-
}
|
|
83
|
-
if (err instanceof UsageError) {
|
|
84
|
-
return this.onUsage(err, lang);
|
|
85
|
-
}
|
|
86
|
-
return this.onUnknown(err, lang);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public static addTemplate(template: ErrorTemplate): void {
|
|
90
|
-
ErrorManager.TEMPLATES.set(template.type, template);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {FieldPacket, PoolConnection, RowDataPacket} from "mysql2/promise";
|
|
2
|
-
import {DatabaseConnection} from "@infrastructure/contracts/DatabaseConnection";
|
|
3
|
-
|
|
4
|
-
export class MysqlConnection implements DatabaseConnection<string, any[], RowDataPacket> {
|
|
5
|
-
|
|
6
|
-
private readonly _conn: PoolConnection;
|
|
7
|
-
|
|
8
|
-
public constructor(conn: PoolConnection) {
|
|
9
|
-
this._conn = conn;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
public async query<R = RowDataPacket>(statement: string, params: any[] = [],): Promise<R[]> {
|
|
13
|
-
const [rows] = await this._conn.query<R[] & RowDataPacket[] & FieldPacket[]>(statement, params);
|
|
14
|
-
return rows as R[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
public async begin(): Promise<void> {
|
|
18
|
-
await this._conn.beginTransaction();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public async commit(): Promise<void> {
|
|
22
|
-
await this._conn.commit();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public async rollback(): Promise<void> {
|
|
26
|
-
await this._conn.rollback();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public async transaction<T>(fn: (conn: this) => Promise<T>): Promise<T> {
|
|
30
|
-
await this.begin();
|
|
31
|
-
try {
|
|
32
|
-
const result: T = await fn(this);
|
|
33
|
-
await this.commit();
|
|
34
|
-
return result;
|
|
35
|
-
} catch (err) {
|
|
36
|
-
await this.rollback();
|
|
37
|
-
throw err;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
public async close(): Promise<void> {
|
|
42
|
-
this._conn.release();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { createPool, Pool, PoolConnection } from 'mysql2/promise';
|
|
2
|
-
import {DatabaseConnector} from "@infrastructure/contracts/DatabaseConnector";
|
|
3
|
-
import {MysqlConnection} from "@infrastructure/mysql/MysqlConnection";
|
|
4
|
-
|
|
5
|
-
export class MysqlConnector implements DatabaseConnector<MysqlConnection> {
|
|
6
|
-
public static readonly DEFAULT_POOL_SIZE: number = 10;
|
|
7
|
-
|
|
8
|
-
private readonly _pool: Pool;
|
|
9
|
-
|
|
10
|
-
public constructor(pool?: Pool) {
|
|
11
|
-
this._pool =
|
|
12
|
-
pool ??
|
|
13
|
-
createPool({
|
|
14
|
-
host: process.env.DB_HOST,
|
|
15
|
-
port: Number(process.env.DB_PORT ?? 3306),
|
|
16
|
-
user: process.env.DB_USER,
|
|
17
|
-
password: process.env.DB_PASSWORD,
|
|
18
|
-
database: process.env.DB_NAME,
|
|
19
|
-
connectionLimit:
|
|
20
|
-
Number(process.env.DB_POOL_SIZE) || MysqlConnector.DEFAULT_POOL_SIZE,
|
|
21
|
-
decimalNumbers: true,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
private async wrap(conn: PoolConnection): Promise<MysqlConnection> {
|
|
26
|
-
return new MysqlConnection(conn);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public async getConnection(): Promise<MysqlConnection> {
|
|
30
|
-
const conn: PoolConnection = await this._pool.getConnection();
|
|
31
|
-
return this.wrap(conn);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public async closePool(): Promise<void> {
|
|
35
|
-
await this._pool.end();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
public static async ping(): Promise<boolean> {
|
|
39
|
-
const connector: MysqlConnector = new MysqlConnector();
|
|
40
|
-
try {
|
|
41
|
-
const conn = await connector._pool.getConnection();
|
|
42
|
-
await conn.ping();
|
|
43
|
-
conn.release();
|
|
44
|
-
return true;
|
|
45
|
-
} catch {
|
|
46
|
-
return false;
|
|
47
|
-
} finally {
|
|
48
|
-
await connector.closePool();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/utils/StringVars.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export class StringVars {
|
|
2
|
-
|
|
3
|
-
public static parse(str: string, ob: { [key: string]: any }): string {
|
|
4
|
-
const regex = /{{(.*?)}}/g;
|
|
5
|
-
return str.replace(regex, (match, variable) => {
|
|
6
|
-
if (ob.hasOwnProperty(variable.trim())) {
|
|
7
|
-
return ob[variable.trim()];
|
|
8
|
-
} else {
|
|
9
|
-
return match;
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Currency } from '@domain/value-objects/Currency';
|
|
2
|
-
|
|
3
|
-
describe('Currency ValueObject', () => {
|
|
4
|
-
|
|
5
|
-
it('creates from alpha code (case‑insensitive) and exposes numeric', () => {
|
|
6
|
-
const uyu = Currency.create('uyu');
|
|
7
|
-
expect(uyu.value).toBe('UYU');
|
|
8
|
-
expect(uyu.numeric).toBe(858);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('creates from numeric code (number) and maps to alpha', () => {
|
|
12
|
-
const uyu = Currency.create(858);
|
|
13
|
-
expect(uyu.value).toBe('UYU');
|
|
14
|
-
expect(uyu.numeric).toBe(858);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('creates from numeric code (string) and maps to alpha', () => {
|
|
18
|
-
const ars = Currency.create('032');
|
|
19
|
-
expect(ars.value).toBe('ARS');
|
|
20
|
-
expect(ars.numeric).toBe(32);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('equals() returns true for different representations of same currency', () => {
|
|
24
|
-
const a = Currency.create('uyu');
|
|
25
|
-
const b = Currency.create(858);
|
|
26
|
-
expect(a.equals(b)).toBe(true);
|
|
27
|
-
expect(a.equals(Currency.UYU)).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('isValid() returns true for valid inputs', () => {
|
|
31
|
-
expect(Currency.isValid('USD')).toBe(true);
|
|
32
|
-
expect(Currency.isValid(840)).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('isValid() returns false for invalid inputs', () => {
|
|
36
|
-
expect(Currency.isValid('US')).toBe(false);
|
|
37
|
-
expect(Currency.isValid('USDX')).toBe(false);
|
|
38
|
-
expect(Currency.isValid(999)).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('throws when alpha code is invalid', () => {
|
|
42
|
-
expect(() => Currency.create('ABC1')).toThrow(Error);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('throws when numeric code is unknown', () => {
|
|
46
|
-
expect(() => Currency.create(999)).toThrow(Error);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { DateTime } from '@domain/value-objects/DateTime';
|
|
2
|
-
|
|
3
|
-
describe('DateTime ValueObject', () => {
|
|
4
|
-
const ISO = '2025-07-27T15:30:00Z';
|
|
5
|
-
const TS = Date.parse(ISO);
|
|
6
|
-
|
|
7
|
-
it('creates from ISO string', () => {
|
|
8
|
-
const dt = DateTime.create(ISO);
|
|
9
|
-
expect(dt.value).toBe('2025-07-27 15:30:00');
|
|
10
|
-
expect(dt.year).toBe(2025);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('creates from timestamp', () => {
|
|
14
|
-
const dt = DateTime.create(TS);
|
|
15
|
-
expect(dt.day).toBe(27);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('plusDays() returns a new DateTime with expected day', () => {
|
|
19
|
-
const dt = DateTime.create(ISO).plusDays(3);
|
|
20
|
-
expect(dt.day).toBe(30);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('minusHours() returns a new DateTime with expected hour', () => {
|
|
24
|
-
const dt = DateTime.create(ISO).minusHours(2);
|
|
25
|
-
expect(dt.hour).toBe(13);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('getMonthName() obeys locale', () => {
|
|
29
|
-
const dt = DateTime.create(ISO);
|
|
30
|
-
expect(dt.getMonthName('es')).toBe('julio'); // Luxon locale
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { Email } from '@domain/value-objects/Email';
|
|
2
|
-
|
|
3
|
-
describe('Email ValueObject', () => {
|
|
4
|
-
|
|
5
|
-
it('creates from a valid e‑mail string', () => {
|
|
6
|
-
const mail = Email.create('user@example.com');
|
|
7
|
-
expect(mail.value).toBe('user@example.com');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('trims whitespace before validation/storage', () => {
|
|
11
|
-
const mail = Email.create(' user@example.com \n');
|
|
12
|
-
expect(mail.value).toBe('user@example.com');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('equals() returns true for the exact same address string', () => {
|
|
16
|
-
const a = Email.create('user@example.com');
|
|
17
|
-
const b = Email.create('user@example.com');
|
|
18
|
-
expect(a.equals(b)).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('equals() is case‑sensitive (default behaviour)', () => {
|
|
22
|
-
const a = Email.create('User@Example.com');
|
|
23
|
-
const b = Email.create('user@example.com');
|
|
24
|
-
expect(a.equals(b)).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('isValid() returns true for a correct address', () => {
|
|
28
|
-
expect(Email.isValid('user@example.com')).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('isValid() returns false for an incorrect address', () => {
|
|
32
|
-
expect(Email.isValid('not‑an‑email')).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('throws when the address is invalid', () => {
|
|
36
|
-
expect(() => Email.create('bad‑mail')).toThrow(Error);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { Language } from '@domain/value-objects/Language';
|
|
2
|
-
|
|
3
|
-
describe('Language ValueObject', () => {
|
|
4
|
-
describe('create()', () => {
|
|
5
|
-
it('creates from lowercase code and preserves value', () => {
|
|
6
|
-
const lang = Language.create('es');
|
|
7
|
-
expect(lang.value).toBe('es');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('creates from uppercase code by normalizing to lowercase', () => {
|
|
11
|
-
const lang = Language.create('ES-AR');
|
|
12
|
-
expect(lang.value).toBe('es-ar');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('replaces underscore with hyphen and normalizes', () => {
|
|
16
|
-
const lang = Language.create('EN_US');
|
|
17
|
-
expect(lang.value).toBe('en-us');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('trims whitespace around the code', () => {
|
|
21
|
-
const lang = Language.create(' en-gb ');
|
|
22
|
-
expect(lang.value).toBe('en-gb');
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('supported constants', () => {
|
|
27
|
-
it('has a DEFAULT constant of "es"', () => {
|
|
28
|
-
expect(Language.DEFAULT.value).toBe('es');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('has an ENGLISH_UNITED_STATES constant of "en-us"', () => {
|
|
32
|
-
expect(Language.ENGLISH_UNITED_STATES.value).toBe('en-us');
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('base()', () => {
|
|
37
|
-
it('returns "es" for Spanish variants', () => {
|
|
38
|
-
const lang = Language.create('es-mx');
|
|
39
|
-
expect(lang.base()).toBe('es');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('returns "en" for English variants', () => {
|
|
43
|
-
const lang = Language.create('en-za');
|
|
44
|
-
expect(lang.base()).toBe('en');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('returns the same value if no region part', () => {
|
|
48
|
-
const lang = Language.create('pt');
|
|
49
|
-
expect(lang.base()).toBe('pt');
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('equals()', () => {
|
|
54
|
-
it('returns true for two identical codes', () => {
|
|
55
|
-
const a = Language.create('es');
|
|
56
|
-
const b = Language.create('ES');
|
|
57
|
-
expect(a.equals(b)).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('returns false for different codes', () => {
|
|
61
|
-
const a = Language.create('en');
|
|
62
|
-
const b = Language.create('es');
|
|
63
|
-
expect(a.equals(b)).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe('validation errors', () => {
|
|
68
|
-
it('throws when creating an unsupported code', () => {
|
|
69
|
-
expect(() => Language.create('fr')).toThrow('Language <fr> is not supported');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('throws when creating an empty string', () => {
|
|
73
|
-
expect(() => Language.create('')).toThrow('Language <> is not supported');
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
});
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { Price } from '@domain/value-objects/Price';
|
|
2
|
-
import { Currency } from '@domain/value-objects/Currency';
|
|
3
|
-
|
|
4
|
-
describe('Price ValueObject', () => {
|
|
5
|
-
describe('create()', () => {
|
|
6
|
-
it('creates with numeric amount and string currency code', () => {
|
|
7
|
-
const p = Price.create(100, 'USD');
|
|
8
|
-
expect(p.amount).toBe(100);
|
|
9
|
-
expect(p.currency.value).toBe('USD');
|
|
10
|
-
expect(p.currency.numeric).toBe(840);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('creates with negative amount ≥ MIN_AMOUNT', () => {
|
|
14
|
-
const min = Price.MIN_AMOUNT;
|
|
15
|
-
const p = Price.create(min, Currency.create('EUR'));
|
|
16
|
-
expect(p.amount).toBe(min);
|
|
17
|
-
expect(p.currency.equals(Currency.EUR)).toBe(true);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe('equals()', () => {
|
|
22
|
-
it('returns true for same amount and currency', () => {
|
|
23
|
-
const a = Price.create(50, 'BRL');
|
|
24
|
-
const b = Price.create(50, '986'); // numeric code
|
|
25
|
-
expect(a.equals(b)).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('returns false if other is null or undefined', () => {
|
|
29
|
-
const a = Price.create(50, 'BRL');
|
|
30
|
-
expect(a.equals(null)).toBe(false);
|
|
31
|
-
expect(a.equals(undefined)).toBe(false);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('returns false for different amount or currency', () => {
|
|
35
|
-
const a = Price.create(50, 'BRL');
|
|
36
|
-
const b = Price.create(60, 'BRL');
|
|
37
|
-
const c = Price.create(50, 'USD');
|
|
38
|
-
expect(a.equals(b)).toBe(false);
|
|
39
|
-
expect(a.equals(c)).toBe(false);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('add()', () => {
|
|
44
|
-
it('adds two prices with the same currency', () => {
|
|
45
|
-
const a = Price.create(100, 'UYU');
|
|
46
|
-
const b = Price.create(200, 858);
|
|
47
|
-
const sum = a.add(b);
|
|
48
|
-
expect(sum.amount).toBe(300);
|
|
49
|
-
expect(sum.currency.equals(a.currency)).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('throws if currencies differ', () => {
|
|
53
|
-
const a = Price.create(100, 'USD');
|
|
54
|
-
const b = Price.create(100, 'EUR');
|
|
55
|
-
expect(() => a.add(b)).toThrow('Cannot operate on Price objects with different currencies');
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('subtract()', () => {
|
|
60
|
-
it('subtracts two prices with the same currency', () => {
|
|
61
|
-
const a = Price.create(300, 'USD');
|
|
62
|
-
const b = Price.create(100, 'USD');
|
|
63
|
-
const diff = a.subtract(b);
|
|
64
|
-
expect(diff.amount).toBe(200);
|
|
65
|
-
expect(diff.currency.value).toBe('USD');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('throws if currencies differ', () => {
|
|
69
|
-
const a = Price.create(100, 'USD');
|
|
70
|
-
const b = Price.create(50, 'UYU');
|
|
71
|
-
expect(() => a.subtract(b)).toThrow('Cannot operate on Price objects with different currencies');
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe('validation', () => {
|
|
76
|
-
it('throws for non-number amount', () => {
|
|
77
|
-
// @ts-ignore
|
|
78
|
-
expect(() => Price.create('foo', 'USD')).toThrow('Price amount <foo> is not a valid number');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('throws for NaN amount', () => {
|
|
82
|
-
expect(() => Price.create(NaN, 'USD')).toThrow('Price amount <NaN> is not a valid number');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('throws for infinite amount', () => {
|
|
86
|
-
expect(() => Price.create(Infinity, 'USD')).toThrow('Price amount <Infinity> is not a valid number');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('throws for amount below MIN_AMOUNT', () => {
|
|
90
|
-
const below = Price.MIN_AMOUNT - 1;
|
|
91
|
-
expect(() => Price.create(below, 'USD')).toThrow(
|
|
92
|
-
`Price amount <${below}> must be ≥ ${Price.MIN_AMOUNT}`
|
|
93
|
-
);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { UUID } from '@domain/value-objects/UUID';
|
|
2
|
-
|
|
3
|
-
describe('UUID ValueObject', () => {
|
|
4
|
-
it('creates a valid v4 UUID via static create()', () => {
|
|
5
|
-
const id = UUID.create();
|
|
6
|
-
expect(UUID.isValid(id.value)).toBe(true);
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it('throws when uuid string is invalid', () => {
|
|
10
|
-
expect(() => UUID.create('not-a-uuid')).toThrow();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('equals() compares by value', () => {
|
|
14
|
-
const a = UUID.create('11111111-1111-4111-8111-111111111111');
|
|
15
|
-
const b = UUID.create('11111111-1111-4111-8111-111111111111');
|
|
16
|
-
expect(a.equals(b)).toBe(true);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { ErrorManager, ErrorTemplate } from '@infrastructure/errors/ErrorManager';
|
|
2
|
-
import { Language } from '@domain/value-objects/Language';
|
|
3
|
-
import {Logger} from "@infrastructure/contracts/Logger";
|
|
4
|
-
import {FatalError} from "@domain/errors/FatalError";
|
|
5
|
-
import {InternalError} from "@domain/errors/InternalError";
|
|
6
|
-
import {UsageError} from "@domain/errors/UsageError";
|
|
7
|
-
|
|
8
|
-
describe('ErrorManager', () => {
|
|
9
|
-
let logger: Logger;
|
|
10
|
-
let manager: ErrorManager;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
// Reset environment and templates
|
|
14
|
-
delete process.env.ENVIRONMENT;
|
|
15
|
-
manager = new ErrorManager(logger = {
|
|
16
|
-
debug: jest.fn(),
|
|
17
|
-
info: jest.fn(),
|
|
18
|
-
warn: jest.fn(),
|
|
19
|
-
error: jest.fn(),
|
|
20
|
-
fatal: jest.fn(),
|
|
21
|
-
});
|
|
22
|
-
// Clear private TEMPLATES map
|
|
23
|
-
(ErrorManager as any).TEMPLATES.clear();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('handle FatalError', () => {
|
|
27
|
-
it('logs fatal and returns default message for exact language', () => {
|
|
28
|
-
const err = new FatalError('TEST_FATAL', 'fatal happened');
|
|
29
|
-
const lang = Language.create('es');
|
|
30
|
-
const res = manager.handle(err, lang);
|
|
31
|
-
expect(logger.fatal).toHaveBeenCalledWith('TEST_FATAL', 'fatal happened');
|
|
32
|
-
expect(res).toEqual({
|
|
33
|
-
status: 'ERROR',
|
|
34
|
-
message: 'Ups, hemos encontrado un error. Nuestro equipo ya está trabajando para solucionarlo',
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('falls back to base language if specific not defined', () => {
|
|
39
|
-
const err = new FatalError('TF', 'msg');
|
|
40
|
-
const lang = Language.create('es-uy');
|
|
41
|
-
const res = manager.handle(err, lang);
|
|
42
|
-
// 'es-uy' not in DEFAULT_MESSAGES, fallback to 'es'
|
|
43
|
-
expect(res.message).toContain('Ups, hemos encontrado un error');
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('handle InternalError', () => {
|
|
48
|
-
it('logs error and returns default message', () => {
|
|
49
|
-
const err = new InternalError('DB_ERR', 'db failure');
|
|
50
|
-
const lang = Language.create('en-gb');
|
|
51
|
-
const res = manager.handle(err, lang);
|
|
52
|
-
expect(logger.error).toHaveBeenCalledWith('DB_ERR', 'db failure');
|
|
53
|
-
expect(res).toEqual({
|
|
54
|
-
status: 'ERROR',
|
|
55
|
-
message: 'Ups, we found an error. Our team is working on it.',
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('handle UsageError without template', () => {
|
|
61
|
-
it('logs template-not-found and returns default fallback', () => {
|
|
62
|
-
const err = new UsageError('MISSING', { foo: 'bar' });
|
|
63
|
-
const lang = Language.create('pt-br');
|
|
64
|
-
const res = manager.handle(err, lang);
|
|
65
|
-
expect(logger.error).toHaveBeenCalledWith('TEMPLATE_NOT_FOUND', 'MISSING');
|
|
66
|
-
expect(res).toEqual({
|
|
67
|
-
status: 'ERROR',
|
|
68
|
-
message: 'Ops, encontramos um bug. Nossa equipe já está trabalhando para resolver isso.',
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('handle UsageError with template', () => {
|
|
74
|
-
it('renders template for exact locale', () => {
|
|
75
|
-
const tmpl: ErrorTemplate = {
|
|
76
|
-
type: 'GREETING',
|
|
77
|
-
languages: {
|
|
78
|
-
'es': 'Hola {{name}}',
|
|
79
|
-
'en': 'Hello {{name}}',
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
ErrorManager.addTemplate(tmpl);
|
|
83
|
-
|
|
84
|
-
const err = new UsageError('GREETING', { name: 'Luis' });
|
|
85
|
-
const lang = Language.create('es');
|
|
86
|
-
const res = manager.handle(err, lang);
|
|
87
|
-
expect(res).toEqual({
|
|
88
|
-
status: 'ERROR',
|
|
89
|
-
message: 'Hola Luis',
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('falls back to base locale if exact not present', () => {
|
|
94
|
-
const tmpl: ErrorTemplate = {
|
|
95
|
-
type: 'BYE',
|
|
96
|
-
languages: {
|
|
97
|
-
'en': 'Bye {{who}}',
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
ErrorManager.addTemplate(tmpl);
|
|
101
|
-
|
|
102
|
-
const err = new UsageError('BYE', { who: 'World' });
|
|
103
|
-
const lang = Language.create('en-au');
|
|
104
|
-
const res = manager.handle(err, lang);
|
|
105
|
-
expect(res).toEqual({
|
|
106
|
-
status: 'ERROR',
|
|
107
|
-
message: 'Bye World',
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('handle unknown Error', () => {
|
|
113
|
-
it('logs unknown and returns default message', () => {
|
|
114
|
-
const err = new Error('something bad');
|
|
115
|
-
const lang = Language.create('en');
|
|
116
|
-
const res = manager.handle(err, lang);
|
|
117
|
-
expect(logger.error).toHaveBeenCalledWith('UNKNOWN_ERROR', 'something bad');
|
|
118
|
-
expect(res).toEqual({
|
|
119
|
-
status: 'ERROR',
|
|
120
|
-
message: 'Ups, we found an error. Our team is working on it.',
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
});
|