@tstdl/base 0.93.182 → 0.93.184
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/api/server/api-request-token.provider.js +1 -1
- package/api/server/gateway.js +6 -1
- package/authentication/authentication.api.d.ts +13 -40
- package/authentication/authentication.api.js +5 -14
- package/authentication/client/authentication.service.d.ts +6 -14
- package/authentication/client/authentication.service.js +22 -4
- package/authentication/client/module.d.ts +1 -1
- package/authentication/client/module.js +4 -4
- package/authentication/models/index.d.ts +1 -0
- package/authentication/models/index.js +1 -0
- package/authentication/models/totp-results.model.d.ts +11 -0
- package/authentication/models/totp-results.model.js +37 -0
- package/authentication/server/authentication.api-controller.d.ts +3 -3
- package/authentication/server/authentication.api-controller.js +31 -4
- package/authentication/server/authentication.service.d.ts +5 -14
- package/authentication/server/authentication.service.js +6 -4
- package/core.d.ts +0 -5
- package/core.js +0 -8
- package/document-management/api/document-management.api.d.ts +2 -2
- package/document-management/service-models/document.service-model.d.ts +1 -1
- package/examples/config.d.ts +25 -0
- package/examples/config.js +26 -0
- package/notification/server/module.d.ts +1 -1
- package/notification/server/module.js +1 -1
- package/package.json +5 -5
- package/signals/api.d.ts +5 -1
- package/signals/api.js +3 -1
- package/signals/implementation/api.d.ts +10 -1
- package/signals/implementation/api.js +7 -1
- package/signals/implementation/asserts.d.ts +2 -2
- package/signals/implementation/asserts.js +3 -3
- package/signals/implementation/computed.d.ts +7 -34
- package/signals/implementation/computed.js +14 -83
- package/signals/implementation/configure.js +6 -2
- package/signals/implementation/effect.d.ts +65 -46
- package/signals/implementation/effect.js +97 -62
- package/signals/implementation/index.d.ts +2 -4
- package/signals/implementation/index.js +2 -4
- package/signals/implementation/linked_signal.d.ts +36 -0
- package/signals/implementation/linked_signal.js +34 -0
- package/signals/implementation/primitive/computed.d.ts +55 -0
- package/signals/implementation/primitive/computed.js +107 -0
- package/signals/implementation/primitive/effect.d.ts +26 -0
- package/signals/implementation/primitive/effect.js +31 -0
- package/signals/implementation/{equality.d.ts → primitive/equality.d.ts} +1 -1
- package/signals/implementation/{equality.js → primitive/equality.js} +1 -1
- package/signals/implementation/primitive/errors.d.ts +10 -0
- package/signals/implementation/{errors.js → primitive/errors.js} +3 -4
- package/signals/implementation/primitive/formatter.d.ts +19 -0
- package/signals/implementation/primitive/formatter.js +136 -0
- package/signals/implementation/{graph.d.ts → primitive/graph.d.ts} +68 -36
- package/signals/implementation/primitive/graph.js +386 -0
- package/signals/implementation/primitive/linked_signal.d.ts +46 -0
- package/signals/implementation/primitive/linked_signal.js +110 -0
- package/signals/implementation/primitive/signal.d.ts +31 -0
- package/signals/implementation/primitive/signal.js +80 -0
- package/signals/implementation/primitive/untracked.d.ts +12 -0
- package/signals/implementation/primitive/untracked.js +23 -0
- package/signals/implementation/{watch.d.ts → primitive/watch.d.ts} +1 -2
- package/signals/implementation/{watch.js → primitive/watch.js} +22 -16
- package/signals/implementation/resource/api.d.ts +275 -0
- package/signals/implementation/resource/api.js +26 -0
- package/signals/implementation/resource/debounce.d.ts +13 -0
- package/signals/implementation/resource/debounce.js +113 -0
- package/signals/implementation/resource/from_snapshots.d.ts +16 -0
- package/signals/implementation/resource/from_snapshots.js +44 -0
- package/signals/implementation/resource/index.d.ts +11 -0
- package/signals/implementation/resource/index.js +11 -0
- package/signals/implementation/resource/resource.d.ts +110 -0
- package/signals/implementation/resource/resource.js +402 -0
- package/signals/implementation/root_effect_scheduler.d.ts +50 -0
- package/signals/implementation/root_effect_scheduler.js +66 -0
- package/signals/implementation/signal.d.ts +42 -18
- package/signals/implementation/signal.js +29 -49
- package/signals/implementation/to-observable.d.ts +12 -5
- package/signals/implementation/to-observable.js +12 -2
- package/signals/implementation/to-signal.d.ts +9 -18
- package/signals/implementation/to-signal.js +46 -13
- package/signals/implementation/untracked.d.ts +1 -1
- package/signals/implementation/untracked.js +3 -11
- package/signals/operators/debounce.d.ts +8 -0
- package/signals/operators/debounce.js +19 -0
- package/signals/operators/derive-async.js +43 -15
- package/signals/operators/index.d.ts +2 -0
- package/signals/operators/index.js +2 -0
- package/signals/operators/throttle.d.ts +8 -0
- package/signals/operators/throttle.js +31 -0
- package/ai/genkit/tests/multi-region.test.d.ts +0 -2
- package/ai/genkit/tests/multi-region.test.js +0 -179
- package/ai/genkit/tests/token-limit-fallback.test.d.ts +0 -2
- package/ai/genkit/tests/token-limit-fallback.test.js +0 -209
- package/ai/prompts/tests/prompt-builder.test.d.ts +0 -1
- package/ai/prompts/tests/prompt-builder.test.js +0 -22
- package/ai/tests/instructions-formatter.test.d.ts +0 -1
- package/ai/tests/instructions-formatter.test.js +0 -116
- package/ai/tests/steering.test.d.ts +0 -1
- package/ai/tests/steering.test.js +0 -37
- package/api/client/tests/api-client.test.d.ts +0 -1
- package/api/client/tests/api-client.test.js +0 -194
- package/api/server/tests/csrf.middleware.test.d.ts +0 -1
- package/api/server/tests/csrf.middleware.test.js +0 -91
- package/authentication/tests/authentication-password-requirements.validator.test.d.ts +0 -1
- package/authentication/tests/authentication-password-requirements.validator.test.js +0 -29
- package/authentication/tests/authentication.api-controller.test.d.ts +0 -1
- package/authentication/tests/authentication.api-controller.test.js +0 -156
- package/authentication/tests/authentication.api-request-token.provider.test.d.ts +0 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +0 -48
- package/authentication/tests/authentication.client-error-handling.test.d.ts +0 -1
- package/authentication/tests/authentication.client-error-handling.test.js +0 -123
- package/authentication/tests/authentication.client-middleware.test.d.ts +0 -1
- package/authentication/tests/authentication.client-middleware.test.js +0 -118
- package/authentication/tests/authentication.client-service-methods.test.d.ts +0 -1
- package/authentication/tests/authentication.client-service-methods.test.js +0 -177
- package/authentication/tests/authentication.client-service-refresh.test.d.ts +0 -1
- package/authentication/tests/authentication.client-service-refresh.test.js +0 -153
- package/authentication/tests/authentication.client-service.test.d.ts +0 -1
- package/authentication/tests/authentication.client-service.test.js +0 -76
- package/authentication/tests/authentication.refresh-busy-loop.test.d.ts +0 -1
- package/authentication/tests/authentication.refresh-busy-loop.test.js +0 -84
- package/authentication/tests/authentication.service.test.d.ts +0 -1
- package/authentication/tests/authentication.service.test.js +0 -167
- package/authentication/tests/authentication.test-ancillary-service.d.ts +0 -9
- package/authentication/tests/authentication.test-ancillary-service.js +0 -27
- package/authentication/tests/brute-force-protection.test.d.ts +0 -1
- package/authentication/tests/brute-force-protection.test.js +0 -211
- package/authentication/tests/helper.test.d.ts +0 -1
- package/authentication/tests/helper.test.js +0 -122
- package/authentication/tests/password-requirements.error.test.d.ts +0 -1
- package/authentication/tests/password-requirements.error.test.js +0 -14
- package/authentication/tests/remember.api.test.d.ts +0 -1
- package/authentication/tests/remember.api.test.js +0 -117
- package/authentication/tests/remember.service.test.d.ts +0 -1
- package/authentication/tests/remember.service.test.js +0 -83
- package/authentication/tests/subject.service.test.d.ts +0 -1
- package/authentication/tests/subject.service.test.js +0 -140
- package/authentication/tests/suspended-subject.test.d.ts +0 -1
- package/authentication/tests/suspended-subject.test.js +0 -120
- package/authentication/tests/totp.enrollment.test.d.ts +0 -1
- package/authentication/tests/totp.enrollment.test.js +0 -123
- package/authentication/tests/totp.login.test.d.ts +0 -1
- package/authentication/tests/totp.login.test.js +0 -213
- package/authentication/tests/totp.recovery-codes.test.d.ts +0 -1
- package/authentication/tests/totp.recovery-codes.test.js +0 -97
- package/authentication/tests/totp.status.test.d.ts +0 -1
- package/authentication/tests/totp.status.test.js +0 -72
- package/cancellation/tests/coverage.test.d.ts +0 -1
- package/cancellation/tests/coverage.test.js +0 -49
- package/cancellation/tests/leak.test.d.ts +0 -1
- package/cancellation/tests/leak.test.js +0 -35
- package/cancellation/tests/token.test.d.ts +0 -1
- package/cancellation/tests/token.test.js +0 -136
- package/circuit-breaker/tests/circuit-breaker.test.d.ts +0 -1
- package/circuit-breaker/tests/circuit-breaker.test.js +0 -116
- package/cryptography/tests/cryptography.test.d.ts +0 -1
- package/cryptography/tests/cryptography.test.js +0 -175
- package/cryptography/tests/jwt.test.d.ts +0 -1
- package/cryptography/tests/jwt.test.js +0 -54
- package/cryptography/tests/modern.test.d.ts +0 -1
- package/cryptography/tests/modern.test.js +0 -105
- package/cryptography/tests/module.test.d.ts +0 -1
- package/cryptography/tests/module.test.js +0 -100
- package/cryptography/tests/totp.test.d.ts +0 -1
- package/cryptography/tests/totp.test.js +0 -108
- package/document-management/tests/ai-config-hierarchy.test.d.ts +0 -1
- package/document-management/tests/ai-config-hierarchy.test.js +0 -59
- package/document-management/tests/ai-config-integration.test.d.ts +0 -1
- package/document-management/tests/ai-config-integration.test.js +0 -125
- package/document-management/tests/ai-config-merge.test.d.ts +0 -1
- package/document-management/tests/ai-config-merge.test.js +0 -46
- package/document-management/tests/document-management-ai-overrides.test.d.ts +0 -1
- package/document-management/tests/document-management-ai-overrides.test.js +0 -63
- package/document-management/tests/document-management-core.test.d.ts +0 -1
- package/document-management/tests/document-management-core.test.js +0 -157
- package/document-management/tests/document-management.api.test.d.ts +0 -1
- package/document-management/tests/document-management.api.test.js +0 -101
- package/document-management/tests/document-statistics.service.test.d.ts +0 -1
- package/document-management/tests/document-statistics.service.test.js +0 -498
- package/document-management/tests/document-validation-ai-overrides.test.d.ts +0 -1
- package/document-management/tests/document-validation-ai-overrides.test.js +0 -87
- package/document-management/tests/document.service.test.d.ts +0 -1
- package/document-management/tests/document.service.test.js +0 -143
- package/document-management/tests/enum-helpers.test.d.ts +0 -1
- package/document-management/tests/enum-helpers.test.js +0 -452
- package/document-management/tests/helper.d.ts +0 -24
- package/document-management/tests/helper.js +0 -39
- package/errors/tests/format.test.d.ts +0 -1
- package/errors/tests/format.test.js +0 -84
- package/http/tests/server-timing.test.d.ts +0 -1
- package/http/tests/server-timing.test.js +0 -42
- package/injector/tests/advanced.test.d.ts +0 -1
- package/injector/tests/advanced.test.js +0 -116
- package/injector/tests/async-init.test.d.ts +0 -1
- package/injector/tests/async-init.test.js +0 -77
- package/injector/tests/basic.test.d.ts +0 -1
- package/injector/tests/basic.test.js +0 -114
- package/injector/tests/hierarchical.test.d.ts +0 -1
- package/injector/tests/hierarchical.test.js +0 -59
- package/injector/tests/leak.test.d.ts +0 -1
- package/injector/tests/leak.test.js +0 -45
- package/injector/tests/lifecycles.test.d.ts +0 -1
- package/injector/tests/lifecycles.test.js +0 -109
- package/logger/tests/pretty-print.test.d.ts +0 -1
- package/logger/tests/pretty-print.test.js +0 -60
- package/notification/tests/notification-api.test.d.ts +0 -1
- package/notification/tests/notification-api.test.js +0 -124
- package/notification/tests/notification-client.test.d.ts +0 -1
- package/notification/tests/notification-client.test.js +0 -101
- package/notification/tests/notification-flow.test.d.ts +0 -1
- package/notification/tests/notification-flow.test.js +0 -296
- package/notification/tests/notification-sse.service.test.d.ts +0 -1
- package/notification/tests/notification-sse.service.test.js +0 -43
- package/notification/tests/notification-type.service.test.d.ts +0 -1
- package/notification/tests/notification-type.service.test.js +0 -41
- package/object-storage/s3/tests/s3.object-storage.integration.test.d.ts +0 -1
- package/object-storage/s3/tests/s3.object-storage.integration.test.js +0 -303
- package/orm/tests/build-jsonb.test.d.ts +0 -1
- package/orm/tests/build-jsonb.test.js +0 -39
- package/orm/tests/data-types.test.d.ts +0 -1
- package/orm/tests/data-types.test.js +0 -39
- package/orm/tests/database-extension.test.d.ts +0 -1
- package/orm/tests/database-extension.test.js +0 -63
- package/orm/tests/database-migration.test.d.ts +0 -1
- package/orm/tests/database-migration.test.js +0 -83
- package/orm/tests/decorators.test.d.ts +0 -1
- package/orm/tests/decorators.test.js +0 -77
- package/orm/tests/encryption.test.d.ts +0 -1
- package/orm/tests/encryption.test.js +0 -31
- package/orm/tests/query-complex.test.d.ts +0 -1
- package/orm/tests/query-complex.test.js +0 -172
- package/orm/tests/query-converter-complex.test.d.ts +0 -1
- package/orm/tests/query-converter-complex.test.js +0 -131
- package/orm/tests/query-converter.test.d.ts +0 -1
- package/orm/tests/query-converter.test.js +0 -123
- package/orm/tests/repository-advanced.test.d.ts +0 -1
- package/orm/tests/repository-advanced.test.js +0 -189
- package/orm/tests/repository-attributes.test.d.ts +0 -1
- package/orm/tests/repository-attributes.test.js +0 -83
- package/orm/tests/repository-compound-primary-key.test.d.ts +0 -2
- package/orm/tests/repository-compound-primary-key.test.js +0 -226
- package/orm/tests/repository-comprehensive.test.d.ts +0 -1
- package/orm/tests/repository-comprehensive.test.js +0 -162
- package/orm/tests/repository-coverage.test.d.ts +0 -2
- package/orm/tests/repository-coverage.test.js +0 -242
- package/orm/tests/repository-cti-complex.test.d.ts +0 -1
- package/orm/tests/repository-cti-complex.test.js +0 -151
- package/orm/tests/repository-cti-embedded.test.d.ts +0 -1
- package/orm/tests/repository-cti-embedded.test.js +0 -178
- package/orm/tests/repository-cti-extensive.test.d.ts +0 -2
- package/orm/tests/repository-cti-extensive.test.js +0 -279
- package/orm/tests/repository-cti-mapping.test.d.ts +0 -2
- package/orm/tests/repository-cti-mapping.test.js +0 -108
- package/orm/tests/repository-cti-search.test.d.ts +0 -1
- package/orm/tests/repository-cti-search.test.js +0 -141
- package/orm/tests/repository-cti-soft-delete.test.d.ts +0 -2
- package/orm/tests/repository-cti-soft-delete.test.js +0 -103
- package/orm/tests/repository-cti-transactions.test.d.ts +0 -1
- package/orm/tests/repository-cti-transactions.test.js +0 -112
- package/orm/tests/repository-cti-upsert-many.test.d.ts +0 -2
- package/orm/tests/repository-cti-upsert-many.test.js +0 -115
- package/orm/tests/repository-cti.test.d.ts +0 -2
- package/orm/tests/repository-cti.test.js +0 -390
- package/orm/tests/repository-edge-cases.test.d.ts +0 -1
- package/orm/tests/repository-edge-cases.test.js +0 -178
- package/orm/tests/repository-expiration.test.d.ts +0 -2
- package/orm/tests/repository-expiration.test.js +0 -140
- package/orm/tests/repository-extra-coverage.test.d.ts +0 -2
- package/orm/tests/repository-extra-coverage.test.js +0 -402
- package/orm/tests/repository-mapping.test.d.ts +0 -2
- package/orm/tests/repository-mapping.test.js +0 -65
- package/orm/tests/repository-regression.test.d.ts +0 -1
- package/orm/tests/repository-regression.test.js +0 -288
- package/orm/tests/repository-search-coverage.test.d.ts +0 -1
- package/orm/tests/repository-search-coverage.test.js +0 -107
- package/orm/tests/repository-search.test.d.ts +0 -1
- package/orm/tests/repository-search.test.js +0 -105
- package/orm/tests/repository-soft-delete.test.d.ts +0 -1
- package/orm/tests/repository-soft-delete.test.js +0 -118
- package/orm/tests/repository-transactions-nested.test.d.ts +0 -1
- package/orm/tests/repository-transactions-nested.test.js +0 -178
- package/orm/tests/repository-types.test.d.ts +0 -1
- package/orm/tests/repository-types.test.js +0 -184
- package/orm/tests/repository-undelete.test.d.ts +0 -2
- package/orm/tests/repository-undelete.test.js +0 -201
- package/orm/tests/schema-converter.test.d.ts +0 -1
- package/orm/tests/schema-converter.test.js +0 -82
- package/orm/tests/schema-generation.test.d.ts +0 -2
- package/orm/tests/schema-generation.test.js +0 -174
- package/orm/tests/sql-helpers.test.d.ts +0 -1
- package/orm/tests/sql-helpers.test.js +0 -67
- package/orm/tests/transaction-safety.test.d.ts +0 -1
- package/orm/tests/transaction-safety.test.js +0 -81
- package/orm/tests/transactional.test.d.ts +0 -1
- package/orm/tests/transactional.test.js +0 -215
- package/orm/tests/utils.test.d.ts +0 -1
- package/orm/tests/utils.test.js +0 -70
- package/pdf/tests/utils.test.d.ts +0 -1
- package/pdf/tests/utils.test.js +0 -187
- package/process/tests/spawn.test.d.ts +0 -1
- package/process/tests/spawn.test.js +0 -182
- package/rate-limit/tests/postgres-rate-limiter.test.d.ts +0 -1
- package/rate-limit/tests/postgres-rate-limiter.test.js +0 -84
- package/renderer/tests/renderer.test.d.ts +0 -1
- package/renderer/tests/renderer.test.js +0 -88
- package/rpc/tests/rpc.integration.test.d.ts +0 -1
- package/rpc/tests/rpc.integration.test.js +0 -615
- package/signals/implementation/errors.d.ts +0 -2
- package/signals/implementation/graph.js +0 -312
- package/signals/implementation/writable-signal.d.ts +0 -48
- package/signals/implementation/writable-signal.js +0 -32
- package/task-queue/tests/coverage-branch.test.d.ts +0 -1
- package/task-queue/tests/coverage-branch.test.js +0 -395
- package/task-queue/tests/coverage-enhancement.test.d.ts +0 -1
- package/task-queue/tests/coverage-enhancement.test.js +0 -150
- package/task-queue/tests/dag.test.d.ts +0 -1
- package/task-queue/tests/dag.test.js +0 -188
- package/task-queue/tests/dependencies.test.d.ts +0 -1
- package/task-queue/tests/dependencies.test.js +0 -296
- package/task-queue/tests/enqueue-batch.test.d.ts +0 -1
- package/task-queue/tests/enqueue-batch.test.js +0 -125
- package/task-queue/tests/enqueue-item.test.d.ts +0 -1
- package/task-queue/tests/enqueue-item.test.js +0 -12
- package/task-queue/tests/fan-out-spawning.test.d.ts +0 -1
- package/task-queue/tests/fan-out-spawning.test.js +0 -94
- package/task-queue/tests/idempotent-replacement.test.d.ts +0 -1
- package/task-queue/tests/idempotent-replacement.test.js +0 -114
- package/task-queue/tests/missing-idempotent-tasks.test.d.ts +0 -1
- package/task-queue/tests/missing-idempotent-tasks.test.js +0 -39
- package/task-queue/tests/optimization-edge-cases.test.d.ts +0 -1
- package/task-queue/tests/optimization-edge-cases.test.js +0 -124
- package/task-queue/tests/queue-generic.test.d.ts +0 -1
- package/task-queue/tests/queue-generic.test.js +0 -8
- package/task-queue/tests/queue.test.d.ts +0 -1
- package/task-queue/tests/queue.test.js +0 -756
- package/task-queue/tests/shutdown.test.d.ts +0 -1
- package/task-queue/tests/shutdown.test.js +0 -41
- package/task-queue/tests/task-context.test.d.ts +0 -1
- package/task-queue/tests/task-context.test.js +0 -7
- package/task-queue/tests/task-union.test.d.ts +0 -1
- package/task-queue/tests/task-union.test.js +0 -18
- package/task-queue/tests/transactions.test.d.ts +0 -1
- package/task-queue/tests/transactions.test.js +0 -47
- package/task-queue/tests/typing.test.d.ts +0 -1
- package/task-queue/tests/typing.test.js +0 -9
- package/task-queue/tests/worker.test.d.ts +0 -1
- package/task-queue/tests/worker.test.js +0 -258
- package/task-queue/tests/zombie-parent.test.d.ts +0 -1
- package/task-queue/tests/zombie-parent.test.js +0 -45
- package/task-queue/tests/zombie-recovery.test.d.ts +0 -1
- package/task-queue/tests/zombie-recovery.test.js +0 -51
- package/utils/tests/backoff.test.d.ts +0 -1
- package/utils/tests/backoff.test.js +0 -41
- package/utils/tests/retry-with-backoff.test.d.ts +0 -1
- package/utils/tests/retry-with-backoff.test.js +0 -49
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { Subject } from 'rxjs';
|
|
2
|
-
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
-
import { SubjectService } from '../../authentication/server/subject.service.js';
|
|
4
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
5
|
-
import { NotificationChannel } from '../models/index.js';
|
|
6
|
-
import { NotificationApiController } from '../server/api/notification.api-controller.js';
|
|
7
|
-
import { NotificationSseService } from '../server/services/notification-sse.service.js';
|
|
8
|
-
import { NotificationTypeService } from '../server/services/notification-type.service.js';
|
|
9
|
-
import { NotificationService } from '../server/services/notification.service.js';
|
|
10
|
-
describe('Notification API (Integration)', () => {
|
|
11
|
-
let injector;
|
|
12
|
-
let database;
|
|
13
|
-
let controller;
|
|
14
|
-
let notificationService;
|
|
15
|
-
let sseService;
|
|
16
|
-
let subjectService;
|
|
17
|
-
const schema = 'notification';
|
|
18
|
-
const tenantId = crypto.randomUUID();
|
|
19
|
-
let userId;
|
|
20
|
-
beforeAll(async () => {
|
|
21
|
-
({ injector, database } = await setupIntegrationTest({
|
|
22
|
-
orm: { schema },
|
|
23
|
-
modules: {
|
|
24
|
-
notification: true,
|
|
25
|
-
authentication: true,
|
|
26
|
-
taskQueue: true,
|
|
27
|
-
rateLimiter: true,
|
|
28
|
-
},
|
|
29
|
-
}));
|
|
30
|
-
controller = injector.resolve(NotificationApiController);
|
|
31
|
-
notificationService = injector.resolve(NotificationService);
|
|
32
|
-
sseService = injector.resolve(NotificationSseService);
|
|
33
|
-
subjectService = injector.resolve(SubjectService);
|
|
34
|
-
});
|
|
35
|
-
beforeEach(async () => {
|
|
36
|
-
vi.clearAllMocks();
|
|
37
|
-
await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
|
|
38
|
-
// Create a dummy user
|
|
39
|
-
const user = await subjectService.createUser({
|
|
40
|
-
tenantId,
|
|
41
|
-
email: 'api-test@example.com',
|
|
42
|
-
firstName: 'Api',
|
|
43
|
-
lastName: 'Test',
|
|
44
|
-
});
|
|
45
|
-
userId = user.id;
|
|
46
|
-
});
|
|
47
|
-
afterEach(async () => {
|
|
48
|
-
await clearTenantData(database, schema, ['in_app', 'in_app_archive', 'log', 'preference', 'web_push_subscription'], tenantId);
|
|
49
|
-
await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
|
|
50
|
-
});
|
|
51
|
-
const createMockContext = (params = {}) => ({
|
|
52
|
-
parameters: params,
|
|
53
|
-
abortSignal: new AbortController().signal,
|
|
54
|
-
serverSentEvents: {},
|
|
55
|
-
getToken: async () => ({
|
|
56
|
-
payload: {
|
|
57
|
-
tenant: tenantId,
|
|
58
|
-
subject: userId,
|
|
59
|
-
},
|
|
60
|
-
}),
|
|
61
|
-
}); // Cast to any for controller compatibility if definition is strict
|
|
62
|
-
test('stream should register sse client', async () => {
|
|
63
|
-
const registerSpy = vi.spyOn(sseService, 'register');
|
|
64
|
-
const subject = new Subject();
|
|
65
|
-
// Mock register to return a dummy source or similar if needed,
|
|
66
|
-
// but the real one might work if it just returns an object.
|
|
67
|
-
// Looking at sseService, register returns a ServerSentEventsSource.
|
|
68
|
-
// We might need to mock implementation if it tries to do something complex
|
|
69
|
-
registerSpy.mockImplementation(() => subject);
|
|
70
|
-
const generator = controller.stream(createMockContext());
|
|
71
|
-
const nextPromise = generator.next();
|
|
72
|
-
subject.complete();
|
|
73
|
-
await nextPromise;
|
|
74
|
-
expect(registerSpy).toHaveBeenCalledWith(tenantId, userId);
|
|
75
|
-
});
|
|
76
|
-
test('types should call service', async () => {
|
|
77
|
-
const notificationTypeService = injector.resolve(NotificationTypeService);
|
|
78
|
-
const getTypesSpy = vi.spyOn(notificationTypeService, 'getTypes').mockResolvedValue({ test: 'Test' });
|
|
79
|
-
const result = await controller.types();
|
|
80
|
-
expect(result).toEqual({ test: 'Test' });
|
|
81
|
-
expect(getTypesSpy).toHaveBeenCalled();
|
|
82
|
-
});
|
|
83
|
-
test('listInApp should call service', async () => {
|
|
84
|
-
const listInAppSpy = vi.spyOn(notificationService, 'listInApp').mockResolvedValue([]);
|
|
85
|
-
const params = { limit: 10, offset: 0 };
|
|
86
|
-
await controller.listInApp(createMockContext(params));
|
|
87
|
-
expect(listInAppSpy).toHaveBeenCalledWith(tenantId, userId, params);
|
|
88
|
-
});
|
|
89
|
-
test('listArchivedInApp should call service', async () => {
|
|
90
|
-
const listArchivedInAppSpy = vi.spyOn(notificationService, 'listArchivedInApp').mockResolvedValue([]);
|
|
91
|
-
const params = { limit: 10, offset: 0 };
|
|
92
|
-
await controller.listArchivedInApp(createMockContext(params));
|
|
93
|
-
expect(listArchivedInAppSpy).toHaveBeenCalledWith(tenantId, userId, params);
|
|
94
|
-
});
|
|
95
|
-
test('markRead should call service', async () => {
|
|
96
|
-
const markReadSpy = vi.spyOn(notificationService, 'markRead').mockResolvedValue();
|
|
97
|
-
const params = { id: 'notif-id' };
|
|
98
|
-
await controller.markRead(createMockContext(params));
|
|
99
|
-
expect(markReadSpy).toHaveBeenCalledWith(tenantId, userId, 'notif-id');
|
|
100
|
-
});
|
|
101
|
-
test('archive should call service', async () => {
|
|
102
|
-
const archiveSpy = vi.spyOn(notificationService, 'archive').mockResolvedValue();
|
|
103
|
-
const params = { id: 'notif-id' };
|
|
104
|
-
await controller.archive(createMockContext(params));
|
|
105
|
-
expect(archiveSpy).toHaveBeenCalledWith(tenantId, userId, 'notif-id');
|
|
106
|
-
});
|
|
107
|
-
test('getPreferences should call service', async () => {
|
|
108
|
-
const getPreferencesSpy = vi.spyOn(notificationService, 'getPreferences').mockResolvedValue([]);
|
|
109
|
-
await controller.getPreferences(createMockContext());
|
|
110
|
-
expect(getPreferencesSpy).toHaveBeenCalledWith(tenantId, userId);
|
|
111
|
-
});
|
|
112
|
-
test('updatePreferences should call service', async () => {
|
|
113
|
-
const updatePreferencesSpy = vi.spyOn(notificationService, 'updatePreferences').mockResolvedValue();
|
|
114
|
-
const params = [{ type: 'test', channel: NotificationChannel.Email, enabled: true }];
|
|
115
|
-
await controller.updatePreferences(createMockContext(params));
|
|
116
|
-
expect(updatePreferencesSpy).toHaveBeenCalledWith(tenantId, userId, params);
|
|
117
|
-
});
|
|
118
|
-
test('registerWebPush should call service', async () => {
|
|
119
|
-
const registerWebPushSpy = vi.spyOn(notificationService, 'registerWebPush').mockResolvedValue();
|
|
120
|
-
const params = { endpoint: 'url', keys: { p256dhBase64: 'key', authBase64: 'auth' } };
|
|
121
|
-
await controller.registerWebPush(createMockContext(params));
|
|
122
|
-
expect(registerWebPushSpy).toHaveBeenCalledWith(tenantId, userId, 'url', expect.any(Uint8Array), expect.any(Uint8Array));
|
|
123
|
-
});
|
|
124
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
-
import { AuthenticationClientService } from '../../authentication/client/authentication.service.js';
|
|
3
|
-
import { Injector } from '../../injector/index.js';
|
|
4
|
-
import { NotificationApiClient } from '../../notification/api/index.js';
|
|
5
|
-
import { NotificationClient } from '../../notification/client/notification-client.js';
|
|
6
|
-
import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
|
|
7
|
-
import { timeout } from '../../utils/timing.js';
|
|
8
|
-
import { BehaviorSubject, of, Subject } from 'rxjs';
|
|
9
|
-
describe('NotificationClient', () => {
|
|
10
|
-
let injector;
|
|
11
|
-
let authenticationServiceMock;
|
|
12
|
-
let notificationApiClientMock;
|
|
13
|
-
let notificationClient;
|
|
14
|
-
const sessionId$ = new BehaviorSubject(undefined);
|
|
15
|
-
const stream$ = new Subject();
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
configureDefaultSignalsImplementation();
|
|
18
|
-
injector = new Injector('TestInjector');
|
|
19
|
-
authenticationServiceMock = {
|
|
20
|
-
sessionId$,
|
|
21
|
-
};
|
|
22
|
-
notificationApiClientMock = {
|
|
23
|
-
listInApp: vi.fn().mockResolvedValue([]),
|
|
24
|
-
unreadCount: vi.fn().mockResolvedValue(0),
|
|
25
|
-
types: vi.fn().mockResolvedValue({}),
|
|
26
|
-
stream: vi.fn().mockReturnValue(of(stream$)),
|
|
27
|
-
};
|
|
28
|
-
injector.register(AuthenticationClientService, { useValue: authenticationServiceMock });
|
|
29
|
-
injector.register(NotificationApiClient, { useValue: notificationApiClientMock });
|
|
30
|
-
notificationClient = injector.resolve(NotificationClient);
|
|
31
|
-
});
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
vi.clearAllMocks();
|
|
34
|
-
sessionId$.next(undefined);
|
|
35
|
-
});
|
|
36
|
-
test('should initialize with empty state', () => {
|
|
37
|
-
expect(notificationClient.notifications()).toEqual([]);
|
|
38
|
-
expect(notificationClient.unreadCount()).toBe(0);
|
|
39
|
-
expect(notificationClient.types()).toEqual({});
|
|
40
|
-
});
|
|
41
|
-
test('should load notifications on session start', async () => {
|
|
42
|
-
const notifications = [{ id: '1', type: 'test' }];
|
|
43
|
-
const unreadCount = 5;
|
|
44
|
-
const types = { test: 'Test' };
|
|
45
|
-
notificationApiClientMock.listInApp.mockResolvedValue(notifications);
|
|
46
|
-
notificationApiClientMock.unreadCount.mockResolvedValue(unreadCount);
|
|
47
|
-
notificationApiClientMock.types.mockResolvedValue(types);
|
|
48
|
-
sessionId$.next('session-1');
|
|
49
|
-
// Wait for async operations (microtasks)
|
|
50
|
-
await timeout(0);
|
|
51
|
-
expect(notificationClient.notifications()).toEqual(notifications);
|
|
52
|
-
expect(notificationClient.unreadCount()).toBe(unreadCount);
|
|
53
|
-
expect(notificationClient.types()).toEqual(types);
|
|
54
|
-
expect(notificationApiClientMock.listInApp).toHaveBeenCalledWith({ limit: 20 });
|
|
55
|
-
expect(notificationApiClientMock.unreadCount).toHaveBeenCalled();
|
|
56
|
-
expect(notificationApiClientMock.types).toHaveBeenCalled();
|
|
57
|
-
expect(notificationApiClientMock.stream).toHaveBeenCalled();
|
|
58
|
-
});
|
|
59
|
-
test('should clear notifications on session end', async () => {
|
|
60
|
-
const notifications = [{ id: '1', type: 'test' }];
|
|
61
|
-
notificationApiClientMock.listInApp.mockResolvedValue(notifications);
|
|
62
|
-
sessionId$.next('session-1');
|
|
63
|
-
await timeout(0);
|
|
64
|
-
expect(notificationClient.notifications()).toHaveLength(1);
|
|
65
|
-
sessionId$.next(undefined);
|
|
66
|
-
await timeout(0);
|
|
67
|
-
expect(notificationClient.notifications()).toEqual([]);
|
|
68
|
-
expect(notificationClient.unreadCount()).toBe(0);
|
|
69
|
-
});
|
|
70
|
-
test('should handle new notification from stream', async () => {
|
|
71
|
-
const initialNotifications = [{ id: '1', type: 'test' }];
|
|
72
|
-
notificationApiClientMock.listInApp.mockResolvedValue(initialNotifications);
|
|
73
|
-
sessionId$.next('session-1');
|
|
74
|
-
await timeout(0);
|
|
75
|
-
const newNotification = { id: '2', type: 'test' };
|
|
76
|
-
stream$.next({ notification: newNotification, unreadCount: 1 });
|
|
77
|
-
expect(notificationClient.notifications()).toEqual([newNotification, ...initialNotifications]);
|
|
78
|
-
expect(notificationClient.unreadCount()).toBe(1);
|
|
79
|
-
});
|
|
80
|
-
test('should handle unread count update from stream', async () => {
|
|
81
|
-
notificationApiClientMock.listInApp.mockResolvedValue([]);
|
|
82
|
-
sessionId$.next('session-1');
|
|
83
|
-
await timeout(0);
|
|
84
|
-
stream$.next({ unreadCount: 10 });
|
|
85
|
-
expect(notificationClient.unreadCount()).toBe(10);
|
|
86
|
-
});
|
|
87
|
-
test('should load next page of notifications', async () => {
|
|
88
|
-
const page1 = [{ id: '2', type: 'test' }];
|
|
89
|
-
const page2 = [{ id: '1', type: 'test' }];
|
|
90
|
-
notificationApiClientMock.listInApp
|
|
91
|
-
.mockResolvedValueOnce(page1) // Initial load
|
|
92
|
-
.mockResolvedValueOnce(page2); // Pagination
|
|
93
|
-
sessionId$.next('session-1');
|
|
94
|
-
await timeout(0);
|
|
95
|
-
expect(notificationClient.notifications()).toEqual(page1);
|
|
96
|
-
notificationClient.loadNext(10);
|
|
97
|
-
await timeout(0);
|
|
98
|
-
expect(notificationClient.notifications()).toEqual([...page1, ...page2]);
|
|
99
|
-
expect(notificationApiClientMock.listInApp).toHaveBeenCalledWith({ limit: 10, after: '2' });
|
|
100
|
-
});
|
|
101
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
};
|
|
7
|
-
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
8
|
-
import { SubjectService } from '../../authentication/server/subject.service.js';
|
|
9
|
-
import { Singleton } from '../../injector/index.js';
|
|
10
|
-
import { MailService } from '../../mail/mail.service.js';
|
|
11
|
-
import { getRepository } from '../../orm/server/index.js';
|
|
12
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
13
|
-
import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationStatus, WebPushSubscription } from '../models/index.js';
|
|
14
|
-
import { configureNotification, EmailChannelProvider, InAppChannelProvider, NotificationAncillaryService, NotificationDeliveryWorker, NotificationService, NotificationSseService, NotificationTypeService } from '../server/index.js';
|
|
15
|
-
let MockNotificationAncillaryService = class MockNotificationAncillaryService extends NotificationAncillaryService {
|
|
16
|
-
async getViewData(_tenantId, notifications) {
|
|
17
|
-
return notifications.map(() => ({}));
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
MockNotificationAncillaryService = __decorate([
|
|
21
|
-
Singleton()
|
|
22
|
-
], MockNotificationAncillaryService);
|
|
23
|
-
describe('Notification Flow (Integration)', () => {
|
|
24
|
-
let injector;
|
|
25
|
-
let database;
|
|
26
|
-
let notificationService;
|
|
27
|
-
let worker;
|
|
28
|
-
let typeService;
|
|
29
|
-
let subjectService;
|
|
30
|
-
let mailServiceMock;
|
|
31
|
-
let logRepo;
|
|
32
|
-
let inAppRepo;
|
|
33
|
-
let webPushRepo;
|
|
34
|
-
const schema = 'notification';
|
|
35
|
-
let tenantId;
|
|
36
|
-
beforeAll(async () => {
|
|
37
|
-
({ injector, database } = await setupIntegrationTest({
|
|
38
|
-
orm: { schema },
|
|
39
|
-
modules: {
|
|
40
|
-
taskQueue: true,
|
|
41
|
-
rateLimiter: true,
|
|
42
|
-
authentication: true,
|
|
43
|
-
messageBus: true,
|
|
44
|
-
signals: true,
|
|
45
|
-
notification: true,
|
|
46
|
-
},
|
|
47
|
-
}));
|
|
48
|
-
// Mock MailService
|
|
49
|
-
mailServiceMock = { send: vi.fn() };
|
|
50
|
-
injector.register(MailService, { useValue: mailServiceMock });
|
|
51
|
-
// Mock NotificationSseService
|
|
52
|
-
injector.register(NotificationSseService, { useValue: { dispatch: vi.fn() } });
|
|
53
|
-
// Mock NotificationAncillaryService
|
|
54
|
-
configureNotification({ injector, ancillaryService: MockNotificationAncillaryService });
|
|
55
|
-
// Resolve Services
|
|
56
|
-
notificationService = injector.resolve(NotificationService);
|
|
57
|
-
worker = injector.resolve(NotificationDeliveryWorker);
|
|
58
|
-
typeService = injector.resolve(NotificationTypeService);
|
|
59
|
-
subjectService = injector.resolve(SubjectService);
|
|
60
|
-
logRepo = injector.resolve(getRepository(NotificationLogEntity));
|
|
61
|
-
inAppRepo = injector.resolve(getRepository(InAppNotification));
|
|
62
|
-
webPushRepo = injector.resolve(getRepository(WebPushSubscription));
|
|
63
|
-
// Register providers
|
|
64
|
-
worker.registerProvider(NotificationChannel.Email, injector.resolve(EmailChannelProvider));
|
|
65
|
-
worker.registerProvider(NotificationChannel.InApp, injector.resolve(InAppChannelProvider));
|
|
66
|
-
});
|
|
67
|
-
beforeEach(async () => {
|
|
68
|
-
tenantId = crypto.randomUUID();
|
|
69
|
-
vi.clearAllMocks();
|
|
70
|
-
await typeService.initializeTypes({
|
|
71
|
-
test: {
|
|
72
|
-
label: 'Test Category',
|
|
73
|
-
escalations: [{ delay: 1000, channel: NotificationChannel.Email }],
|
|
74
|
-
},
|
|
75
|
-
throttled: {
|
|
76
|
-
label: 'Throttled Category',
|
|
77
|
-
throttling: { limit: 1, interval: 60000 },
|
|
78
|
-
},
|
|
79
|
-
readTest: {
|
|
80
|
-
label: 'Read Test',
|
|
81
|
-
escalations: [{ delay: 1000, channel: NotificationChannel.Email }],
|
|
82
|
-
},
|
|
83
|
-
prefTest: { label: 'Preference Test' },
|
|
84
|
-
unknownTest: { label: 'Unknown Test' },
|
|
85
|
-
manageTest: { label: 'Manage Test' },
|
|
86
|
-
testType: { label: 'Test Type' },
|
|
87
|
-
auto: { label: 'Auto Test' },
|
|
88
|
-
archive: { label: 'Archive All Test' },
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
afterEach(async () => {
|
|
92
|
-
await clearTenantData(database, schema, ['in_app', 'in_app_archive', 'log', 'preference', 'web_push_subscription'], tenantId);
|
|
93
|
-
await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
|
|
94
|
-
});
|
|
95
|
-
test('should execute full notification flow with escalation', async () => {
|
|
96
|
-
const user = await subjectService.createUser({
|
|
97
|
-
tenantId,
|
|
98
|
-
email: 'test@example.com',
|
|
99
|
-
firstName: 'Test',
|
|
100
|
-
lastName: 'User',
|
|
101
|
-
});
|
|
102
|
-
await notificationService.send(tenantId, user.id, {
|
|
103
|
-
type: 'test',
|
|
104
|
-
priority: 'high',
|
|
105
|
-
triggerSubjectId: user.id,
|
|
106
|
-
payload: { message: 'Hello', testField: 'Test Value' },
|
|
107
|
-
});
|
|
108
|
-
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
109
|
-
expect(logs).toHaveLength(1);
|
|
110
|
-
const log = logs[0];
|
|
111
|
-
expect(log.status).toBe(NotificationStatus.Pending);
|
|
112
|
-
expect(log.currentStep).toBe(0);
|
|
113
|
-
// Step 0 (In-App)
|
|
114
|
-
const result0 = await worker.deliver(log.id);
|
|
115
|
-
expect(result0.payload.action).toBe('reschedule');
|
|
116
|
-
const inApps = await inAppRepo.loadManyByQuery({ tenantId });
|
|
117
|
-
expect(inApps).toHaveLength(1);
|
|
118
|
-
expect(inApps[0].logId).toBe(log.id);
|
|
119
|
-
const logAfterStep0 = await logRepo.load(log.id);
|
|
120
|
-
expect(logAfterStep0.status).toBe(NotificationStatus.Sent);
|
|
121
|
-
expect(logAfterStep0.currentStep).toBe(1);
|
|
122
|
-
// Step 1 (Email Escalation)
|
|
123
|
-
const result1 = await worker.deliver(log.id);
|
|
124
|
-
expect(result1.payload.action).toBe('complete');
|
|
125
|
-
expect(mailServiceMock.send).toHaveBeenCalled();
|
|
126
|
-
const mailArgs = mailServiceMock.send.mock.calls[0][0];
|
|
127
|
-
expect(mailArgs.to).toBe('test@example.com');
|
|
128
|
-
const logAfterStep1 = await logRepo.load(log.id);
|
|
129
|
-
expect(logAfterStep1.currentStep).toBe(2);
|
|
130
|
-
});
|
|
131
|
-
test('should handle throttling correctly', async () => {
|
|
132
|
-
const user = await subjectService.createUser({ tenantId, email: 'throttled@example.com', firstName: 'Throttled', lastName: 'User' });
|
|
133
|
-
await notificationService.send(tenantId, user.id, {
|
|
134
|
-
type: 'throttled', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
135
|
-
});
|
|
136
|
-
await notificationService.send(tenantId, user.id, {
|
|
137
|
-
type: 'throttled', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
138
|
-
});
|
|
139
|
-
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
140
|
-
logs.sort((a, b) => Number(a.timestamp) - Number(b.timestamp));
|
|
141
|
-
// First one should pass
|
|
142
|
-
const result1 = await worker.deliver(logs[0].id);
|
|
143
|
-
expect(result1.payload.action).toBe('complete'); // No escalations
|
|
144
|
-
// Second one should be throttled
|
|
145
|
-
const result2 = await worker.deliver(logs[1].id);
|
|
146
|
-
expect(result2.payload.action).toBe('reschedule');
|
|
147
|
-
});
|
|
148
|
-
test('should skip escalation if notification is read', async () => {
|
|
149
|
-
const user = await subjectService.createUser({ tenantId, email: 'read@example.com', firstName: 'Read', lastName: 'User' });
|
|
150
|
-
await notificationService.send(tenantId, user.id, {
|
|
151
|
-
type: 'readTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
152
|
-
});
|
|
153
|
-
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
154
|
-
const log = logs[0];
|
|
155
|
-
// Step 0: Deliver In-App
|
|
156
|
-
await worker.deliver(log.id);
|
|
157
|
-
// Mark as Read
|
|
158
|
-
const inAppNotifications = await notificationService.listInApp(tenantId, user.id);
|
|
159
|
-
expect(inAppNotifications).toHaveLength(1);
|
|
160
|
-
await notificationService.markRead(tenantId, user.id, inAppNotifications[0].id);
|
|
161
|
-
// Step 1: Attempt Escalation
|
|
162
|
-
const result = await worker.deliver(log.id);
|
|
163
|
-
expect(result.payload.action).toBe('complete');
|
|
164
|
-
expect(mailServiceMock.send).not.toHaveBeenCalled();
|
|
165
|
-
});
|
|
166
|
-
test('should respect user preferences', async () => {
|
|
167
|
-
const user = await subjectService.createUser({ tenantId, email: 'pref@example.com', firstName: 'Pref', lastName: 'User' });
|
|
168
|
-
// Disable InApp, Enable Email (even though not default)
|
|
169
|
-
await notificationService.updatePreference(tenantId, user.id, 'prefTest', NotificationChannel.InApp, false);
|
|
170
|
-
await notificationService.updatePreference(tenantId, user.id, 'prefTest', NotificationChannel.Email, true);
|
|
171
|
-
await notificationService.send(tenantId, user.id, {
|
|
172
|
-
type: 'prefTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
173
|
-
});
|
|
174
|
-
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
175
|
-
const log = logs[0];
|
|
176
|
-
// Deliver
|
|
177
|
-
await worker.deliver(log.id);
|
|
178
|
-
// Should have sent Email but NOT InApp
|
|
179
|
-
expect(mailServiceMock.send).toHaveBeenCalled();
|
|
180
|
-
const inAppNotifications = await notificationService.listInApp(tenantId, user.id);
|
|
181
|
-
expect(inAppNotifications).toHaveLength(0);
|
|
182
|
-
});
|
|
183
|
-
test('should handle unknown channels gracefully', async () => {
|
|
184
|
-
const user = await subjectService.createUser({ tenantId, email: 'unknown@example.com', firstName: 'Unknown', lastName: 'User' });
|
|
185
|
-
// Force a preference for an unknown channel/unregistered provider (e.g., WebPush if not registered or some random string if type allowed)
|
|
186
|
-
// Since NotificationChannel is an enum, we use WebPush which we haven't registered in the test setup (wait, we didn't register WebPush)
|
|
187
|
-
await notificationService.updatePreference(tenantId, user.id, 'unknownTest', NotificationChannel.WebPush, true);
|
|
188
|
-
// Disable InApp so only WebPush is attempted
|
|
189
|
-
await notificationService.updatePreference(tenantId, user.id, 'unknownTest', NotificationChannel.InApp, false);
|
|
190
|
-
await notificationService.send(tenantId, user.id, {
|
|
191
|
-
type: 'unknownTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
192
|
-
});
|
|
193
|
-
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
194
|
-
const log = logs[0];
|
|
195
|
-
// Deliver
|
|
196
|
-
const result = await worker.deliver(log.id);
|
|
197
|
-
// Should complete without error, just logging a warning (which we can't easily assert on unless we spy logger)
|
|
198
|
-
expect(result.payload.action).toBe('complete');
|
|
199
|
-
});
|
|
200
|
-
test('should manage notification lists and status (markRead, archive)', async () => {
|
|
201
|
-
const user = await subjectService.createUser({ tenantId, email: 'manage@example.com', firstName: 'Manage', lastName: 'User' });
|
|
202
|
-
await notificationService.send(tenantId, user.id, {
|
|
203
|
-
type: 'manageTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
|
|
204
|
-
});
|
|
205
|
-
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
206
|
-
await worker.deliver(logs[0].id);
|
|
207
|
-
// List
|
|
208
|
-
let list = await notificationService.listInApp(tenantId, user.id);
|
|
209
|
-
expect(list).toHaveLength(1);
|
|
210
|
-
expect(list[0].readTimestamp).toBeNull();
|
|
211
|
-
expect(list[0].notification).toBeDefined();
|
|
212
|
-
// Mark Read
|
|
213
|
-
await notificationService.markRead(tenantId, user.id, list[0].id);
|
|
214
|
-
list = await notificationService.listInApp(tenantId, user.id);
|
|
215
|
-
expect(list[0].readTimestamp).not.toBeNull();
|
|
216
|
-
// Archive
|
|
217
|
-
await notificationService.archive(tenantId, user.id, list[0].id);
|
|
218
|
-
// List (excludes archived)
|
|
219
|
-
list = await notificationService.listInApp(tenantId, user.id);
|
|
220
|
-
expect(list).toHaveLength(0);
|
|
221
|
-
// List archived
|
|
222
|
-
list = await notificationService.listArchivedInApp(tenantId, user.id);
|
|
223
|
-
expect(list).toHaveLength(1);
|
|
224
|
-
expect(list[0].archiveTimestamp).not.toBeNull();
|
|
225
|
-
});
|
|
226
|
-
test('should register web push subscription', async () => {
|
|
227
|
-
const user = await subjectService.createUser({ tenantId, email: 'push@example.com', firstName: 'Push', lastName: 'User' });
|
|
228
|
-
await notificationService.registerWebPush(tenantId, user.id, 'https://endpoint.com', new Uint8Array(32), new Uint8Array(32));
|
|
229
|
-
const subs = await webPushRepo.loadManyByQuery({ tenantId, userId: user.id });
|
|
230
|
-
expect(subs).toHaveLength(1);
|
|
231
|
-
expect(subs[0].endpoint).toBe('https://endpoint.com');
|
|
232
|
-
});
|
|
233
|
-
test('should retrieve user preferences', async () => {
|
|
234
|
-
const user = await subjectService.createUser({ tenantId, email: 'getpref@example.com', firstName: 'GetPref', lastName: 'User' });
|
|
235
|
-
await notificationService.updatePreference(tenantId, user.id, 'testType', NotificationChannel.Email, true);
|
|
236
|
-
const prefs = await notificationService.getPreferences(tenantId, user.id);
|
|
237
|
-
expect(prefs).toHaveLength(1);
|
|
238
|
-
prefs.sort((a, b) => a.type.localeCompare(b.type));
|
|
239
|
-
expect(prefs[0].type).toBe('testType');
|
|
240
|
-
expect(prefs[0].channel).toBe(NotificationChannel.Email);
|
|
241
|
-
expect(prefs[0].enabled).toBe(true);
|
|
242
|
-
});
|
|
243
|
-
test('should support keyset pagination with after and orderBy', async () => {
|
|
244
|
-
const user = await subjectService.createUser({ tenantId, email: 'pagination@example.com', firstName: 'Pagination', lastName: 'User' });
|
|
245
|
-
// Create 3 notifications
|
|
246
|
-
await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: { index: 1 } });
|
|
247
|
-
await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: { index: 2 } });
|
|
248
|
-
await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: { index: 3 } });
|
|
249
|
-
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
250
|
-
for (const log of logs) {
|
|
251
|
-
await worker.deliver(log.id);
|
|
252
|
-
}
|
|
253
|
-
// Default list (desc timestamp, then desc id)
|
|
254
|
-
const list = await notificationService.listInApp(tenantId, user.id);
|
|
255
|
-
expect(list).toHaveLength(3);
|
|
256
|
-
const firstId = list[0].id;
|
|
257
|
-
const secondId = list[1].id;
|
|
258
|
-
// After first
|
|
259
|
-
const afterFirst = await notificationService.listInApp(tenantId, user.id, { after: firstId });
|
|
260
|
-
expect(afterFirst).toHaveLength(2);
|
|
261
|
-
expect(afterFirst[0].id).toBe(secondId);
|
|
262
|
-
// After second
|
|
263
|
-
const afterSecond = await notificationService.listInApp(tenantId, user.id, { after: secondId });
|
|
264
|
-
expect(afterSecond).toHaveLength(1);
|
|
265
|
-
expect(afterSecond[0].id).toBe(list[2].id);
|
|
266
|
-
});
|
|
267
|
-
test('should auto-archive old notifications', async () => {
|
|
268
|
-
const user = await subjectService.createUser({ tenantId, email: 'auto@example.com', firstName: 'Auto', lastName: 'User' });
|
|
269
|
-
await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: {} });
|
|
270
|
-
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
271
|
-
await worker.deliver(logs[0].id);
|
|
272
|
-
// Verify active
|
|
273
|
-
expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(1);
|
|
274
|
-
// Manually update timestamp to be old (31 days ago)
|
|
275
|
-
const oldTimestamp = Date.now() - 31 * 24 * 60 * 60 * 1000;
|
|
276
|
-
await logRepo.updateByQuery({ id: logs[0].id }, { timestamp: oldTimestamp });
|
|
277
|
-
await inAppRepo.updateByQuery({ tenantId, logId: logs[0].id }, { timestamp: oldTimestamp });
|
|
278
|
-
await notificationService.runAutoArchive();
|
|
279
|
-
// Verify archived
|
|
280
|
-
expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(0);
|
|
281
|
-
expect(await notificationService.listArchivedInApp(tenantId, user.id)).toHaveLength(1);
|
|
282
|
-
});
|
|
283
|
-
test('should archive all notifications for a user', async () => {
|
|
284
|
-
const user = await subjectService.createUser({ tenantId, email: 'archiveall@example.com', firstName: 'Archive', lastName: 'All' });
|
|
285
|
-
await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: {} });
|
|
286
|
-
await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: {} });
|
|
287
|
-
const logs = await logRepo.loadManyByQuery({ tenantId });
|
|
288
|
-
for (const log of logs) {
|
|
289
|
-
await worker.deliver(log.id);
|
|
290
|
-
}
|
|
291
|
-
expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(2);
|
|
292
|
-
await notificationService.archiveAll(tenantId, user.id);
|
|
293
|
-
expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(0);
|
|
294
|
-
expect(await notificationService.listArchivedInApp(tenantId, user.id)).toHaveLength(2);
|
|
295
|
-
});
|
|
296
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import { setupIntegrationTest } from '../../testing/index.js';
|
|
3
|
-
import { NotificationSseService } from '../server/services/notification-sse.service.js';
|
|
4
|
-
describe('NotificationSseService', () => {
|
|
5
|
-
test('should register and publish to bus', async () => {
|
|
6
|
-
const { injector } = await setupIntegrationTest({ modules: { messageBus: true, signals: true } });
|
|
7
|
-
const service = injector.resolve(NotificationSseService);
|
|
8
|
-
const tenantId = 't1';
|
|
9
|
-
const userId = 'u1';
|
|
10
|
-
const source = service.register(tenantId, userId);
|
|
11
|
-
expect(source).toBeDefined();
|
|
12
|
-
// We can't easily spy on the LocalMessageBus internals without more complex setup,
|
|
13
|
-
// but we can verify that sending doesn't throw.
|
|
14
|
-
const msg = { id: 'l1', tenantId, userId, logId: 'l1' };
|
|
15
|
-
await expect(service.dispatch(tenantId, userId, { notification: msg, unreadCount: 1 })).resolves.not.toThrow();
|
|
16
|
-
});
|
|
17
|
-
test('should dispatch unread count update', async () => {
|
|
18
|
-
const { injector } = await setupIntegrationTest({ modules: { messageBus: true, signals: true } });
|
|
19
|
-
const service = injector.resolve(NotificationSseService);
|
|
20
|
-
const tenantId = 't1';
|
|
21
|
-
const userId = 'u1';
|
|
22
|
-
const source = service.register(tenantId, userId);
|
|
23
|
-
const messages = [];
|
|
24
|
-
source.subscribe((msg) => messages.push(msg));
|
|
25
|
-
await service.dispatch(tenantId, userId, { unreadCount: 5 });
|
|
26
|
-
expect(messages).toHaveLength(1);
|
|
27
|
-
expect(messages[0]).toEqual({ unreadCount: 5 });
|
|
28
|
-
});
|
|
29
|
-
test('should dispatch mark read and mark all read', async () => {
|
|
30
|
-
const { injector } = await setupIntegrationTest({ modules: { messageBus: true, signals: true } });
|
|
31
|
-
const service = injector.resolve(NotificationSseService);
|
|
32
|
-
const tenantId = 't1';
|
|
33
|
-
const userId = 'u1';
|
|
34
|
-
const source = service.register(tenantId, userId);
|
|
35
|
-
const messages = [];
|
|
36
|
-
source.subscribe((msg) => messages.push(msg));
|
|
37
|
-
await service.dispatch(tenantId, userId, { readId: 'n1', unreadCount: 2 });
|
|
38
|
-
await service.dispatch(tenantId, userId, { readAll: true, unreadCount: 0 });
|
|
39
|
-
expect(messages).toHaveLength(2);
|
|
40
|
-
expect(messages[0]).toEqual({ readId: 'n1', unreadCount: 2 });
|
|
41
|
-
expect(messages[1]).toEqual({ readAll: true, unreadCount: 0 });
|
|
42
|
-
});
|
|
43
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
-
import { setupIntegrationTest, truncateTables } from '../../testing/index.js';
|
|
3
|
-
import { NotificationTypeService } from '../server/services/notification-type.service.js';
|
|
4
|
-
describe('NotificationTypeService', () => {
|
|
5
|
-
let injector;
|
|
6
|
-
let database;
|
|
7
|
-
beforeEach(async () => {
|
|
8
|
-
({ injector, database } = await setupIntegrationTest({ modules: { notification: true, authentication: true } }));
|
|
9
|
-
await truncateTables(database, 'notification', ['type']);
|
|
10
|
-
});
|
|
11
|
-
afterEach(async () => {
|
|
12
|
-
await injector?.dispose();
|
|
13
|
-
});
|
|
14
|
-
test('should initialize types correctly', async () => {
|
|
15
|
-
const service = injector.resolve(NotificationTypeService);
|
|
16
|
-
const prefix = crypto.randomUUID();
|
|
17
|
-
const type1 = `${prefix}_TYPE1`;
|
|
18
|
-
const type2 = `${prefix}_TYPE2`;
|
|
19
|
-
const typeData = {
|
|
20
|
-
[type1]: { label: 'Type 1' },
|
|
21
|
-
[type2]: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } },
|
|
22
|
-
};
|
|
23
|
-
const result = await service.initializeTypes(typeData);
|
|
24
|
-
expect(result[type1]?.label).toBe('Type 1');
|
|
25
|
-
expect(result[type2]?.key).toBe(type2);
|
|
26
|
-
expect(result[type2]?.throttling?.limit).toBe(1);
|
|
27
|
-
// Verify persistence
|
|
28
|
-
const dbTypes = await service.repository.loadManyByQuery({ key: { $in: [type1, type2] } });
|
|
29
|
-
expect(dbTypes).toHaveLength(2);
|
|
30
|
-
// Update
|
|
31
|
-
const updatedData = {
|
|
32
|
-
[type1]: { label: 'Type 1 Updated' },
|
|
33
|
-
[type2]: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } },
|
|
34
|
-
};
|
|
35
|
-
const resultUpdated = await service.initializeTypes(updatedData);
|
|
36
|
-
expect(resultUpdated[type1]?.label).toBe('Type 1 Updated');
|
|
37
|
-
const dbTypesUpdated = await service.repository.loadManyByQuery({ key: { $in: [type1, type2] } });
|
|
38
|
-
expect(dbTypesUpdated).toHaveLength(2);
|
|
39
|
-
expect(dbTypesUpdated.find((c) => c.key == type1)?.label).toBe('Type 1 Updated');
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|