@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,177 +0,0 @@
|
|
|
1
|
-
import { Subject } from 'rxjs';
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
-
import { AuthenticationClientService } from '../../authentication/client/authentication.service.js';
|
|
4
|
-
import { AUTHENTICATION_API_CLIENT } from '../../authentication/client/tokens.js';
|
|
5
|
-
import { CancellationSignal, CancellationToken } from '../../cancellation/token.js';
|
|
6
|
-
import { Injector } from '../../injector/index.js';
|
|
7
|
-
import { Lock } from '../../lock/index.js';
|
|
8
|
-
import { Logger } from '../../logger/index.js';
|
|
9
|
-
import { MessageBus } from '../../message-bus/index.js';
|
|
10
|
-
import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
|
|
11
|
-
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
12
|
-
describe('AuthenticationClientService Methods', () => {
|
|
13
|
-
let injector;
|
|
14
|
-
let service;
|
|
15
|
-
let mockApiClient;
|
|
16
|
-
let mockLock;
|
|
17
|
-
let mockTokenUpdateBus;
|
|
18
|
-
let mockLoggedOutBus;
|
|
19
|
-
let mockLogger;
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
const storage = new Map();
|
|
22
|
-
globalThis.localStorage = {
|
|
23
|
-
getItem: vi.fn((key) => storage.get(key) ?? null),
|
|
24
|
-
setItem: vi.fn((key, value) => storage.set(key, value)),
|
|
25
|
-
removeItem: vi.fn((key) => storage.delete(key)),
|
|
26
|
-
clear: vi.fn(() => storage.clear()),
|
|
27
|
-
};
|
|
28
|
-
configureDefaultSignalsImplementation();
|
|
29
|
-
injector = new Injector('TestInjector');
|
|
30
|
-
mockApiClient = {
|
|
31
|
-
login: vi.fn(),
|
|
32
|
-
refresh: vi.fn(),
|
|
33
|
-
impersonate: vi.fn(),
|
|
34
|
-
unimpersonate: vi.fn(),
|
|
35
|
-
changePassword: vi.fn(),
|
|
36
|
-
initPasswordReset: vi.fn(),
|
|
37
|
-
resetPassword: vi.fn(),
|
|
38
|
-
checkPassword: vi.fn(),
|
|
39
|
-
listSessions: vi.fn(),
|
|
40
|
-
invalidateAllOtherSessions: vi.fn(),
|
|
41
|
-
timestamp: vi.fn().mockResolvedValue(currentTimestampSeconds()),
|
|
42
|
-
endSession: vi.fn().mockResolvedValue(undefined),
|
|
43
|
-
};
|
|
44
|
-
mockLock = {
|
|
45
|
-
tryUse: vi.fn(async (_timeout, callback) => {
|
|
46
|
-
const result = await callback({ lost: false });
|
|
47
|
-
return { success: true, result };
|
|
48
|
-
}),
|
|
49
|
-
use: vi.fn(async (_timeout, callback) => {
|
|
50
|
-
return await callback({ lost: false });
|
|
51
|
-
}),
|
|
52
|
-
};
|
|
53
|
-
mockTokenUpdateBus = {
|
|
54
|
-
publishAndForget: vi.fn(),
|
|
55
|
-
messages$: new Subject(),
|
|
56
|
-
allMessages$: new Subject(),
|
|
57
|
-
dispose: vi.fn(),
|
|
58
|
-
};
|
|
59
|
-
mockLoggedOutBus = {
|
|
60
|
-
publishAndForget: vi.fn(),
|
|
61
|
-
messages$: new Subject(),
|
|
62
|
-
allMessages$: new Subject(),
|
|
63
|
-
dispose: vi.fn(),
|
|
64
|
-
};
|
|
65
|
-
mockLogger = {
|
|
66
|
-
error: vi.fn(),
|
|
67
|
-
warn: vi.fn(),
|
|
68
|
-
info: vi.fn(),
|
|
69
|
-
debug: vi.fn(),
|
|
70
|
-
};
|
|
71
|
-
injector.register(AUTHENTICATION_API_CLIENT, { useValue: mockApiClient });
|
|
72
|
-
injector.register(Lock, { useValue: mockLock });
|
|
73
|
-
injector.register(MessageBus, {
|
|
74
|
-
useFactory: (argument) => {
|
|
75
|
-
if (argument === 'AuthenticationService:tokenUpdate')
|
|
76
|
-
return mockTokenUpdateBus;
|
|
77
|
-
if (argument === 'AuthenticationService:loggedOut')
|
|
78
|
-
return mockLoggedOutBus;
|
|
79
|
-
return undefined;
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
injector.register(Logger, { useValue: mockLogger });
|
|
83
|
-
const disposeToken = new CancellationToken();
|
|
84
|
-
injector.register(CancellationSignal, { useValue: disposeToken.signal });
|
|
85
|
-
service = injector.resolve(AuthenticationClientService);
|
|
86
|
-
});
|
|
87
|
-
afterEach(async () => {
|
|
88
|
-
await service.dispose();
|
|
89
|
-
});
|
|
90
|
-
test('impersonate should acquire lock and call api', async () => {
|
|
91
|
-
const token = { exp: Date.now() + 3600, jti: 'impersonated-token', subject: 'sub', impersonator: 'admin' };
|
|
92
|
-
mockApiClient.impersonate.mockResolvedValue(token);
|
|
93
|
-
await service.impersonate('target-subject');
|
|
94
|
-
expect(mockLock.use).toHaveBeenCalled();
|
|
95
|
-
expect(mockApiClient.impersonate).toHaveBeenCalledWith({ subject: 'target-subject', data: undefined });
|
|
96
|
-
expect(service.token()).toEqual(token);
|
|
97
|
-
expect(service.impersonated()).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
test('impersonate should fail if already impersonating', async () => {
|
|
100
|
-
const token = { exp: Date.now() + 3600, jti: 'impersonated-token', subject: 'sub', impersonator: 'admin' };
|
|
101
|
-
service.setNewToken(token); // Force state
|
|
102
|
-
await expect(service.impersonate('another-target')).rejects.toThrow('Already impersonating');
|
|
103
|
-
});
|
|
104
|
-
test('unimpersonate should acquire lock and call api', async () => {
|
|
105
|
-
const token = { exp: Date.now() + 3600, jti: 'original-token', subject: 'admin' };
|
|
106
|
-
mockApiClient.unimpersonate.mockResolvedValue(token);
|
|
107
|
-
await service.unimpersonate();
|
|
108
|
-
expect(mockLock.use).toHaveBeenCalled();
|
|
109
|
-
expect(mockApiClient.unimpersonate).toHaveBeenCalled();
|
|
110
|
-
expect(service.token()).toEqual(token);
|
|
111
|
-
});
|
|
112
|
-
test('changePassword should call api', async () => {
|
|
113
|
-
await service.changePassword('old', 'new');
|
|
114
|
-
expect(mockApiClient.changePassword).toHaveBeenCalledWith({ currentPassword: 'old', newPassword: 'new' });
|
|
115
|
-
});
|
|
116
|
-
test('initPasswordReset should call api', async () => {
|
|
117
|
-
await service.initPasswordReset({ tenantId: 't', subject: 's' }, { some: 'data' });
|
|
118
|
-
expect(mockApiClient.initPasswordReset).toHaveBeenCalledWith({ tenantId: 't', subject: 's', data: { some: 'data' } });
|
|
119
|
-
});
|
|
120
|
-
test('resetPassword should call api', async () => {
|
|
121
|
-
await service.resetPassword('token', 'new-password');
|
|
122
|
-
expect(mockApiClient.resetPassword).toHaveBeenCalledWith({ token: 'token', newPassword: 'new-password' });
|
|
123
|
-
});
|
|
124
|
-
test('updateRawTokens should update signals and storage', async () => {
|
|
125
|
-
service.updateRawTokens('raw', 'refresh', 'impersonator');
|
|
126
|
-
expect(service.rawToken()).toBe('raw');
|
|
127
|
-
expect(service.rawRefreshToken()).toBe('refresh');
|
|
128
|
-
expect(service.rawImpersonatorRefreshToken()).toBe('impersonator');
|
|
129
|
-
expect(globalThis.localStorage.setItem).toHaveBeenCalledWith('AuthenticationService:raw-token', JSON.stringify('raw'));
|
|
130
|
-
});
|
|
131
|
-
test('impersonate should rollback data on failure', async () => {
|
|
132
|
-
service.setAdditionalData({ role: 'admin' });
|
|
133
|
-
const originalData = service.authenticationData;
|
|
134
|
-
mockApiClient.impersonate.mockRejectedValue(new Error('Impersonation failed'));
|
|
135
|
-
await expect(service.impersonate('target')).rejects.toThrow('Impersonation failed');
|
|
136
|
-
// Should have restored original data
|
|
137
|
-
expect(service.authenticationData).toEqual(originalData);
|
|
138
|
-
expect(service.impersonatorAuthenticationData).toBeUndefined();
|
|
139
|
-
});
|
|
140
|
-
test('unimpersonate should handle failure', async () => {
|
|
141
|
-
mockApiClient.unimpersonate.mockRejectedValue(new Error('Unimpersonation failed'));
|
|
142
|
-
await expect(service.unimpersonate()).rejects.toThrow('Unimpersonation failed');
|
|
143
|
-
});
|
|
144
|
-
test('logout should handle concurrent calls and avoid multiple api requests', async () => {
|
|
145
|
-
let resolveEndSession;
|
|
146
|
-
const endSessionPromise = new Promise((resolve) => {
|
|
147
|
-
resolveEndSession = resolve;
|
|
148
|
-
});
|
|
149
|
-
mockApiClient.endSession.mockReturnValue(endSessionPromise);
|
|
150
|
-
const logout1 = service.logout();
|
|
151
|
-
const logout2 = service.logout();
|
|
152
|
-
// logout1 and logout2 will be different promises because the method is async
|
|
153
|
-
expect(mockApiClient.endSession).toHaveBeenCalledTimes(1);
|
|
154
|
-
resolveEndSession(undefined);
|
|
155
|
-
await Promise.all([logout1, logout2]);
|
|
156
|
-
expect(service.isLoggedIn()).toBe(false);
|
|
157
|
-
expect(mockTokenUpdateBus.publishAndForget).toHaveBeenCalledWith(undefined);
|
|
158
|
-
expect(mockLoggedOutBus.publishAndForget).toHaveBeenCalled();
|
|
159
|
-
});
|
|
160
|
-
test('syncClock should handle errors gracefully', async () => {
|
|
161
|
-
mockApiClient.timestamp.mockRejectedValue(new Error('Time sync failed'));
|
|
162
|
-
await service.syncClock();
|
|
163
|
-
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to synchronize clock'));
|
|
164
|
-
expect(service.clockOffset).toBe(0);
|
|
165
|
-
});
|
|
166
|
-
test('listSessions should call api', async () => {
|
|
167
|
-
const sessions = [{ id: '1', begin: 100, end: 200 }];
|
|
168
|
-
mockApiClient.listSessions.mockResolvedValue(sessions);
|
|
169
|
-
const result = await service.listSessions();
|
|
170
|
-
expect(mockApiClient.listSessions).toHaveBeenCalled();
|
|
171
|
-
expect(result).toEqual(sessions);
|
|
172
|
-
});
|
|
173
|
-
test('invalidateAllOtherSessions should call api', async () => {
|
|
174
|
-
await service.invalidateAllOtherSessions();
|
|
175
|
-
expect(mockApiClient.invalidateAllOtherSessions).toHaveBeenCalled();
|
|
176
|
-
});
|
|
177
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { Subject } from 'rxjs';
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
-
import { AuthenticationClientService } from '../../authentication/client/authentication.service.js';
|
|
4
|
-
import { AUTHENTICATION_API_CLIENT } from '../../authentication/client/tokens.js';
|
|
5
|
-
import { CancellationSignal, CancellationToken } from '../../cancellation/token.js';
|
|
6
|
-
import { Injector } from '../../injector/index.js';
|
|
7
|
-
import { Lock } from '../../lock/index.js';
|
|
8
|
-
import { Logger } from '../../logger/index.js';
|
|
9
|
-
import { MessageBus } from '../../message-bus/index.js';
|
|
10
|
-
import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
|
|
11
|
-
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
12
|
-
import { timeout } from '../../utils/timing.js';
|
|
13
|
-
describe('AuthenticationClientService Refresh Loop Reproduction', () => {
|
|
14
|
-
let injector;
|
|
15
|
-
let service;
|
|
16
|
-
let mockApiClient;
|
|
17
|
-
let mockLock;
|
|
18
|
-
let mockMessageBus;
|
|
19
|
-
let mockLogger;
|
|
20
|
-
let disposeToken;
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
const storage = new Map();
|
|
23
|
-
globalThis.localStorage = {
|
|
24
|
-
getItem: vi.fn((key) => storage.get(key) ?? null),
|
|
25
|
-
setItem: vi.fn((key, value) => storage.set(key, value)),
|
|
26
|
-
removeItem: vi.fn((key) => storage.delete(key)),
|
|
27
|
-
clear: vi.fn(() => storage.clear()),
|
|
28
|
-
};
|
|
29
|
-
configureDefaultSignalsImplementation();
|
|
30
|
-
injector = new Injector('Test');
|
|
31
|
-
mockApiClient = {
|
|
32
|
-
login: vi.fn(),
|
|
33
|
-
refresh: vi.fn(),
|
|
34
|
-
timestamp: vi.fn().mockResolvedValue(currentTimestampSeconds()),
|
|
35
|
-
endSession: vi.fn().mockResolvedValue(undefined),
|
|
36
|
-
};
|
|
37
|
-
mockLock = {
|
|
38
|
-
tryUse: vi.fn(async (_timeout, callback) => {
|
|
39
|
-
const result = await callback({ lost: false });
|
|
40
|
-
return { success: true, result };
|
|
41
|
-
}),
|
|
42
|
-
use: vi.fn(async (_timeout, callback) => {
|
|
43
|
-
return await callback({ lost: false });
|
|
44
|
-
}),
|
|
45
|
-
};
|
|
46
|
-
mockMessageBus = {
|
|
47
|
-
publishAndForget: vi.fn(),
|
|
48
|
-
messages$: new Subject(),
|
|
49
|
-
dispose: vi.fn(),
|
|
50
|
-
};
|
|
51
|
-
mockLogger = {
|
|
52
|
-
error: vi.fn(),
|
|
53
|
-
warn: vi.fn(),
|
|
54
|
-
info: vi.fn(),
|
|
55
|
-
debug: vi.fn(),
|
|
56
|
-
};
|
|
57
|
-
injector.register(AUTHENTICATION_API_CLIENT, { useValue: mockApiClient });
|
|
58
|
-
injector.register(Lock, { useValue: mockLock });
|
|
59
|
-
injector.register(MessageBus, { useValue: mockMessageBus });
|
|
60
|
-
injector.register(Logger, { useValue: mockLogger });
|
|
61
|
-
disposeToken = new CancellationToken();
|
|
62
|
-
injector.register(CancellationSignal, { useValue: disposeToken.signal });
|
|
63
|
-
});
|
|
64
|
-
afterEach(async () => {
|
|
65
|
-
disposeToken.set();
|
|
66
|
-
await injector.dispose();
|
|
67
|
-
});
|
|
68
|
-
test('Zombie Timer: loop should wake up immediately when token changes', async () => {
|
|
69
|
-
// 1. Mock a long expiration
|
|
70
|
-
const now = currentTimestampSeconds();
|
|
71
|
-
const initialToken = { iat: now - 3600, exp: now + 3600, jti: 'initial' };
|
|
72
|
-
// Set in storage so initialize() (called by resolve) loads it
|
|
73
|
-
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(initialToken));
|
|
74
|
-
service = injector.resolve(AuthenticationClientService);
|
|
75
|
-
// Wait for loop to enter the race condition (wait phase)
|
|
76
|
-
await timeout(20);
|
|
77
|
-
// 2. Change token
|
|
78
|
-
const newToken = { iat: now - 1800, exp: now + 3600, jti: 'new' };
|
|
79
|
-
mockApiClient.refresh.mockResolvedValue(newToken);
|
|
80
|
-
service.requestRefresh(); // This should trigger immediate wake up
|
|
81
|
-
// Wait for loop to process
|
|
82
|
-
await timeout(20);
|
|
83
|
-
expect(mockApiClient.refresh).toHaveBeenCalled();
|
|
84
|
-
});
|
|
85
|
-
test('Forced Refresh Loss: forceRefreshToken should not be cleared on failure', async () => {
|
|
86
|
-
const now = currentTimestampSeconds();
|
|
87
|
-
const initialToken = { iat: now - 3600, exp: now + 3600, jti: 'initial' };
|
|
88
|
-
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(initialToken));
|
|
89
|
-
service = injector.resolve(AuthenticationClientService);
|
|
90
|
-
await timeout(20);
|
|
91
|
-
// 1. Mock refresh failure
|
|
92
|
-
mockApiClient.refresh.mockRejectedValue(new Error('Network Error'));
|
|
93
|
-
service.requestRefresh();
|
|
94
|
-
// Wait for loop to attempt refresh and fail
|
|
95
|
-
await timeout(50);
|
|
96
|
-
expect(mockApiClient.refresh).toHaveBeenCalled();
|
|
97
|
-
expect(service.forceRefreshRequested()).toBe(true); // Should STILL be set
|
|
98
|
-
});
|
|
99
|
-
test('Lock Contention Backoff: should wait 5 seconds and not busy-loop', async () => {
|
|
100
|
-
const now = currentTimestampSeconds();
|
|
101
|
-
const initialToken = { iat: now - 3600, exp: now + 5, jti: 'initial' }; // Expiring soon
|
|
102
|
-
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(initialToken));
|
|
103
|
-
// 1. Mock lock already held
|
|
104
|
-
mockLock.tryUse.mockResolvedValue(undefined); // lockAcquired = false
|
|
105
|
-
const startTime = Date.now();
|
|
106
|
-
service = injector.resolve(AuthenticationClientService);
|
|
107
|
-
// We expect it to try once, fail to get lock, and then wait some time.
|
|
108
|
-
await timeout(50);
|
|
109
|
-
expect(mockLock.tryUse).toHaveBeenCalledTimes(1);
|
|
110
|
-
// Check if it's still waiting (not finished loop)
|
|
111
|
-
const duration = Date.now() - startTime;
|
|
112
|
-
expect(duration).toBeLessThan(500);
|
|
113
|
-
});
|
|
114
|
-
test('Busy Loop: should not busy loop when forceRefreshToken is set and lock is held', async () => {
|
|
115
|
-
const now = currentTimestampSeconds();
|
|
116
|
-
const initialToken = { iat: now - 3600, exp: now + 3600, jti: 'initial' };
|
|
117
|
-
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(initialToken));
|
|
118
|
-
// Mock lock already held
|
|
119
|
-
mockLock.tryUse.mockResolvedValue(undefined);
|
|
120
|
-
service = injector.resolve(AuthenticationClientService);
|
|
121
|
-
await timeout(20);
|
|
122
|
-
service.requestRefresh(); // Set the flag
|
|
123
|
-
await timeout(50);
|
|
124
|
-
// If it busy loops, this will be much higher than 1.
|
|
125
|
-
expect(mockLock.tryUse.mock.calls.length).toBeLessThan(5);
|
|
126
|
-
});
|
|
127
|
-
test('refresh() should call timestamp() to sync clock', async () => {
|
|
128
|
-
service = injector.resolve(AuthenticationClientService);
|
|
129
|
-
const newToken = { iat: 1000, exp: 2000, jti: 'new' };
|
|
130
|
-
mockApiClient.refresh.mockResolvedValue(newToken);
|
|
131
|
-
await service.refresh();
|
|
132
|
-
expect(mockApiClient.timestamp).toHaveBeenCalled();
|
|
133
|
-
});
|
|
134
|
-
test('Cross-tab Sync: should not refresh if another tab already did (simulated via localStorage update)', async () => {
|
|
135
|
-
const now = currentTimestampSeconds();
|
|
136
|
-
const initialToken = { iat: now - 3600, exp: now + 5, jti: 'initial' }; // Expiring soon
|
|
137
|
-
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(initialToken));
|
|
138
|
-
// 1. Mock lock behavior to simulate another tab refreshing while we wait for the lock
|
|
139
|
-
mockLock.tryUse.mockImplementation(async (_timeout, callback) => {
|
|
140
|
-
// Simulate another tab refreshing and updating localStorage
|
|
141
|
-
const newToken = { iat: now, exp: now + 3600, jti: 'refreshed-by-other-tab' };
|
|
142
|
-
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(newToken));
|
|
143
|
-
const result = await callback({ lost: false });
|
|
144
|
-
return { success: true, result };
|
|
145
|
-
});
|
|
146
|
-
service = injector.resolve(AuthenticationClientService);
|
|
147
|
-
// Wait for loop to run
|
|
148
|
-
await timeout(100);
|
|
149
|
-
// Should NOT have called refresh because loadToken() inside the lock updated the token
|
|
150
|
-
expect(mockApiClient.refresh).not.toHaveBeenCalled();
|
|
151
|
-
expect(service.token()?.jti).toBe('refreshed-by-other-tab');
|
|
152
|
-
});
|
|
153
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
-
import { AuthenticationClientService } from '../../authentication/client/index.js';
|
|
3
|
-
import { AuthenticationService as AuthenticationServerService } from '../../authentication/server/index.js';
|
|
4
|
-
import { HttpClientOptions } from '../../http/client/index.js';
|
|
5
|
-
import { HttpServer } from '../../http/server/index.js';
|
|
6
|
-
import { runInInjectionContext } from '../../injector/index.js';
|
|
7
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
8
|
-
import { SubjectService } from '../server/subject.service.js';
|
|
9
|
-
import { DefaultAuthenticationAncillaryService } from './authentication.test-ancillary-service.js';
|
|
10
|
-
describe('AuthenticationClientService Integration', () => {
|
|
11
|
-
let injector;
|
|
12
|
-
let database;
|
|
13
|
-
let service;
|
|
14
|
-
let serverService;
|
|
15
|
-
let subjectService;
|
|
16
|
-
let server;
|
|
17
|
-
const schema = 'authentication';
|
|
18
|
-
const tenantId = crypto.randomUUID();
|
|
19
|
-
beforeAll(async () => {
|
|
20
|
-
const storage = new Map();
|
|
21
|
-
globalThis.localStorage = {
|
|
22
|
-
getItem: vi.fn((key) => storage.get(key) ?? null),
|
|
23
|
-
setItem: vi.fn((key, value) => storage.set(key, value)),
|
|
24
|
-
removeItem: vi.fn((key) => storage.delete(key)),
|
|
25
|
-
clear: vi.fn(() => storage.clear()),
|
|
26
|
-
};
|
|
27
|
-
({ injector, database } = await setupIntegrationTest({
|
|
28
|
-
modules: { authentication: true, audit: true, keyValueStore: true, signals: true, api: true },
|
|
29
|
-
authentication: {
|
|
30
|
-
ancillaryService: DefaultAuthenticationAncillaryService,
|
|
31
|
-
},
|
|
32
|
-
}));
|
|
33
|
-
await runInInjectionContext(injector, async () => {
|
|
34
|
-
server = injector.resolve(HttpServer);
|
|
35
|
-
const httpClientOptions = injector.resolve(HttpClientOptions);
|
|
36
|
-
httpClientOptions.baseUrl = `http://localhost:${server.port}`;
|
|
37
|
-
serverService = await injector.resolveAsync(AuthenticationServerService);
|
|
38
|
-
subjectService = await injector.resolveAsync(SubjectService);
|
|
39
|
-
service = await injector.resolveAsync(AuthenticationClientService);
|
|
40
|
-
service.initialize();
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
afterAll(async () => {
|
|
44
|
-
await server?.close(1000);
|
|
45
|
-
await injector?.dispose();
|
|
46
|
-
});
|
|
47
|
-
beforeEach(async () => {
|
|
48
|
-
globalThis.localStorage?.clear();
|
|
49
|
-
await clearTenantData(database, schema, ['password', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
50
|
-
});
|
|
51
|
-
test('login and logout should work', async () => {
|
|
52
|
-
const user = await subjectService.createUser({ tenantId, email: 'client-test@example.com', firstName: 'C', lastName: 'T' });
|
|
53
|
-
await serverService.setPassword(user, 'Strong-Pass-2026!');
|
|
54
|
-
expect(service.isLoggedIn()).toBe(false);
|
|
55
|
-
await service.login({ tenantId, subject: user.id }, 'Strong-Pass-2026!');
|
|
56
|
-
expect(service.isLoggedIn()).toBe(true);
|
|
57
|
-
expect(service.subjectId()).toBe(user.id);
|
|
58
|
-
await service.logout();
|
|
59
|
-
expect(service.isLoggedIn()).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
test('refresh should work', async () => {
|
|
62
|
-
const user = await subjectService.createUser({ tenantId, email: 'refresh-test@example.com', firstName: 'R', lastName: 'T' });
|
|
63
|
-
await serverService.setPassword(user, 'Strong-Pass-2026!');
|
|
64
|
-
await service.login({ tenantId, subject: user.id }, 'Strong-Pass-2026!');
|
|
65
|
-
const initialToken = service.token()?.jti;
|
|
66
|
-
await service.refresh();
|
|
67
|
-
expect(service.token()?.jti).not.toBe(initialToken);
|
|
68
|
-
expect(service.isLoggedIn()).toBe(true);
|
|
69
|
-
});
|
|
70
|
-
test('checkPassword should work', async () => {
|
|
71
|
-
const result = await service.checkPassword('123');
|
|
72
|
-
expect(result.strength).toBeLessThan(2);
|
|
73
|
-
const strongResult = await service.checkPassword('Very-Strong-Password-2026-!@#$');
|
|
74
|
-
expect(strongResult.strength).toBeGreaterThanOrEqual(2);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
-
import { AuthenticationClientService } from '../../authentication/client/authentication.service.js';
|
|
3
|
-
import { AUTHENTICATION_API_CLIENT } from '../../authentication/client/tokens.js';
|
|
4
|
-
import { CancellationSignal, CancellationToken } from '../../cancellation/token.js';
|
|
5
|
-
import { Injector } from '../../injector/index.js';
|
|
6
|
-
import { Lock } from '../../lock/index.js';
|
|
7
|
-
import { Logger } from '../../logger/index.js';
|
|
8
|
-
import { MessageBus } from '../../message-bus/index.js';
|
|
9
|
-
import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
|
|
10
|
-
import { timeout } from '../../utils/timing.js';
|
|
11
|
-
import { Subject } from 'rxjs';
|
|
12
|
-
describe('AuthenticationClientService Refresh Busy Loop Prevention', () => {
|
|
13
|
-
let injector;
|
|
14
|
-
let service;
|
|
15
|
-
let mockApiClient;
|
|
16
|
-
let mockLock;
|
|
17
|
-
let mockMessageBus;
|
|
18
|
-
let mockLogger;
|
|
19
|
-
let disposeToken;
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
const storage = new Map();
|
|
22
|
-
globalThis.localStorage = {
|
|
23
|
-
getItem: vi.fn((key) => storage.get(key) ?? null),
|
|
24
|
-
setItem: vi.fn((key, value) => storage.set(key, value)),
|
|
25
|
-
removeItem: vi.fn((key) => storage.delete(key)),
|
|
26
|
-
clear: vi.fn(() => storage.clear()),
|
|
27
|
-
};
|
|
28
|
-
configureDefaultSignalsImplementation();
|
|
29
|
-
injector = new Injector('Test');
|
|
30
|
-
mockApiClient = {
|
|
31
|
-
login: vi.fn(),
|
|
32
|
-
refresh: vi.fn(),
|
|
33
|
-
timestamp: vi.fn().mockResolvedValue(1000), // Fixed timestamp
|
|
34
|
-
endSession: vi.fn().mockResolvedValue(undefined),
|
|
35
|
-
};
|
|
36
|
-
mockLock = {
|
|
37
|
-
tryUse: vi.fn(async (_timeout, callback) => {
|
|
38
|
-
const result = await callback({ lost: false });
|
|
39
|
-
return { success: true, result };
|
|
40
|
-
}),
|
|
41
|
-
use: vi.fn(async (_timeout, callback) => {
|
|
42
|
-
return await callback({ lost: false });
|
|
43
|
-
}),
|
|
44
|
-
};
|
|
45
|
-
mockMessageBus = {
|
|
46
|
-
publishAndForget: vi.fn(),
|
|
47
|
-
messages$: new Subject(),
|
|
48
|
-
dispose: vi.fn(),
|
|
49
|
-
};
|
|
50
|
-
mockLogger = {
|
|
51
|
-
error: vi.fn(),
|
|
52
|
-
warn: vi.fn(),
|
|
53
|
-
info: vi.fn(),
|
|
54
|
-
debug: vi.fn(),
|
|
55
|
-
};
|
|
56
|
-
injector.register(AUTHENTICATION_API_CLIENT, { useValue: mockApiClient });
|
|
57
|
-
injector.register(Lock, { useValue: mockLock });
|
|
58
|
-
injector.register(MessageBus, { useValue: mockMessageBus });
|
|
59
|
-
injector.register(Logger, { useValue: mockLogger });
|
|
60
|
-
disposeToken = new CancellationToken();
|
|
61
|
-
injector.register(CancellationSignal, { useValue: disposeToken.signal });
|
|
62
|
-
});
|
|
63
|
-
afterEach(async () => {
|
|
64
|
-
disposeToken.set();
|
|
65
|
-
await injector.dispose();
|
|
66
|
-
});
|
|
67
|
-
test('should not busy loop when refresh fails to produce a valid token', async () => {
|
|
68
|
-
// 1. Mock a token that is ALWAYS expired
|
|
69
|
-
// Server time is 1000. Token expires at 500.
|
|
70
|
-
const expiredToken = { iat: 0, exp: 500, jti: 'expired', session: 's', tenant: 't', subject: 'sub' };
|
|
71
|
-
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(expiredToken));
|
|
72
|
-
// refresh() will return the SAME expired token
|
|
73
|
-
mockApiClient.refresh.mockResolvedValue(expiredToken);
|
|
74
|
-
// 2. Start service
|
|
75
|
-
service = injector.resolve(AuthenticationClientService);
|
|
76
|
-
// 3. Wait a bit
|
|
77
|
-
// With 100ms mandatory yield + minRefreshDelay (1 min) protection, it should only try once or twice in 500ms
|
|
78
|
-
// WITHOUT protection, it would try thousands of times.
|
|
79
|
-
await timeout(500);
|
|
80
|
-
// It should have tried to refresh
|
|
81
|
-
expect(mockApiClient.refresh.mock.calls.length).toBeLessThan(5);
|
|
82
|
-
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('Token still needs refresh after attempt'));
|
|
83
|
-
});
|
|
84
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|