@jmlq/logger 0.1.0-alpha.6 → 0.1.0-alpha.8
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/README.md +232 -551
- package/dist/examples/data-source-service.example.d.ts +3 -0
- package/dist/examples/data-source-service.example.js +174 -0
- package/dist/examples/flush-buffers-use-case.example.d.ts +3 -0
- package/dist/examples/flush-buffers-use-case.example.js +60 -0
- package/dist/examples/get-logs-use-case.example.d.ts +3 -0
- package/dist/examples/get-logs-use-case.example.js +110 -0
- package/dist/examples/index.example.d.ts +8 -0
- package/dist/examples/index.example.js +116 -0
- package/dist/examples/logger-factory.example.d.ts +39 -0
- package/dist/examples/logger-factory.example.js +158 -0
- package/dist/examples/normalize-message.example.d.ts +3 -0
- package/dist/examples/normalize-message.example.js +80 -0
- package/dist/examples/pii-redactor.example.d.ts +3 -0
- package/dist/examples/pii-redactor.example.js +129 -0
- package/dist/examples/save-log-use-case.example.d.ts +3 -0
- package/dist/examples/save-log-use-case.example.js +150 -0
- package/dist/examples/to-log-level.example.d.ts +3 -0
- package/dist/examples/to-log-level.example.js +49 -0
- package/dist/examples/to-pii-regex.example.d.ts +3 -0
- package/dist/examples/to-pii-regex.example.js +75 -0
- package/dist/{presentation → src/application}/factory/create-logger.d.ts +1 -1
- package/dist/{presentation → src/application}/factory/create-logger.js +3 -3
- package/dist/src/application/factory/index.d.ts +1 -0
- package/dist/{presentation → src/application}/factory/index.js +1 -1
- package/dist/src/application/factory/logger.factory.d.ts +12 -0
- package/dist/src/application/factory/logger.factory.js +74 -0
- package/dist/src/application/index.d.ts +2 -0
- package/dist/src/application/index.js +18 -0
- package/dist/src/application/use-cases/flush-buffers.use-case.d.ts +6 -0
- package/dist/{application/use-cases/flush-buffers.js → src/application/use-cases/flush-buffers.use-case.js} +3 -3
- package/dist/src/application/use-cases/get-logs.use-case.d.ts +8 -0
- package/dist/{application/use-cases/get-logs.js → src/application/use-cases/get-logs.use-case.js} +3 -3
- package/dist/src/application/use-cases/index.d.ts +3 -0
- package/dist/{application → src/application}/use-cases/index.js +3 -3
- package/dist/src/application/use-cases/save-log.use-case.d.ts +8 -0
- package/dist/src/application/use-cases/save-log.use-case.js +26 -0
- package/dist/src/domain/index.d.ts +6 -0
- package/dist/src/domain/index.js +22 -0
- package/dist/src/domain/ports/create-logger-options.port.d.ts +7 -0
- package/dist/src/domain/ports/index.d.ts +6 -0
- package/dist/src/domain/ports/index.js +22 -0
- package/dist/src/domain/ports/log-datasource.port.d.ts +9 -0
- package/dist/src/domain/ports/logger-factory-config.port.d.ts +28 -0
- package/dist/src/domain/ports/logger-service.port.d.ts +19 -0
- package/dist/{domain/contracts/logger.d.ts → src/domain/ports/logger.port.d.ts} +0 -9
- package/dist/src/domain/ports/logger.port.js +2 -0
- package/dist/src/domain/ports/pii-redactor.port.d.ts +5 -0
- package/dist/src/domain/ports/pii-redactor.port.js +2 -0
- package/dist/src/domain/request/get-logs-filter.props.d.ts +9 -0
- package/dist/src/domain/request/get-logs-filter.props.js +2 -0
- package/dist/src/domain/request/index.d.ts +5 -0
- package/dist/src/domain/request/index.js +21 -0
- package/dist/src/domain/request/log.props.d.ts +7 -0
- package/dist/src/domain/request/log.props.js +2 -0
- package/dist/src/domain/request/pii-options.props.d.ts +8 -0
- package/dist/src/domain/request/pii-options.props.js +2 -0
- package/dist/src/domain/request/pii-replacement.props.d.ts +5 -0
- package/dist/src/domain/request/pii-replacement.props.js +2 -0
- package/dist/src/domain/request/save-log.props.d.ts +7 -0
- package/dist/src/domain/request/save-log.props.js +2 -0
- package/dist/src/domain/response/index.d.ts +1 -0
- package/dist/{domain/value-objects → src/domain/response}/index.js +1 -1
- package/dist/src/domain/response/log.response.d.ts +7 -0
- package/dist/src/domain/response/log.response.js +2 -0
- package/dist/src/domain/services/index.d.ts +4 -0
- package/dist/{domain/contracts → src/domain/services}/index.js +4 -3
- package/dist/src/domain/services/log-level.service.d.ts +2 -0
- package/dist/src/domain/services/log-level.service.js +27 -0
- package/dist/src/domain/services/message-normalizer.service.d.ts +3 -0
- package/dist/src/domain/services/message-normalizer.service.js +8 -0
- package/dist/src/domain/services/pii-pattern.service.d.ts +2 -0
- package/dist/src/domain/services/pii-pattern.service.js +13 -0
- package/dist/src/domain/services/pii-redactor.d.ts +10 -0
- package/dist/{domain → src/domain}/services/pii-redactor.js +8 -17
- package/dist/src/domain/services/pii-redactor.service.d.ts +10 -0
- package/dist/src/domain/services/pii-redactor.service.js +68 -0
- package/dist/src/domain/types/index.d.ts +1 -0
- package/dist/src/domain/types/index.js +17 -0
- package/dist/src/domain/types/log-message.type.d.ts +1 -0
- package/dist/src/domain/types/log-message.type.js +2 -0
- package/dist/src/domain/value-objects/index.d.ts +1 -0
- package/dist/{domain/services → src/domain/value-objects}/index.js +1 -1
- package/dist/{domain/value-objects/log-level.d.ts → src/domain/value-objects/log-level.vo.d.ts} +0 -1
- package/dist/src/domain/value-objects/log-level.vo.js +13 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +22 -0
- package/dist/src/infrastructure/datasources/in-memory-log.datasource.d.ts +1 -0
- package/dist/src/infrastructure/datasources/in-memory-log.datasource.js +2 -0
- package/dist/src/infrastructure/datasources/index.d.ts +1 -0
- package/dist/{infrastructure/adapters → src/infrastructure/datasources}/index.js +1 -1
- package/dist/src/infrastructure/index.d.ts +1 -0
- package/dist/{domain/types → src/infrastructure}/index.js +1 -1
- package/dist/src/infrastructure/services/data-source-error-handler.type.d.ts +5 -0
- package/dist/src/infrastructure/services/data-source-error-handler.type.js +2 -0
- package/dist/src/infrastructure/services/datasource.service.d.ts +15 -0
- package/dist/src/infrastructure/services/datasource.service.js +63 -0
- package/dist/src/infrastructure/services/index.d.ts +2 -0
- package/dist/src/infrastructure/services/index.js +18 -0
- package/dist/tests/application/factory/logger-factory.spec.d.ts +1 -0
- package/dist/tests/application/factory/logger-factory.spec.js +161 -0
- package/dist/tests/application/use-cases/flush-buffers.use-case.spec.d.ts +1 -0
- package/dist/tests/application/use-cases/flush-buffers.use-case.spec.js +38 -0
- package/dist/tests/application/use-cases/get-logs.use-case.spec.d.ts +1 -0
- package/dist/tests/application/use-cases/get-logs.use-case.spec.js +114 -0
- package/dist/tests/application/use-cases/save-log.use-case.spec.d.ts +1 -0
- package/dist/tests/application/use-cases/save-log.use-case.spec.js +138 -0
- package/dist/tests/domain/services/log-level.service.spec.d.ts +1 -0
- package/dist/tests/domain/services/log-level.service.spec.js +68 -0
- package/dist/tests/domain/services/normalize-message.spec.d.ts +1 -0
- package/dist/tests/domain/services/normalize-message.spec.js +83 -0
- package/dist/tests/domain/services/pii-redactor.spec.d.ts +1 -0
- package/dist/tests/domain/services/pii-redactor.spec.js +170 -0
- package/dist/tests/domain/services/to-pii-regex.spec.d.ts +1 -0
- package/dist/tests/domain/services/to-pii-regex.spec.js +82 -0
- package/dist/tests/infrastructure/services/datasource.service.spec.d.ts +1 -0
- package/dist/tests/infrastructure/services/datasource.service.spec.js +128 -0
- package/dist/tests/test-utils/create-pii-redactor-mock.d.ts +5 -0
- package/dist/tests/test-utils/create-pii-redactor-mock.js +10 -0
- package/package.json +27 -19
- package/LICENSE +0 -21
- package/dist/application/use-cases/flush-buffers.d.ts +0 -6
- package/dist/application/use-cases/get-logs.d.ts +0 -7
- package/dist/application/use-cases/index.d.ts +0 -3
- package/dist/application/use-cases/save-log.d.ts +0 -13
- package/dist/application/use-cases/save-log.js +0 -30
- package/dist/domain/contracts/index.d.ts +0 -3
- package/dist/domain/contracts/log.datasource.d.ts +0 -8
- package/dist/domain/contracts/pii.d.ts +0 -5
- package/dist/domain/services/index.d.ts +0 -1
- package/dist/domain/services/pii-redactor.d.ts +0 -10
- package/dist/domain/types/index.d.ts +0 -1
- package/dist/domain/types/log.types.d.ts +0 -28
- package/dist/domain/value-objects/index.d.ts +0 -1
- package/dist/domain/value-objects/log-level.js +0 -37
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -10
- package/dist/infrastructure/adapters/composite.datasource.d.ts +0 -11
- package/dist/infrastructure/adapters/composite.datasource.js +0 -46
- package/dist/infrastructure/adapters/index.d.ts +0 -1
- package/dist/presentation/factory/index.d.ts +0 -1
- /package/dist/{domain/contracts/log.datasource.js → src/domain/ports/create-logger-options.port.js} +0 -0
- /package/dist/{domain/contracts/logger.js → src/domain/ports/log-datasource.port.js} +0 -0
- /package/dist/{domain/contracts/pii.js → src/domain/ports/logger-factory-config.port.js} +0 -0
- /package/dist/{domain/types/log.types.js → src/domain/ports/logger-service.port.js} +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const get_logs_use_case_1 = require("../../../src/application/use-cases/get-logs.use-case");
|
|
4
|
+
const value_objects_1 = require("../../../src/domain/value-objects");
|
|
5
|
+
describe("GetLogsUseCase", () => {
|
|
6
|
+
it("debe retornar [] si el datasource no implementa find", async () => {
|
|
7
|
+
const dsSinFind = {
|
|
8
|
+
save: jest.fn().mockResolvedValue(undefined),
|
|
9
|
+
name: "no-find-ds",
|
|
10
|
+
// sin find
|
|
11
|
+
};
|
|
12
|
+
const useCase = new get_logs_use_case_1.GetLogsUseCase(dsSinFind);
|
|
13
|
+
const result = await useCase.execute();
|
|
14
|
+
expect(result).toEqual([]);
|
|
15
|
+
});
|
|
16
|
+
it("debe llamar a ds.find con undefined cuando no se pasa filtro y find está implementado", async () => {
|
|
17
|
+
const logsSimulados = [
|
|
18
|
+
{
|
|
19
|
+
level: value_objects_1.LogLevel.INFO,
|
|
20
|
+
message: "log 1",
|
|
21
|
+
timestamp: Date.now(),
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
const findMock = jest.fn(async () => logsSimulados);
|
|
25
|
+
const dsMock = {
|
|
26
|
+
save: jest.fn().mockResolvedValue(undefined),
|
|
27
|
+
find: findMock,
|
|
28
|
+
name: "memory",
|
|
29
|
+
};
|
|
30
|
+
const useCase = new get_logs_use_case_1.GetLogsUseCase(dsMock);
|
|
31
|
+
const result = await useCase.execute();
|
|
32
|
+
expect(findMock).toHaveBeenCalledTimes(1);
|
|
33
|
+
expect(findMock).toHaveBeenCalledWith(undefined);
|
|
34
|
+
expect(result).toBe(logsSimulados);
|
|
35
|
+
});
|
|
36
|
+
it("debe sanitizar limit (tope 5000, ignora <= 0) y offset (solo >= 0) antes de llamar a ds.find", async () => {
|
|
37
|
+
let capturedFilter;
|
|
38
|
+
const findMock = jest.fn(async (filter) => {
|
|
39
|
+
capturedFilter = filter;
|
|
40
|
+
return [];
|
|
41
|
+
});
|
|
42
|
+
const dsMock = {
|
|
43
|
+
save: jest.fn().mockResolvedValue(undefined),
|
|
44
|
+
find: findMock,
|
|
45
|
+
name: "memory",
|
|
46
|
+
};
|
|
47
|
+
const useCase = new get_logs_use_case_1.GetLogsUseCase(dsMock);
|
|
48
|
+
const rawFilter = {
|
|
49
|
+
levelMin: value_objects_1.LogLevel.WARN,
|
|
50
|
+
since: 1000,
|
|
51
|
+
until: 2000,
|
|
52
|
+
limit: 10000, // debería ser recortado a 5000
|
|
53
|
+
offset: -5, // debería convertirse en undefined
|
|
54
|
+
query: "error",
|
|
55
|
+
};
|
|
56
|
+
await useCase.execute(rawFilter);
|
|
57
|
+
expect(findMock).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(capturedFilter).toBeDefined();
|
|
59
|
+
const safe = capturedFilter;
|
|
60
|
+
expect(safe.levelMin).toBe(value_objects_1.LogLevel.WARN);
|
|
61
|
+
expect(safe.since).toBe(1000);
|
|
62
|
+
expect(safe.until).toBe(2000);
|
|
63
|
+
expect(safe.query).toBe("error");
|
|
64
|
+
// Sanitización:
|
|
65
|
+
expect(safe.limit).toBe(5000);
|
|
66
|
+
expect(safe.offset).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
it("debe eliminar limit cuando es <= 0 y offset cuando es negativo o 0 (según implementación actual)", async () => {
|
|
69
|
+
let capturedFilter;
|
|
70
|
+
const findMock = jest.fn(async (filter) => {
|
|
71
|
+
capturedFilter = filter;
|
|
72
|
+
return [];
|
|
73
|
+
});
|
|
74
|
+
const dsMock = {
|
|
75
|
+
save: jest.fn().mockResolvedValue(undefined),
|
|
76
|
+
find: findMock,
|
|
77
|
+
name: "memory",
|
|
78
|
+
};
|
|
79
|
+
const useCase = new get_logs_use_case_1.GetLogsUseCase(dsMock);
|
|
80
|
+
const filter = {
|
|
81
|
+
limit: 0, // <= 0 → undefined
|
|
82
|
+
offset: 0, // por la condición actual, también termina como undefined
|
|
83
|
+
};
|
|
84
|
+
await useCase.execute(filter);
|
|
85
|
+
expect(findMock).toHaveBeenCalledTimes(1);
|
|
86
|
+
expect(capturedFilter).toBeDefined();
|
|
87
|
+
const safe = capturedFilter;
|
|
88
|
+
expect(safe.limit).toBeUndefined();
|
|
89
|
+
expect(safe.offset).toBeUndefined();
|
|
90
|
+
});
|
|
91
|
+
it("debe mantener limit y offset válidos cuando están en rango aceptable", async () => {
|
|
92
|
+
let capturedFilter;
|
|
93
|
+
const findMock = jest.fn(async (filter) => {
|
|
94
|
+
capturedFilter = filter;
|
|
95
|
+
return [];
|
|
96
|
+
});
|
|
97
|
+
const dsMock = {
|
|
98
|
+
save: jest.fn().mockResolvedValue(undefined),
|
|
99
|
+
find: findMock,
|
|
100
|
+
name: "memory",
|
|
101
|
+
};
|
|
102
|
+
const useCase = new get_logs_use_case_1.GetLogsUseCase(dsMock);
|
|
103
|
+
const filter = {
|
|
104
|
+
limit: 100,
|
|
105
|
+
offset: 10,
|
|
106
|
+
};
|
|
107
|
+
await useCase.execute(filter);
|
|
108
|
+
expect(findMock).toHaveBeenCalledTimes(1);
|
|
109
|
+
expect(capturedFilter).toBeDefined();
|
|
110
|
+
const safe = capturedFilter;
|
|
111
|
+
expect(safe.limit).toBe(100);
|
|
112
|
+
expect(safe.offset).toBe(10);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const use_cases_1 = require("../../../src/application/use-cases");
|
|
4
|
+
const value_objects_1 = require("../../../src/domain/value-objects");
|
|
5
|
+
const services_1 = require("../../../src/domain/services");
|
|
6
|
+
const create_pii_redactor_mock_1 = require("../../test-utils/create-pii-redactor-mock");
|
|
7
|
+
// Mock explícito del servicio de normalización/PII del mensaje
|
|
8
|
+
jest.mock("../../../src/domain/services", () => ({
|
|
9
|
+
normalizeMessage: jest.fn(),
|
|
10
|
+
}));
|
|
11
|
+
describe("SaveLogUseCase", () => {
|
|
12
|
+
let dsMock;
|
|
13
|
+
let redactorMock;
|
|
14
|
+
let props;
|
|
15
|
+
let useCase;
|
|
16
|
+
let fixedNow;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
fixedNow = 1700000000000; // número fijo para timestamp
|
|
20
|
+
jest.spyOn(Date, "now").mockReturnValue(fixedNow);
|
|
21
|
+
dsMock = {
|
|
22
|
+
save: jest.fn().mockResolvedValue(undefined),
|
|
23
|
+
find: jest.fn(),
|
|
24
|
+
flush: jest.fn(),
|
|
25
|
+
dispose: jest.fn(),
|
|
26
|
+
name: "test-datasource",
|
|
27
|
+
};
|
|
28
|
+
redactorMock = (0, create_pii_redactor_mock_1.createPiiRedactorMock)();
|
|
29
|
+
props = {
|
|
30
|
+
ds: dsMock,
|
|
31
|
+
minLevel: value_objects_1.LogLevel.INFO,
|
|
32
|
+
redactor: redactorMock,
|
|
33
|
+
};
|
|
34
|
+
useCase = new use_cases_1.SaveLogUseCase(props);
|
|
35
|
+
});
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
// restaurar Date.now si hay otros tests en el proyecto
|
|
38
|
+
Date.now.mockRestore?.();
|
|
39
|
+
});
|
|
40
|
+
it("no debe guardar el log si el nivel es menor que el minLevel (filtro por nivel)", async () => {
|
|
41
|
+
const level = value_objects_1.LogLevel.DEBUG; // < INFO
|
|
42
|
+
const message = "Mensaje de debug que no debería guardarse";
|
|
43
|
+
await useCase.execute(level, message);
|
|
44
|
+
// No se normaliza ni se redacta nada
|
|
45
|
+
expect(services_1.normalizeMessage).not.toHaveBeenCalled();
|
|
46
|
+
expect(redactorMock.redact).not.toHaveBeenCalled();
|
|
47
|
+
// No se persiste el log
|
|
48
|
+
expect(dsMock.save).not.toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
it("debe guardar el log si el nivel es igual al minLevel", async () => {
|
|
51
|
+
const level = value_objects_1.LogLevel.INFO; // == minLevel
|
|
52
|
+
const message = "Mensaje de info";
|
|
53
|
+
services_1.normalizeMessage.mockReturnValue("mensaje-normalizado");
|
|
54
|
+
await useCase.execute(level, message);
|
|
55
|
+
expect(services_1.normalizeMessage).toHaveBeenCalledTimes(1);
|
|
56
|
+
expect(services_1.normalizeMessage).toHaveBeenCalledWith(message, redactorMock);
|
|
57
|
+
expect(dsMock.save).toHaveBeenCalledTimes(1);
|
|
58
|
+
const savedLog = dsMock.save.mock.calls[0][0];
|
|
59
|
+
expect(savedLog.level).toBe(level);
|
|
60
|
+
expect(savedLog.message).toBe("mensaje-normalizado");
|
|
61
|
+
expect(savedLog.meta).toBeUndefined();
|
|
62
|
+
expect(savedLog.timestamp).toBe(fixedNow);
|
|
63
|
+
});
|
|
64
|
+
it("debe normalizar el mensaje, redactar meta y guardar el log cuando level >= minLevel (ejemplo de PII)", async () => {
|
|
65
|
+
const level = value_objects_1.LogLevel.ERROR;
|
|
66
|
+
const rawMessage = "Error al procesar tarjeta 4111-1111-1111-1111 del usuario john.doe@example.com";
|
|
67
|
+
const metaConPii = {
|
|
68
|
+
user: {
|
|
69
|
+
email: "john.doe@example.com",
|
|
70
|
+
card: "4111-1111-1111-1111",
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
// Simulamos que normalizeMessage aplica cierta redacción básica de PII
|
|
74
|
+
services_1.normalizeMessage.mockImplementation((msg, _redactor) => {
|
|
75
|
+
if (typeof msg === "string") {
|
|
76
|
+
return msg
|
|
77
|
+
.replace(/\d{4}-\d{4}-\d{4}-\d{4}/g, "****-****-****-****")
|
|
78
|
+
.replace(/[\w.-]+@[\w.-]+/g, "***@***");
|
|
79
|
+
}
|
|
80
|
+
return msg;
|
|
81
|
+
});
|
|
82
|
+
// Simulamos que el redactor también actúa sobre meta (ejemplo PII)
|
|
83
|
+
redactorMock.redact.mockImplementation((value) => {
|
|
84
|
+
if (value?.user) {
|
|
85
|
+
return {
|
|
86
|
+
...value,
|
|
87
|
+
user: {
|
|
88
|
+
...value.user,
|
|
89
|
+
email: "***@***",
|
|
90
|
+
card: "****-****-****-****",
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
});
|
|
96
|
+
await useCase.execute(level, rawMessage, metaConPii);
|
|
97
|
+
// 1) Se debe normalizar el mensaje con el redactor
|
|
98
|
+
expect(services_1.normalizeMessage).toHaveBeenCalledTimes(1);
|
|
99
|
+
expect(services_1.normalizeMessage).toHaveBeenCalledWith(rawMessage, redactorMock);
|
|
100
|
+
// 2) Se debe redactar la meta
|
|
101
|
+
expect(redactorMock.redact).toHaveBeenCalledTimes(1);
|
|
102
|
+
expect(redactorMock.redact).toHaveBeenCalledWith(metaConPii);
|
|
103
|
+
// 3) Se debe llamar a save con el log ya procesado
|
|
104
|
+
expect(dsMock.save).toHaveBeenCalledTimes(1);
|
|
105
|
+
const savedLog = dsMock.save.mock.calls[0][0];
|
|
106
|
+
expect(savedLog.level).toBe(level);
|
|
107
|
+
expect(savedLog.timestamp).toBe(fixedNow);
|
|
108
|
+
// Mensaje con PII de tarjeta + email redactado por normalizeMessage simulado
|
|
109
|
+
expect(savedLog.message).toBe("Error al procesar tarjeta ****-****-****-**** del usuario ***@***");
|
|
110
|
+
// Meta con PII redactado por redactorMock
|
|
111
|
+
expect(savedLog.meta).toEqual({
|
|
112
|
+
user: {
|
|
113
|
+
email: "***@***",
|
|
114
|
+
card: "****-****-****-****",
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
it("debe llamar a redact(meta) solo cuando meta está definido", async () => {
|
|
119
|
+
const level = value_objects_1.LogLevel.WARN;
|
|
120
|
+
const message = "Advertencia con y sin meta";
|
|
121
|
+
services_1.normalizeMessage.mockReturnValue("msg-warn");
|
|
122
|
+
// 1️⃣ Llamada SIN meta
|
|
123
|
+
await useCase.execute(level, message);
|
|
124
|
+
expect(redactorMock.redact).not.toHaveBeenCalled();
|
|
125
|
+
expect(dsMock.save).toHaveBeenCalledTimes(1);
|
|
126
|
+
const logSinMeta = dsMock.save.mock.calls[0][0];
|
|
127
|
+
expect(logSinMeta.meta).toBeUndefined();
|
|
128
|
+
// 2️⃣ Llamada CON meta
|
|
129
|
+
const meta = { foo: "bar" };
|
|
130
|
+
await useCase.execute(level, message, meta);
|
|
131
|
+
// Ahora sí debe haber sido llamada para la segunda ejecución
|
|
132
|
+
expect(redactorMock.redact).toHaveBeenCalledTimes(1);
|
|
133
|
+
expect(redactorMock.redact).toHaveBeenCalledWith(meta);
|
|
134
|
+
expect(dsMock.save).toHaveBeenCalledTimes(2);
|
|
135
|
+
const logConMeta = dsMock.save.mock.calls[1][0];
|
|
136
|
+
expect(logConMeta.meta).toBe(meta); // como redactorMock.redact devuelve el mismo valor
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const domain_1 = require("src/domain");
|
|
4
|
+
describe("toLogLevel()", () => {
|
|
5
|
+
// --------------------------------------------------------------------------
|
|
6
|
+
// 1. Strings válidos
|
|
7
|
+
// --------------------------------------------------------------------------
|
|
8
|
+
it("debe convertir strings válidos al LogLevel correcto", () => {
|
|
9
|
+
expect((0, domain_1.toLogLevel)("trace")).toBe(domain_1.LogLevel.TRACE);
|
|
10
|
+
expect((0, domain_1.toLogLevel)("debug")).toBe(domain_1.LogLevel.DEBUG);
|
|
11
|
+
expect((0, domain_1.toLogLevel)("info")).toBe(domain_1.LogLevel.INFO);
|
|
12
|
+
expect((0, domain_1.toLogLevel)("warn")).toBe(domain_1.LogLevel.WARN);
|
|
13
|
+
expect((0, domain_1.toLogLevel)("error")).toBe(domain_1.LogLevel.ERROR);
|
|
14
|
+
expect((0, domain_1.toLogLevel)("fatal")).toBe(domain_1.LogLevel.FATAL);
|
|
15
|
+
});
|
|
16
|
+
// --------------------------------------------------------------------------
|
|
17
|
+
// 2. Insensibilidad a mayúsculas/minúsculas
|
|
18
|
+
// --------------------------------------------------------------------------
|
|
19
|
+
it("debe aceptar strings en cualquier tipo de casing", () => {
|
|
20
|
+
expect((0, domain_1.toLogLevel)("TRACE")).toBe(domain_1.LogLevel.TRACE);
|
|
21
|
+
expect((0, domain_1.toLogLevel)("DeBuG")).toBe(domain_1.LogLevel.DEBUG);
|
|
22
|
+
expect((0, domain_1.toLogLevel)("Warn")).toBe(domain_1.LogLevel.WARN);
|
|
23
|
+
});
|
|
24
|
+
// --------------------------------------------------------------------------
|
|
25
|
+
// 3. Valores numéricos válidos
|
|
26
|
+
// --------------------------------------------------------------------------
|
|
27
|
+
it("debe aceptar valores numéricos válidos del enum", () => {
|
|
28
|
+
expect((0, domain_1.toLogLevel)(domain_1.LogLevel.TRACE)).toBe(domain_1.LogLevel.TRACE);
|
|
29
|
+
expect((0, domain_1.toLogLevel)(domain_1.LogLevel.FATAL)).toBe(domain_1.LogLevel.FATAL);
|
|
30
|
+
});
|
|
31
|
+
// --------------------------------------------------------------------------
|
|
32
|
+
// 4. Valores numéricos inválidos
|
|
33
|
+
// --------------------------------------------------------------------------
|
|
34
|
+
it("debe retornar el valor por defecto si el número no pertenece al enum", () => {
|
|
35
|
+
expect((0, domain_1.toLogLevel)(999)).toBe(domain_1.LogLevel.INFO); // default implícito
|
|
36
|
+
expect((0, domain_1.toLogLevel)(999, domain_1.LogLevel.ERROR)).toBe(domain_1.LogLevel.ERROR); // default explícito
|
|
37
|
+
});
|
|
38
|
+
// --------------------------------------------------------------------------
|
|
39
|
+
// 5. Valores undefined o null
|
|
40
|
+
// --------------------------------------------------------------------------
|
|
41
|
+
it("debe usar el valor por defecto si el valor es undefined o null", () => {
|
|
42
|
+
expect((0, domain_1.toLogLevel)(undefined)).toBe(domain_1.LogLevel.INFO);
|
|
43
|
+
expect((0, domain_1.toLogLevel)(null)).toBe(domain_1.LogLevel.INFO);
|
|
44
|
+
expect((0, domain_1.toLogLevel)(undefined, domain_1.LogLevel.WARN)).toBe(domain_1.LogLevel.WARN);
|
|
45
|
+
});
|
|
46
|
+
// --------------------------------------------------------------------------
|
|
47
|
+
// 6. Strings desconocidos
|
|
48
|
+
// --------------------------------------------------------------------------
|
|
49
|
+
it("debe retornar el default si recibe un string no reconocido", () => {
|
|
50
|
+
expect((0, domain_1.toLogLevel)("unknown")).toBe(domain_1.LogLevel.INFO);
|
|
51
|
+
expect((0, domain_1.toLogLevel)("invalid-level", domain_1.LogLevel.ERROR)).toBe(domain_1.LogLevel.ERROR);
|
|
52
|
+
});
|
|
53
|
+
// --------------------------------------------------------------------------
|
|
54
|
+
// 7. Otros tipos no soportados
|
|
55
|
+
// --------------------------------------------------------------------------
|
|
56
|
+
it("debe retornar el default si recibe objetos, booleanos u otros tipos", () => {
|
|
57
|
+
expect((0, domain_1.toLogLevel)({})).toBe(domain_1.LogLevel.INFO);
|
|
58
|
+
expect((0, domain_1.toLogLevel)([])).toBe(domain_1.LogLevel.INFO);
|
|
59
|
+
expect((0, domain_1.toLogLevel)(true)).toBe(domain_1.LogLevel.INFO);
|
|
60
|
+
});
|
|
61
|
+
// --------------------------------------------------------------------------
|
|
62
|
+
// 8. Verifica que el default param funciona correctamente
|
|
63
|
+
// --------------------------------------------------------------------------
|
|
64
|
+
it("debe aplicar correctamente el default proporcionado", () => {
|
|
65
|
+
expect((0, domain_1.toLogLevel)("something", domain_1.LogLevel.WARN)).toBe(domain_1.LogLevel.WARN);
|
|
66
|
+
expect((0, domain_1.toLogLevel)(undefined, domain_1.LogLevel.FATAL)).toBe(domain_1.LogLevel.FATAL);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const domain_1 = require("src/domain");
|
|
4
|
+
describe("normalizeMessage()", () => {
|
|
5
|
+
let redactorMock;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
redactorMock = {
|
|
8
|
+
redact: jest.fn((value) => value), // por defecto retorna lo mismo
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
// -----------------------------------------------------------------------
|
|
12
|
+
// 1️⃣ Si el mensaje es string
|
|
13
|
+
// -----------------------------------------------------------------------
|
|
14
|
+
it("debe redactor un mensaje tipo string", () => {
|
|
15
|
+
const msg = "hello world";
|
|
16
|
+
const result = (0, domain_1.normalizeMessage)(msg, redactorMock);
|
|
17
|
+
expect(redactorMock.redact).toHaveBeenCalledWith("hello world");
|
|
18
|
+
expect(result).toBe("hello world");
|
|
19
|
+
});
|
|
20
|
+
// -----------------------------------------------------------------------
|
|
21
|
+
// 2️⃣ Si el mensaje es objeto
|
|
22
|
+
// -----------------------------------------------------------------------
|
|
23
|
+
it("debe redactor un objeto", () => {
|
|
24
|
+
const msg = { user: "test", email: "a@b.com" };
|
|
25
|
+
const redactedObj = { user: "test", email: "***" };
|
|
26
|
+
// Redactor retorna un valor distinto
|
|
27
|
+
redactorMock.redact.mockReturnValue(redactedObj);
|
|
28
|
+
const result = (0, domain_1.normalizeMessage)(msg, redactorMock);
|
|
29
|
+
expect(redactorMock.redact).toHaveBeenCalledWith(msg);
|
|
30
|
+
expect(result).toEqual(redactedObj);
|
|
31
|
+
});
|
|
32
|
+
// -----------------------------------------------------------------------
|
|
33
|
+
// 3️⃣ Si el mensaje es una función perezosa
|
|
34
|
+
// -----------------------------------------------------------------------
|
|
35
|
+
it("debe evaluar funciones perezosas y redactor su resultado", () => {
|
|
36
|
+
const lazyFn = jest.fn(() => "lazy-result");
|
|
37
|
+
const result = (0, domain_1.normalizeMessage)(lazyFn, redactorMock);
|
|
38
|
+
expect(lazyFn).toHaveBeenCalledTimes(1);
|
|
39
|
+
expect(redactorMock.redact).toHaveBeenCalledWith("lazy-result");
|
|
40
|
+
expect(result).toBe("lazy-result");
|
|
41
|
+
});
|
|
42
|
+
// -----------------------------------------------------------------------
|
|
43
|
+
// 4️⃣ Función perezosa retornando un objeto
|
|
44
|
+
// -----------------------------------------------------------------------
|
|
45
|
+
it("debe procesar funciones perezosas que retornan objetos", () => {
|
|
46
|
+
const lazyObj = jest.fn(() => ({ ok: true }));
|
|
47
|
+
const result = (0, domain_1.normalizeMessage)(lazyObj, redactorMock);
|
|
48
|
+
expect(lazyObj).toHaveBeenCalledTimes(1);
|
|
49
|
+
expect(redactorMock.redact).toHaveBeenCalledWith({ ok: true });
|
|
50
|
+
expect(result).toEqual({ ok: true });
|
|
51
|
+
});
|
|
52
|
+
// -----------------------------------------------------------------------
|
|
53
|
+
// 5️⃣ El redactor puede transformar el valor
|
|
54
|
+
// -----------------------------------------------------------------------
|
|
55
|
+
it("debe retornar el valor transformado por el redactor", () => {
|
|
56
|
+
redactorMock.redact.mockReturnValue("***redacted***");
|
|
57
|
+
const result = (0, domain_1.normalizeMessage)("secret", redactorMock);
|
|
58
|
+
expect(result).toBe("***redacted***");
|
|
59
|
+
});
|
|
60
|
+
// -----------------------------------------------------------------------
|
|
61
|
+
// 6️⃣ Verifica que no ejecuta funciones más de una vez
|
|
62
|
+
// -----------------------------------------------------------------------
|
|
63
|
+
it("solo debe evaluar una vez la función perezosa", () => {
|
|
64
|
+
const lazyFn = jest.fn(() => "value");
|
|
65
|
+
(0, domain_1.normalizeMessage)(lazyFn, redactorMock);
|
|
66
|
+
(0, domain_1.normalizeMessage)(lazyFn, redactorMock);
|
|
67
|
+
// Cada llamada a normalizeMessage ejecuta UNA vez la función
|
|
68
|
+
expect(lazyFn).toHaveBeenCalledTimes(2);
|
|
69
|
+
});
|
|
70
|
+
// -----------------------------------------------------------------------
|
|
71
|
+
// 7️⃣ Mensajes complejos
|
|
72
|
+
// -----------------------------------------------------------------------
|
|
73
|
+
it("debe soportar objetos complejos anidados", () => {
|
|
74
|
+
const complexObj = {
|
|
75
|
+
user: {
|
|
76
|
+
id: 1,
|
|
77
|
+
profile: { email: "a@b.com", phone: "12345" },
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
(0, domain_1.normalizeMessage)(complexObj, redactorMock);
|
|
81
|
+
expect(redactorMock.redact).toHaveBeenCalledWith(complexObj);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const services_1 = require("../../../src/domain/services");
|
|
4
|
+
describe("PiiRedactor", () => {
|
|
5
|
+
// -------------------------------------------------------------------------
|
|
6
|
+
// 1️⃣ Desactivado: debe retornar el valor sin modificar
|
|
7
|
+
// -------------------------------------------------------------------------
|
|
8
|
+
it("debe retornar el valor original si enabled=false", () => {
|
|
9
|
+
const redactor = new services_1.PiiRedactor({ enabled: false });
|
|
10
|
+
expect(redactor.redact("secret 123")).toBe("secret 123");
|
|
11
|
+
expect(redactor.redact({ email: "a@b.com" })).toEqual({
|
|
12
|
+
email: "a@b.com",
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
// -------------------------------------------------------------------------
|
|
16
|
+
// 2️⃣ Aplicación de patrones sobre strings
|
|
17
|
+
// -------------------------------------------------------------------------
|
|
18
|
+
it("debe aplicar patrones sobre strings cuando está enabled", () => {
|
|
19
|
+
const patterns = [
|
|
20
|
+
{ pattern: "\\d+", replaceWith: "[NUM]" },
|
|
21
|
+
{ pattern: "[\\w.-]+@[\\w.-]+\\.[A-Za-z]{2,}", replaceWith: "[EMAIL]" },
|
|
22
|
+
];
|
|
23
|
+
const redactor = new services_1.PiiRedactor({ enabled: true, patterns });
|
|
24
|
+
const result = redactor.redact("Pago 123 por usuario test@example.com");
|
|
25
|
+
expect(result).toBe("Pago [NUM] por usuario [EMAIL]");
|
|
26
|
+
});
|
|
27
|
+
// -------------------------------------------------------------------------
|
|
28
|
+
// 3️⃣ Redacción profunda con objetos anidados
|
|
29
|
+
// -------------------------------------------------------------------------
|
|
30
|
+
it("debe aplicar redacción profunda en objetos con deep=true", () => {
|
|
31
|
+
const patterns = [
|
|
32
|
+
{ pattern: "\\d{10}", replaceWith: "[PHONE]" },
|
|
33
|
+
];
|
|
34
|
+
const redactor = new services_1.PiiRedactor({
|
|
35
|
+
enabled: true,
|
|
36
|
+
deep: true,
|
|
37
|
+
patterns,
|
|
38
|
+
});
|
|
39
|
+
const input = {
|
|
40
|
+
level1: {
|
|
41
|
+
phone: "0987654321",
|
|
42
|
+
nested: {
|
|
43
|
+
phone2: "0987654321",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
const output = redactor.redact(input);
|
|
48
|
+
expect(output.level1.phone).toBe("[PHONE]");
|
|
49
|
+
expect(output.level1.nested.phone2).toBe("[PHONE]");
|
|
50
|
+
// Verifica que no muta el objeto original
|
|
51
|
+
expect(input.level1.phone).toBe("0987654321");
|
|
52
|
+
});
|
|
53
|
+
// -------------------------------------------------------------------------
|
|
54
|
+
// 4️⃣ whitelistKeys debe impedir redacción
|
|
55
|
+
// -------------------------------------------------------------------------
|
|
56
|
+
it("whitelistKeys debe evitar la redacción de claves específicas", () => {
|
|
57
|
+
const patterns = [
|
|
58
|
+
{ pattern: "\\d+", replaceWith: "*" },
|
|
59
|
+
];
|
|
60
|
+
const redactor = new services_1.PiiRedactor({
|
|
61
|
+
enabled: true,
|
|
62
|
+
patterns,
|
|
63
|
+
whitelistKeys: ["safeField"],
|
|
64
|
+
});
|
|
65
|
+
const input = {
|
|
66
|
+
safeField: "12345",
|
|
67
|
+
normalField: "12345",
|
|
68
|
+
};
|
|
69
|
+
const result = redactor.redact(input);
|
|
70
|
+
expect(result.safeField).toBe("12345"); // No redactor
|
|
71
|
+
expect(result.normalField).toBe("*"); // Sí redactor
|
|
72
|
+
});
|
|
73
|
+
// -------------------------------------------------------------------------
|
|
74
|
+
// 5️⃣ blacklistKeys debe forzar redacción total
|
|
75
|
+
// -------------------------------------------------------------------------
|
|
76
|
+
it("blacklistKeys debe forzar '[REDACTED]'", () => {
|
|
77
|
+
const redactor = new services_1.PiiRedactor({
|
|
78
|
+
enabled: true,
|
|
79
|
+
blacklistKeys: ["password"],
|
|
80
|
+
});
|
|
81
|
+
const input = {
|
|
82
|
+
password: "super-secret",
|
|
83
|
+
other: "hello",
|
|
84
|
+
};
|
|
85
|
+
const result = redactor.redact(input);
|
|
86
|
+
expect(result.password).toBe("[REDACTED]");
|
|
87
|
+
expect(result.other).toBe("hello");
|
|
88
|
+
});
|
|
89
|
+
// -------------------------------------------------------------------------
|
|
90
|
+
// 6️⃣ Redacción profunda con arrays
|
|
91
|
+
// -------------------------------------------------------------------------
|
|
92
|
+
it("debe procesar arrays de forma profunda", () => {
|
|
93
|
+
const patterns = [
|
|
94
|
+
{ pattern: "\\d+", replaceWith: "X" },
|
|
95
|
+
];
|
|
96
|
+
const redactor = new services_1.PiiRedactor({
|
|
97
|
+
enabled: true,
|
|
98
|
+
deep: true,
|
|
99
|
+
patterns,
|
|
100
|
+
});
|
|
101
|
+
const input = {
|
|
102
|
+
arr: ["123", "abc", { phone: "456" }],
|
|
103
|
+
};
|
|
104
|
+
const result = redactor.redact(input);
|
|
105
|
+
expect(result.arr[0]).toBe("X");
|
|
106
|
+
expect(result.arr[1]).toBe("abc");
|
|
107
|
+
expect(result.arr[2]).toEqual({ phone: "X" });
|
|
108
|
+
});
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
// 7️⃣ deep=false → no debe entrar a objetos anidados
|
|
111
|
+
// -------------------------------------------------------------------------
|
|
112
|
+
it("deep=false no debe redactor objetos anidados", () => {
|
|
113
|
+
const patterns = [
|
|
114
|
+
{ pattern: "\\d+", replaceWith: "*" },
|
|
115
|
+
];
|
|
116
|
+
const redactor = new services_1.PiiRedactor({
|
|
117
|
+
enabled: true,
|
|
118
|
+
deep: false,
|
|
119
|
+
patterns,
|
|
120
|
+
});
|
|
121
|
+
const input = {
|
|
122
|
+
inner: {
|
|
123
|
+
code: "123",
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
const result = redactor.redact(input);
|
|
127
|
+
expect(result.inner.code).toBe("123"); // No redactor en objetos internos
|
|
128
|
+
});
|
|
129
|
+
// -------------------------------------------------------------------------
|
|
130
|
+
// 8️⃣ Patrón inválido → no debe romper procesamiento
|
|
131
|
+
// -------------------------------------------------------------------------
|
|
132
|
+
it("si un patrón es inválido debe usar fallback y no romper redacción", () => {
|
|
133
|
+
const patterns = [
|
|
134
|
+
{ pattern: "[invalid", replaceWith: "X" }, // inválido
|
|
135
|
+
{ pattern: "\\d+", replaceWith: "#" },
|
|
136
|
+
];
|
|
137
|
+
const redactor = new services_1.PiiRedactor({
|
|
138
|
+
enabled: true,
|
|
139
|
+
patterns,
|
|
140
|
+
});
|
|
141
|
+
const result = redactor.redact("abc123");
|
|
142
|
+
expect(result).toBe("abc#"); // Se aplicó el patrón válido
|
|
143
|
+
});
|
|
144
|
+
// -------------------------------------------------------------------------
|
|
145
|
+
// 9️⃣ updateOptions debe fusionar superficialmente props
|
|
146
|
+
// -------------------------------------------------------------------------
|
|
147
|
+
it("updateOptions debe fusionar opciones sin mutar referencia previa", () => {
|
|
148
|
+
const redactor = new services_1.PiiRedactor({
|
|
149
|
+
enabled: true,
|
|
150
|
+
deep: false,
|
|
151
|
+
});
|
|
152
|
+
redactor.updateOptions({
|
|
153
|
+
deep: true,
|
|
154
|
+
whitelistKeys: ["email"],
|
|
155
|
+
});
|
|
156
|
+
const after = redactor.props;
|
|
157
|
+
expect(after.enabled).toBe(true);
|
|
158
|
+
expect(after.deep).toBe(true);
|
|
159
|
+
expect(after.whitelistKeys).toEqual(["email"]);
|
|
160
|
+
});
|
|
161
|
+
// -------------------------------------------------------------------------
|
|
162
|
+
// 🔟 Tipos no soportados (number, boolean, function) no se redactan
|
|
163
|
+
// -------------------------------------------------------------------------
|
|
164
|
+
it("no debe redactor tipos primitivos no string/objeto", () => {
|
|
165
|
+
const redactor = new services_1.PiiRedactor({ enabled: true });
|
|
166
|
+
expect(redactor.redact(123)).toBe(123);
|
|
167
|
+
expect(redactor.redact(true)).toBe(true);
|
|
168
|
+
expect(redactor.redact(() => "hi")).toBeInstanceOf(Function);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|