@pikku/core 0.12.78 → 0.12.80
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/CHANGELOG.md +234 -0
- package/dist/dev/hot-reload.js +1 -6
- package/dist/ecosystem.d.ts +27 -0
- package/dist/ecosystem.js +26 -0
- package/dist/errors/error-handler.js +4 -2
- package/dist/function/function-runner.js +20 -42
- package/dist/function/functions.types.d.ts +9 -5
- package/dist/index.d.ts +4 -9
- package/dist/index.js +3 -8
- package/dist/middleware/auth-cookie.d.ts +0 -4
- package/dist/middleware/auth-cookie.js +38 -2
- package/dist/middleware/cors.js +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.js +14 -3
- package/dist/permissions.d.ts +8 -10
- package/dist/permissions.js +0 -11
- package/dist/pikku-state.js +12 -5
- package/dist/schema.js +35 -1
- package/dist/services/ai-run-state-service.d.ts +7 -1
- package/dist/services/in-memory-ai-run-state-service.d.ts +1 -1
- package/dist/services/in-memory-ai-run-state-service.js +2 -1
- package/dist/services/in-memory-workflow-service.d.ts +10 -0
- package/dist/services/in-memory-workflow-service.js +25 -0
- package/dist/services/local-content-request-handler.d.ts +21 -0
- package/dist/services/local-content-request-handler.js +72 -53
- package/dist/services/local-content.d.ts +6 -0
- package/dist/services/local-content.js +14 -1
- package/dist/services/workflow-service.d.ts +4 -2
- package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
- package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
- package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/ai-storage-service-tests.js +226 -0
- package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/channel-store-tests.js +72 -0
- package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/credential-service-tests.js +109 -0
- package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/deployment-service-tests.js +21 -0
- package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
- package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/secret-service-tests.js +80 -0
- package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/session-store-tests.js +43 -0
- package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
- package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-service-tests.js +150 -0
- package/dist/testing/service-tests.d.ts +6 -0
- package/dist/testing/service-tests.js +26 -791
- package/dist/types/core.types.d.ts +11 -1
- package/dist/types/state.types.d.ts +1 -1
- package/dist/utils/node-host-resolver.d.ts +12 -0
- package/dist/utils/node-host-resolver.js +16 -0
- package/dist/utils/safe-fetch.d.ts +18 -0
- package/dist/utils/safe-fetch.js +167 -29
- package/dist/wirings/actor-flow/run-conversation.js +1 -6
- package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
- package/dist/wirings/ai-agent/agent-rpc.js +53 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
- package/dist/wirings/ai-agent/ai-agent-memory.js +3 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +3 -9
- package/dist/wirings/ai-agent/ai-agent-runner.js +28 -107
- package/dist/wirings/ai-agent/ai-agent-stream.js +20 -61
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +56 -0
- package/dist/wirings/ai-agent/ai-agent-turn.js +81 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +1 -1
- package/dist/wirings/ai-agent/voice-input.js +1 -6
- package/dist/wirings/ai-agent/voice-output.js +2 -12
- package/dist/wirings/channel/channel-common.js +1 -0
- package/dist/wirings/channel/channel-handler.js +3 -5
- package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
- package/dist/wirings/channel/channel-rpc-service.js +0 -8
- package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
- package/dist/wirings/channel/channel-rpc.types.js +8 -0
- package/dist/wirings/channel/channel-runner.d.ts +1 -3
- package/dist/wirings/channel/channel-runner.js +16 -8
- package/dist/wirings/channel/channel.types.d.ts +2 -0
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
- package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
- package/dist/wirings/cli/cli-runner.js +4 -2
- package/dist/wirings/cli/cli.types.d.ts +0 -8
- package/dist/wirings/cli/command-parser.js +13 -0
- package/dist/wirings/gateway/gateway-runner.js +41 -5
- package/dist/wirings/http/http-routes.js +2 -0
- package/dist/wirings/http/http-runner.d.ts +0 -10
- package/dist/wirings/http/http-runner.js +6 -13
- package/dist/wirings/http/http.types.d.ts +0 -10
- package/dist/wirings/http/index.d.ts +1 -1
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +0 -7
- package/dist/wirings/mcp/mcp-runner.js +0 -6
- package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
- package/dist/wirings/rpc/rpc-runner.js +53 -86
- package/dist/wirings/rpc/rpc-types.d.ts +3 -0
- package/dist/wirings/secret/validate-secret-definitions.js +2 -0
- package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
- package/dist/wirings/trigger/trigger-runner.js +1 -0
- package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
- package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
- package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +29 -2
- package/dist/wirings/workflow/feature.js +7 -4
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
- package/dist/wirings/workflow/graph/graph-runner.js +8 -7
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
- package/dist/wirings/workflow/index.d.ts +7 -2
- package/dist/wirings/workflow/index.js +5 -1
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
- package/dist/wirings/workflow/pikku-scenario-service.js +38 -10
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +44 -111
- package/dist/wirings/workflow/pikku-workflow-service.js +86 -404
- package/dist/wirings/workflow/workflow-approval.d.ts +39 -0
- package/dist/wirings/workflow/workflow-approval.js +114 -0
- package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
- package/dist/wirings/workflow/workflow-constants.js +35 -0
- package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
- package/dist/wirings/workflow/workflow-errors.js +112 -0
- package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
- package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
- package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
- package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
- package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
- package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
- package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
- package/dist/wirings/workflow/workflow-recovery.js +101 -0
- package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
- package/dist/wirings/workflow/workflow-run-engine.types.js +1 -0
- package/dist/wirings/workflow/workflow-run-ownership.d.ts +16 -0
- package/dist/wirings/workflow/workflow-run-ownership.js +29 -0
- package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
- package/dist/wirings/workflow/workflow-suspend.js +33 -0
- package/dist/wirings/workflow/workflow.types.d.ts +7 -0
- package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
- package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
- package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
- package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
- package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
- package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
- package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
- package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
- package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
- package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
- package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
- package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
- package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
- package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
- package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
- package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
- package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
- package/knowledge/decisions/internals/index.md +38 -26
- package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
- package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
- package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
- package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
- package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
- package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
- package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
- package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
- package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
- package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
- package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
- package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
- package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
- package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
- package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
- package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
- package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
- package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
- package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
- package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
- package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
- package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
- package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
- package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +34 -0
- package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
- package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
- package/knowledge/decisions/security/gateway-handlers-run-through-the-function-runner-gate.md +13 -3
- package/knowledge/decisions/security/index.md +7 -0
- package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
- package/knowledge/questions/index.md +2 -1
- package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
- package/package.json +14 -3
- package/scripts/generate-api-report.d.mts +1 -0
- package/scripts/generate-api-report.mjs +89 -0
- package/scripts/generate-api-report.mts +218 -0
- package/src/api-report.test.ts +69 -0
- package/src/column-form.ts +1 -4
- package/src/crypto-utils.test.ts +15 -2
- package/src/dev/hot-reload.ts +2 -7
- package/src/ecosystem.ts +40 -0
- package/src/errors/error-handler.ts +5 -3
- package/src/errors/error.test.ts +4 -1
- package/src/function/function-runner.test.ts +1 -1
- package/src/function/function-runner.ts +28 -39
- package/src/function/functions.types.ts +15 -9
- package/src/handle-error.ts +1 -1
- package/src/index.ts +3 -22
- package/src/middleware/auth-cookie.test.ts +50 -0
- package/src/middleware/auth-cookie.ts +38 -2
- package/src/middleware/cors.ts +2 -1
- package/src/middleware/index.ts +1 -1
- package/src/middleware/remote-auth.test.ts +43 -0
- package/src/middleware/remote-auth.ts +17 -3
- package/src/middleware-runner.ts +4 -2
- package/src/no-any-casts.test.ts +57 -0
- package/src/permissions.test.ts +3 -1
- package/src/permissions.ts +22 -24
- package/src/pikku-state.ts +17 -6
- package/src/public-surface.json +578 -0
- package/src/public-surface.json.README +25 -0
- package/src/public-surface.test.ts +105 -0
- package/src/removed-legacy-exports.test.ts +62 -0
- package/src/schema.test.ts +78 -0
- package/src/schema.ts +36 -1
- package/src/services/ai-run-state-service.ts +7 -1
- package/src/services/audit-service.ts +2 -2
- package/src/services/in-memory-ai-run-state-service.ts +3 -2
- package/src/services/in-memory-workflow-service.ts +25 -0
- package/src/services/index.ts +1 -4
- package/src/services/local-content-request-handler.test.ts +43 -5
- package/src/services/local-content-request-handler.ts +103 -74
- package/src/services/local-content.ts +15 -1
- package/src/services/local-email-service.ts +5 -1
- package/src/services/system-role-guard.test.ts +4 -1
- package/src/services/workflow-service.ts +9 -2
- package/src/side-effects-are-declared.test.ts +84 -0
- package/src/source-files-stay-composable.test.ts +41 -0
- package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
- package/src/testing/service-tests/ai-storage-service-tests.ts +286 -0
- package/src/testing/service-tests/channel-store-tests.ts +98 -0
- package/src/testing/service-tests/credential-service-tests.ts +143 -0
- package/src/testing/service-tests/deployment-service-tests.ts +33 -0
- package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
- package/src/testing/service-tests/secret-service-tests.ts +105 -0
- package/src/testing/service-tests/session-store-tests.ts +62 -0
- package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
- package/src/testing/service-tests/workflow-service-tests.ts +308 -0
- package/src/testing/service-tests.ts +31 -1111
- package/src/types/core.types.ts +15 -1
- package/src/types/state.types.ts +1 -1
- package/src/utils/node-host-resolver.ts +20 -0
- package/src/utils/safe-fetch.test.ts +143 -1
- package/src/utils/safe-fetch.ts +200 -25
- package/src/wirings/actor-flow/run-conversation.ts +1 -6
- package/src/wirings/ai-agent/agent-rpc.ts +120 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
- package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
- package/src/wirings/ai-agent/ai-agent-memory.ts +8 -1
- package/src/wirings/ai-agent/ai-agent-prepare.ts +10 -12
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
- package/src/wirings/ai-agent/ai-agent-runner.ts +49 -120
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +36 -63
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-turn.ts +121 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +1 -1
- package/src/wirings/ai-agent/voice-input.ts +1 -6
- package/src/wirings/ai-agent/voice-output.ts +2 -12
- package/src/wirings/channel/channel-common.ts +5 -2
- package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
- package/src/wirings/channel/channel-handler.ts +7 -7
- package/src/wirings/channel/channel-rpc-service.ts +0 -14
- package/src/wirings/channel/channel-rpc.test.ts +25 -4
- package/src/wirings/channel/channel-rpc.types.ts +14 -0
- package/src/wirings/channel/channel-runner.ts +33 -20
- package/src/wirings/channel/channel.types.ts +2 -0
- package/src/wirings/channel/local/local-channel-runner.ts +1 -1
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
- package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
- package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
- package/src/wirings/cli/cli-runner.ts +7 -4
- package/src/wirings/cli/cli.types.ts +0 -39
- package/src/wirings/cli/command-parser.test.ts +19 -0
- package/src/wirings/cli/command-parser.ts +17 -1
- package/src/wirings/gateway/gateway-authorization.test.ts +131 -0
- package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
- package/src/wirings/gateway/gateway-runner.ts +62 -22
- package/src/wirings/http/http-routes.ts +4 -1
- package/src/wirings/http/http-runner.ts +11 -26
- package/src/wirings/http/http.types.ts +0 -15
- package/src/wirings/http/index.ts +1 -7
- package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
- package/src/wirings/http/web-request.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +2 -16
- package/src/wirings/persona/persona-environments.test.ts +14 -3
- package/src/wirings/persona/persona.test.ts +13 -3
- package/src/wirings/persona/validate-personas.ts +5 -1
- package/src/wirings/queue/queue-runner.ts +1 -1
- package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
- package/src/wirings/rpc/rpc-runner.ts +58 -136
- package/src/wirings/rpc/rpc-types.ts +7 -0
- package/src/wirings/rpc/wire-addon.ts +3 -1
- package/src/wirings/secret/validate-secret-definitions.test.ts +69 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -0
- package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
- package/src/wirings/trigger/trigger-runner.ts +7 -5
- package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
- package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
- package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
- package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
- package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
- package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
- package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +35 -2
- package/src/wirings/workflow/feature.ts +7 -4
- package/src/wirings/workflow/graph/graph-node.ts +1 -1
- package/src/wirings/workflow/graph/graph-runner.test.ts +4 -2
- package/src/wirings/workflow/graph/graph-runner.ts +13 -16
- package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
- package/src/wirings/workflow/index.ts +12 -4
- package/src/wirings/workflow/pikku-scenario-service.ts +54 -25
- package/src/wirings/workflow/pikku-workflow-service.test.ts +2 -2
- package/src/wirings/workflow/pikku-workflow-service.ts +208 -630
- package/src/wirings/workflow/scenario-hooks.test.ts +51 -0
- package/src/wirings/workflow/workflow-approval.ts +185 -0
- package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
- package/src/wirings/workflow/workflow-constants.ts +47 -0
- package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
- package/src/wirings/workflow/workflow-dispatch-relay.test.ts +128 -0
- package/src/wirings/workflow/workflow-errors.ts +128 -0
- package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
- package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
- package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
- package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
- package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
- package/src/wirings/workflow/workflow-recovery.ts +162 -0
- package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
- package/src/wirings/workflow/workflow-run-authority.test.ts +215 -0
- package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
- package/src/wirings/workflow/workflow-run-ownership.ts +36 -0
- package/src/wirings/workflow/workflow-suspend.ts +61 -0
- package/src/wirings/workflow/workflow.types.ts +7 -0
- package/src/wirings-stay-decoupled.test.ts +123 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/src/internal.ts +0 -10
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { describe, test, before } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
/** Conformance suite for `aiStorageService`. Runs only when a backend supplies one. */
|
|
4
|
+
export const defineAiStorageServiceTests = (name, aiStorageService) => {
|
|
5
|
+
const factory = aiStorageService;
|
|
6
|
+
describe(`AIStorageService [${name}]`, () => {
|
|
7
|
+
let storage;
|
|
8
|
+
before(async () => {
|
|
9
|
+
storage = await factory();
|
|
10
|
+
});
|
|
11
|
+
test('createThread and getThread', async () => {
|
|
12
|
+
const thread = await storage.createThread('resource-1', {
|
|
13
|
+
title: 'Test Thread',
|
|
14
|
+
metadata: { key: 'val' },
|
|
15
|
+
});
|
|
16
|
+
assert.ok(thread.id);
|
|
17
|
+
assert.equal(thread.resourceId, 'resource-1');
|
|
18
|
+
assert.equal(thread.title, 'Test Thread');
|
|
19
|
+
assert.deepEqual(thread.metadata, { key: 'val' });
|
|
20
|
+
const fetched = await storage.getThread(thread.id);
|
|
21
|
+
assert.equal(fetched.id, thread.id);
|
|
22
|
+
assert.equal(fetched.title, 'Test Thread');
|
|
23
|
+
});
|
|
24
|
+
test('getThreads', async () => {
|
|
25
|
+
const threads = await storage.getThreads('resource-1');
|
|
26
|
+
assert.ok(threads.length >= 1);
|
|
27
|
+
assert.ok(threads.every((t) => t.resourceId === 'resource-1'));
|
|
28
|
+
});
|
|
29
|
+
test('getThread throws for missing', async () => {
|
|
30
|
+
await assert.rejects(() => storage.getThread('missing-thread'));
|
|
31
|
+
});
|
|
32
|
+
test('saveMessages and getMessages', async () => {
|
|
33
|
+
const thread = await storage.createThread('resource-2');
|
|
34
|
+
const now = new Date();
|
|
35
|
+
await storage.saveMessages(thread.id, [
|
|
36
|
+
{ id: 'msg-1', role: 'user', content: 'Hello', createdAt: now },
|
|
37
|
+
{
|
|
38
|
+
id: 'msg-2',
|
|
39
|
+
role: 'assistant',
|
|
40
|
+
content: 'Hi there',
|
|
41
|
+
createdAt: new Date(now.getTime() + 1000),
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
const messages = await storage.getMessages(thread.id);
|
|
45
|
+
assert.equal(messages.length, 2);
|
|
46
|
+
assert.equal(messages[0].role, 'user');
|
|
47
|
+
assert.equal(messages[0].content, 'Hello');
|
|
48
|
+
assert.equal(messages[1].role, 'assistant');
|
|
49
|
+
assert.equal(messages[1].content, 'Hi there');
|
|
50
|
+
});
|
|
51
|
+
test('saveMessages with tool calls and results', async () => {
|
|
52
|
+
const thread = await storage.createThread('resource-3');
|
|
53
|
+
const now = new Date();
|
|
54
|
+
await storage.saveMessages(thread.id, [
|
|
55
|
+
{
|
|
56
|
+
id: 'msg-tc',
|
|
57
|
+
role: 'assistant',
|
|
58
|
+
content: 'Let me call a tool',
|
|
59
|
+
toolCalls: [{ id: 'tc-1', name: 'search', args: { query: 'test' } }],
|
|
60
|
+
createdAt: now,
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
await storage.saveMessages(thread.id, [
|
|
64
|
+
{
|
|
65
|
+
id: 'tool-results-msg-tc',
|
|
66
|
+
role: 'tool',
|
|
67
|
+
toolResults: [{ id: 'tc-1', name: 'search', result: 'found it' }],
|
|
68
|
+
createdAt: new Date(now.getTime() + 1000),
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
const messages = await storage.getMessages(thread.id);
|
|
72
|
+
assert.equal(messages.length, 2);
|
|
73
|
+
const assistantMsg = messages[0];
|
|
74
|
+
assert.equal(assistantMsg.role, 'assistant');
|
|
75
|
+
assert.ok(assistantMsg.toolCalls);
|
|
76
|
+
assert.equal(assistantMsg.toolCalls[0].name, 'search');
|
|
77
|
+
const toolMsg = messages[1];
|
|
78
|
+
assert.equal(toolMsg.role, 'tool');
|
|
79
|
+
assert.ok(toolMsg.toolResults);
|
|
80
|
+
assert.equal(toolMsg.toolResults[0].result, 'found it');
|
|
81
|
+
});
|
|
82
|
+
test('getMessages with lastN', async () => {
|
|
83
|
+
const thread = await storage.createThread('resource-4');
|
|
84
|
+
const base = Date.now();
|
|
85
|
+
await storage.saveMessages(thread.id, Array.from({ length: 5 }, (_, i) => ({
|
|
86
|
+
id: `bulk-msg-${i}`,
|
|
87
|
+
role: 'user',
|
|
88
|
+
content: `Message ${i}`,
|
|
89
|
+
createdAt: new Date(base + i * 1000),
|
|
90
|
+
})));
|
|
91
|
+
const messages = await storage.getMessages(thread.id, { lastN: 2 });
|
|
92
|
+
assert.equal(messages.length, 2);
|
|
93
|
+
assert.equal(messages[0].content, 'Message 3');
|
|
94
|
+
assert.equal(messages[1].content, 'Message 4');
|
|
95
|
+
});
|
|
96
|
+
test('working memory: save and get', async () => {
|
|
97
|
+
await storage.saveWorkingMemory('res-1', 'resource', {
|
|
98
|
+
key: 'value',
|
|
99
|
+
});
|
|
100
|
+
const mem = await storage.getWorkingMemory('res-1', 'resource');
|
|
101
|
+
assert.deepEqual(mem, { key: 'value' });
|
|
102
|
+
});
|
|
103
|
+
test('working memory: upsert overwrites', async () => {
|
|
104
|
+
await storage.saveWorkingMemory('res-1', 'resource', {
|
|
105
|
+
key: 'updated',
|
|
106
|
+
});
|
|
107
|
+
const mem = await storage.getWorkingMemory('res-1', 'resource');
|
|
108
|
+
assert.deepEqual(mem, { key: 'updated' });
|
|
109
|
+
});
|
|
110
|
+
test('working memory: returns null for missing', async () => {
|
|
111
|
+
const mem = await storage.getWorkingMemory('missing', 'thread');
|
|
112
|
+
assert.equal(mem, null);
|
|
113
|
+
});
|
|
114
|
+
test('createRun and getRun', async () => {
|
|
115
|
+
const thread = await storage.createThread('resource-5');
|
|
116
|
+
const now = new Date();
|
|
117
|
+
const runId = await storage.createRun({
|
|
118
|
+
agentName: 'test-agent',
|
|
119
|
+
threadId: thread.id,
|
|
120
|
+
resourceId: 'resource-5',
|
|
121
|
+
status: 'running',
|
|
122
|
+
usage: { inputTokens: 100, outputTokens: 50, model: 'test-model' },
|
|
123
|
+
createdAt: now,
|
|
124
|
+
updatedAt: now,
|
|
125
|
+
});
|
|
126
|
+
assert.ok(runId);
|
|
127
|
+
const run = await storage.getRun(runId);
|
|
128
|
+
assert.ok(run);
|
|
129
|
+
assert.equal(run.agentName, 'test-agent');
|
|
130
|
+
assert.equal(run.status, 'running');
|
|
131
|
+
assert.equal(run.usage.inputTokens, 100);
|
|
132
|
+
});
|
|
133
|
+
test('updateRun', async () => {
|
|
134
|
+
const thread = await storage.createThread('resource-6');
|
|
135
|
+
const now = new Date();
|
|
136
|
+
const runId = await storage.createRun({
|
|
137
|
+
agentName: 'update-agent',
|
|
138
|
+
threadId: thread.id,
|
|
139
|
+
resourceId: 'resource-6',
|
|
140
|
+
status: 'running',
|
|
141
|
+
usage: { inputTokens: 0, outputTokens: 0, model: 'test' },
|
|
142
|
+
createdAt: now,
|
|
143
|
+
updatedAt: now,
|
|
144
|
+
});
|
|
145
|
+
await storage.updateRun(runId, {
|
|
146
|
+
status: 'completed',
|
|
147
|
+
usage: { inputTokens: 200, outputTokens: 100, model: 'test-v2' },
|
|
148
|
+
});
|
|
149
|
+
const run = await storage.getRun(runId);
|
|
150
|
+
assert.ok(run);
|
|
151
|
+
assert.equal(run.status, 'completed');
|
|
152
|
+
assert.equal(run.usage.inputTokens, 200);
|
|
153
|
+
assert.equal(run.usage.model, 'test-v2');
|
|
154
|
+
});
|
|
155
|
+
test('getRunsByThread', async () => {
|
|
156
|
+
const thread = await storage.createThread('resource-7');
|
|
157
|
+
const now = new Date();
|
|
158
|
+
await storage.createRun({
|
|
159
|
+
agentName: 'multi-agent',
|
|
160
|
+
threadId: thread.id,
|
|
161
|
+
resourceId: 'resource-7',
|
|
162
|
+
status: 'completed',
|
|
163
|
+
usage: { inputTokens: 10, outputTokens: 5, model: 'test' },
|
|
164
|
+
createdAt: now,
|
|
165
|
+
updatedAt: now,
|
|
166
|
+
});
|
|
167
|
+
const runs = await storage.getRunsByThread(thread.id);
|
|
168
|
+
assert.ok(runs.length >= 1);
|
|
169
|
+
assert.ok(runs.every((r) => r.threadId === thread.id));
|
|
170
|
+
});
|
|
171
|
+
test('resolveApproval', async () => {
|
|
172
|
+
const thread = await storage.createThread('resource-8');
|
|
173
|
+
const now = new Date();
|
|
174
|
+
await storage.saveMessages(thread.id, [
|
|
175
|
+
{
|
|
176
|
+
id: 'approval-msg',
|
|
177
|
+
role: 'assistant',
|
|
178
|
+
toolCalls: [{ id: 'approval-tc', name: 'dangerous-tool', args: {} }],
|
|
179
|
+
createdAt: now,
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
const runId = await storage.createRun({
|
|
183
|
+
agentName: 'approval-agent',
|
|
184
|
+
threadId: thread.id,
|
|
185
|
+
resourceId: 'resource-8',
|
|
186
|
+
status: 'suspended',
|
|
187
|
+
suspendReason: 'approval',
|
|
188
|
+
pendingApprovals: [
|
|
189
|
+
{
|
|
190
|
+
type: 'tool-call',
|
|
191
|
+
toolCallId: 'approval-tc',
|
|
192
|
+
toolName: 'dangerous-tool',
|
|
193
|
+
args: {},
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
usage: { inputTokens: 0, outputTokens: 0, model: 'test' },
|
|
197
|
+
createdAt: now,
|
|
198
|
+
updatedAt: now,
|
|
199
|
+
});
|
|
200
|
+
let run = await storage.getRun(runId);
|
|
201
|
+
assert.ok(run);
|
|
202
|
+
assert.ok(run.pendingApprovals);
|
|
203
|
+
assert.equal(run.pendingApprovals.length, 1);
|
|
204
|
+
const claimed = await storage.resolveApproval('approval-tc', 'approved');
|
|
205
|
+
assert.equal(claimed, true, 'the first approver claims the approval');
|
|
206
|
+
run = await storage.getRun(runId);
|
|
207
|
+
assert.ok(run);
|
|
208
|
+
assert.equal(run.pendingApprovals, undefined);
|
|
209
|
+
const second = await storage.resolveApproval('approval-tc', 'approved');
|
|
210
|
+
assert.equal(second, false, 'a second approver of the same tool call claims nothing');
|
|
211
|
+
});
|
|
212
|
+
test('deleteThread cascades', async () => {
|
|
213
|
+
const thread = await storage.createThread('resource-del');
|
|
214
|
+
await storage.saveMessages(thread.id, [
|
|
215
|
+
{
|
|
216
|
+
id: 'del-msg',
|
|
217
|
+
role: 'user',
|
|
218
|
+
content: 'goodbye',
|
|
219
|
+
createdAt: new Date(),
|
|
220
|
+
},
|
|
221
|
+
]);
|
|
222
|
+
await storage.deleteThread(thread.id);
|
|
223
|
+
await assert.rejects(() => storage.getThread(thread.id));
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ServiceTestConfig } from '../service-tests.js';
|
|
2
|
+
/** Conformance suite for `channelStore`. Runs only when a backend supplies one. */
|
|
3
|
+
export declare const defineChannelStoreTests: (name: string, channelStore: NonNullable<ServiceTestConfig["services"]["channelStore"]>) => void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, test, before } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
/** Conformance suite for `channelStore`. Runs only when a backend supplies one. */
|
|
4
|
+
export const defineChannelStoreTests = (name, channelStore) => {
|
|
5
|
+
const factory = channelStore;
|
|
6
|
+
describe(`ChannelStore [${name}]`, () => {
|
|
7
|
+
let store;
|
|
8
|
+
before(async () => {
|
|
9
|
+
store = await factory();
|
|
10
|
+
});
|
|
11
|
+
test('addChannel and getChannel', async () => {
|
|
12
|
+
await store.addChannel({
|
|
13
|
+
channelId: 'ch-1',
|
|
14
|
+
channelName: 'test-channel',
|
|
15
|
+
openingData: { foo: 'bar' },
|
|
16
|
+
});
|
|
17
|
+
const result = await store.getChannel('ch-1');
|
|
18
|
+
assert.equal(result.channelId, 'ch-1');
|
|
19
|
+
assert.equal(result.channelName, 'test-channel');
|
|
20
|
+
assert.deepEqual(result.openingData, { foo: 'bar' });
|
|
21
|
+
assert.equal(result.pikkuUserId, undefined);
|
|
22
|
+
});
|
|
23
|
+
test('setPikkuUserId', async () => {
|
|
24
|
+
await store.setPikkuUserId('ch-1', 'user-1');
|
|
25
|
+
const result = await store.getChannel('ch-1');
|
|
26
|
+
assert.equal(result.pikkuUserId, 'user-1');
|
|
27
|
+
});
|
|
28
|
+
test('setPikkuUserId to null', async () => {
|
|
29
|
+
await store.setPikkuUserId('ch-1', null);
|
|
30
|
+
const result = await store.getChannel('ch-1');
|
|
31
|
+
assert.equal(result.pikkuUserId, undefined);
|
|
32
|
+
});
|
|
33
|
+
test('getChannel throws for missing channel', async () => {
|
|
34
|
+
await assert.rejects(async () => {
|
|
35
|
+
await store.getChannel('missing');
|
|
36
|
+
}, { message: 'Channel not found: missing' });
|
|
37
|
+
});
|
|
38
|
+
test('removeChannels', async () => {
|
|
39
|
+
await store.addChannel({
|
|
40
|
+
channelId: 'ch-2',
|
|
41
|
+
channelName: 'temp-channel',
|
|
42
|
+
});
|
|
43
|
+
await store.removeChannels(['ch-2']);
|
|
44
|
+
await assert.rejects(async () => {
|
|
45
|
+
await store.getChannel('ch-2');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
test('removeChannels with empty array is no-op', async () => {
|
|
49
|
+
await store.removeChannels([]);
|
|
50
|
+
});
|
|
51
|
+
test('session round-trip — set / get / clear', async () => {
|
|
52
|
+
await store.addChannel({
|
|
53
|
+
channelId: 'ch-state',
|
|
54
|
+
channelName: 'test-channel',
|
|
55
|
+
});
|
|
56
|
+
const empty = await store.getState('ch-state');
|
|
57
|
+
assert.equal(empty, undefined);
|
|
58
|
+
const payload = { userId: 'u-7', meta: { foo: 1 } };
|
|
59
|
+
await store.setState('ch-state', payload);
|
|
60
|
+
const got = await store.getState('ch-state');
|
|
61
|
+
assert.deepEqual(got, payload);
|
|
62
|
+
const next = { userId: 'u-8' };
|
|
63
|
+
await store.setState('ch-state', next);
|
|
64
|
+
const got2 = await store.getState('ch-state');
|
|
65
|
+
assert.deepEqual(got2, next);
|
|
66
|
+
await store.clearState('ch-state');
|
|
67
|
+
const after = await store.getState('ch-state');
|
|
68
|
+
assert.equal(after, undefined);
|
|
69
|
+
await store.removeChannels(['ch-state']);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ServiceTestConfig } from '../service-tests.js';
|
|
2
|
+
/** Conformance suite for `credentialService`. Runs only when a backend supplies one. */
|
|
3
|
+
export declare const defineCredentialServiceTests: (name: string, credentialService: NonNullable<ServiceTestConfig["services"]["credentialService"]>) => void;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
/** Conformance suite for `credentialService`. Runs only when a backend supplies one. */
|
|
4
|
+
export const defineCredentialServiceTests = (name, credentialService) => {
|
|
5
|
+
const factory = credentialService;
|
|
6
|
+
const kek = 'test-key-encryption-key-32chars!';
|
|
7
|
+
describe(`CredentialService [${name}]`, () => {
|
|
8
|
+
test('set and get (global)', async () => {
|
|
9
|
+
const service = await factory({ key: kek });
|
|
10
|
+
await service.set('stripe', { apiKey: 'sk-123' });
|
|
11
|
+
const result = await service.get('stripe');
|
|
12
|
+
assert.deepEqual(result, { apiKey: 'sk-123' });
|
|
13
|
+
});
|
|
14
|
+
test('get returns null for missing', async () => {
|
|
15
|
+
const service = await factory({ key: kek });
|
|
16
|
+
const result = await service.get('nonexistent');
|
|
17
|
+
assert.equal(result, null);
|
|
18
|
+
});
|
|
19
|
+
test('has returns true/false', async () => {
|
|
20
|
+
const service = await factory({ key: kek });
|
|
21
|
+
await service.set('exists', { key: 'val' });
|
|
22
|
+
assert.strictEqual(await service.has('exists'), true);
|
|
23
|
+
assert.strictEqual(await service.has('nope'), false);
|
|
24
|
+
});
|
|
25
|
+
test('set upserts existing credential', async () => {
|
|
26
|
+
const service = await factory({ key: kek });
|
|
27
|
+
await service.set('upsert', { v: 1 });
|
|
28
|
+
await service.set('upsert', { v: 2 });
|
|
29
|
+
const result = await service.get('upsert');
|
|
30
|
+
assert.deepEqual(result, { v: 2 });
|
|
31
|
+
});
|
|
32
|
+
test('delete removes credential', async () => {
|
|
33
|
+
const service = await factory({ key: kek });
|
|
34
|
+
await service.set('to-delete', { bye: true });
|
|
35
|
+
assert.strictEqual(await service.has('to-delete'), true);
|
|
36
|
+
await service.delete('to-delete');
|
|
37
|
+
assert.strictEqual(await service.has('to-delete'), false);
|
|
38
|
+
});
|
|
39
|
+
test('per-user isolation', async () => {
|
|
40
|
+
const service = await factory({ key: kek });
|
|
41
|
+
await service.set('token', { access: 'user1' }, 'user-1');
|
|
42
|
+
await service.set('token', { access: 'user2' }, 'user-2');
|
|
43
|
+
const u1 = await service.get('token', 'user-1');
|
|
44
|
+
const u2 = await service.get('token', 'user-2');
|
|
45
|
+
assert.deepEqual(u1, { access: 'user1' });
|
|
46
|
+
assert.deepEqual(u2, { access: 'user2' });
|
|
47
|
+
});
|
|
48
|
+
test('per-user delete does not affect other users', async () => {
|
|
49
|
+
const service = await factory({ key: kek });
|
|
50
|
+
await service.set('cred', { val: 'a' }, 'user-a');
|
|
51
|
+
await service.set('cred', { val: 'b' }, 'user-b');
|
|
52
|
+
await service.delete('cred', 'user-a');
|
|
53
|
+
assert.strictEqual(await service.has('cred', 'user-a'), false);
|
|
54
|
+
assert.strictEqual(await service.has('cred', 'user-b'), true);
|
|
55
|
+
});
|
|
56
|
+
test('global and per-user are separate', async () => {
|
|
57
|
+
const service = await factory({ key: kek });
|
|
58
|
+
await service.set('shared', { scope: 'global' });
|
|
59
|
+
await service.set('shared', { scope: 'user' }, 'user-1');
|
|
60
|
+
const global = await service.get('shared');
|
|
61
|
+
const perUser = await service.get('shared', 'user-1');
|
|
62
|
+
assert.deepEqual(global, { scope: 'global' });
|
|
63
|
+
assert.deepEqual(perUser, { scope: 'user' });
|
|
64
|
+
});
|
|
65
|
+
test('getAll returns all credentials for a user', async () => {
|
|
66
|
+
const service = await factory({ key: kek });
|
|
67
|
+
await service.set('cred-a', { a: 1 }, 'user-x');
|
|
68
|
+
await service.set('cred-b', { b: 2 }, 'user-x');
|
|
69
|
+
await service.set('cred-c', { c: 3 }, 'user-y');
|
|
70
|
+
const all = await service.getAll('user-x');
|
|
71
|
+
assert.deepEqual(all['cred-a'], { a: 1 });
|
|
72
|
+
assert.deepEqual(all['cred-b'], { b: 2 });
|
|
73
|
+
assert.strictEqual(all['cred-c'], undefined);
|
|
74
|
+
});
|
|
75
|
+
test('getAll returns empty for unknown user', async () => {
|
|
76
|
+
const service = await factory({ key: kek });
|
|
77
|
+
const all = await service.getAll('nobody');
|
|
78
|
+
assert.deepEqual(all, {});
|
|
79
|
+
});
|
|
80
|
+
test('rotateKEK re-wraps all credentials', async () => {
|
|
81
|
+
const newKEK = 'new-key-encryption-key-rotated!';
|
|
82
|
+
const oldService = await factory({ key: kek });
|
|
83
|
+
await oldService.set('rotate-cred', { important: 'data' }, 'user-r');
|
|
84
|
+
const rotatedService = await factory({
|
|
85
|
+
key: newKEK,
|
|
86
|
+
keyVersion: 2,
|
|
87
|
+
previousKey: kek,
|
|
88
|
+
});
|
|
89
|
+
const before = await rotatedService.get('rotate-cred', 'user-r');
|
|
90
|
+
assert.deepEqual(before, { important: 'data' });
|
|
91
|
+
assert.ok(rotatedService.rotateKEK);
|
|
92
|
+
const count = await rotatedService.rotateKEK();
|
|
93
|
+
assert.ok(count > 0);
|
|
94
|
+
const newOnlyService = await factory({
|
|
95
|
+
key: newKEK,
|
|
96
|
+
keyVersion: 2,
|
|
97
|
+
});
|
|
98
|
+
const after = await newOnlyService.get('rotate-cred', 'user-r');
|
|
99
|
+
assert.deepEqual(after, { important: 'data' });
|
|
100
|
+
});
|
|
101
|
+
test('rotateKEK throws without previousKey', async () => {
|
|
102
|
+
const service = await factory({ key: kek });
|
|
103
|
+
assert.ok(service.rotateKEK);
|
|
104
|
+
await assert.rejects(() => service.rotateKEK(), {
|
|
105
|
+
message: 'No previousKey configured — nothing to rotate from',
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ServiceTestConfig } from '../service-tests.js';
|
|
2
|
+
/** Conformance suite for `deploymentService`. Runs only when a backend supplies one. */
|
|
3
|
+
export declare const defineDeploymentServiceTests: (name: string, deploymentService: NonNullable<ServiceTestConfig["services"]["deploymentService"]>) => void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, test, before, after } from 'node:test';
|
|
2
|
+
/** Conformance suite for `deploymentService`. Runs only when a backend supplies one. */
|
|
3
|
+
export const defineDeploymentServiceTests = (name, deploymentService) => {
|
|
4
|
+
const factory = deploymentService;
|
|
5
|
+
describe(`DeploymentService [${name}]`, () => {
|
|
6
|
+
let service;
|
|
7
|
+
before(async () => {
|
|
8
|
+
service = await factory();
|
|
9
|
+
});
|
|
10
|
+
after(async () => {
|
|
11
|
+
await service.stop();
|
|
12
|
+
});
|
|
13
|
+
test('start registers deployment', async () => {
|
|
14
|
+
await service.start({
|
|
15
|
+
deploymentId: 'deploy-1',
|
|
16
|
+
endpoint: 'http://localhost:3000',
|
|
17
|
+
functions: ['funcA', 'funcB'],
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ServiceTestConfig } from '../service-tests.js';
|
|
2
|
+
/** Conformance suite for `eventHubStore`. Runs only when a backend supplies one. */
|
|
3
|
+
export declare const defineEventHubStoreTests: (name: string, eventHubStore: NonNullable<ServiceTestConfig["services"]["eventHubStore"]>) => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, test, before } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
/** Conformance suite for `eventHubStore`. Runs only when a backend supplies one. */
|
|
4
|
+
export const defineEventHubStoreTests = (name, eventHubStore) => {
|
|
5
|
+
const factory = eventHubStore;
|
|
6
|
+
describe(`EventHubStore [${name}]`, () => {
|
|
7
|
+
let store;
|
|
8
|
+
before(async () => {
|
|
9
|
+
store = await factory();
|
|
10
|
+
});
|
|
11
|
+
test('subscribe and getChannelIdsForTopic', async () => {
|
|
12
|
+
const result = await store.subscribe('topic-1', 'ch-1');
|
|
13
|
+
assert.equal(result, true);
|
|
14
|
+
const ids = await store.getChannelIdsForTopic('topic-1');
|
|
15
|
+
assert.deepEqual(ids, ['ch-1']);
|
|
16
|
+
});
|
|
17
|
+
test('subscribe duplicate is idempotent', async () => {
|
|
18
|
+
const result = await store.subscribe('topic-1', 'ch-1');
|
|
19
|
+
assert.equal(result, true);
|
|
20
|
+
});
|
|
21
|
+
test('unsubscribe returns true when exists', async () => {
|
|
22
|
+
const result = await store.unsubscribe('topic-1', 'ch-1');
|
|
23
|
+
assert.equal(result, true);
|
|
24
|
+
});
|
|
25
|
+
test('unsubscribe returns false when not exists', async () => {
|
|
26
|
+
const result = await store.unsubscribe('topic-1', 'ch-1');
|
|
27
|
+
assert.equal(result, false);
|
|
28
|
+
});
|
|
29
|
+
test('getChannelIdsForTopic returns empty for unknown topic', async () => {
|
|
30
|
+
const ids = await store.getChannelIdsForTopic('unknown');
|
|
31
|
+
assert.deepEqual(ids, []);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ServiceTestConfig } from '../service-tests.js';
|
|
2
|
+
/** Conformance suite for `secretService`. Runs only when a backend supplies one. */
|
|
3
|
+
export declare const defineSecretServiceTests: (name: string, secretService: NonNullable<ServiceTestConfig["services"]["secretService"]>) => void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
/** Conformance suite for `secretService`. Runs only when a backend supplies one. */
|
|
4
|
+
export const defineSecretServiceTests = (name, secretService) => {
|
|
5
|
+
const factory = secretService;
|
|
6
|
+
const kek = 'test-key-encryption-key-32chars!';
|
|
7
|
+
describe(`SecretService [${name}]`, () => {
|
|
8
|
+
test('setSecret and getSecret', async () => {
|
|
9
|
+
const service = await factory({ key: kek });
|
|
10
|
+
await service.setSecret('api-key', {
|
|
11
|
+
token: 'sk-123',
|
|
12
|
+
endpoint: 'https://api.example.com',
|
|
13
|
+
});
|
|
14
|
+
const result = await service.getSecret('api-key');
|
|
15
|
+
assert.deepEqual(result.reveal(), {
|
|
16
|
+
token: 'sk-123',
|
|
17
|
+
endpoint: 'https://api.example.com',
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
test('getSecret wraps the raw string rather than returning it', async () => {
|
|
21
|
+
const service = await factory({ key: kek });
|
|
22
|
+
await service.setSecret('string-secret', 'plain-value');
|
|
23
|
+
const result = await service.getSecret('string-secret');
|
|
24
|
+
assert.strictEqual(result.reveal(), 'plain-value');
|
|
25
|
+
});
|
|
26
|
+
test('hasSecret returns true/false', async () => {
|
|
27
|
+
const service = await factory({ key: kek });
|
|
28
|
+
assert.strictEqual(await service.hasSecret('api-key'), true);
|
|
29
|
+
assert.strictEqual(await service.hasSecret('nonexistent'), false);
|
|
30
|
+
});
|
|
31
|
+
test('getSecret throws for missing key', async () => {
|
|
32
|
+
const service = await factory({ key: kek });
|
|
33
|
+
await assert.rejects(() => service.getSecret('nonexistent'), {
|
|
34
|
+
message: 'Requested secret not found',
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
test('setSecret upserts existing key', async () => {
|
|
38
|
+
const service = await factory({ key: kek });
|
|
39
|
+
await service.setSecret('upsert-key', { v: 1 });
|
|
40
|
+
await service.setSecret('upsert-key', { v: 2 });
|
|
41
|
+
const result = await service.getSecret('upsert-key');
|
|
42
|
+
assert.deepEqual(result.reveal(), { v: 2 });
|
|
43
|
+
});
|
|
44
|
+
test('deleteSecret removes the key', async () => {
|
|
45
|
+
const service = await factory({ key: kek });
|
|
46
|
+
await service.setSecret('to-delete', 'bye');
|
|
47
|
+
assert.strictEqual(await service.hasSecret('to-delete'), true);
|
|
48
|
+
await service.deleteSecret('to-delete');
|
|
49
|
+
assert.strictEqual(await service.hasSecret('to-delete'), false);
|
|
50
|
+
});
|
|
51
|
+
test('rotateKEK re-wraps all secrets', async () => {
|
|
52
|
+
const newKEK = 'new-key-encryption-key-rotated!';
|
|
53
|
+
const oldService = await factory({ key: kek });
|
|
54
|
+
await oldService.setSecret('rotate-test', { important: 'data' });
|
|
55
|
+
const rotatedService = await factory({
|
|
56
|
+
key: newKEK,
|
|
57
|
+
keyVersion: 2,
|
|
58
|
+
previousKey: kek,
|
|
59
|
+
});
|
|
60
|
+
const before = await rotatedService.getSecret('rotate-test');
|
|
61
|
+
assert.deepEqual(before.reveal(), { important: 'data' });
|
|
62
|
+
assert.ok(rotatedService.rotateKEK);
|
|
63
|
+
const count = await rotatedService.rotateKEK();
|
|
64
|
+
assert.ok(count > 0);
|
|
65
|
+
const newOnlyService = await factory({
|
|
66
|
+
key: newKEK,
|
|
67
|
+
keyVersion: 2,
|
|
68
|
+
});
|
|
69
|
+
const after = await newOnlyService.getSecret('rotate-test');
|
|
70
|
+
assert.deepEqual(after.reveal(), { important: 'data' });
|
|
71
|
+
});
|
|
72
|
+
test('rotateKEK throws without previousKey', async () => {
|
|
73
|
+
const service = await factory({ key: kek });
|
|
74
|
+
assert.ok(service.rotateKEK);
|
|
75
|
+
await assert.rejects(() => service.rotateKEK(), {
|
|
76
|
+
message: 'No previousKey configured — nothing to rotate from',
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ServiceTestConfig } from '../service-tests.js';
|
|
2
|
+
/** Conformance suite for `sessionStore`. Runs only when a backend supplies one. */
|
|
3
|
+
export declare const defineSessionStoreTests: (name: string, sessionStore: NonNullable<ServiceTestConfig["services"]["sessionStore"]>) => void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, test, before } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
/** Conformance suite for `sessionStore`. Runs only when a backend supplies one. */
|
|
4
|
+
export const defineSessionStoreTests = (name, sessionStore) => {
|
|
5
|
+
const factory = sessionStore;
|
|
6
|
+
describe(`SessionStore [${name}]`, () => {
|
|
7
|
+
let store;
|
|
8
|
+
before(async () => {
|
|
9
|
+
store = await factory();
|
|
10
|
+
});
|
|
11
|
+
test('get returns undefined for unknown user', async () => {
|
|
12
|
+
const result = await store.get('unknown-user');
|
|
13
|
+
assert.equal(result, undefined);
|
|
14
|
+
});
|
|
15
|
+
test('set and get round-trip', async () => {
|
|
16
|
+
const session = {
|
|
17
|
+
userId: 'user-1',
|
|
18
|
+
organizationId: 'org-1',
|
|
19
|
+
};
|
|
20
|
+
await store.set('user-1', session);
|
|
21
|
+
const result = await store.get('user-1');
|
|
22
|
+
assert.deepEqual(result, session);
|
|
23
|
+
});
|
|
24
|
+
test('set overwrites previous session', async () => {
|
|
25
|
+
const asAdmin = { userId: 'user-2', role: 'admin' };
|
|
26
|
+
const asMember = { userId: 'user-2', role: 'member' };
|
|
27
|
+
await store.set('user-2', asAdmin);
|
|
28
|
+
await store.set('user-2', asMember);
|
|
29
|
+
const result = await store.get('user-2');
|
|
30
|
+
assert.deepEqual(result, { userId: 'user-2', role: 'member' });
|
|
31
|
+
});
|
|
32
|
+
test('clear removes session', async () => {
|
|
33
|
+
await store.set('user-3', { userId: 'user-3' });
|
|
34
|
+
assert.ok(await store.get('user-3'));
|
|
35
|
+
await store.clear('user-3');
|
|
36
|
+
const result = await store.get('user-3');
|
|
37
|
+
assert.equal(result, undefined);
|
|
38
|
+
});
|
|
39
|
+
test('clear is no-op for unknown user', async () => {
|
|
40
|
+
await store.clear('nonexistent');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ServiceTestConfig } from '../service-tests.js';
|
|
2
|
+
/** Conformance suite for `workflowRunService`. Runs only when a backend supplies one. */
|
|
3
|
+
export declare const defineWorkflowRunServiceTests: (name: string, workflowRunService: NonNullable<ServiceTestConfig["services"]["workflowRunService"]>) => void;
|