@stigmer/sdk 3.34.0 → 3.36.0
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/activity.d.ts.map +1 -1
- package/activity.js.map +1 -1
- package/artifact-routing.d.ts.map +1 -1
- package/artifact-routing.js.map +1 -1
- package/authorization-config.d.ts.map +1 -1
- package/authorization-config.js.map +1 -1
- package/billing.d.ts.map +1 -1
- package/billing.js.map +1 -1
- package/config.d.ts.map +1 -1
- package/config.js.map +1 -1
- package/cursor-accounts.d.ts.map +1 -1
- package/cursor-accounts.js.map +1 -1
- package/ensure-identity-account.d.ts.map +1 -1
- package/ensure-identity-account.js.map +1 -1
- package/errors.d.ts.map +1 -1
- package/errors.js.map +1 -1
- package/execution/approval-provenance.d.ts.map +1 -1
- package/execution/approval-provenance.js.map +1 -1
- package/execution/conversation-rules.d.ts.map +1 -1
- package/execution/conversation-rules.js.map +1 -1
- package/execution/execution-phases.d.ts.map +1 -1
- package/execution/execution-phases.js.map +1 -1
- package/execution/file-review-fold.d.ts.map +1 -1
- package/execution/file-review-fold.js.map +1 -1
- package/execution/to-display-file-change.d.ts.map +1 -1
- package/execution/to-display-file-change.js.map +1 -1
- package/execution/tool-view.d.ts.map +1 -1
- package/execution/tool-view.js.map +1 -1
- package/execution/transcript.d.ts.map +1 -1
- package/execution/transcript.js.map +1 -1
- package/gen/agent.d.ts.map +1 -1
- package/gen/agent.js.map +1 -1
- package/gen/agentchannel.d.ts.map +1 -1
- package/gen/agentchannel.js.map +1 -1
- package/gen/agentexecution.d.ts.map +1 -1
- package/gen/agentexecution.js.map +1 -1
- package/gen/agentinstance.d.ts.map +1 -1
- package/gen/agentinstance.js.map +1 -1
- package/gen/agentshare.d.ts.map +1 -1
- package/gen/agentshare.js.map +1 -1
- package/gen/apikey.d.ts.map +1 -1
- package/gen/apikey.js.map +1 -1
- package/gen/artifact.d.ts.map +1 -1
- package/gen/artifact.js.map +1 -1
- package/gen/authorization-config.d.ts.map +1 -1
- package/gen/authorization-config.js +2 -2
- package/gen/authorization-config.js.map +1 -1
- package/gen/bidi-stream.d.ts.map +1 -1
- package/gen/bidi-stream.js.map +1 -1
- package/gen/channelapp.d.ts.map +1 -1
- package/gen/channelapp.js.map +1 -1
- package/gen/client.d.ts.map +1 -1
- package/gen/client.js.map +1 -1
- package/gen/environment.d.ts.map +1 -1
- package/gen/environment.js.map +1 -1
- package/gen/errors.d.ts.map +1 -1
- package/gen/errors.js.map +1 -1
- package/gen/executioncontext.d.ts.map +1 -1
- package/gen/executioncontext.js.map +1 -1
- package/gen/iampolicy.d.ts.map +1 -1
- package/gen/iampolicy.js.map +1 -1
- package/gen/identityaccount.d.ts.map +1 -1
- package/gen/identityaccount.js.map +1 -1
- package/gen/identityprovider.d.ts.map +1 -1
- package/gen/identityprovider.js.map +1 -1
- package/gen/invitation.d.ts.map +1 -1
- package/gen/invitation.js.map +1 -1
- package/gen/license.d.ts.map +1 -1
- package/gen/license.js.map +1 -1
- package/gen/mcpserver.d.ts.map +1 -1
- package/gen/mcpserver.js.map +1 -1
- package/gen/memory.d.ts.map +1 -1
- package/gen/memory.js.map +1 -1
- package/gen/oauthapp.d.ts.map +1 -1
- package/gen/oauthapp.js.map +1 -1
- package/gen/organization.d.ts.map +1 -1
- package/gen/organization.js.map +1 -1
- package/gen/plan.d.ts.map +1 -1
- package/gen/plan.js.map +1 -1
- package/gen/platformclient.d.ts.map +1 -1
- package/gen/platformclient.js.map +1 -1
- package/gen/plugin.d.ts.map +1 -1
- package/gen/plugin.js.map +1 -1
- package/gen/proto-utils.d.ts.map +1 -1
- package/gen/proto-utils.js.map +1 -1
- package/gen/resource-availability.d.ts.map +1 -1
- package/gen/resource-availability.js.map +1 -1
- package/gen/schedule.d.ts.map +1 -1
- package/gen/schedule.js.map +1 -1
- package/gen/session.d.ts.map +1 -1
- package/gen/session.js.map +1 -1
- package/gen/skill.d.ts.map +1 -1
- package/gen/skill.js.map +1 -1
- package/gen/subscription.d.ts.map +1 -1
- package/gen/subscription.js.map +1 -1
- package/gen/team.d.ts.map +1 -1
- package/gen/team.js.map +1 -1
- package/gen/types.d.ts.map +1 -1
- package/gen/types.js.map +1 -1
- package/gen/workflow.d.ts.map +1 -1
- package/gen/workflow.js.map +1 -1
- package/gen/workflowexecution.d.ts.map +1 -1
- package/gen/workflowexecution.js.map +1 -1
- package/gen/workflowinstance.d.ts.map +1 -1
- package/gen/workflowinstance.js.map +1 -1
- package/github.d.ts.map +1 -1
- package/github.js.map +1 -1
- package/guest-auth.d.ts.map +1 -1
- package/guest-auth.js.map +1 -1
- package/iam-principal.d.ts.map +1 -1
- package/iam-principal.js.map +1 -1
- package/iam-role.d.ts.map +1 -1
- package/iam-role.js.map +1 -1
- package/index.d.ts.map +1 -1
- package/index.js.map +1 -1
- package/internal/interceptors.d.ts.map +1 -1
- package/internal/interceptors.js.map +1 -1
- package/manifest/client.d.ts.map +1 -1
- package/manifest/client.js.map +1 -1
- package/manifest/document.d.ts.map +1 -1
- package/manifest/document.js.map +1 -1
- package/manifest/index.d.ts.map +1 -1
- package/manifest/index.js.map +1 -1
- package/manifest/parse.d.ts.map +1 -1
- package/manifest/parse.js.map +1 -1
- package/manifest/redaction.d.ts.map +1 -1
- package/manifest/redaction.js.map +1 -1
- package/manifest/registry.d.ts.map +1 -1
- package/manifest/registry.js.map +1 -1
- package/manifest/serialize.d.ts.map +1 -1
- package/manifest/serialize.js.map +1 -1
- package/mcpserver-connect.d.ts.map +1 -1
- package/mcpserver-connect.js.map +1 -1
- package/node.d.ts.map +1 -1
- package/node.js.map +1 -1
- package/package.json +2 -2
- package/platform-client-auth.d.ts.map +1 -1
- package/platform-client-auth.js.map +1 -1
- package/platform.d.ts.map +1 -1
- package/platform.js.map +1 -1
- package/plugin.d.ts.map +1 -1
- package/plugin.js.map +1 -1
- package/provider-standing.d.ts.map +1 -1
- package/provider-standing.js.map +1 -1
- package/resource-availability.d.ts.map +1 -1
- package/resource-availability.js.map +1 -1
- package/runner-adapter.d.ts.map +1 -1
- package/runner-adapter.js.map +1 -1
- package/search.d.ts.map +1 -1
- package/search.js.map +1 -1
- package/session.d.ts.map +1 -1
- package/session.js.map +1 -1
- package/sharing.d.ts.map +1 -1
- package/sharing.js.map +1 -1
- package/skill.d.ts.map +1 -1
- package/skill.js.map +1 -1
- package/src/gen/authorization-config.ts +2 -2
- package/stigmer.d.ts.map +1 -1
- package/stigmer.js.map +1 -1
- package/transport.d.ts.map +1 -1
- package/transport.js.map +1 -1
- package/__tests__/billing.test.d.ts +0 -2
- package/__tests__/billing.test.d.ts.map +0 -1
- package/__tests__/billing.test.js +0 -120
- package/__tests__/billing.test.js.map +0 -1
- package/__tests__/config.test.d.ts +0 -2
- package/__tests__/config.test.d.ts.map +0 -1
- package/__tests__/config.test.js +0 -35
- package/__tests__/config.test.js.map +0 -1
- package/__tests__/ensure-identity-account.test.d.ts +0 -2
- package/__tests__/ensure-identity-account.test.d.ts.map +0 -1
- package/__tests__/ensure-identity-account.test.js +0 -108
- package/__tests__/ensure-identity-account.test.js.map +0 -1
- package/__tests__/errors.test.d.ts +0 -2
- package/__tests__/errors.test.d.ts.map +0 -1
- package/__tests__/errors.test.js +0 -228
- package/__tests__/errors.test.js.map +0 -1
- package/__tests__/gen/errors.test.d.ts +0 -2
- package/__tests__/gen/errors.test.d.ts.map +0 -1
- package/__tests__/gen/errors.test.js +0 -97
- package/__tests__/gen/errors.test.js.map +0 -1
- package/__tests__/gen/invitation-client.test.d.ts +0 -2
- package/__tests__/gen/invitation-client.test.d.ts.map +0 -1
- package/__tests__/gen/invitation-client.test.js +0 -106
- package/__tests__/gen/invitation-client.test.js.map +0 -1
- package/__tests__/gen/proto-utils.test.d.ts +0 -2
- package/__tests__/gen/proto-utils.test.d.ts.map +0 -1
- package/__tests__/gen/proto-utils.test.js +0 -85
- package/__tests__/gen/proto-utils.test.js.map +0 -1
- package/__tests__/gen/session-client.test.d.ts +0 -2
- package/__tests__/gen/session-client.test.d.ts.map +0 -1
- package/__tests__/gen/session-client.test.js +0 -152
- package/__tests__/gen/session-client.test.js.map +0 -1
- package/__tests__/guest-auth.test.d.ts +0 -2
- package/__tests__/guest-auth.test.d.ts.map +0 -1
- package/__tests__/guest-auth.test.js +0 -223
- package/__tests__/guest-auth.test.js.map +0 -1
- package/__tests__/iam-principal.test.d.ts +0 -2
- package/__tests__/iam-principal.test.d.ts.map +0 -1
- package/__tests__/iam-principal.test.js +0 -65
- package/__tests__/iam-principal.test.js.map +0 -1
- package/__tests__/manifest.test.d.ts +0 -2
- package/__tests__/manifest.test.d.ts.map +0 -1
- package/__tests__/manifest.test.js +0 -306
- package/__tests__/manifest.test.js.map +0 -1
- package/__tests__/mcpserver-connect.test.d.ts +0 -2
- package/__tests__/mcpserver-connect.test.d.ts.map +0 -1
- package/__tests__/mcpserver-connect.test.js +0 -146
- package/__tests__/mcpserver-connect.test.js.map +0 -1
- package/__tests__/node.test.d.ts +0 -2
- package/__tests__/node.test.d.ts.map +0 -1
- package/__tests__/node.test.js +0 -67
- package/__tests__/node.test.js.map +0 -1
- package/__tests__/platform-client-auth.test.d.ts +0 -2
- package/__tests__/platform-client-auth.test.d.ts.map +0 -1
- package/__tests__/platform-client-auth.test.js +0 -59
- package/__tests__/platform-client-auth.test.js.map +0 -1
- package/__tests__/platform.test.d.ts +0 -2
- package/__tests__/platform.test.d.ts.map +0 -1
- package/__tests__/platform.test.js +0 -52
- package/__tests__/platform.test.js.map +0 -1
- package/__tests__/plugin.test.d.ts +0 -2
- package/__tests__/plugin.test.d.ts.map +0 -1
- package/__tests__/plugin.test.js +0 -74
- package/__tests__/plugin.test.js.map +0 -1
- package/__tests__/resource-availability.test.d.ts +0 -2
- package/__tests__/resource-availability.test.d.ts.map +0 -1
- package/__tests__/resource-availability.test.js +0 -80
- package/__tests__/resource-availability.test.js.map +0 -1
- package/__tests__/runner-adapter.test.d.ts +0 -2
- package/__tests__/runner-adapter.test.d.ts.map +0 -1
- package/__tests__/runner-adapter.test.js +0 -88
- package/__tests__/runner-adapter.test.js.map +0 -1
- package/__tests__/session.test.d.ts +0 -6
- package/__tests__/session.test.d.ts.map +0 -1
- package/__tests__/session.test.js +0 -50
- package/__tests__/session.test.js.map +0 -1
- package/__tests__/sharing.test.d.ts +0 -2
- package/__tests__/sharing.test.d.ts.map +0 -1
- package/__tests__/sharing.test.js +0 -106
- package/__tests__/sharing.test.js.map +0 -1
- package/__tests__/skill.test.d.ts +0 -2
- package/__tests__/skill.test.d.ts.map +0 -1
- package/__tests__/skill.test.js +0 -117
- package/__tests__/skill.test.js.map +0 -1
- package/__tests__/stigmer.test.d.ts +0 -2
- package/__tests__/stigmer.test.d.ts.map +0 -1
- package/__tests__/stigmer.test.js +0 -127
- package/__tests__/stigmer.test.js.map +0 -1
- package/__tests__/update-input-roundtrip.test.d.ts +0 -2
- package/__tests__/update-input-roundtrip.test.d.ts.map +0 -1
- package/__tests__/update-input-roundtrip.test.js +0 -775
- package/__tests__/update-input-roundtrip.test.js.map +0 -1
- package/__tests__/update-input.test.d.ts +0 -2
- package/__tests__/update-input.test.d.ts.map +0 -1
- package/__tests__/update-input.test.js +0 -188
- package/__tests__/update-input.test.js.map +0 -1
- package/execution/__tests__/approval-provenance.test.d.ts +0 -2
- package/execution/__tests__/approval-provenance.test.d.ts.map +0 -1
- package/execution/__tests__/approval-provenance.test.js +0 -42
- package/execution/__tests__/approval-provenance.test.js.map +0 -1
- package/execution/__tests__/file-review-fold.corpus.test.d.ts +0 -2
- package/execution/__tests__/file-review-fold.corpus.test.d.ts.map +0 -1
- package/execution/__tests__/file-review-fold.corpus.test.js +0 -82
- package/execution/__tests__/file-review-fold.corpus.test.js.map +0 -1
- package/execution/__tests__/file-review-fold.test.d.ts +0 -2
- package/execution/__tests__/file-review-fold.test.d.ts.map +0 -1
- package/execution/__tests__/file-review-fold.test.js +0 -184
- package/execution/__tests__/file-review-fold.test.js.map +0 -1
- package/execution/__tests__/to-display-file-change.test.d.ts +0 -2
- package/execution/__tests__/to-display-file-change.test.d.ts.map +0 -1
- package/execution/__tests__/to-display-file-change.test.js +0 -81
- package/execution/__tests__/to-display-file-change.test.js.map +0 -1
- package/execution/__tests__/tool-view.file-changes.test.d.ts +0 -2
- package/execution/__tests__/tool-view.file-changes.test.d.ts.map +0 -1
- package/execution/__tests__/tool-view.file-changes.test.js +0 -86
- package/execution/__tests__/tool-view.file-changes.test.js.map +0 -1
- package/execution/__tests__/tool-view.fixtures.test.d.ts +0 -2
- package/execution/__tests__/tool-view.fixtures.test.d.ts.map +0 -1
- package/execution/__tests__/tool-view.fixtures.test.js +0 -152
- package/execution/__tests__/tool-view.fixtures.test.js.map +0 -1
- package/execution/__tests__/tool-view.output-ref.test.d.ts +0 -2
- package/execution/__tests__/tool-view.output-ref.test.d.ts.map +0 -1
- package/execution/__tests__/tool-view.output-ref.test.js +0 -64
- package/execution/__tests__/tool-view.output-ref.test.js.map +0 -1
- package/execution/__tests__/tool-view.read.test.d.ts +0 -2
- package/execution/__tests__/tool-view.read.test.d.ts.map +0 -1
- package/execution/__tests__/tool-view.read.test.js +0 -56
- package/execution/__tests__/tool-view.read.test.js.map +0 -1
- package/execution/__tests__/tool-view.search.test.d.ts +0 -2
- package/execution/__tests__/tool-view.search.test.d.ts.map +0 -1
- package/execution/__tests__/tool-view.search.test.js +0 -200
- package/execution/__tests__/tool-view.search.test.js.map +0 -1
- package/execution/__tests__/tool-view.shell.test.d.ts +0 -2
- package/execution/__tests__/tool-view.shell.test.d.ts.map +0 -1
- package/execution/__tests__/tool-view.shell.test.js +0 -104
- package/execution/__tests__/tool-view.shell.test.js.map +0 -1
- package/execution/__tests__/transcript.test.d.ts +0 -2
- package/execution/__tests__/transcript.test.d.ts.map +0 -1
- package/execution/__tests__/transcript.test.js +0 -591
- package/execution/__tests__/transcript.test.js.map +0 -1
- package/node.test.d.ts +0 -2
- package/node.test.d.ts.map +0 -1
- package/node.test.js +0 -35
- package/node.test.js.map +0 -1
- package/src/__tests__/billing.test.ts +0 -146
- package/src/__tests__/config.test.ts +0 -52
- package/src/__tests__/ensure-identity-account.test.ts +0 -146
- package/src/__tests__/errors.test.ts +0 -349
- package/src/__tests__/gen/errors.test.ts +0 -138
- package/src/__tests__/gen/invitation-client.test.ts +0 -132
- package/src/__tests__/gen/proto-utils.test.ts +0 -99
- package/src/__tests__/gen/session-client.test.ts +0 -208
- package/src/__tests__/guest-auth.test.ts +0 -287
- package/src/__tests__/iam-principal.test.ts +0 -82
- package/src/__tests__/manifest.test.ts +0 -362
- package/src/__tests__/mcpserver-connect.test.ts +0 -181
- package/src/__tests__/node.test.ts +0 -74
- package/src/__tests__/platform-client-auth.test.ts +0 -76
- package/src/__tests__/platform.test.ts +0 -74
- package/src/__tests__/plugin.test.ts +0 -92
- package/src/__tests__/resource-availability.test.ts +0 -118
- package/src/__tests__/runner-adapter.test.ts +0 -122
- package/src/__tests__/session.test.ts +0 -66
- package/src/__tests__/sharing.test.ts +0 -159
- package/src/__tests__/skill.test.ts +0 -150
- package/src/__tests__/stigmer.test.ts +0 -170
- package/src/__tests__/update-input-roundtrip.test.ts +0 -1007
- package/src/__tests__/update-input.test.ts +0 -250
- package/src/execution/__tests__/approval-provenance.test.ts +0 -60
- package/src/execution/__tests__/file-review-fold.corpus.test.ts +0 -127
- package/src/execution/__tests__/file-review-fold.test.ts +0 -281
- package/src/execution/__tests__/to-display-file-change.test.ts +0 -106
- package/src/execution/__tests__/tool-view.file-changes.test.ts +0 -100
- package/src/execution/__tests__/tool-view.fixtures.test.ts +0 -225
- package/src/execution/__tests__/tool-view.output-ref.test.ts +0 -76
- package/src/execution/__tests__/tool-view.read.test.ts +0 -71
- package/src/execution/__tests__/tool-view.search.test.ts +0 -264
- package/src/execution/__tests__/tool-view.shell.test.ts +0 -127
- package/src/execution/__tests__/transcript.golden.md +0 -100
- package/src/execution/__tests__/transcript.test.ts +0 -697
- package/src/node.test.ts +0 -38
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { stripUndefined, toTimestamp } from "../../gen/proto-utils";
|
|
3
|
-
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
4
|
-
|
|
5
|
-
describe("stripUndefined", () => {
|
|
6
|
-
it("removes keys with undefined values", () => {
|
|
7
|
-
const result = stripUndefined({ a: 1, b: undefined, c: "hello" });
|
|
8
|
-
expect(result).toEqual({ a: 1, c: "hello" });
|
|
9
|
-
expect("b" in result).toBe(false);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("preserves null values (falsy but defined)", () => {
|
|
13
|
-
const result = stripUndefined({ a: null });
|
|
14
|
-
expect(result).toEqual({ a: null });
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("preserves 0 (falsy but defined)", () => {
|
|
18
|
-
const result = stripUndefined({ count: 0 });
|
|
19
|
-
expect(result).toEqual({ count: 0 });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("preserves empty string (falsy but defined)", () => {
|
|
23
|
-
const result = stripUndefined({ name: "" });
|
|
24
|
-
expect(result).toEqual({ name: "" });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("preserves false (falsy but defined)", () => {
|
|
28
|
-
const result = stripUndefined({ enabled: false });
|
|
29
|
-
expect(result).toEqual({ enabled: false });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("returns empty object when all values are undefined", () => {
|
|
33
|
-
const result = stripUndefined({ a: undefined, b: undefined });
|
|
34
|
-
expect(result).toEqual({});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("returns all keys when nothing is undefined", () => {
|
|
38
|
-
const input = { x: 1, y: "two", z: true };
|
|
39
|
-
const result = stripUndefined(input);
|
|
40
|
-
expect(result).toEqual(input);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("preserves nested objects without deep-stripping", () => {
|
|
44
|
-
const nested = { inner: undefined };
|
|
45
|
-
const result = stripUndefined({ outer: nested });
|
|
46
|
-
expect(result).toEqual({ outer: nested });
|
|
47
|
-
expect((result as Record<string, unknown>).outer).toBe(nested);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("returns empty object for empty input", () => {
|
|
51
|
-
const result = stripUndefined({});
|
|
52
|
-
expect(result).toEqual({});
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe("toTimestamp", () => {
|
|
57
|
-
it("converts a Date to a Timestamp with correct seconds", () => {
|
|
58
|
-
const date = new Date("2026-05-19T12:00:00.000Z");
|
|
59
|
-
const ts = toTimestamp(date);
|
|
60
|
-
expect(ts.seconds).toBe(BigInt(Math.floor(date.getTime() / 1000)));
|
|
61
|
-
expect(ts.nanos).toBe(0);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("converts a Date with milliseconds to a Timestamp with nanos", () => {
|
|
65
|
-
const date = new Date("2026-01-15T08:30:00.456Z");
|
|
66
|
-
const ts = toTimestamp(date);
|
|
67
|
-
const expectedSeconds = BigInt(Math.floor(date.getTime() / 1000));
|
|
68
|
-
expect(ts.seconds).toBe(expectedSeconds);
|
|
69
|
-
expect(ts.nanos).toBe(456_000_000);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("converts an ISO string to a Timestamp", () => {
|
|
73
|
-
const isoString = "2026-06-01T00:00:00.000Z";
|
|
74
|
-
const ts = toTimestamp(isoString);
|
|
75
|
-
const expected = new Date(isoString);
|
|
76
|
-
expect(ts.seconds).toBe(BigInt(Math.floor(expected.getTime() / 1000)));
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("roundtrips correctly through timestampDate", () => {
|
|
80
|
-
const original = new Date("2026-03-10T15:45:30.000Z");
|
|
81
|
-
const ts = toTimestamp(original);
|
|
82
|
-
const roundtripped = timestampDate(ts);
|
|
83
|
-
expect(roundtripped.getTime()).toBe(original.getTime());
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("handles epoch (Unix timestamp 0)", () => {
|
|
87
|
-
const epoch = new Date(0);
|
|
88
|
-
const ts = toTimestamp(epoch);
|
|
89
|
-
expect(ts.seconds).toBe(BigInt(0));
|
|
90
|
-
expect(ts.nanos).toBe(0);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("handles far-future dates", () => {
|
|
94
|
-
const future = new Date("2099-12-31T23:59:59.000Z");
|
|
95
|
-
const ts = toTimestamp(future);
|
|
96
|
-
expect(ts.seconds).toBe(BigInt(Math.floor(future.getTime() / 1000)));
|
|
97
|
-
expect(timestampDate(ts).getTime()).toBe(future.getTime());
|
|
98
|
-
});
|
|
99
|
-
});
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import type { Transport } from "@connectrpc/connect";
|
|
3
|
-
import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
4
|
-
import { ExecutionTarget } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
|
|
5
|
-
import { SessionClient, type SessionInput } from "../../gen/session";
|
|
6
|
-
|
|
7
|
-
interface CapturedRequest {
|
|
8
|
-
methodName: string;
|
|
9
|
-
message: unknown;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function createCapturingTransport(): {
|
|
13
|
-
transport: Transport;
|
|
14
|
-
captured: CapturedRequest[];
|
|
15
|
-
} {
|
|
16
|
-
const captured: CapturedRequest[] = [];
|
|
17
|
-
|
|
18
|
-
const transport = {
|
|
19
|
-
unary: async (
|
|
20
|
-
method: { name: string },
|
|
21
|
-
_signal: unknown,
|
|
22
|
-
_timeout: unknown,
|
|
23
|
-
_header: unknown,
|
|
24
|
-
message: unknown,
|
|
25
|
-
) => ({
|
|
26
|
-
header: new Headers(),
|
|
27
|
-
trailer: new Headers(),
|
|
28
|
-
message,
|
|
29
|
-
}),
|
|
30
|
-
stream: async () => {
|
|
31
|
-
throw new Error("streaming not implemented in test transport");
|
|
32
|
-
},
|
|
33
|
-
} as unknown as Transport;
|
|
34
|
-
|
|
35
|
-
const originalUnary = (transport as unknown as Record<string, unknown>).unary as (
|
|
36
|
-
...args: unknown[]
|
|
37
|
-
) => Promise<unknown>;
|
|
38
|
-
|
|
39
|
-
(transport as unknown as Record<string, unknown>).unary = async (
|
|
40
|
-
method: { name: string },
|
|
41
|
-
signal: unknown,
|
|
42
|
-
timeout: unknown,
|
|
43
|
-
header: unknown,
|
|
44
|
-
message: unknown,
|
|
45
|
-
...rest: unknown[]
|
|
46
|
-
) => {
|
|
47
|
-
captured.push({ methodName: method.name, message });
|
|
48
|
-
return originalUnary(method, signal, timeout, header, message, ...rest);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
return { transport, captured };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
describe("SessionClient proto serialization", () => {
|
|
55
|
-
let client: SessionClient;
|
|
56
|
-
let captured: CapturedRequest[];
|
|
57
|
-
|
|
58
|
-
beforeEach(() => {
|
|
59
|
-
const ctx = createCapturingTransport();
|
|
60
|
-
client = new SessionClient(ctx.transport);
|
|
61
|
-
captured = ctx.captured;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("sets apiVersion and kind on the proto", async () => {
|
|
65
|
-
await client.create({ name: "test-session", org: "my-org" });
|
|
66
|
-
|
|
67
|
-
const proto = captured[0].message as Session;
|
|
68
|
-
expect(proto.apiVersion).toBe("agentic.stigmer.ai/v1");
|
|
69
|
-
expect(proto.kind).toBe("Session");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("sets required fields on metadata", async () => {
|
|
73
|
-
await client.create({ name: "my-session", org: "acme" });
|
|
74
|
-
|
|
75
|
-
const proto = captured[0].message as Session;
|
|
76
|
-
expect(proto.metadata?.name).toBe("my-session");
|
|
77
|
-
expect(proto.metadata?.org).toBe("acme");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("sets optional spec fields when provided", async () => {
|
|
81
|
-
const input: SessionInput = {
|
|
82
|
-
name: "s1",
|
|
83
|
-
org: "o1",
|
|
84
|
-
agentInstanceId: "ai-123",
|
|
85
|
-
subject: "Help with deployment",
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
await client.create(input);
|
|
89
|
-
|
|
90
|
-
const proto = captured[0].message as Session;
|
|
91
|
-
expect(proto.spec?.agentInstanceId).toBe("ai-123");
|
|
92
|
-
expect(proto.spec?.subject).toBe("Help with deployment");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("does not overwrite protobuf defaults when optional fields are omitted", async () => {
|
|
96
|
-
await client.create({ name: "minimal", org: "org" });
|
|
97
|
-
|
|
98
|
-
const proto = captured[0].message as Session;
|
|
99
|
-
expect(proto.spec?.agentInstanceId).toBe("");
|
|
100
|
-
expect(proto.spec?.subject).toBe("");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("serializes executionTarget when provided", async () => {
|
|
104
|
-
await client.create({
|
|
105
|
-
name: "s1",
|
|
106
|
-
org: "o1",
|
|
107
|
-
executionTarget: ExecutionTarget.LOCAL,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const proto = captured[0].message as Session;
|
|
111
|
-
expect(proto.spec?.executionTarget).toBe(ExecutionTarget.LOCAL);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("serializes metadata map when provided", async () => {
|
|
115
|
-
await client.create({
|
|
116
|
-
name: "s1",
|
|
117
|
-
org: "o1",
|
|
118
|
-
metadata: { env: "production", region: "us-east-1" },
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const proto = captured[0].message as Session;
|
|
122
|
-
expect(proto.spec?.metadata).toEqual({
|
|
123
|
-
env: "production",
|
|
124
|
-
region: "us-east-1",
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("serializes workspace entries with git repo source", async () => {
|
|
129
|
-
await client.create({
|
|
130
|
-
name: "s1",
|
|
131
|
-
org: "o1",
|
|
132
|
-
workspaceEntries: [
|
|
133
|
-
{
|
|
134
|
-
name: "my-repo",
|
|
135
|
-
source: {
|
|
136
|
-
gitRepo: {
|
|
137
|
-
url: "https://github.com/example/repo.git",
|
|
138
|
-
branch: "main",
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const proto = captured[0].message as Session;
|
|
146
|
-
const entries = proto.spec?.workspaceEntries;
|
|
147
|
-
expect(entries).toHaveLength(1);
|
|
148
|
-
expect(entries![0].name).toBe("my-repo");
|
|
149
|
-
|
|
150
|
-
const source = entries![0].source;
|
|
151
|
-
expect(source?.source?.case).toBe("gitRepo");
|
|
152
|
-
if (source?.source?.case === "gitRepo") {
|
|
153
|
-
expect(source.source.value.url).toBe(
|
|
154
|
-
"https://github.com/example/repo.git",
|
|
155
|
-
);
|
|
156
|
-
expect(source.source.value.branch).toBe("main");
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("serializes workspace entries with local path source", async () => {
|
|
161
|
-
await client.create({
|
|
162
|
-
name: "s1",
|
|
163
|
-
org: "o1",
|
|
164
|
-
workspaceEntries: [
|
|
165
|
-
{
|
|
166
|
-
name: "local",
|
|
167
|
-
source: {
|
|
168
|
-
localPath: { path: "/home/user/project" },
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const proto = captured[0].message as Session;
|
|
175
|
-
const entries = proto.spec?.workspaceEntries;
|
|
176
|
-
expect(entries).toHaveLength(1);
|
|
177
|
-
|
|
178
|
-
const source = entries![0].source;
|
|
179
|
-
expect(source?.source?.case).toBe("localPath");
|
|
180
|
-
if (source?.source?.case === "localPath") {
|
|
181
|
-
expect(source.source.value.path).toBe("/home/user/project");
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("routes create through the command controller", async () => {
|
|
186
|
-
await client.create({ name: "s1", org: "o1" });
|
|
187
|
-
expect(captured[0].methodName).toBe("create");
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it("routes apply through the command controller", async () => {
|
|
191
|
-
await client.apply({ name: "s1", org: "o1" });
|
|
192
|
-
expect(captured[0].methodName).toBe("apply");
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it("routes get through the query controller with SessionId", async () => {
|
|
196
|
-
await client.get("session-id-123");
|
|
197
|
-
const message = captured[0].message as { value: string };
|
|
198
|
-
expect(captured[0].methodName).toBe("get");
|
|
199
|
-
expect(message.value).toBe("session-id-123");
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("routes delete through the command controller with SessionId", async () => {
|
|
203
|
-
await client.delete("session-id-456");
|
|
204
|
-
const message = captured[0].message as { value: string };
|
|
205
|
-
expect(captured[0].methodName).toBe("delete");
|
|
206
|
-
expect(message.value).toBe("session-id-456");
|
|
207
|
-
});
|
|
208
|
-
});
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { create } from "@bufbuild/protobuf";
|
|
3
|
-
import { MintGuestTokenResponseSchema } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/token_pb";
|
|
4
|
-
import { ConnectError, Code } from "@connectrpc/connect";
|
|
5
|
-
|
|
6
|
-
// Intercept the token client so no transport is exercised. The mock
|
|
7
|
-
// preserves the real module (create/Code/ConnectError) and only stubs
|
|
8
|
-
// createClient, which GuestAuth uses to build its RPC client.
|
|
9
|
-
const mintGuestToken = vi.fn();
|
|
10
|
-
vi.mock("@connectrpc/connect", async (importOriginal) => {
|
|
11
|
-
const actual = await importOriginal<typeof import("@connectrpc/connect")>();
|
|
12
|
-
return {
|
|
13
|
-
...actual,
|
|
14
|
-
createClient: () => ({ mintGuestToken }),
|
|
15
|
-
};
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
import { createGuestAuth, GuestAuth, type GuestIdStorage } from "../guest-auth";
|
|
19
|
-
|
|
20
|
-
const CONFIG = {
|
|
21
|
-
baseUrl: "https://api.stigmer.ai",
|
|
22
|
-
org: "acme",
|
|
23
|
-
slug: "support-agent",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function createMemoryStorage(): GuestIdStorage & { store: Map<string, string> } {
|
|
27
|
-
const store = new Map<string, string>();
|
|
28
|
-
return {
|
|
29
|
-
store,
|
|
30
|
-
getItem: (key) => store.get(key) ?? null,
|
|
31
|
-
setItem: (key, value) => {
|
|
32
|
-
store.set(key, value);
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function mintResponse(overrides?: {
|
|
38
|
-
accessToken?: string;
|
|
39
|
-
expiresIn?: number;
|
|
40
|
-
guestCookieId?: string;
|
|
41
|
-
}) {
|
|
42
|
-
return create(MintGuestTokenResponseSchema, {
|
|
43
|
-
accessToken: overrides?.accessToken ?? "guest-jwt-1",
|
|
44
|
-
tokenType: "Bearer",
|
|
45
|
-
expiresIn: overrides?.expiresIn ?? 900,
|
|
46
|
-
guestCookieId: overrides?.guestCookieId ?? "cookie-abc",
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
mintGuestToken.mockReset();
|
|
52
|
-
vi.useFakeTimers();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
afterEach(() => {
|
|
56
|
-
vi.useRealTimers();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("createGuestAuth", () => {
|
|
60
|
-
it("throws when baseUrl is missing", () => {
|
|
61
|
-
expect(() =>
|
|
62
|
-
createGuestAuth({ ...CONFIG, baseUrl: "" }),
|
|
63
|
-
).toThrow("baseUrl is required");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("throws when org is missing", () => {
|
|
67
|
-
expect(() => createGuestAuth({ ...CONFIG, org: "" })).toThrow(
|
|
68
|
-
"org is required",
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("throws when slug is missing", () => {
|
|
73
|
-
expect(() => createGuestAuth({ ...CONFIG, slug: "" })).toThrow(
|
|
74
|
-
"slug is required",
|
|
75
|
-
);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("returns a GuestAuth instance with valid config", () => {
|
|
79
|
-
expect(createGuestAuth(CONFIG)).toBeInstanceOf(GuestAuth);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe("GuestAuth.getAccessToken", () => {
|
|
84
|
-
it("mints on first call and sends an empty guest id", async () => {
|
|
85
|
-
mintGuestToken.mockResolvedValue(mintResponse());
|
|
86
|
-
const auth = createGuestAuth({ ...CONFIG, storage: createMemoryStorage() });
|
|
87
|
-
|
|
88
|
-
const token = await auth.getAccessToken();
|
|
89
|
-
|
|
90
|
-
expect(token).toBe("guest-jwt-1");
|
|
91
|
-
expect(mintGuestToken).toHaveBeenCalledTimes(1);
|
|
92
|
-
expect(mintGuestToken.mock.calls[0][0]).toMatchObject({
|
|
93
|
-
org: "acme",
|
|
94
|
-
slug: "support-agent",
|
|
95
|
-
guestCookieId: "",
|
|
96
|
-
// No embedOrigin configured (the unframed hosted page): the field
|
|
97
|
-
// must stay empty so the server's absence-means-exempt rule applies.
|
|
98
|
-
embedOrigin: "",
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("passes the configured embedOrigin through to every mint", async () => {
|
|
103
|
-
mintGuestToken.mockResolvedValue(mintResponse());
|
|
104
|
-
const auth = createGuestAuth({
|
|
105
|
-
...CONFIG,
|
|
106
|
-
storage: createMemoryStorage(),
|
|
107
|
-
embedOrigin: "https://docs.example.com",
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
await auth.getAccessToken();
|
|
111
|
-
// Expire the cached token, forcing a re-mint — the origin must persist.
|
|
112
|
-
vi.advanceTimersByTime(900_000);
|
|
113
|
-
await auth.getAccessToken();
|
|
114
|
-
|
|
115
|
-
expect(mintGuestToken).toHaveBeenCalledTimes(2);
|
|
116
|
-
for (const call of mintGuestToken.mock.calls) {
|
|
117
|
-
expect(call[0]).toMatchObject({ embedOrigin: "https://docs.example.com" });
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("passes the configured linkToken through to every mint", async () => {
|
|
122
|
-
mintGuestToken.mockResolvedValue(mintResponse());
|
|
123
|
-
const auth = createGuestAuth({
|
|
124
|
-
...CONFIG,
|
|
125
|
-
storage: createMemoryStorage(),
|
|
126
|
-
linkToken: "tok123",
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
await auth.getAccessToken();
|
|
130
|
-
// Expire the cached token, forcing a re-mint — the token must persist
|
|
131
|
-
// so a locked link keeps working across silent re-mints.
|
|
132
|
-
vi.advanceTimersByTime(900_000);
|
|
133
|
-
await auth.getAccessToken();
|
|
134
|
-
|
|
135
|
-
expect(mintGuestToken).toHaveBeenCalledTimes(2);
|
|
136
|
-
for (const call of mintGuestToken.mock.calls) {
|
|
137
|
-
expect(call[0]).toMatchObject({ linkToken: "tok123" });
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("sends an empty linkToken when none is configured (plain link)", async () => {
|
|
142
|
-
mintGuestToken.mockResolvedValue(mintResponse());
|
|
143
|
-
const auth = createGuestAuth({ ...CONFIG, storage: createMemoryStorage() });
|
|
144
|
-
|
|
145
|
-
await auth.getAccessToken();
|
|
146
|
-
|
|
147
|
-
expect(mintGuestToken.mock.calls[0][0]).toMatchObject({ linkToken: "" });
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("rejects with permission-denied when the embed origin is refused", async () => {
|
|
151
|
-
mintGuestToken.mockRejectedValue(
|
|
152
|
-
new ConnectError(
|
|
153
|
-
"This agent can\u2019t be embedded on this site.",
|
|
154
|
-
Code.PermissionDenied,
|
|
155
|
-
),
|
|
156
|
-
);
|
|
157
|
-
const auth = createGuestAuth({
|
|
158
|
-
...CONFIG,
|
|
159
|
-
storage: createMemoryStorage(),
|
|
160
|
-
embedOrigin: "https://evil.example.com",
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
await expect(auth.getAccessToken()).rejects.toMatchObject({
|
|
164
|
-
code: "permission-denied",
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("persists the server-issued guest id and presents it on re-mint", async () => {
|
|
169
|
-
const storage = createMemoryStorage();
|
|
170
|
-
mintGuestToken.mockResolvedValue(mintResponse({ guestCookieId: "cookie-abc" }));
|
|
171
|
-
const auth = createGuestAuth({ ...CONFIG, storage });
|
|
172
|
-
|
|
173
|
-
await auth.getAccessToken();
|
|
174
|
-
expect(storage.store.get("stigmer:guest-id:acme")).toBe("cookie-abc");
|
|
175
|
-
expect(auth.guestCookieId).toBe("cookie-abc");
|
|
176
|
-
|
|
177
|
-
// Expire the cached token, forcing a second mint.
|
|
178
|
-
vi.advanceTimersByTime(900_000);
|
|
179
|
-
await auth.getAccessToken();
|
|
180
|
-
|
|
181
|
-
expect(mintGuestToken).toHaveBeenCalledTimes(2);
|
|
182
|
-
expect(mintGuestToken.mock.calls[1][0]).toMatchObject({
|
|
183
|
-
guestCookieId: "cookie-abc",
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("restores a persisted guest id from a prior visit", async () => {
|
|
188
|
-
const storage = createMemoryStorage();
|
|
189
|
-
storage.setItem("stigmer:guest-id:acme", "cookie-from-last-visit");
|
|
190
|
-
mintGuestToken.mockResolvedValue(mintResponse());
|
|
191
|
-
const auth = createGuestAuth({ ...CONFIG, storage });
|
|
192
|
-
|
|
193
|
-
await auth.getAccessToken();
|
|
194
|
-
|
|
195
|
-
expect(mintGuestToken.mock.calls[0][0]).toMatchObject({
|
|
196
|
-
guestCookieId: "cookie-from-last-visit",
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it("returns the cached token while it is fresh", async () => {
|
|
201
|
-
mintGuestToken.mockResolvedValue(mintResponse());
|
|
202
|
-
const auth = createGuestAuth({ ...CONFIG, storage: createMemoryStorage() });
|
|
203
|
-
|
|
204
|
-
await auth.getAccessToken();
|
|
205
|
-
// 13 minutes into a 15-minute token: still beyond the 60s skew.
|
|
206
|
-
vi.advanceTimersByTime(13 * 60_000);
|
|
207
|
-
const token = await auth.getAccessToken();
|
|
208
|
-
|
|
209
|
-
expect(token).toBe("guest-jwt-1");
|
|
210
|
-
expect(mintGuestToken).toHaveBeenCalledTimes(1);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("re-mints when the cached token is within the expiry skew", async () => {
|
|
214
|
-
mintGuestToken
|
|
215
|
-
.mockResolvedValueOnce(mintResponse({ accessToken: "guest-jwt-1" }))
|
|
216
|
-
.mockResolvedValueOnce(mintResponse({ accessToken: "guest-jwt-2" }));
|
|
217
|
-
const auth = createGuestAuth({ ...CONFIG, storage: createMemoryStorage() });
|
|
218
|
-
|
|
219
|
-
await auth.getAccessToken();
|
|
220
|
-
// 30 seconds of life left — inside the 60s skew window.
|
|
221
|
-
vi.advanceTimersByTime(900_000 - 30_000);
|
|
222
|
-
const token = await auth.getAccessToken();
|
|
223
|
-
|
|
224
|
-
expect(token).toBe("guest-jwt-2");
|
|
225
|
-
expect(mintGuestToken).toHaveBeenCalledTimes(2);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("collapses concurrent callers into a single mint", async () => {
|
|
229
|
-
let release!: (value: ReturnType<typeof mintResponse>) => void;
|
|
230
|
-
mintGuestToken.mockImplementation(
|
|
231
|
-
() => new Promise((resolve) => { release = resolve; }),
|
|
232
|
-
);
|
|
233
|
-
const auth = createGuestAuth({ ...CONFIG, storage: createMemoryStorage() });
|
|
234
|
-
|
|
235
|
-
const first = auth.getAccessToken();
|
|
236
|
-
const second = auth.getAccessToken();
|
|
237
|
-
release(mintResponse());
|
|
238
|
-
|
|
239
|
-
await expect(first).resolves.toBe("guest-jwt-1");
|
|
240
|
-
await expect(second).resolves.toBe("guest-jwt-1");
|
|
241
|
-
expect(mintGuestToken).toHaveBeenCalledTimes(1);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("rejects with a wrapped StigmerError when sharing is revoked", async () => {
|
|
245
|
-
mintGuestToken.mockRejectedValue(
|
|
246
|
-
new ConnectError("agent not found", Code.NotFound),
|
|
247
|
-
);
|
|
248
|
-
const auth = createGuestAuth({ ...CONFIG, storage: createMemoryStorage() });
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
await auth.getAccessToken();
|
|
252
|
-
expect.fail("should have thrown");
|
|
253
|
-
} catch (e: unknown) {
|
|
254
|
-
expect(e).toHaveProperty("name", "StigmerError");
|
|
255
|
-
expect(e).toHaveProperty("code", "not-found");
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it("recovers after a failed mint instead of caching the failure", async () => {
|
|
260
|
-
mintGuestToken
|
|
261
|
-
.mockRejectedValueOnce(new ConnectError("unavailable", Code.Unavailable))
|
|
262
|
-
.mockResolvedValueOnce(mintResponse());
|
|
263
|
-
const auth = createGuestAuth({ ...CONFIG, storage: createMemoryStorage() });
|
|
264
|
-
|
|
265
|
-
await expect(auth.getAccessToken()).rejects.toMatchObject({
|
|
266
|
-
code: "unavailable",
|
|
267
|
-
});
|
|
268
|
-
await expect(auth.getAccessToken()).resolves.toBe("guest-jwt-1");
|
|
269
|
-
expect(mintGuestToken).toHaveBeenCalledTimes(2);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
it("still mints when storage throws", async () => {
|
|
273
|
-
const brokenStorage: GuestIdStorage = {
|
|
274
|
-
getItem: () => {
|
|
275
|
-
throw new Error("storage disabled");
|
|
276
|
-
},
|
|
277
|
-
setItem: () => {
|
|
278
|
-
throw new Error("storage disabled");
|
|
279
|
-
},
|
|
280
|
-
};
|
|
281
|
-
mintGuestToken.mockResolvedValue(mintResponse());
|
|
282
|
-
const auth = createGuestAuth({ ...CONFIG, storage: brokenStorage });
|
|
283
|
-
|
|
284
|
-
await expect(auth.getAccessToken()).resolves.toBe("guest-jwt-1");
|
|
285
|
-
expect(auth.guestCookieId).toBeNull();
|
|
286
|
-
});
|
|
287
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pins the grantee vocabulary (`iam-principal.ts`) and the team half of the
|
|
3
|
-
* authorization config (`authorization-config.ts`):
|
|
4
|
-
*
|
|
5
|
-
* - a team is always granted as `team:<id>#member` and a person always
|
|
6
|
-
* unqualified, because the server's revoke matches the qualifier
|
|
7
|
-
* exactly and a dropped `#member` revokes nothing while reporting
|
|
8
|
-
* success;
|
|
9
|
-
* - reading an access-list entry back is the inverse of building one,
|
|
10
|
-
* and anything else (a structural principal, another team qualifier)
|
|
11
|
-
* is refused rather than guessed at;
|
|
12
|
-
* - a team may only ever be offered a subset of a person's roles, never
|
|
13
|
-
* `owner`, and nothing on `organization` (the circular grant the
|
|
14
|
-
* model's membership bound forbids).
|
|
15
|
-
*/
|
|
16
|
-
import { describe, expect, it } from "vitest";
|
|
17
|
-
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
18
|
-
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
getGrantableRoles,
|
|
22
|
-
getTeamGrantableRoles,
|
|
23
|
-
hasTeamGrantableRoles,
|
|
24
|
-
isRoleTeamGrantable,
|
|
25
|
-
} from "../authorization-config";
|
|
26
|
-
import { TEAM_GRANTABLE_ROLES } from "../gen/authorization-config";
|
|
27
|
-
import {
|
|
28
|
-
granteeFromView,
|
|
29
|
-
granteeRef,
|
|
30
|
-
personGrantee,
|
|
31
|
-
teamGrantee,
|
|
32
|
-
} from "../iam-principal";
|
|
33
|
-
|
|
34
|
-
describe("granteeRef — the one spelling of a grantee on the wire", () => {
|
|
35
|
-
it("names a team as its members", () => {
|
|
36
|
-
const ref = granteeRef(teamGrantee("tm_sre"));
|
|
37
|
-
expect([ref.kind, ref.id, ref.relation]).toEqual(["team", "tm_sre", "member"]);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("names a person with no relation qualifier", () => {
|
|
41
|
-
const ref = granteeRef(personGrantee("ida_alice"));
|
|
42
|
-
expect([ref.kind, ref.id, ref.relation]).toEqual(["identity_account", "ida_alice", ""]);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("granteeFromView — reading an access-list entry back", () => {
|
|
47
|
-
it("round-trips both grantees through their wire reference", () => {
|
|
48
|
-
for (const grantee of [teamGrantee("tm_sre"), personGrantee("ida_alice")]) {
|
|
49
|
-
expect(granteeFromView(granteeRef(grantee))).toEqual(grantee);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("refuses what it would misname: a team without its members qualifier, a qualified person, a structural principal, an empty id", () => {
|
|
54
|
-
expect(granteeFromView({ kind: "team", id: "tm_sre", relation: "" })).toBeUndefined();
|
|
55
|
-
expect(granteeFromView({ kind: "team", id: "tm_sre", relation: "maintainer" })).toBeUndefined();
|
|
56
|
-
expect(granteeFromView({ kind: "identity_account", id: "ida_alice", relation: "member" })).toBeUndefined();
|
|
57
|
-
expect(granteeFromView({ kind: "organization", id: "acme", relation: "" })).toBeUndefined();
|
|
58
|
-
expect(granteeFromView({ kind: "identity_account", id: "", relation: "" })).toBeUndefined();
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe("team grantable roles", () => {
|
|
63
|
-
it("are, for every kind, a subset of the person's roles and never owner", () => {
|
|
64
|
-
for (const [kind, teamRoles] of TEAM_GRANTABLE_ROLES) {
|
|
65
|
-
const personRoles = getGrantableRoles(kind);
|
|
66
|
-
for (const role of teamRoles) {
|
|
67
|
-
expect(personRoles).toContain(role);
|
|
68
|
-
expect(role).not.toBe(IamRole.owner);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("list nothing on organization, so an organization is never shared with a team", () => {
|
|
74
|
-
expect(hasTeamGrantableRoles(ApiResourceKind.organization)).toBe(false);
|
|
75
|
-
expect(getTeamGrantableRoles(ApiResourceKind.organization)).toEqual([]);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("answer per role on a kind a team may be shared", () => {
|
|
79
|
-
expect(isRoleTeamGrantable(ApiResourceKind.agent, IamRole.viewer)).toBe(true);
|
|
80
|
-
expect(isRoleTeamGrantable(ApiResourceKind.agent, IamRole.owner)).toBe(false);
|
|
81
|
-
});
|
|
82
|
-
});
|