@indigoai-us/hq-cloud 6.15.0 → 6.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/sync-mutation.d.ts +16 -0
- package/dist/bin/sync-mutation.d.ts.map +1 -0
- package/dist/bin/sync-mutation.js +60 -0
- package/dist/bin/sync-mutation.js.map +1 -0
- package/dist/bin/sync-mutation.test.d.ts +2 -0
- package/dist/bin/sync-mutation.test.d.ts.map +1 -0
- package/dist/bin/sync-mutation.test.js +165 -0
- package/dist/bin/sync-mutation.test.js.map +1 -0
- package/dist/bin/sync-runner-company.d.ts +8 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +16 -0
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-company.test.d.ts +2 -0
- package/dist/bin/sync-runner-company.test.d.ts.map +1 -0
- package/dist/bin/sync-runner-company.test.js +36 -0
- package/dist/bin/sync-runner-company.test.js.map +1 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +98 -8
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +17 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +109 -0
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/conflict-recovery.test.d.ts +2 -0
- package/dist/cli/conflict-recovery.test.d.ts.map +1 -0
- package/dist/cli/conflict-recovery.test.js +201 -0
- package/dist/cli/conflict-recovery.test.js.map +1 -0
- package/dist/cli/conflict.d.ts +60 -0
- package/dist/cli/conflict.d.ts.map +1 -1
- package/dist/cli/conflict.js +333 -0
- package/dist/cli/conflict.js.map +1 -1
- package/dist/cli/sync.d.ts +35 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +100 -0
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +85 -1
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/skill-telemetry.d.ts +6 -0
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +14 -2
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +79 -0
- package/dist/skill-telemetry.test.js.map +1 -1
- package/dist/sync/candidate-uploader.d.ts +88 -0
- package/dist/sync/candidate-uploader.d.ts.map +1 -0
- package/dist/sync/candidate-uploader.js +212 -0
- package/dist/sync/candidate-uploader.js.map +1 -0
- package/dist/sync/candidate-uploader.test.d.ts +2 -0
- package/dist/sync/candidate-uploader.test.d.ts.map +1 -0
- package/dist/sync/candidate-uploader.test.js +132 -0
- package/dist/sync/candidate-uploader.test.js.map +1 -0
- package/dist/sync/delta-client.d.ts +73 -0
- package/dist/sync/delta-client.d.ts.map +1 -0
- package/dist/sync/delta-client.js +201 -0
- package/dist/sync/delta-client.js.map +1 -0
- package/dist/sync/delta-client.test.d.ts +2 -0
- package/dist/sync/delta-client.test.d.ts.map +1 -0
- package/dist/sync/delta-client.test.js +97 -0
- package/dist/sync/delta-client.test.js.map +1 -0
- package/dist/sync/durable-apply.d.ts +76 -0
- package/dist/sync/durable-apply.d.ts.map +1 -0
- package/dist/sync/durable-apply.js +530 -0
- package/dist/sync/durable-apply.js.map +1 -0
- package/dist/sync/durable-apply.test.d.ts +2 -0
- package/dist/sync/durable-apply.test.d.ts.map +1 -0
- package/dist/sync/durable-apply.test.js +180 -0
- package/dist/sync/durable-apply.test.js.map +1 -0
- package/dist/sync/event-sync.d.ts +33 -1
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +149 -1
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/event-sync.test.js +142 -1
- package/dist/sync/event-sync.test.js.map +1 -1
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/multipart-uploader.d.ts +99 -0
- package/dist/sync/multipart-uploader.d.ts.map +1 -0
- package/dist/sync/multipart-uploader.js +447 -0
- package/dist/sync/multipart-uploader.js.map +1 -0
- package/dist/sync/multipart-uploader.test.d.ts +2 -0
- package/dist/sync/multipart-uploader.test.d.ts.map +1 -0
- package/dist/sync/multipart-uploader.test.js +119 -0
- package/dist/sync/multipart-uploader.test.js.map +1 -0
- package/dist/sync/mutation-client.d.ts +85 -0
- package/dist/sync/mutation-client.d.ts.map +1 -0
- package/dist/sync/mutation-client.js +245 -0
- package/dist/sync/mutation-client.js.map +1 -0
- package/dist/sync/mutation-client.test.d.ts +2 -0
- package/dist/sync/mutation-client.test.d.ts.map +1 -0
- package/dist/sync/mutation-client.test.js +51 -0
- package/dist/sync/mutation-client.test.js.map +1 -0
- package/dist/sync/push-receiver.d.ts +45 -0
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +101 -0
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +54 -2
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/sync/scope-inventory-client.d.ts +69 -0
- package/dist/sync/scope-inventory-client.d.ts.map +1 -0
- package/dist/sync/scope-inventory-client.js +210 -0
- package/dist/sync/scope-inventory-client.js.map +1 -0
- package/dist/sync/scope-inventory-client.test.d.ts +2 -0
- package/dist/sync/scope-inventory-client.test.d.ts.map +1 -0
- package/dist/sync/scope-inventory-client.test.js +94 -0
- package/dist/sync/scope-inventory-client.test.js.map +1 -0
- package/dist/sync/snapshot-client.d.ts +98 -0
- package/dist/sync/snapshot-client.d.ts.map +1 -0
- package/dist/sync/snapshot-client.js +402 -0
- package/dist/sync/snapshot-client.js.map +1 -0
- package/dist/sync/snapshot-client.test.d.ts +2 -0
- package/dist/sync/snapshot-client.test.d.ts.map +1 -0
- package/dist/sync/snapshot-client.test.js +169 -0
- package/dist/sync/snapshot-client.test.js.map +1 -0
- package/dist/sync/uploader-finalization.d.ts +97 -0
- package/dist/sync/uploader-finalization.d.ts.map +1 -0
- package/dist/sync/uploader-finalization.js +273 -0
- package/dist/sync/uploader-finalization.js.map +1 -0
- package/dist/sync/uploader-finalization.test.d.ts +2 -0
- package/dist/sync/uploader-finalization.test.d.ts.map +1 -0
- package/dist/sync/uploader-finalization.test.js +92 -0
- package/dist/sync/uploader-finalization.test.js.map +1 -0
- package/dist/telemetry.d.ts +11 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +21 -2
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.js +80 -0
- package/dist/telemetry.test.js.map +1 -1
- package/package.json +6 -1
- package/.claude/policies/hq-cloud-esm-cannot-spy-fs-builtins.md +0 -30
- package/.claude/policies/hq-cloud-strip-types-no-parameter-properties.md +0 -22
- package/.github/workflows/ci.yml +0 -84
- package/.github/workflows/publish.yml +0 -56
- package/.github/workflows/unreleased-commits-nag.yml +0 -256
- package/eslint.config.js +0 -67
- package/pnpm-workspace.yaml +0 -2
- package/scripts/presign-transport-e2e.mjs +0 -250
- package/scripts/vault-rebaseline.sh +0 -323
- package/scripts/vault-rescue.sh +0 -332
- package/src/active-company.test.ts +0 -188
- package/src/active-company.ts +0 -168
- package/src/agent-codex-instructions.test.ts +0 -332
- package/src/agent-codex-instructions.ts +0 -309
- package/src/auth.ts +0 -146
- package/src/backup-prune.test.ts +0 -98
- package/src/backup-prune.ts +0 -182
- package/src/bin/backup-prune-runner.ts +0 -33
- package/src/bin/rescue-runner.ts +0 -25
- package/src/bin/sync-runner-company.ts +0 -695
- package/src/bin/sync-runner-events.test.ts +0 -143
- package/src/bin/sync-runner-events.ts +0 -55
- package/src/bin/sync-runner-planning.test.ts +0 -311
- package/src/bin/sync-runner-planning.ts +0 -258
- package/src/bin/sync-runner-rollup.test.ts +0 -37
- package/src/bin/sync-runner-rollup.ts +0 -97
- package/src/bin/sync-runner-telemetry.ts +0 -15
- package/src/bin/sync-runner-watch-loop.ts +0 -1235
- package/src/bin/sync-runner-watch-routes.test.ts +0 -71
- package/src/bin/sync-runner-watch-routes.ts +0 -184
- package/src/bin/sync-runner.test.ts +0 -8767
- package/src/bin/sync-runner.ts +0 -2190
- package/src/cli/accept.ts +0 -124
- package/src/cli/conflict.ts +0 -119
- package/src/cli/doctor.test.ts +0 -581
- package/src/cli/doctor.ts +0 -642
- package/src/cli/index.ts +0 -49
- package/src/cli/invite.test.ts +0 -250
- package/src/cli/invite.ts +0 -214
- package/src/cli/promote.ts +0 -157
- package/src/cli/reindex-knowledge.test.ts +0 -307
- package/src/cli/reindex-knowledge.ts +0 -450
- package/src/cli/reindex.test.ts +0 -957
- package/src/cli/reindex.ts +0 -979
- package/src/cli/rescue-classify-ordering.test.ts +0 -548
- package/src/cli/rescue-clone-diagnostics.test.ts +0 -120
- package/src/cli/rescue-core.ts +0 -3011
- package/src/cli/rescue-drift-reconcile.test.ts +0 -179
- package/src/cli/rescue-drop-dir-symlink.test.ts +0 -224
- package/src/cli/rescue-exec-bit-preserve.test.ts +0 -187
- package/src/cli/rescue-hq-root-guard.test.ts +0 -232
- package/src/cli/rescue-journal-reconcile.test.ts +0 -215
- package/src/cli/rescue-mtime-preserve.test.ts +0 -203
- package/src/cli/rescue-settings-reconcile.test.ts +0 -637
- package/src/cli/rescue-snapshot.test.ts +0 -57
- package/src/cli/rescue-snapshot.ts +0 -51
- package/src/cli/rescue.reindex.test.ts +0 -63
- package/src/cli/rescue.test.ts +0 -131
- package/src/cli/rescue.ts +0 -182
- package/src/cli/share.test.ts +0 -7843
- package/src/cli/share.ts +0 -3663
- package/src/cli/sync-scope.test.ts +0 -652
- package/src/cli/sync.test.ts +0 -5207
- package/src/cli/sync.ts +0 -3470
- package/src/cli/tombstones.ts +0 -106
- package/src/cli/watch-event-push-conflict.test.ts +0 -234
- package/src/client-info.test.ts +0 -214
- package/src/client-info.ts +0 -121
- package/src/cognito-auth.test.ts +0 -712
- package/src/cognito-auth.ts +0 -1422
- package/src/company-resolver.test.ts +0 -618
- package/src/company-resolver.ts +0 -521
- package/src/context.test.ts +0 -583
- package/src/context.ts +0 -378
- package/src/daemon-worker.ts +0 -26
- package/src/daemon.ts +0 -99
- package/src/entity-resolver.test.ts +0 -315
- package/src/entity-resolver.ts +0 -180
- package/src/ignore.test.ts +0 -466
- package/src/ignore.ts +0 -469
- package/src/index.ts +0 -439
- package/src/journal.test.ts +0 -968
- package/src/journal.ts +0 -765
- package/src/lib/cloud-authoritative.test.ts +0 -45
- package/src/lib/cloud-authoritative.ts +0 -59
- package/src/lib/conflict-file.ts +0 -86
- package/src/lib/conflict-index.ts +0 -289
- package/src/lib/conflict.test.ts +0 -348
- package/src/lib/describe-error.test.ts +0 -100
- package/src/lib/describe-error.ts +0 -58
- package/src/lib/exit-codes.ts +0 -24
- package/src/lib/machine-id.test.ts +0 -231
- package/src/lib/machine-id.ts +0 -175
- package/src/lib/net-errors.test.ts +0 -65
- package/src/lib/net-errors.ts +0 -86
- package/src/lib/readlink-safe.test.ts +0 -43
- package/src/lib/readlink-safe.ts +0 -29
- package/src/local-path-codec.test.ts +0 -138
- package/src/local-path-codec.ts +0 -161
- package/src/machine-auth.test.ts +0 -1323
- package/src/manifest-reconcile.test.ts +0 -1123
- package/src/manifest-reconcile.ts +0 -518
- package/src/object-io.test.ts +0 -1221
- package/src/object-io.ts +0 -1306
- package/src/operation-lock.test.ts +0 -484
- package/src/operation-lock.ts +0 -680
- package/src/outcome-telemetry.test.ts +0 -498
- package/src/outcome-telemetry.ts +0 -639
- package/src/personal-vault-exclusions.test.ts +0 -308
- package/src/personal-vault-exclusions.ts +0 -354
- package/src/personal-vault.test.ts +0 -756
- package/src/personal-vault.ts +0 -496
- package/src/prefix-coalesce.test.ts +0 -240
- package/src/prefix-coalesce.ts +0 -273
- package/src/public-surface.test.ts +0 -117
- package/src/qmd-reindex.test.ts +0 -877
- package/src/qmd-reindex.ts +0 -842
- package/src/read-only-state-dir.test.ts +0 -188
- package/src/remote-pull.test.ts +0 -1130
- package/src/remote-pull.ts +0 -618
- package/src/s3.symlink-materialize.test.ts +0 -492
- package/src/s3.test.ts +0 -1789
- package/src/s3.ts +0 -1532
- package/src/schemas/signal-types.test.ts +0 -82
- package/src/schemas/signal-types.ts +0 -38
- package/src/schemas/source-channels.test.ts +0 -82
- package/src/schemas/source-channels.ts +0 -53
- package/src/scope-shrink.test.ts +0 -633
- package/src/scope-shrink.ts +0 -481
- package/src/signals/get.test.ts +0 -310
- package/src/signals/get.ts +0 -75
- package/src/signals/internals.ts +0 -195
- package/src/signals/list.test.ts +0 -420
- package/src/signals/list.ts +0 -79
- package/src/signals/parse.ts +0 -8
- package/src/signals/types.ts +0 -91
- package/src/skill-telemetry.test.ts +0 -1825
- package/src/skill-telemetry.ts +0 -1439
- package/src/sources/get.test.ts +0 -293
- package/src/sources/get.ts +0 -66
- package/src/sources/internals.ts +0 -198
- package/src/sources/list.test.ts +0 -402
- package/src/sources/list.ts +0 -84
- package/src/sources/parse.ts +0 -43
- package/src/sources/types.ts +0 -84
- package/src/sync/event-sync.test.ts +0 -594
- package/src/sync/event-sync.ts +0 -545
- package/src/sync/feature-flags.test.ts +0 -378
- package/src/sync/feature-flags.ts +0 -62
- package/src/sync/index.ts +0 -76
- package/src/sync/lease-client.test.ts +0 -128
- package/src/sync/lease-client.ts +0 -207
- package/src/sync/logger.test.ts +0 -242
- package/src/sync/logger.ts +0 -79
- package/src/sync/metrics.test.ts +0 -462
- package/src/sync/metrics.ts +0 -213
- package/src/sync/pull-scope.ts +0 -265
- package/src/sync/push-event.test.ts +0 -266
- package/src/sync/push-event.ts +0 -224
- package/src/sync/push-receiver.test.ts +0 -566
- package/src/sync/push-receiver.ts +0 -1048
- package/src/sync/push-transport.ts +0 -231
- package/src/sync/realtime-rollout.test.ts +0 -86
- package/src/sync/realtime-rollout.ts +0 -262
- package/src/sync/state-store.test.ts +0 -194
- package/src/sync/state-store.ts +0 -727
- package/src/sync-core.ts +0 -58
- package/src/sync-progress.test.ts +0 -94
- package/src/sync-progress.ts +0 -140
- package/src/telemetry-events.test.ts +0 -88
- package/src/telemetry-events.ts +0 -205
- package/src/telemetry.test.ts +0 -1280
- package/src/telemetry.ts +0 -1109
- package/src/types.ts +0 -314
- package/src/vault-client.test.ts +0 -1380
- package/src/vault-client.ts +0 -1694
- package/src/version.ts +0 -24
- package/src/watch-roots.test.ts +0 -278
- package/src/watch-roots.ts +0 -162
- package/src/watcher-event-gate.test.ts +0 -212
- package/src/watcher.test.ts +0 -1079
- package/src/watcher.ts +0 -1741
- package/test/e2e/sync/cross-tenant-isolation.test.ts +0 -630
- package/test/e2e/sync/skill-telemetry-oversized-transcript.test.ts +0 -124
- package/test/e2e/sync/transient-company-leg.test.ts +0 -384
- package/test/e2e/sync/windows-unreadable-link-leg.test.ts +0 -191
- package/test/e2e/watcher-real-chokidar.test.ts +0 -165
- package/test/e2e/watcher-recursive-backend.test.ts +0 -181
- package/test/e2e/watcher-scoped-coverage.test.ts +0 -381
- package/test/invite-flow.integration.test.ts +0 -244
- package/test/joiner-manifest-reconcile.integration.test.ts +0 -322
- package/test/share-sync.integration.test.ts +0 -213
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -22
package/src/context.test.ts
DELETED
|
@@ -1,583 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for context.ts — entity context resolution (VLT-5 US-001).
|
|
3
|
-
*
|
|
4
|
-
* Uses a mock fetch to simulate vault-service API responses.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
8
|
-
import {
|
|
9
|
-
resolveEntityContext,
|
|
10
|
-
refreshEntityContext,
|
|
11
|
-
clearContextCache,
|
|
12
|
-
isExpiringSoon,
|
|
13
|
-
} from "./context.js";
|
|
14
|
-
import type { VaultServiceConfig } from "./types.js";
|
|
15
|
-
|
|
16
|
-
const mockConfig: VaultServiceConfig = {
|
|
17
|
-
apiUrl: "https://vault-api.test",
|
|
18
|
-
authToken: "test-jwt-token",
|
|
19
|
-
region: "us-east-1",
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const mockEntity = {
|
|
23
|
-
uid: "cmp_01ABCDEF",
|
|
24
|
-
slug: "acme",
|
|
25
|
-
bucketName: "hq-vault-acme-123",
|
|
26
|
-
status: "active",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const mockVendResponse = {
|
|
30
|
-
credentials: {
|
|
31
|
-
accessKeyId: "ASIA_TEST_KEY",
|
|
32
|
-
secretAccessKey: "test-secret",
|
|
33
|
-
sessionToken: "test-session-token",
|
|
34
|
-
expiration: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
|
|
35
|
-
},
|
|
36
|
-
expiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
function setupFetchMock(overrides?: {
|
|
40
|
-
entityStatus?: number;
|
|
41
|
-
entityBody?: unknown;
|
|
42
|
-
vendStatus?: number;
|
|
43
|
-
vendBody?: unknown;
|
|
44
|
-
}) {
|
|
45
|
-
const fetchMock = vi.fn();
|
|
46
|
-
|
|
47
|
-
fetchMock.mockImplementation(async (url: string) => {
|
|
48
|
-
const urlStr = String(url);
|
|
49
|
-
|
|
50
|
-
// New per-user-namespace slug resolver (hq-pro PR 67). Maps slug
|
|
51
|
-
// lookups to `{available: false, conflictingCompanyUid}` so the
|
|
52
|
-
// caller follows up with `/entity/{uid}`, which lands in the
|
|
53
|
-
// `/entity/cmp_` branch below — that's where `entityBody` and
|
|
54
|
-
// `entityStatus` overrides apply. The check-slug branch only
|
|
55
|
-
// honors `entityStatus` (so tests can simulate a 404/500 on the
|
|
56
|
-
// namespace lookup itself); its response shape stays fixed.
|
|
57
|
-
if (urlStr.includes("/entity/check-slug/me")) {
|
|
58
|
-
return {
|
|
59
|
-
ok: (overrides?.entityStatus ?? 200) < 400,
|
|
60
|
-
status: overrides?.entityStatus ?? 200,
|
|
61
|
-
json: async () => ({
|
|
62
|
-
available: false,
|
|
63
|
-
conflictingCompanyUid: mockEntity.uid,
|
|
64
|
-
}),
|
|
65
|
-
text: async () =>
|
|
66
|
-
JSON.stringify({
|
|
67
|
-
available: false,
|
|
68
|
-
conflictingCompanyUid: mockEntity.uid,
|
|
69
|
-
}),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Kept for any tests that still mock the legacy global endpoint
|
|
74
|
-
// directly (none should, post-PR 67 — but harmless if invoked).
|
|
75
|
-
if (urlStr.includes("/entity/by-slug/")) {
|
|
76
|
-
return {
|
|
77
|
-
ok: (overrides?.entityStatus ?? 200) < 400,
|
|
78
|
-
status: overrides?.entityStatus ?? 200,
|
|
79
|
-
json: async () => overrides?.entityBody ?? { entity: mockEntity },
|
|
80
|
-
text: async () => JSON.stringify(overrides?.entityBody ?? { entity: mockEntity }),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (urlStr.includes("/entity/cmp_")) {
|
|
85
|
-
return {
|
|
86
|
-
ok: (overrides?.entityStatus ?? 200) < 400,
|
|
87
|
-
status: overrides?.entityStatus ?? 200,
|
|
88
|
-
json: async () => overrides?.entityBody ?? { entity: mockEntity },
|
|
89
|
-
text: async () => JSON.stringify(overrides?.entityBody ?? { entity: mockEntity }),
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (urlStr.includes("/sts/vend")) {
|
|
94
|
-
return {
|
|
95
|
-
ok: (overrides?.vendStatus ?? 200) < 400,
|
|
96
|
-
status: overrides?.vendStatus ?? 200,
|
|
97
|
-
json: async () => overrides?.vendBody ?? mockVendResponse,
|
|
98
|
-
text: async () => JSON.stringify(overrides?.vendBody ?? mockVendResponse),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return { ok: false, status: 404, text: async () => "Not found" };
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
106
|
-
return fetchMock;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
describe("resolveEntityContext", () => {
|
|
110
|
-
beforeEach(() => {
|
|
111
|
-
clearContextCache();
|
|
112
|
-
vi.restoreAllMocks();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("resolves context by slug", async () => {
|
|
116
|
-
const fetchMock = setupFetchMock();
|
|
117
|
-
|
|
118
|
-
const ctx = await resolveEntityContext("acme", mockConfig);
|
|
119
|
-
|
|
120
|
-
expect(ctx.uid).toBe("cmp_01ABCDEF");
|
|
121
|
-
expect(ctx.bucketName).toBe("hq-vault-acme-123");
|
|
122
|
-
expect(ctx.credentials?.accessKeyId).toBe("ASIA_TEST_KEY");
|
|
123
|
-
expect(ctx.region).toBe("us-east-1");
|
|
124
|
-
|
|
125
|
-
// Verify entity lookup used the new per-user-namespace endpoint
|
|
126
|
-
// (PR 67) + a follow-up `/entity/{uid}` materialization + STS.
|
|
127
|
-
expect(fetchMock).toHaveBeenCalledTimes(3);
|
|
128
|
-
expect(String(fetchMock.mock.calls[0][0])).toContain(
|
|
129
|
-
"/entity/check-slug/me?type=company&slug=acme",
|
|
130
|
-
);
|
|
131
|
-
expect(String(fetchMock.mock.calls[1][0])).toContain(
|
|
132
|
-
`/entity/${mockEntity.uid}`,
|
|
133
|
-
);
|
|
134
|
-
expect(String(fetchMock.mock.calls[2][0])).toContain("/sts/vend");
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("resolves context by UID directly", async () => {
|
|
138
|
-
const fetchMock = setupFetchMock();
|
|
139
|
-
|
|
140
|
-
const ctx = await resolveEntityContext("cmp_01ABCDEF", mockConfig);
|
|
141
|
-
|
|
142
|
-
expect(ctx.uid).toBe("cmp_01ABCDEF");
|
|
143
|
-
// Verify entity lookup used direct UID endpoint
|
|
144
|
-
expect(String(fetchMock.mock.calls[0][0])).toContain("/entity/cmp_01ABCDEF");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("returns cached context when credentials are fresh", async () => {
|
|
148
|
-
const fetchMock = setupFetchMock();
|
|
149
|
-
|
|
150
|
-
const ctx1 = await resolveEntityContext("acme", mockConfig);
|
|
151
|
-
const ctx2 = await resolveEntityContext("acme", mockConfig);
|
|
152
|
-
|
|
153
|
-
expect(ctx1).toBe(ctx2); // Same reference
|
|
154
|
-
// 3 fetches per resolve under the new model: check-slug + entity.get + sts/vend.
|
|
155
|
-
// 1 resolve here (second call hits cache, no new fetches).
|
|
156
|
-
expect(fetchMock).toHaveBeenCalledTimes(3);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("auto-refreshes when credentials expire soon", async () => {
|
|
160
|
-
const almostExpired = new Date(Date.now() + 60 * 1000).toISOString(); // 1 min left
|
|
161
|
-
const fetchMock = setupFetchMock({
|
|
162
|
-
vendBody: {
|
|
163
|
-
credentials: mockVendResponse.credentials,
|
|
164
|
-
expiresAt: almostExpired,
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
const ctx1 = await resolveEntityContext("acme", mockConfig);
|
|
169
|
-
|
|
170
|
-
// Second call should refresh because <2 min remaining
|
|
171
|
-
const ctx2 = await resolveEntityContext("acme", mockConfig);
|
|
172
|
-
expect(ctx2).not.toBe(ctx1);
|
|
173
|
-
// 2 resolves × 3 fetches each = 6 under the new model.
|
|
174
|
-
expect(fetchMock).toHaveBeenCalledTimes(6);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("throws when entity has no bucket", async () => {
|
|
178
|
-
setupFetchMock({
|
|
179
|
-
entityBody: { entity: { ...mockEntity, bucketName: undefined } },
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
await expect(resolveEntityContext("acme", mockConfig)).rejects.toThrow(
|
|
183
|
-
/no bucket provisioned/,
|
|
184
|
-
);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("throws on entity lookup failure", async () => {
|
|
188
|
-
setupFetchMock({ entityStatus: 404 });
|
|
189
|
-
|
|
190
|
-
// The namespace lookup fails first under the new model — error
|
|
191
|
-
// message now reflects "Failed to check slug" before the
|
|
192
|
-
// entity.get(uid) step is reached.
|
|
193
|
-
await expect(resolveEntityContext("nonexistent", mockConfig)).rejects.toThrow(
|
|
194
|
-
/Failed to check slug/,
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("throws on STS vend failure", async () => {
|
|
199
|
-
setupFetchMock({ vendStatus: 403 });
|
|
200
|
-
|
|
201
|
-
await expect(resolveEntityContext("acme", mockConfig)).rejects.toThrow(
|
|
202
|
-
/STS.*vend.*failed/,
|
|
203
|
-
);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe("routing by UID prefix and vend-self dispatch", () => {
|
|
208
|
-
beforeEach(() => {
|
|
209
|
-
clearContextCache();
|
|
210
|
-
vi.restoreAllMocks();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("prs_* UID: entity resolved via /entity/{uid} and credentials via /sts/vend-self", async () => {
|
|
214
|
-
const prsEntity = {
|
|
215
|
-
uid: "prs_01PERSON",
|
|
216
|
-
slug: "test-person",
|
|
217
|
-
bucketName: "hq-vault-prs-01person",
|
|
218
|
-
status: "active",
|
|
219
|
-
};
|
|
220
|
-
const calls: string[] = [];
|
|
221
|
-
vi.stubGlobal("fetch", vi.fn().mockImplementation(async (url: string) => {
|
|
222
|
-
const u = String(url);
|
|
223
|
-
calls.push(u);
|
|
224
|
-
if (u.includes("/entity/prs_")) {
|
|
225
|
-
return { ok: true, status: 200, json: async () => ({ entity: prsEntity }), text: async () => "" };
|
|
226
|
-
}
|
|
227
|
-
if (u.includes("/sts/vend-self")) {
|
|
228
|
-
return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
|
|
229
|
-
}
|
|
230
|
-
return { ok: false, status: 404, text: async () => "Not found" };
|
|
231
|
-
}));
|
|
232
|
-
|
|
233
|
-
await resolveEntityContext("prs_01PERSON", mockConfig);
|
|
234
|
-
|
|
235
|
-
expect(calls.some((u) => u.includes("/entity/prs_01PERSON"))).toBe(true);
|
|
236
|
-
const vendCalls = calls.filter((u) => u.includes("/sts/vend"));
|
|
237
|
-
expect(vendCalls).toHaveLength(1);
|
|
238
|
-
expect(vendCalls[0]).toContain("/sts/vend-self");
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it("foo_bar slug: entity resolved via /entity/check-slug/me + /entity/<uid> and credentials via /sts/vend", async () => {
|
|
242
|
-
const calls: string[] = [];
|
|
243
|
-
vi.stubGlobal("fetch", vi.fn().mockImplementation(async (url: string) => {
|
|
244
|
-
const u = String(url);
|
|
245
|
-
calls.push(u);
|
|
246
|
-
if (u.includes("/entity/check-slug/me")) {
|
|
247
|
-
return {
|
|
248
|
-
ok: true,
|
|
249
|
-
status: 200,
|
|
250
|
-
json: async () => ({ available: false, conflictingCompanyUid: mockEntity.uid }),
|
|
251
|
-
text: async () => "",
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
if (u.includes(`/entity/${mockEntity.uid}`)) {
|
|
255
|
-
return { ok: true, status: 200, json: async () => ({ entity: mockEntity }), text: async () => "" };
|
|
256
|
-
}
|
|
257
|
-
if (u.includes("/sts/vend")) {
|
|
258
|
-
return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
|
|
259
|
-
}
|
|
260
|
-
return { ok: false, status: 404, text: async () => "Not found" };
|
|
261
|
-
}));
|
|
262
|
-
|
|
263
|
-
await resolveEntityContext("foo_bar", mockConfig);
|
|
264
|
-
|
|
265
|
-
expect(
|
|
266
|
-
calls.some((u) => u.includes("/entity/check-slug/me?type=company&slug=foo_bar")),
|
|
267
|
-
).toBe(true);
|
|
268
|
-
const vendCalls = calls.filter((u) => u.includes("/sts/vend"));
|
|
269
|
-
expect(vendCalls).toHaveLength(1);
|
|
270
|
-
expect(vendCalls[0]).not.toContain("/sts/vend-self");
|
|
271
|
-
expect(vendCalls[0]).toContain("/sts/vend");
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it("team_alpha slug: entity resolved via /entity/check-slug/me + /entity/<uid> and credentials via /sts/vend", async () => {
|
|
275
|
-
const calls: string[] = [];
|
|
276
|
-
vi.stubGlobal("fetch", vi.fn().mockImplementation(async (url: string) => {
|
|
277
|
-
const u = String(url);
|
|
278
|
-
calls.push(u);
|
|
279
|
-
if (u.includes("/entity/check-slug/me")) {
|
|
280
|
-
return {
|
|
281
|
-
ok: true,
|
|
282
|
-
status: 200,
|
|
283
|
-
json: async () => ({ available: false, conflictingCompanyUid: mockEntity.uid }),
|
|
284
|
-
text: async () => "",
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
if (u.includes(`/entity/${mockEntity.uid}`)) {
|
|
288
|
-
return { ok: true, status: 200, json: async () => ({ entity: mockEntity }), text: async () => "" };
|
|
289
|
-
}
|
|
290
|
-
if (u.includes("/sts/vend")) {
|
|
291
|
-
return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
|
|
292
|
-
}
|
|
293
|
-
return { ok: false, status: 404, text: async () => "Not found" };
|
|
294
|
-
}));
|
|
295
|
-
|
|
296
|
-
await resolveEntityContext("team_alpha", mockConfig);
|
|
297
|
-
|
|
298
|
-
expect(
|
|
299
|
-
calls.some((u) => u.includes("/entity/check-slug/me?type=company&slug=team_alpha")),
|
|
300
|
-
).toBe(true);
|
|
301
|
-
const vendCalls = calls.filter((u) => u.includes("/sts/vend"));
|
|
302
|
-
expect(vendCalls).toHaveLength(1);
|
|
303
|
-
expect(vendCalls[0]).not.toContain("/sts/vend-self");
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it("cmp_* UID: entity resolved via /entity/{uid} and credentials via /sts/vend", async () => {
|
|
307
|
-
const calls: string[] = [];
|
|
308
|
-
vi.stubGlobal("fetch", vi.fn().mockImplementation(async (url: string) => {
|
|
309
|
-
const u = String(url);
|
|
310
|
-
calls.push(u);
|
|
311
|
-
if (u.includes("/entity/cmp_")) {
|
|
312
|
-
return { ok: true, status: 200, json: async () => ({ entity: mockEntity }), text: async () => "" };
|
|
313
|
-
}
|
|
314
|
-
if (u.includes("/sts/vend")) {
|
|
315
|
-
return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
|
|
316
|
-
}
|
|
317
|
-
return { ok: false, status: 404, text: async () => "Not found" };
|
|
318
|
-
}));
|
|
319
|
-
|
|
320
|
-
await resolveEntityContext("cmp_01ABCDEF", mockConfig);
|
|
321
|
-
|
|
322
|
-
expect(calls.some((u) => u.includes("/entity/cmp_01ABCDEF"))).toBe(true);
|
|
323
|
-
const vendCalls = calls.filter((u) => u.includes("/sts/vend"));
|
|
324
|
-
expect(vendCalls).toHaveLength(1);
|
|
325
|
-
expect(vendCalls[0]).not.toContain("/sts/vend-self");
|
|
326
|
-
expect(vendCalls[0]).toContain("/sts/vend");
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// ---------------------------------------------------------------------------
|
|
331
|
-
// HQ-59 company vend-skip (presign transport)
|
|
332
|
-
//
|
|
333
|
-
// When the run uses the presigned-URL transport for company vaults
|
|
334
|
-
// (`companyVaultUsesPresign`), resolveEntityContext must NOT call the company
|
|
335
|
-
// `/sts/vend` route — the presign transport needs no STS creds, and the mere
|
|
336
|
-
// presence of a cmp_ vend call is the pre-6.11.6 signal the hq-pro min-version
|
|
337
|
-
// gate denies on. Personal vaults must still vend self.
|
|
338
|
-
// ---------------------------------------------------------------------------
|
|
339
|
-
describe("HQ-59 company vend-skip (companyVaultUsesPresign)", () => {
|
|
340
|
-
const presignConfig: VaultServiceConfig = {
|
|
341
|
-
...mockConfig,
|
|
342
|
-
companyVaultUsesPresign: true,
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
beforeEach(() => {
|
|
346
|
-
clearContextCache();
|
|
347
|
-
vi.restoreAllMocks();
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it("cmp_* UID: SKIPS /sts/vend, returns no credentials + a far-future expiry", async () => {
|
|
351
|
-
const calls: string[] = [];
|
|
352
|
-
vi.stubGlobal(
|
|
353
|
-
"fetch",
|
|
354
|
-
vi.fn().mockImplementation(async (url: string) => {
|
|
355
|
-
const u = String(url);
|
|
356
|
-
calls.push(u);
|
|
357
|
-
if (u.includes("/entity/cmp_")) {
|
|
358
|
-
return { ok: true, status: 200, json: async () => ({ entity: mockEntity }), text: async () => "" };
|
|
359
|
-
}
|
|
360
|
-
// If a vend ever fires, surface it as a hard failure so the test fails
|
|
361
|
-
// LOUDLY rather than silently allowing the route the gate watches.
|
|
362
|
-
if (u.includes("/sts/vend")) {
|
|
363
|
-
return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
|
|
364
|
-
}
|
|
365
|
-
return { ok: false, status: 404, text: async () => "Not found" };
|
|
366
|
-
}),
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
const ctx = await resolveEntityContext("cmp_01ABCDEF", presignConfig);
|
|
370
|
-
|
|
371
|
-
// The load-bearing assertion: NO company vend route was hit.
|
|
372
|
-
expect(calls.some((u) => u.includes("/sts/vend"))).toBe(false);
|
|
373
|
-
expect(calls.some((u) => u.includes("/entity/cmp_01ABCDEF"))).toBe(true);
|
|
374
|
-
// Credential-less context with a sentinel far-future expiry → never re-vends.
|
|
375
|
-
expect(ctx.credentials).toBeUndefined();
|
|
376
|
-
expect(ctx.bucketName).toBe(mockEntity.bucketName);
|
|
377
|
-
expect(isExpiringSoon(ctx.expiresAt)).toBe(false);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
it("company slug: SKIPS /sts/vend after the namespace + entity lookup", async () => {
|
|
381
|
-
const calls: string[] = [];
|
|
382
|
-
vi.stubGlobal(
|
|
383
|
-
"fetch",
|
|
384
|
-
vi.fn().mockImplementation(async (url: string) => {
|
|
385
|
-
const u = String(url);
|
|
386
|
-
calls.push(u);
|
|
387
|
-
if (u.includes("/entity/check-slug/me")) {
|
|
388
|
-
return {
|
|
389
|
-
ok: true,
|
|
390
|
-
status: 200,
|
|
391
|
-
json: async () => ({ available: false, conflictingCompanyUid: mockEntity.uid }),
|
|
392
|
-
text: async () => "",
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
if (u.includes(`/entity/${mockEntity.uid}`)) {
|
|
396
|
-
return { ok: true, status: 200, json: async () => ({ entity: mockEntity }), text: async () => "" };
|
|
397
|
-
}
|
|
398
|
-
if (u.includes("/sts/vend")) {
|
|
399
|
-
return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
|
|
400
|
-
}
|
|
401
|
-
return { ok: false, status: 404, text: async () => "Not found" };
|
|
402
|
-
}),
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
const ctx = await resolveEntityContext("acme", presignConfig);
|
|
406
|
-
|
|
407
|
-
expect(calls.some((u) => u.includes("/sts/vend"))).toBe(false);
|
|
408
|
-
expect(ctx.credentials).toBeUndefined();
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
it("prs_* UID: STILL vends self even with the company flag set", async () => {
|
|
412
|
-
const prsEntity = {
|
|
413
|
-
uid: "prs_01PERSON",
|
|
414
|
-
slug: "test-person",
|
|
415
|
-
bucketName: "hq-vault-prs-01person",
|
|
416
|
-
status: "active",
|
|
417
|
-
};
|
|
418
|
-
const calls: string[] = [];
|
|
419
|
-
vi.stubGlobal(
|
|
420
|
-
"fetch",
|
|
421
|
-
vi.fn().mockImplementation(async (url: string) => {
|
|
422
|
-
const u = String(url);
|
|
423
|
-
calls.push(u);
|
|
424
|
-
if (u.includes("/entity/prs_")) {
|
|
425
|
-
return { ok: true, status: 200, json: async () => ({ entity: prsEntity }), text: async () => "" };
|
|
426
|
-
}
|
|
427
|
-
if (u.includes("/sts/vend-self")) {
|
|
428
|
-
return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
|
|
429
|
-
}
|
|
430
|
-
return { ok: false, status: 404, text: async () => "Not found" };
|
|
431
|
-
}),
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
const ctx = await resolveEntityContext("prs_01PERSON", presignConfig);
|
|
435
|
-
|
|
436
|
-
const vendCalls = calls.filter((u) => u.includes("/sts/vend"));
|
|
437
|
-
expect(vendCalls).toHaveLength(1);
|
|
438
|
-
expect(vendCalls[0]).toContain("/sts/vend-self");
|
|
439
|
-
expect(ctx.credentials?.accessKeyId).toBe(mockVendResponse.credentials.accessKeyId);
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
it("flag OFF (default): cmp_* still vends — no behavior change without the flag", async () => {
|
|
443
|
-
const calls: string[] = [];
|
|
444
|
-
vi.stubGlobal(
|
|
445
|
-
"fetch",
|
|
446
|
-
vi.fn().mockImplementation(async (url: string) => {
|
|
447
|
-
const u = String(url);
|
|
448
|
-
calls.push(u);
|
|
449
|
-
if (u.includes("/entity/cmp_")) {
|
|
450
|
-
return { ok: true, status: 200, json: async () => ({ entity: mockEntity }), text: async () => "" };
|
|
451
|
-
}
|
|
452
|
-
if (u.includes("/sts/vend")) {
|
|
453
|
-
return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
|
|
454
|
-
}
|
|
455
|
-
return { ok: false, status: 404, text: async () => "Not found" };
|
|
456
|
-
}),
|
|
457
|
-
);
|
|
458
|
-
|
|
459
|
-
const ctx = await resolveEntityContext("cmp_01ABCDEF", mockConfig);
|
|
460
|
-
|
|
461
|
-
const vendCalls = calls.filter((u) => u.includes("/sts/vend"));
|
|
462
|
-
expect(vendCalls).toHaveLength(1);
|
|
463
|
-
expect(vendCalls[0]).toContain("/sts/vend");
|
|
464
|
-
expect(ctx.credentials?.accessKeyId).toBe(mockVendResponse.credentials.accessKeyId);
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
describe("refreshEntityContext", () => {
|
|
469
|
-
beforeEach(() => {
|
|
470
|
-
clearContextCache();
|
|
471
|
-
vi.restoreAllMocks();
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it("evicts cache and fetches fresh credentials", async () => {
|
|
475
|
-
const fetchMock = setupFetchMock();
|
|
476
|
-
|
|
477
|
-
const ctx1 = await resolveEntityContext("acme", mockConfig);
|
|
478
|
-
const ctx2 = await refreshEntityContext("acme", mockConfig);
|
|
479
|
-
|
|
480
|
-
expect(ctx2).not.toBe(ctx1);
|
|
481
|
-
// 2 resolves × 3 fetches each = 6 under the new model
|
|
482
|
-
// (check-slug + entity.get + sts/vend each).
|
|
483
|
-
expect(fetchMock).toHaveBeenCalledTimes(6);
|
|
484
|
-
});
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
describe("isExpiringSoon", () => {
|
|
488
|
-
it("returns false when well within TTL", () => {
|
|
489
|
-
const future = new Date(Date.now() + 10 * 60 * 1000).toISOString();
|
|
490
|
-
expect(isExpiringSoon(future)).toBe(false);
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
it("returns true when within 2 minutes", () => {
|
|
494
|
-
const soon = new Date(Date.now() + 90 * 1000).toISOString();
|
|
495
|
-
expect(isExpiringSoon(soon)).toBe(true);
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it("returns true when already expired", () => {
|
|
499
|
-
const past = new Date(Date.now() - 1000).toISOString();
|
|
500
|
-
expect(isExpiringSoon(past)).toBe(true);
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// ---------------------------------------------------------------------------
|
|
505
|
-
// Refreshable authToken getter
|
|
506
|
-
//
|
|
507
|
-
// Regression for the personal-sync 401: a long-running `hq-sync-runner` run
|
|
508
|
-
// captures vaultConfig.authToken as a string at startup, then `refreshEntityContext`
|
|
509
|
-
// fires ~13 min into the personal-company sync (STS expiry) and uses that
|
|
510
|
-
// stale string against API Gateway's JWT authorizer → 401. The getter form
|
|
511
|
-
// lets every fetchEntity / postVend call resolve the latest token from disk.
|
|
512
|
-
// ---------------------------------------------------------------------------
|
|
513
|
-
|
|
514
|
-
describe("authToken getter is invoked per-request (regression: personal-sync 401)", () => {
|
|
515
|
-
beforeEach(() => {
|
|
516
|
-
clearContextCache();
|
|
517
|
-
vi.restoreAllMocks();
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
it("calls the getter on entity fetch AND on vend (every request)", async () => {
|
|
521
|
-
setupFetchMock();
|
|
522
|
-
const getter = vi.fn(async () => "fresh-token");
|
|
523
|
-
|
|
524
|
-
await resolveEntityContext("cmp_01ABCDEF", {
|
|
525
|
-
apiUrl: "https://vault-api.test",
|
|
526
|
-
authToken: getter,
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
// Two upstream requests: fetchEntity (UID looks like a UID) + postVend.
|
|
530
|
-
expect(getter).toHaveBeenCalledTimes(2);
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
it("picks up a rotated token between resolveEntityContext and refreshEntityContext", async () => {
|
|
534
|
-
const fetchMock = setupFetchMock();
|
|
535
|
-
let current = "stale-token";
|
|
536
|
-
const cfg = {
|
|
537
|
-
apiUrl: "https://vault-api.test",
|
|
538
|
-
authToken: async () => current,
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
await resolveEntityContext("cmp_01ABCDEF", cfg);
|
|
542
|
-
|
|
543
|
-
// fetchEntity + vend during initial resolution — both used "stale-token"
|
|
544
|
-
const initialAuth = (fetchMock.mock.calls as [string, RequestInit][]).map(
|
|
545
|
-
([, init]) =>
|
|
546
|
-
(init.headers as Record<string, string>).Authorization,
|
|
547
|
-
);
|
|
548
|
-
expect(initialAuth.every((a) => a === "Bearer stale-token")).toBe(true);
|
|
549
|
-
|
|
550
|
-
// Simulate the on-disk token rotating mid-flight.
|
|
551
|
-
current = "fresh-token";
|
|
552
|
-
|
|
553
|
-
await refreshEntityContext("cmp_01ABCDEF", cfg);
|
|
554
|
-
|
|
555
|
-
// The post-refresh calls must use the new token. Without the per-request
|
|
556
|
-
// getter, refreshEntityContext would still send "Bearer stale-token" and
|
|
557
|
-
// 401 against the gateway.
|
|
558
|
-
const allCalls = fetchMock.mock.calls as [string, RequestInit][];
|
|
559
|
-
const postRefreshCalls = allCalls.slice(initialAuth.length);
|
|
560
|
-
expect(postRefreshCalls.length).toBeGreaterThan(0);
|
|
561
|
-
for (const [, init] of postRefreshCalls) {
|
|
562
|
-
expect((init.headers as Record<string, string>).Authorization).toBe(
|
|
563
|
-
"Bearer fresh-token",
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
it("static-string authToken still works (back-compat)", async () => {
|
|
569
|
-
const fetchMock = setupFetchMock();
|
|
570
|
-
await resolveEntityContext("cmp_01ABCDEF", {
|
|
571
|
-
apiUrl: "https://vault-api.test",
|
|
572
|
-
authToken: "static-token",
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
const calls = fetchMock.mock.calls as [string, RequestInit][];
|
|
576
|
-
expect(calls.length).toBeGreaterThan(0);
|
|
577
|
-
for (const [, init] of calls) {
|
|
578
|
-
expect((init.headers as Record<string, string>).Authorization).toBe(
|
|
579
|
-
"Bearer static-token",
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
|
-
});
|