@flusys/nestjs-shared 7.1.0 → 8.0.0-beta.1
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/classes/api-controller.class.d.ts +4 -4
- package/classes/api-service.class.d.ts +7 -7
- package/classes/disabled-event-bus.class.d.ts +1 -1
- package/classes/domain-event.publisher.d.ts +1 -1
- package/classes/event-bus.registry.d.ts +1 -1
- package/classes/event-transports/event-transport.registry.d.ts +1 -1
- package/classes/event-transports/index.d.ts +2 -2
- package/classes/event-transports/kafka-transport.optional.d.ts +1 -1
- package/classes/event-transports/memory-transport.d.ts +1 -1
- package/classes/event-transports/rabbitmq-transport.optional.d.ts +1 -1
- package/classes/hybrid-event-bus.class.d.ts +1 -1
- package/classes/index.d.ts +11 -11
- package/constants/index.d.ts +2 -2
- package/decorators/api-response.decorator.d.ts +1 -1
- package/decorators/current-user.decorator.d.ts +1 -1
- package/decorators/index.d.ts +6 -6
- package/decorators/require-permission.decorator.d.ts +1 -1
- package/dtos/filter-and-pagination.dto.d.ts +1 -1
- package/dtos/index.d.ts +6 -6
- package/entities/index.d.ts +3 -3
- package/enums/index.d.ts +3 -3
- package/exceptions/index.d.ts +2 -2
- package/fesm/470.js +2713 -0
- package/fesm/509.js +793 -0
- package/fesm/667.js +606 -0
- package/fesm/67.js +808 -0
- package/fesm/720.js +914 -0
- package/{cjs/classes/event-transports/rabbitmq-transport.optional.js → fesm/chunks/123.js} +78 -55
- package/fesm/{classes/event-transports/kafka-transport.optional.js → chunks/37.js} +74 -41
- package/fesm/classes/index.js +62 -12
- package/fesm/constants/index.js +445 -17
- package/fesm/decorators/index.js +341 -6
- package/fesm/dtos/index.js +23 -6
- package/fesm/entities/index.js +336 -3
- package/fesm/enums/index.js +57 -3
- package/fesm/exceptions/index.js +280 -2
- package/fesm/guards/index.js +1003 -3
- package/fesm/index.js +1524 -14
- package/fesm/interceptors/index.js +924 -5
- package/fesm/interfaces/index.js +57 -11
- package/fesm/middlewares/index.js +33 -1
- package/fesm/modules/index.js +432 -9
- package/fesm/runtime.js +134 -0
- package/fesm/utils/index.js +1045 -8
- package/filters/global-exception.filter.d.ts +1 -1
- package/filters/index.d.ts +1 -1
- package/guards/index.d.ts +3 -3
- package/guards/jwt-auth.guard.d.ts +1 -1
- package/guards/optional-jwt-auth.guard.d.ts +1 -1
- package/guards/permission.guard.d.ts +2 -2
- package/index.d.ts +14 -14
- package/interceptors/delete-empty-id-from-body.interceptor.d.ts +1 -1
- package/interceptors/idempotency.interceptor.d.ts +2 -2
- package/interceptors/index.d.ts +5 -5
- package/interceptors/response-meta.interceptor.d.ts +1 -1
- package/interceptors/set-user-field-on-body.interceptor.d.ts +1 -1
- package/interceptors/slug.interceptor.d.ts +2 -2
- package/interfaces/ai-assistant-adapter.interface.d.ts +1 -1
- package/interfaces/api.interface.d.ts +2 -2
- package/interfaces/datasource.interface.d.ts +1 -1
- package/interfaces/event-manager-adapter.interface.d.ts +2 -2
- package/interfaces/index.d.ts +11 -11
- package/interfaces/module-config.interface.d.ts +1 -1
- package/interfaces/notification-adapter.interface.d.ts +1 -1
- package/middlewares/index.d.ts +1 -1
- package/middlewares/logger.middleware.d.ts +2 -2
- package/modules/cache/cache.module.d.ts +1 -1
- package/modules/datasource/index.d.ts +1 -1
- package/modules/datasource/multi-tenant-datasource.service.d.ts +4 -4
- package/modules/event-bus/event-bus.explorer.d.ts +2 -2
- package/modules/event-bus/event-bus.module.d.ts +2 -2
- package/modules/event-bus/module-events.provider.d.ts +1 -1
- package/modules/index.d.ts +9 -9
- package/modules/shared-permission-cache/shared-permission-cache.service.d.ts +3 -3
- package/modules/utils/utils.service.d.ts +1 -1
- package/package.json +30 -44
- package/utils/index.d.ts +8 -8
- package/utils/permission-cache-key.util.d.ts +1 -1
- package/utils/query-helpers.util.d.ts +2 -2
- package/utils/request.util.d.ts +1 -1
- package/cjs/classes/api-controller.class.js +0 -728
- package/cjs/classes/api-controller.class.spec.js +0 -684
- package/cjs/classes/api-service.class.js +0 -631
- package/cjs/classes/api-service.class.spec.js +0 -1137
- package/cjs/classes/disabled-event-bus.class.js +0 -15
- package/cjs/classes/disabled-event-bus.class.spec.js +0 -40
- package/cjs/classes/domain-event.publisher.js +0 -76
- package/cjs/classes/domain-event.publisher.spec.js +0 -234
- package/cjs/classes/event-bus.registry.js +0 -68
- package/cjs/classes/event-bus.registry.spec.js +0 -141
- package/cjs/classes/event-transports/event-transport.registry.js +0 -41
- package/cjs/classes/event-transports/event-transport.registry.spec.js +0 -31
- package/cjs/classes/event-transports/index.js +0 -23
- package/cjs/classes/event-transports/kafka-transport.optional.js +0 -151
- package/cjs/classes/event-transports/kafka-transport.optional.spec.js +0 -198
- package/cjs/classes/event-transports/memory-transport.js +0 -53
- package/cjs/classes/event-transports/memory-transport.spec.js +0 -57
- package/cjs/classes/event-transports/rabbitmq-transport.optional.spec.js +0 -255
- package/cjs/classes/handle-custome-db.js +0 -37
- package/cjs/classes/handle-custome-db.spec.js +0 -82
- package/cjs/classes/hybrid-cache.class.js +0 -96
- package/cjs/classes/hybrid-cache.class.spec.js +0 -168
- package/cjs/classes/hybrid-event-bus.class.js +0 -281
- package/cjs/classes/hybrid-event-bus.class.spec.js +0 -218
- package/cjs/classes/index.js +0 -28
- package/cjs/classes/winston.logger.class.js +0 -274
- package/cjs/classes/winston.logger.class.spec.js +0 -330
- package/cjs/constants/index.js +0 -88
- package/cjs/constants/message-keys.js +0 -68
- package/cjs/constants/permissions.js +0 -307
- package/cjs/decorators/api-response.decorator.js +0 -52
- package/cjs/decorators/api-response.decorator.spec.js +0 -126
- package/cjs/decorators/current-user.decorator.js +0 -16
- package/cjs/decorators/current-user.decorator.spec.js +0 -74
- package/cjs/decorators/index.js +0 -23
- package/cjs/decorators/log-action.decorator.js +0 -149
- package/cjs/decorators/log-action.decorator.spec.js +0 -353
- package/cjs/decorators/on-domain-event.decorator.js +0 -13
- package/cjs/decorators/on-domain-event.decorator.spec.js +0 -50
- package/cjs/decorators/public.decorator.js +0 -13
- package/cjs/decorators/public.decorator.spec.js +0 -50
- package/cjs/decorators/require-permission.decorator.js +0 -32
- package/cjs/decorators/require-permission.decorator.spec.js +0 -147
- package/cjs/dtos/delete.dto.js +0 -92
- package/cjs/dtos/filter-and-pagination.dto.js +0 -164
- package/cjs/dtos/get-by-ids.dto.js +0 -65
- package/cjs/dtos/identity-response.dto.js +0 -108
- package/cjs/dtos/index.js +0 -23
- package/cjs/dtos/pagination.dto.js +0 -67
- package/cjs/dtos/response-payload.dto.js +0 -369
- package/cjs/entities/identity.js +0 -100
- package/cjs/entities/index.js +0 -20
- package/cjs/entities/raw-type.js +0 -4
- package/cjs/entities/user-root.js +0 -189
- package/cjs/enums/index.js +0 -20
- package/cjs/enums/notification-type.enum.js +0 -17
- package/cjs/enums/participant-status.enum.js +0 -17
- package/cjs/enums/recurrence-type.enum.js +0 -18
- package/cjs/exceptions/base-app.exception.js +0 -151
- package/cjs/exceptions/base-app.exception.spec.js +0 -157
- package/cjs/exceptions/index.js +0 -19
- package/cjs/exceptions/permission.exception.js +0 -56
- package/cjs/exceptions/permission.exception.spec.js +0 -82
- package/cjs/filters/global-exception.filter.js +0 -279
- package/cjs/filters/global-exception.filter.spec.js +0 -440
- package/cjs/filters/index.js +0 -18
- package/cjs/guards/index.js +0 -20
- package/cjs/guards/jwt-auth.guard.js +0 -75
- package/cjs/guards/jwt-auth.guard.spec.js +0 -75
- package/cjs/guards/optional-jwt-auth.guard.js +0 -67
- package/cjs/guards/optional-jwt-auth.guard.spec.js +0 -55
- package/cjs/guards/permission.guard.js +0 -179
- package/cjs/guards/permission.guard.spec.js +0 -373
- package/cjs/index.js +0 -31
- package/cjs/interceptors/delete-empty-id-from-body.interceptor.js +0 -40
- package/cjs/interceptors/delete-empty-id-from-body.interceptor.spec.js +0 -107
- package/cjs/interceptors/idempotency.interceptor.js +0 -96
- package/cjs/interceptors/idempotency.interceptor.spec.js +0 -116
- package/cjs/interceptors/index.js +0 -22
- package/cjs/interceptors/response-meta.interceptor.js +0 -47
- package/cjs/interceptors/response-meta.interceptor.spec.js +0 -89
- package/cjs/interceptors/set-user-field-on-body.interceptor.js +0 -60
- package/cjs/interceptors/set-user-field-on-body.interceptor.spec.js +0 -128
- package/cjs/interceptors/slug.interceptor.js +0 -75
- package/cjs/interceptors/slug.interceptor.spec.js +0 -110
- package/cjs/interfaces/ai-assistant-adapter.interface.js +0 -24
- package/cjs/interfaces/api.interface.js +0 -4
- package/cjs/interfaces/datasource.interface.js +0 -4
- package/cjs/interfaces/event-bus.interface.js +0 -4
- package/cjs/interfaces/event-manager-adapter.interface.js +0 -11
- package/cjs/interfaces/identity.interface.js +0 -4
- package/cjs/interfaces/index.js +0 -28
- package/cjs/interfaces/logged-user-info.interface.js +0 -6
- package/cjs/interfaces/module-config.interface.js +0 -4
- package/cjs/interfaces/notification-adapter.interface.js +0 -11
- package/cjs/interfaces/permission-sync-adapter.interface.js +0 -11
- package/cjs/interfaces/permission.interface.js +0 -4
- package/cjs/middlewares/index.js +0 -18
- package/cjs/middlewares/logger.middleware.js +0 -264
- package/cjs/middlewares/logger.middleware.spec.js +0 -462
- package/cjs/modules/cache/cache.module.js +0 -39
- package/cjs/modules/cache/cache.module.spec.js +0 -48
- package/cjs/modules/datasource/index.js +0 -18
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +0 -222
- package/cjs/modules/datasource/multi-tenant-datasource.service.spec.js +0 -381
- package/cjs/modules/event-bus/event-bus.explorer.js +0 -124
- package/cjs/modules/event-bus/event-bus.explorer.spec.js +0 -189
- package/cjs/modules/event-bus/event-bus.integration.spec.js +0 -253
- package/cjs/modules/event-bus/event-bus.module.js +0 -94
- package/cjs/modules/event-bus/event-bus.module.spec.js +0 -149
- package/cjs/modules/event-bus/module-events.provider.js +0 -39
- package/cjs/modules/event-bus/module-events.provider.spec.js +0 -56
- package/cjs/modules/index.js +0 -26
- package/cjs/modules/shared-permission-cache/shared-permission-cache.module.js +0 -32
- package/cjs/modules/shared-permission-cache/shared-permission-cache.service.js +0 -101
- package/cjs/modules/shared-permission-cache/shared-permission-cache.service.spec.js +0 -165
- package/cjs/modules/utils/utils.module.js +0 -32
- package/cjs/modules/utils/utils.service.js +0 -76
- package/cjs/modules/utils/utils.service.spec.js +0 -169
- package/cjs/utils/date-time.util.js +0 -94
- package/cjs/utils/date-time.util.spec.js +0 -84
- package/cjs/utils/event-pattern.util.js +0 -103
- package/cjs/utils/event-pattern.util.spec.js +0 -251
- package/cjs/utils/html-sanitizer.util.js +0 -64
- package/cjs/utils/html-sanitizer.util.spec.js +0 -76
- package/cjs/utils/index.js +0 -25
- package/cjs/utils/optional-module.util.js +0 -35
- package/cjs/utils/optional-module.util.spec.js +0 -14
- package/cjs/utils/permission-cache-key.util.js +0 -31
- package/cjs/utils/permission-cache-key.util.spec.js +0 -88
- package/cjs/utils/query-helpers.util.js +0 -60
- package/cjs/utils/query-helpers.util.spec.js +0 -209
- package/cjs/utils/request.util.js +0 -96
- package/cjs/utils/request.util.spec.js +0 -249
- package/cjs/utils/string.util.js +0 -63
- package/cjs/utils/string.util.spec.js +0 -63
- package/fesm/classes/api-controller.class.js +0 -718
- package/fesm/classes/api-controller.class.spec.js +0 -680
- package/fesm/classes/api-service.class.js +0 -621
- package/fesm/classes/api-service.class.spec.js +0 -1133
- package/fesm/classes/disabled-event-bus.class.js +0 -11
- package/fesm/classes/disabled-event-bus.class.spec.js +0 -36
- package/fesm/classes/domain-event.publisher.js +0 -63
- package/fesm/classes/domain-event.publisher.spec.js +0 -230
- package/fesm/classes/event-bus.registry.js +0 -64
- package/fesm/classes/event-bus.registry.spec.js +0 -137
- package/fesm/classes/event-transports/event-transport.registry.js +0 -35
- package/fesm/classes/event-transports/event-transport.registry.spec.js +0 -27
- package/fesm/classes/event-transports/index.js +0 -6
- package/fesm/classes/event-transports/kafka-transport.optional.spec.js +0 -194
- package/fesm/classes/event-transports/memory-transport.js +0 -49
- package/fesm/classes/event-transports/memory-transport.spec.js +0 -53
- package/fesm/classes/event-transports/rabbitmq-transport.optional.js +0 -174
- package/fesm/classes/event-transports/rabbitmq-transport.optional.spec.js +0 -251
- package/fesm/classes/handle-custome-db.js +0 -19
- package/fesm/classes/handle-custome-db.spec.js +0 -78
- package/fesm/classes/hybrid-cache.class.js +0 -81
- package/fesm/classes/hybrid-cache.class.spec.js +0 -164
- package/fesm/classes/hybrid-event-bus.class.js +0 -237
- package/fesm/classes/hybrid-event-bus.class.spec.js +0 -214
- package/fesm/classes/winston.logger.class.js +0 -228
- package/fesm/classes/winston.logger.class.spec.js +0 -327
- package/fesm/constants/message-keys.js +0 -49
- package/fesm/constants/permissions.js +0 -215
- package/fesm/decorators/api-response.decorator.js +0 -42
- package/fesm/decorators/api-response.decorator.spec.js +0 -122
- package/fesm/decorators/current-user.decorator.js +0 -18
- package/fesm/decorators/current-user.decorator.spec.js +0 -73
- package/fesm/decorators/log-action.decorator.js +0 -139
- package/fesm/decorators/log-action.decorator.spec.js +0 -349
- package/fesm/decorators/on-domain-event.decorator.js +0 -10
- package/fesm/decorators/on-domain-event.decorator.spec.js +0 -46
- package/fesm/decorators/public.decorator.js +0 -20
- package/fesm/decorators/public.decorator.spec.js +0 -46
- package/fesm/decorators/require-permission.decorator.js +0 -27
- package/fesm/decorators/require-permission.decorator.spec.js +0 -143
- package/fesm/dtos/delete.dto.js +0 -82
- package/fesm/dtos/filter-and-pagination.dto.js +0 -148
- package/fesm/dtos/get-by-ids.dto.js +0 -55
- package/fesm/dtos/identity-response.dto.js +0 -98
- package/fesm/dtos/pagination.dto.js +0 -57
- package/fesm/dtos/response-payload.dto.js +0 -336
- package/fesm/entities/identity.js +0 -90
- package/fesm/entities/raw-type.js +0 -1
- package/fesm/entities/user-root.js +0 -179
- package/fesm/enums/notification-type.enum.js +0 -7
- package/fesm/enums/participant-status.enum.js +0 -7
- package/fesm/enums/recurrence-type.enum.js +0 -8
- package/fesm/exceptions/base-app.exception.js +0 -115
- package/fesm/exceptions/base-app.exception.spec.js +0 -153
- package/fesm/exceptions/permission.exception.js +0 -35
- package/fesm/exceptions/permission.exception.spec.js +0 -78
- package/fesm/filters/global-exception.filter.js +0 -269
- package/fesm/filters/global-exception.filter.spec.js +0 -436
- package/fesm/filters/index.js +0 -1
- package/fesm/guards/jwt-auth.guard.js +0 -65
- package/fesm/guards/jwt-auth.guard.spec.js +0 -71
- package/fesm/guards/optional-jwt-auth.guard.js +0 -57
- package/fesm/guards/optional-jwt-auth.guard.spec.js +0 -51
- package/fesm/guards/permission.guard.js +0 -169
- package/fesm/guards/permission.guard.spec.js +0 -369
- package/fesm/interceptors/delete-empty-id-from-body.interceptor.js +0 -30
- package/fesm/interceptors/delete-empty-id-from-body.interceptor.spec.js +0 -103
- package/fesm/interceptors/idempotency.interceptor.js +0 -86
- package/fesm/interceptors/idempotency.interceptor.spec.js +0 -112
- package/fesm/interceptors/response-meta.interceptor.js +0 -37
- package/fesm/interceptors/response-meta.interceptor.spec.js +0 -85
- package/fesm/interceptors/set-user-field-on-body.interceptor.js +0 -39
- package/fesm/interceptors/set-user-field-on-body.interceptor.spec.js +0 -124
- package/fesm/interceptors/slug.interceptor.js +0 -65
- package/fesm/interceptors/slug.interceptor.spec.js +0 -106
- package/fesm/interfaces/ai-assistant-adapter.interface.js +0 -3
- package/fesm/interfaces/api.interface.js +0 -1
- package/fesm/interfaces/datasource.interface.js +0 -1
- package/fesm/interfaces/event-bus.interface.js +0 -1
- package/fesm/interfaces/event-manager-adapter.interface.js +0 -1
- package/fesm/interfaces/identity.interface.js +0 -1
- package/fesm/interfaces/logged-user-info.interface.js +0 -3
- package/fesm/interfaces/module-config.interface.js +0 -5
- package/fesm/interfaces/notification-adapter.interface.js +0 -1
- package/fesm/interfaces/permission-sync-adapter.interface.js +0 -1
- package/fesm/interfaces/permission.interface.js +0 -1
- package/fesm/middlewares/logger.middleware.js +0 -235
- package/fesm/middlewares/logger.middleware.spec.js +0 -459
- package/fesm/modules/cache/cache.module.js +0 -29
- package/fesm/modules/cache/cache.module.spec.js +0 -44
- package/fesm/modules/datasource/index.js +0 -1
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +0 -212
- package/fesm/modules/datasource/multi-tenant-datasource.service.spec.js +0 -377
- package/fesm/modules/event-bus/event-bus.explorer.js +0 -114
- package/fesm/modules/event-bus/event-bus.explorer.spec.js +0 -185
- package/fesm/modules/event-bus/event-bus.integration.spec.js +0 -249
- package/fesm/modules/event-bus/event-bus.module.js +0 -84
- package/fesm/modules/event-bus/event-bus.module.spec.js +0 -145
- package/fesm/modules/event-bus/module-events.provider.js +0 -26
- package/fesm/modules/event-bus/module-events.provider.spec.js +0 -52
- package/fesm/modules/shared-permission-cache/shared-permission-cache.module.js +0 -22
- package/fesm/modules/shared-permission-cache/shared-permission-cache.service.js +0 -91
- package/fesm/modules/shared-permission-cache/shared-permission-cache.service.spec.js +0 -161
- package/fesm/modules/utils/utils.module.js +0 -22
- package/fesm/modules/utils/utils.service.js +0 -66
- package/fesm/modules/utils/utils.service.spec.js +0 -165
- package/fesm/utils/date-time.util.js +0 -70
- package/fesm/utils/date-time.util.spec.js +0 -80
- package/fesm/utils/event-pattern.util.js +0 -91
- package/fesm/utils/event-pattern.util.spec.js +0 -247
- package/fesm/utils/html-sanitizer.util.js +0 -69
- package/fesm/utils/html-sanitizer.util.spec.js +0 -72
- package/fesm/utils/optional-module.util.js +0 -25
- package/fesm/utils/optional-module.util.spec.js +0 -10
- package/fesm/utils/permission-cache-key.util.js +0 -10
- package/fesm/utils/permission-cache-key.util.spec.js +0 -84
- package/fesm/utils/query-helpers.util.js +0 -85
- package/fesm/utils/query-helpers.util.spec.js +0 -205
- package/fesm/utils/request.util.js +0 -81
- package/fesm/utils/request.util.spec.js +0 -245
- package/fesm/utils/string.util.js +0 -71
- package/fesm/utils/string.util.spec.js +0 -59
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { UtilsService } from './utils.service';
|
|
2
|
-
describe('UtilsService', ()=>{
|
|
3
|
-
let service;
|
|
4
|
-
let mockCache;
|
|
5
|
-
beforeEach(()=>{
|
|
6
|
-
service = new UtilsService();
|
|
7
|
-
mockCache = {
|
|
8
|
-
get: jest.fn(),
|
|
9
|
-
set: jest.fn(),
|
|
10
|
-
del: jest.fn()
|
|
11
|
-
};
|
|
12
|
-
});
|
|
13
|
-
describe('getCacheKey', ()=>{
|
|
14
|
-
it('should build an "all" key from the entity name and params when no id is given', ()=>{
|
|
15
|
-
const key = service.getCacheKey('product', {
|
|
16
|
-
page: 1
|
|
17
|
-
});
|
|
18
|
-
expect(key).toBe('entity_product_all_{"page":1}');
|
|
19
|
-
});
|
|
20
|
-
it('should build a per-id key with a select suffix when params are given', ()=>{
|
|
21
|
-
const key = service.getCacheKey('product', {
|
|
22
|
-
fields: [
|
|
23
|
-
'name'
|
|
24
|
-
]
|
|
25
|
-
}, 'prod-1');
|
|
26
|
-
expect(key).toBe('entity_product_id_prod-1_select_{"fields":["name"]}');
|
|
27
|
-
});
|
|
28
|
-
it('should build a per-id key without a select suffix when params are falsy', ()=>{
|
|
29
|
-
const key = service.getCacheKey('product', null, 'prod-1');
|
|
30
|
-
expect(key).toBe('entity_product_id_prod-1');
|
|
31
|
-
});
|
|
32
|
-
it('should prefix the key with the tenant id when provided', ()=>{
|
|
33
|
-
const key = service.getCacheKey('product', {
|
|
34
|
-
page: 1
|
|
35
|
-
}, undefined, 'tenant-a');
|
|
36
|
-
expect(key).toBe('tenant_tenant-a_entity_product_all_{"page":1}');
|
|
37
|
-
});
|
|
38
|
-
it('should prefix a per-id key with the tenant id too', ()=>{
|
|
39
|
-
const key = service.getCacheKey('product', undefined, 'prod-1', 'tenant-a');
|
|
40
|
-
expect(key).toBe('tenant_tenant-a_entity_product_id_prod-1');
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
describe('trackCacheKey', ()=>{
|
|
44
|
-
it('should create a new tracking list when none exists yet', async ()=>{
|
|
45
|
-
mockCache.get.mockResolvedValue(undefined);
|
|
46
|
-
await service.trackCacheKey('cache-key-1', 'product', mockCache);
|
|
47
|
-
expect(mockCache.set).toHaveBeenCalledWith('entity_product_keys', [
|
|
48
|
-
'cache-key-1'
|
|
49
|
-
]);
|
|
50
|
-
});
|
|
51
|
-
it('should append to an existing tracking list without duplicating entries', async ()=>{
|
|
52
|
-
mockCache.get.mockResolvedValue([
|
|
53
|
-
'cache-key-1'
|
|
54
|
-
]);
|
|
55
|
-
await service.trackCacheKey('cache-key-1', 'product', mockCache);
|
|
56
|
-
expect(mockCache.set).toHaveBeenCalledWith('entity_product_keys', [
|
|
57
|
-
'cache-key-1'
|
|
58
|
-
]);
|
|
59
|
-
});
|
|
60
|
-
it('should append a new key alongside existing ones', async ()=>{
|
|
61
|
-
mockCache.get.mockResolvedValue([
|
|
62
|
-
'cache-key-1'
|
|
63
|
-
]);
|
|
64
|
-
await service.trackCacheKey('cache-key-2', 'product', mockCache);
|
|
65
|
-
expect(mockCache.set).toHaveBeenCalledWith('entity_product_keys', [
|
|
66
|
-
'cache-key-1',
|
|
67
|
-
'cache-key-2'
|
|
68
|
-
]);
|
|
69
|
-
});
|
|
70
|
-
it('should scope the tracking key by entity id and tenant', async ()=>{
|
|
71
|
-
mockCache.get.mockResolvedValue(undefined);
|
|
72
|
-
await service.trackCacheKey('cache-key-1', 'product', mockCache, 'prod-1', 'tenant-a');
|
|
73
|
-
expect(mockCache.get).toHaveBeenCalledWith('tenant_tenant-a_entity_product_id_prod-1_keys');
|
|
74
|
-
expect(mockCache.set).toHaveBeenCalledWith('tenant_tenant-a_entity_product_id_prod-1_keys', [
|
|
75
|
-
'cache-key-1'
|
|
76
|
-
]);
|
|
77
|
-
});
|
|
78
|
-
it('should swallow cache read errors without throwing', async ()=>{
|
|
79
|
-
mockCache.get.mockRejectedValue(new Error('cache down'));
|
|
80
|
-
await expect(service.trackCacheKey('k', 'product', mockCache)).resolves.toBeUndefined();
|
|
81
|
-
expect(mockCache.set).not.toHaveBeenCalled();
|
|
82
|
-
});
|
|
83
|
-
it('should swallow cache write errors without throwing', async ()=>{
|
|
84
|
-
mockCache.get.mockResolvedValue([]);
|
|
85
|
-
mockCache.set.mockRejectedValue(new Error('cache down'));
|
|
86
|
-
await expect(service.trackCacheKey('k', 'product', mockCache)).resolves.toBeUndefined();
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
describe('clearCache', ()=>{
|
|
90
|
-
it('should delete every tracked key plus the tracking key itself', async ()=>{
|
|
91
|
-
mockCache.get.mockResolvedValue([
|
|
92
|
-
'k1',
|
|
93
|
-
'k2'
|
|
94
|
-
]);
|
|
95
|
-
await service.clearCache('product', mockCache);
|
|
96
|
-
expect(mockCache.del).toHaveBeenCalledWith('k1');
|
|
97
|
-
expect(mockCache.del).toHaveBeenCalledWith('k2');
|
|
98
|
-
expect(mockCache.del).toHaveBeenCalledWith('entity_product_keys');
|
|
99
|
-
expect(mockCache.del).toHaveBeenCalledTimes(3);
|
|
100
|
-
});
|
|
101
|
-
it('should only delete the tracking key when no keys were tracked', async ()=>{
|
|
102
|
-
mockCache.get.mockResolvedValue(undefined);
|
|
103
|
-
await service.clearCache('product', mockCache);
|
|
104
|
-
expect(mockCache.del).toHaveBeenCalledTimes(1);
|
|
105
|
-
expect(mockCache.del).toHaveBeenCalledWith('entity_product_keys');
|
|
106
|
-
});
|
|
107
|
-
it('should continue deleting remaining keys even if one deletion rejects', async ()=>{
|
|
108
|
-
mockCache.get.mockResolvedValue([
|
|
109
|
-
'k1',
|
|
110
|
-
'k2'
|
|
111
|
-
]);
|
|
112
|
-
mockCache.del.mockImplementation((key)=>key === 'k1' ? Promise.reject(new Error('fail')) : Promise.resolve());
|
|
113
|
-
await service.clearCache('product', mockCache);
|
|
114
|
-
expect(mockCache.del).toHaveBeenCalledWith('k2');
|
|
115
|
-
expect(mockCache.del).toHaveBeenCalledWith('entity_product_keys');
|
|
116
|
-
});
|
|
117
|
-
it('should scope deletion by entity id and tenant', async ()=>{
|
|
118
|
-
mockCache.get.mockResolvedValue([
|
|
119
|
-
'k1'
|
|
120
|
-
]);
|
|
121
|
-
await service.clearCache('product', mockCache, 'prod-1', 'tenant-a');
|
|
122
|
-
expect(mockCache.get).toHaveBeenCalledWith('tenant_tenant-a_entity_product_id_prod-1_keys');
|
|
123
|
-
});
|
|
124
|
-
it('should swallow errors from the initial cache read', async ()=>{
|
|
125
|
-
mockCache.get.mockRejectedValue(new Error('cache down'));
|
|
126
|
-
await expect(service.clearCache('product', mockCache)).resolves.toBeUndefined();
|
|
127
|
-
expect(mockCache.del).not.toHaveBeenCalled();
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
describe('transformToSlug', ()=>{
|
|
131
|
-
it('should lowercase and dash-separate a normal string', ()=>{
|
|
132
|
-
expect(service.transformToSlug('Hello World')).toBe('hello-world');
|
|
133
|
-
});
|
|
134
|
-
it('should collapse runs of non-alphanumeric characters into a single dash', ()=>{
|
|
135
|
-
expect(service.transformToSlug('Hello World!!!')).toBe('hello-world-');
|
|
136
|
-
});
|
|
137
|
-
it('should trim leading/trailing whitespace before slugging', ()=>{
|
|
138
|
-
expect(service.transformToSlug(' Hello World ')).toBe('hello-world');
|
|
139
|
-
});
|
|
140
|
-
it('should append a random numeric salt when requested', ()=>{
|
|
141
|
-
const randomSpy = jest.spyOn(service, 'getRandomInt').mockReturnValue(42);
|
|
142
|
-
expect(service.transformToSlug('Hello World', true)).toBe('hello-world-42');
|
|
143
|
-
randomSpy.mockRestore();
|
|
144
|
-
});
|
|
145
|
-
it('should handle an empty string', ()=>{
|
|
146
|
-
expect(service.transformToSlug('')).toBe('');
|
|
147
|
-
});
|
|
148
|
-
it('should handle strings that are entirely non-alphanumeric', ()=>{
|
|
149
|
-
expect(service.transformToSlug('!!!')).toBe('-');
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
describe('getRandomInt', ()=>{
|
|
153
|
-
it('should return the exact bound when min equals max', ()=>{
|
|
154
|
-
expect(service.getRandomInt(5, 5)).toBe(5);
|
|
155
|
-
});
|
|
156
|
-
it('should stay within an inclusive [min, max] range across many samples', ()=>{
|
|
157
|
-
for(let i = 0; i < 200; i++){
|
|
158
|
-
const value = service.getRandomInt(1, 10);
|
|
159
|
-
expect(value).toBeGreaterThanOrEqual(1);
|
|
160
|
-
expect(value).toBeLessThanOrEqual(10);
|
|
161
|
-
expect(Number.isInteger(value)).toBe(true);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
function getOffsetMinutes(utcDate, timezone) {
|
|
2
|
-
const extract = (tz)=>{
|
|
3
|
-
const parts = new Intl.DateTimeFormat('en-US', {
|
|
4
|
-
timeZone: tz,
|
|
5
|
-
year: 'numeric',
|
|
6
|
-
month: '2-digit',
|
|
7
|
-
day: '2-digit',
|
|
8
|
-
hour: '2-digit',
|
|
9
|
-
minute: '2-digit',
|
|
10
|
-
second: '2-digit',
|
|
11
|
-
hour12: false
|
|
12
|
-
}).formatToParts(utcDate);
|
|
13
|
-
const p = Object.fromEntries(parts.filter((x)=>x.type !== 'literal').map((x)=>[
|
|
14
|
-
x.type,
|
|
15
|
-
parseInt(x.value)
|
|
16
|
-
]));
|
|
17
|
-
return Date.UTC(p['year'], p['month'] - 1, p['day'], p['hour'], p['minute'], p['second']);
|
|
18
|
-
};
|
|
19
|
-
return (extract(timezone) - extract('UTC')) / 60_000;
|
|
20
|
-
}
|
|
21
|
-
export function localToUtc(dateStr, timeStr, timezone) {
|
|
22
|
-
const [year, month, day] = dateStr.split('-').map(Number);
|
|
23
|
-
const [hours, minutes] = timeStr.split(':').map(Number);
|
|
24
|
-
const approx = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0));
|
|
25
|
-
const offset = getOffsetMinutes(approx, timezone);
|
|
26
|
-
const utcDate = new Date(approx.getTime() - offset * 60_000);
|
|
27
|
-
// DST boundary guard: re-check offset after adjustment
|
|
28
|
-
const finalOffset = getOffsetMinutes(utcDate, timezone);
|
|
29
|
-
return finalOffset !== offset ? new Date(approx.getTime() - finalOffset * 60_000) : utcDate;
|
|
30
|
-
}
|
|
31
|
-
export function utcToLocal(utcDate, timezone) {
|
|
32
|
-
const d = typeof utcDate === 'string' ? new Date(utcDate) : utcDate;
|
|
33
|
-
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
34
|
-
timeZone: timezone,
|
|
35
|
-
year: 'numeric',
|
|
36
|
-
month: '2-digit',
|
|
37
|
-
day: '2-digit',
|
|
38
|
-
hour: '2-digit',
|
|
39
|
-
minute: '2-digit',
|
|
40
|
-
hour12: false
|
|
41
|
-
}).formatToParts(d);
|
|
42
|
-
const p = Object.fromEntries(parts.filter((x)=>x.type !== 'literal').map((x)=>[
|
|
43
|
-
x.type,
|
|
44
|
-
x.value
|
|
45
|
-
]));
|
|
46
|
-
// en-CA formats midnight hour as '24' — normalize to '00'
|
|
47
|
-
const hour = p['hour'] === '24' ? '00' : p['hour'];
|
|
48
|
-
return {
|
|
49
|
-
date: `${p['year']}-${p['month']}-${p['day']}`,
|
|
50
|
-
time: `${hour}:${p['minute']}`,
|
|
51
|
-
display: new Intl.DateTimeFormat('en-US', {
|
|
52
|
-
timeZone: timezone,
|
|
53
|
-
dateStyle: 'medium',
|
|
54
|
-
timeStyle: 'short'
|
|
55
|
-
}).format(d)
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
export function formatInTimezone(utcDate, timezone, options = {
|
|
59
|
-
dateStyle: 'medium',
|
|
60
|
-
timeStyle: 'short'
|
|
61
|
-
}) {
|
|
62
|
-
const d = typeof utcDate === 'string' ? new Date(utcDate) : utcDate;
|
|
63
|
-
return new Intl.DateTimeFormat('en-US', {
|
|
64
|
-
...options,
|
|
65
|
-
timeZone: timezone
|
|
66
|
-
}).format(d);
|
|
67
|
-
}
|
|
68
|
-
export function getTodayInTimezone(timezone) {
|
|
69
|
-
return utcToLocal(new Date(), timezone).date;
|
|
70
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { formatInTimezone, getTodayInTimezone, localToUtc, utcToLocal } from './date-time.util';
|
|
2
|
-
describe('date-time.util', ()=>{
|
|
3
|
-
describe('localToUtc', ()=>{
|
|
4
|
-
it('should convert a local date/time in a fixed-offset timezone to UTC', ()=>{
|
|
5
|
-
// Asia/Dhaka is UTC+6, no DST
|
|
6
|
-
const result = localToUtc('2026-01-15', '10:00', 'Asia/Dhaka');
|
|
7
|
-
expect(result.toISOString()).toBe('2026-01-15T04:00:00.000Z');
|
|
8
|
-
});
|
|
9
|
-
it('should convert local time in UTC timezone unchanged', ()=>{
|
|
10
|
-
const result = localToUtc('2026-06-01', '12:30', 'UTC');
|
|
11
|
-
expect(result.toISOString()).toBe('2026-06-01T12:30:00.000Z');
|
|
12
|
-
});
|
|
13
|
-
it('should handle a negative UTC offset timezone (America/New_York in winter, no DST)', ()=>{
|
|
14
|
-
// America/New_York is UTC-5 in January (standard time, no DST)
|
|
15
|
-
const result = localToUtc('2026-01-15', '09:00', 'America/New_York');
|
|
16
|
-
expect(result.toISOString()).toBe('2026-01-15T14:00:00.000Z');
|
|
17
|
-
});
|
|
18
|
-
it('should account for DST when converting a summer date in America/New_York (UTC-4)', ()=>{
|
|
19
|
-
const result = localToUtc('2026-07-15', '09:00', 'America/New_York');
|
|
20
|
-
expect(result.toISOString()).toBe('2026-07-15T13:00:00.000Z');
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
describe('utcToLocal', ()=>{
|
|
24
|
-
it('should convert a UTC Date to local date/time/display parts', ()=>{
|
|
25
|
-
const result = utcToLocal(new Date('2026-01-15T04:00:00.000Z'), 'Asia/Dhaka');
|
|
26
|
-
expect(result.date).toBe('2026-01-15');
|
|
27
|
-
expect(result.time).toBe('10:00');
|
|
28
|
-
expect(result.display).toEqual(expect.any(String));
|
|
29
|
-
});
|
|
30
|
-
it('should accept a UTC date string as input', ()=>{
|
|
31
|
-
const result = utcToLocal('2026-01-15T04:00:00.000Z', 'Asia/Dhaka');
|
|
32
|
-
expect(result.date).toBe('2026-01-15');
|
|
33
|
-
expect(result.time).toBe('10:00');
|
|
34
|
-
});
|
|
35
|
-
it('should normalize midnight (en-CA "24") to "00"', ()=>{
|
|
36
|
-
// 00:00 Dhaka time == 18:00 UTC the previous day
|
|
37
|
-
const result = utcToLocal('2026-01-14T18:00:00.000Z', 'Asia/Dhaka');
|
|
38
|
-
expect(result.date).toBe('2026-01-15');
|
|
39
|
-
expect(result.time).toBe('00:00');
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
describe('formatInTimezone', ()=>{
|
|
43
|
-
it('should format a date in the given timezone with default options', ()=>{
|
|
44
|
-
const result = formatInTimezone('2026-01-15T04:00:00.000Z', 'Asia/Dhaka');
|
|
45
|
-
expect(typeof result).toBe('string');
|
|
46
|
-
expect(result.length).toBeGreaterThan(0);
|
|
47
|
-
});
|
|
48
|
-
it('should respect custom Intl.DateTimeFormatOptions', ()=>{
|
|
49
|
-
const result = formatInTimezone('2026-01-15T04:00:00.000Z', 'UTC', {
|
|
50
|
-
year: 'numeric'
|
|
51
|
-
});
|
|
52
|
-
expect(result).toBe('2026');
|
|
53
|
-
});
|
|
54
|
-
it('should accept a Date object as input', ()=>{
|
|
55
|
-
const result = formatInTimezone(new Date('2026-01-15T04:00:00.000Z'), 'UTC', {
|
|
56
|
-
year: 'numeric'
|
|
57
|
-
});
|
|
58
|
-
expect(result).toBe('2026');
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
describe('getTodayInTimezone', ()=>{
|
|
62
|
-
it('should return a date string in YYYY-MM-DD format', ()=>{
|
|
63
|
-
const result = getTodayInTimezone('UTC');
|
|
64
|
-
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
65
|
-
});
|
|
66
|
-
it('should match utcToLocal(now, timezone).date', ()=>{
|
|
67
|
-
const now = new Date();
|
|
68
|
-
const expected = utcToLocal(now, 'Asia/Dhaka').date;
|
|
69
|
-
expect(getTodayInTimezone('Asia/Dhaka')).toBe(expected);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
describe('round-trip', ()=>{
|
|
73
|
-
it('should round-trip localToUtc -> utcToLocal for a fixed-offset zone', ()=>{
|
|
74
|
-
const utc = localToUtc('2026-03-10', '08:15', 'Asia/Dhaka');
|
|
75
|
-
const back = utcToLocal(utc, 'Asia/Dhaka');
|
|
76
|
-
expect(back.date).toBe('2026-03-10');
|
|
77
|
-
expect(back.time).toBe('08:15');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
const SENSITIVE_KEYS = [
|
|
2
|
-
'password',
|
|
3
|
-
'secret',
|
|
4
|
-
'token',
|
|
5
|
-
'apikey',
|
|
6
|
-
'authorization',
|
|
7
|
-
'salt',
|
|
8
|
-
'hash',
|
|
9
|
-
'credential',
|
|
10
|
-
'accesskey',
|
|
11
|
-
'accountkey',
|
|
12
|
-
'privatekey',
|
|
13
|
-
'connectionstring',
|
|
14
|
-
'passphrase'
|
|
15
|
-
];
|
|
16
|
-
/**
|
|
17
|
-
* Matched whole, not as a substring - `pass` must be redacted without also
|
|
18
|
-
* redacting `bypassCache`, and a storage row's `key` is a path, not a secret.
|
|
19
|
-
*
|
|
20
|
-
* `config` is the provider settings blob on the storage and email config rows:
|
|
21
|
-
* S3, Azure, SFTP and SMTP credentials live in it, so it never leaves the
|
|
22
|
-
* process, even though the row itself still publishes.
|
|
23
|
-
*/ const SENSITIVE_EXACT_KEYS = [
|
|
24
|
-
'pass',
|
|
25
|
-
'pwd',
|
|
26
|
-
'config'
|
|
27
|
-
];
|
|
28
|
-
const REDACTED = '[REDACTED]';
|
|
29
|
-
const CIRCULAR = '[CIRCULAR]';
|
|
30
|
-
const TRUNCATED = '[TRUNCATED]';
|
|
31
|
-
const BINARY = '[BINARY]';
|
|
32
|
-
const MAX_DEPTH = 8;
|
|
33
|
-
/**
|
|
34
|
-
* Matches a dot separated event name against a routing pattern.
|
|
35
|
-
* `*` matches exactly one segment, `#` and `**` match zero or more segments.
|
|
36
|
-
*/ export function matchEventPattern(pattern, eventName) {
|
|
37
|
-
if (pattern === eventName) return true;
|
|
38
|
-
const patternSegments = pattern.split('.');
|
|
39
|
-
const nameSegments = eventName.split('.');
|
|
40
|
-
return matchSegments(patternSegments, 0, nameSegments, 0);
|
|
41
|
-
}
|
|
42
|
-
export function isMultiWildcard(segment) {
|
|
43
|
-
return segment === '#' || segment === '**';
|
|
44
|
-
}
|
|
45
|
-
function matchSegments(pattern, patternIndex, name, nameIndex) {
|
|
46
|
-
if (patternIndex === pattern.length) return nameIndex === name.length;
|
|
47
|
-
const segment = pattern[patternIndex];
|
|
48
|
-
if (isMultiWildcard(segment)) {
|
|
49
|
-
for(let skip = nameIndex; skip <= name.length; skip++){
|
|
50
|
-
if (matchSegments(pattern, patternIndex + 1, name, skip)) return true;
|
|
51
|
-
}
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
if (nameIndex === name.length) return false;
|
|
55
|
-
if (segment !== '*' && segment !== name[nameIndex]) return false;
|
|
56
|
-
return matchSegments(pattern, patternIndex + 1, name, nameIndex + 1);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Strips credential-like fields before a row leaves the process on an event.
|
|
60
|
-
*
|
|
61
|
-
* Saved TypeORM rows routinely carry bidirectional relations, so the walk
|
|
62
|
-
* tracks the current path and caps its depth - a cycle or a very deep graph
|
|
63
|
-
* must never turn an event into a stack overflow.
|
|
64
|
-
*/ export function sanitizeEventPayload(value) {
|
|
65
|
-
return sanitizeValue(value, new WeakSet(), 0);
|
|
66
|
-
}
|
|
67
|
-
function sanitizeValue(value, ancestors, depth) {
|
|
68
|
-
if (value === null || typeof value !== 'object') return value;
|
|
69
|
-
if (value instanceof Date) return value;
|
|
70
|
-
if (Buffer.isBuffer(value)) return BINARY;
|
|
71
|
-
if (ancestors.has(value)) return CIRCULAR;
|
|
72
|
-
if (depth >= MAX_DEPTH) return TRUNCATED;
|
|
73
|
-
ancestors.add(value);
|
|
74
|
-
try {
|
|
75
|
-
if (Array.isArray(value)) {
|
|
76
|
-
return value.map((item)=>sanitizeValue(item, ancestors, depth + 1));
|
|
77
|
-
}
|
|
78
|
-
const sanitized = {};
|
|
79
|
-
for (const [key, entry] of Object.entries(value)){
|
|
80
|
-
sanitized[key] = isSensitiveKey(key) ? REDACTED : sanitizeValue(entry, ancestors, depth + 1);
|
|
81
|
-
}
|
|
82
|
-
return sanitized;
|
|
83
|
-
} finally{
|
|
84
|
-
ancestors.delete(value);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
function isSensitiveKey(key) {
|
|
88
|
-
const lowered = key.toLowerCase();
|
|
89
|
-
if (SENSITIVE_EXACT_KEYS.includes(lowered)) return true;
|
|
90
|
-
return SENSITIVE_KEYS.some((sensitive)=>lowered.includes(sensitive));
|
|
91
|
-
}
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import { isMultiWildcard, matchEventPattern, sanitizeEventPayload } from './event-pattern.util';
|
|
2
|
-
describe('matchEventPattern', ()=>{
|
|
3
|
-
it('should match an exact event name', ()=>{
|
|
4
|
-
expect(matchEventPattern('storage.folder.created', 'storage.folder.created')).toBe(true);
|
|
5
|
-
});
|
|
6
|
-
it('should not match a different event name', ()=>{
|
|
7
|
-
expect(matchEventPattern('storage.folder.created', 'storage.folder.updated')).toBe(false);
|
|
8
|
-
});
|
|
9
|
-
it('should treat * as exactly one segment', ()=>{
|
|
10
|
-
expect(matchEventPattern('storage.*.created', 'storage.folder.created')).toBe(true);
|
|
11
|
-
expect(matchEventPattern('storage.*', 'storage.folder.created')).toBe(false);
|
|
12
|
-
});
|
|
13
|
-
it('should treat # and ** as any number of segments', ()=>{
|
|
14
|
-
expect(matchEventPattern('storage.#', 'storage.folder.created')).toBe(true);
|
|
15
|
-
expect(matchEventPattern('**', 'auth.appuser.deleted')).toBe(true);
|
|
16
|
-
expect(matchEventPattern('storage.**', 'storage.folder.created')).toBe(true);
|
|
17
|
-
});
|
|
18
|
-
it('should let a multi wildcard match zero segments', ()=>{
|
|
19
|
-
expect(matchEventPattern('storage.folder.created.#', 'storage.folder.created')).toBe(true);
|
|
20
|
-
});
|
|
21
|
-
it('should not match across a different module', ()=>{
|
|
22
|
-
expect(matchEventPattern('storage.#', 'auth.appuser.created')).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
describe('isMultiWildcard', ()=>{
|
|
26
|
-
it.each([
|
|
27
|
-
[
|
|
28
|
-
'#',
|
|
29
|
-
true
|
|
30
|
-
],
|
|
31
|
-
[
|
|
32
|
-
'**',
|
|
33
|
-
true
|
|
34
|
-
],
|
|
35
|
-
[
|
|
36
|
-
'*',
|
|
37
|
-
false
|
|
38
|
-
],
|
|
39
|
-
[
|
|
40
|
-
'folder',
|
|
41
|
-
false
|
|
42
|
-
]
|
|
43
|
-
])('should classify %s as %s', (segment, expected)=>{
|
|
44
|
-
expect(isMultiWildcard(segment)).toBe(expected);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
describe('sanitizeEventPayload', ()=>{
|
|
48
|
-
it('should redact credential-like fields', ()=>{
|
|
49
|
-
const result = sanitizeEventPayload({
|
|
50
|
-
id: '1',
|
|
51
|
-
password: 'secret',
|
|
52
|
-
apiKey: 'k'
|
|
53
|
-
});
|
|
54
|
-
expect(result).toEqual({
|
|
55
|
-
id: '1',
|
|
56
|
-
password: '[REDACTED]',
|
|
57
|
-
apiKey: '[REDACTED]'
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
it('should redact the whole provider settings blob of a storage or email config row', ()=>{
|
|
61
|
-
const result = sanitizeEventPayload({
|
|
62
|
-
name: 'primary-s3',
|
|
63
|
-
provider: 's3',
|
|
64
|
-
isActive: true,
|
|
65
|
-
config: {
|
|
66
|
-
region: 'us-east-1',
|
|
67
|
-
bucket: 'assets',
|
|
68
|
-
accessKeyId: 'AKIA...',
|
|
69
|
-
secretAccessKey: 's3cr3t'
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
expect(result).toEqual({
|
|
73
|
-
name: 'primary-s3',
|
|
74
|
-
provider: 's3',
|
|
75
|
-
isActive: true,
|
|
76
|
-
config: '[REDACTED]'
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
it('should redact the credential keys used by the storage and email providers', ()=>{
|
|
80
|
-
const result = sanitizeEventPayload({
|
|
81
|
-
settings: {
|
|
82
|
-
region: 'us-east-1',
|
|
83
|
-
bucket: 'assets',
|
|
84
|
-
accessKeyId: 'AKIA...',
|
|
85
|
-
secretAccessKey: 's3cr3t',
|
|
86
|
-
accountKey: 'azure-key',
|
|
87
|
-
privateKey: '-----BEGIN...',
|
|
88
|
-
connectionString: 'DefaultEndpointsProtocol=...;AccountKey=abc',
|
|
89
|
-
auth: {
|
|
90
|
-
user: 'mailer',
|
|
91
|
-
pass: 'hunter2'
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
expect(result).toEqual({
|
|
96
|
-
settings: {
|
|
97
|
-
region: 'us-east-1',
|
|
98
|
-
bucket: 'assets',
|
|
99
|
-
accessKeyId: '[REDACTED]',
|
|
100
|
-
secretAccessKey: '[REDACTED]',
|
|
101
|
-
accountKey: '[REDACTED]',
|
|
102
|
-
privateKey: '[REDACTED]',
|
|
103
|
-
connectionString: '[REDACTED]',
|
|
104
|
-
auth: {
|
|
105
|
-
user: 'mailer',
|
|
106
|
-
pass: '[REDACTED]'
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
it('should not over-redact keys that merely contain a credential word', ()=>{
|
|
112
|
-
const result = sanitizeEventPayload({
|
|
113
|
-
key: 'uploads/2026/report.pdf',
|
|
114
|
-
bypassCache: true,
|
|
115
|
-
passengerCount: 4
|
|
116
|
-
});
|
|
117
|
-
expect(result).toEqual({
|
|
118
|
-
key: 'uploads/2026/report.pdf',
|
|
119
|
-
bypassCache: true,
|
|
120
|
-
passengerCount: 4
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
it('should redact nested fields', ()=>{
|
|
124
|
-
const result = sanitizeEventPayload({
|
|
125
|
-
user: {
|
|
126
|
-
name: 'Ada',
|
|
127
|
-
passwordHash: 'x'
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
expect(result).toEqual({
|
|
131
|
-
user: {
|
|
132
|
-
name: 'Ada',
|
|
133
|
-
passwordHash: '[REDACTED]'
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
it('should map over arrays', ()=>{
|
|
138
|
-
const result = sanitizeEventPayload([
|
|
139
|
-
{
|
|
140
|
-
token: 't'
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
token: 'u'
|
|
144
|
-
}
|
|
145
|
-
]);
|
|
146
|
-
expect(result).toEqual([
|
|
147
|
-
{
|
|
148
|
-
token: '[REDACTED]'
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
token: '[REDACTED]'
|
|
152
|
-
}
|
|
153
|
-
]);
|
|
154
|
-
});
|
|
155
|
-
it('should leave dates and primitives untouched', ()=>{
|
|
156
|
-
const date = new Date('2026-01-01T00:00:00.000Z');
|
|
157
|
-
expect(sanitizeEventPayload({
|
|
158
|
-
createdAt: date,
|
|
159
|
-
count: 3
|
|
160
|
-
})).toEqual({
|
|
161
|
-
createdAt: date,
|
|
162
|
-
count: 3
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
it('should mark a circular relation instead of recursing forever', ()=>{
|
|
166
|
-
const folder = {
|
|
167
|
-
id: 'folder-1',
|
|
168
|
-
name: 'Docs'
|
|
169
|
-
};
|
|
170
|
-
const file = {
|
|
171
|
-
id: 'file-1',
|
|
172
|
-
folder
|
|
173
|
-
};
|
|
174
|
-
folder.files = [
|
|
175
|
-
file
|
|
176
|
-
];
|
|
177
|
-
expect(sanitizeEventPayload(folder)).toEqual({
|
|
178
|
-
id: 'folder-1',
|
|
179
|
-
name: 'Docs',
|
|
180
|
-
files: [
|
|
181
|
-
{
|
|
182
|
-
id: 'file-1',
|
|
183
|
-
folder: '[CIRCULAR]'
|
|
184
|
-
}
|
|
185
|
-
]
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
it('should survive a self referencing row', ()=>{
|
|
189
|
-
const row = {
|
|
190
|
-
id: '1'
|
|
191
|
-
};
|
|
192
|
-
row.self = row;
|
|
193
|
-
expect(()=>sanitizeEventPayload(row)).not.toThrow();
|
|
194
|
-
expect(sanitizeEventPayload(row)).toEqual({
|
|
195
|
-
id: '1',
|
|
196
|
-
self: '[CIRCULAR]'
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
it('should repeat a shared row rather than calling the second one circular', ()=>{
|
|
200
|
-
const shared = {
|
|
201
|
-
id: 'shared'
|
|
202
|
-
};
|
|
203
|
-
expect(sanitizeEventPayload([
|
|
204
|
-
{
|
|
205
|
-
a: shared
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
b: shared
|
|
209
|
-
}
|
|
210
|
-
])).toEqual([
|
|
211
|
-
{
|
|
212
|
-
a: {
|
|
213
|
-
id: 'shared'
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
b: {
|
|
218
|
-
id: 'shared'
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
]);
|
|
222
|
-
});
|
|
223
|
-
it('should truncate a graph deeper than the depth cap', ()=>{
|
|
224
|
-
const deep = {};
|
|
225
|
-
let cursor = deep;
|
|
226
|
-
for(let level = 0; level < 12; level++){
|
|
227
|
-
const next = {};
|
|
228
|
-
cursor.child = next;
|
|
229
|
-
cursor = next;
|
|
230
|
-
}
|
|
231
|
-
let result = sanitizeEventPayload(deep);
|
|
232
|
-
for(let level = 0; level < 7; level++){
|
|
233
|
-
result = result.child;
|
|
234
|
-
}
|
|
235
|
-
expect(result.child).toBe('[TRUNCATED]');
|
|
236
|
-
});
|
|
237
|
-
it('should replace a buffer with a marker instead of exploding it into indexes', ()=>{
|
|
238
|
-
const result = sanitizeEventPayload({
|
|
239
|
-
id: '1',
|
|
240
|
-
content: Buffer.from('hello')
|
|
241
|
-
});
|
|
242
|
-
expect(result).toEqual({
|
|
243
|
-
id: '1',
|
|
244
|
-
content: '[BINARY]'
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
});
|