@vertesia/common 1.6.0-dev.20260923.085325Z → 1.6.0-dev.20261008.064737Z
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/lib/access-control-values.d.ts +2 -2
- package/lib/access-control-values.d.ts.map +1 -1
- package/lib/access-control-values.js +2 -2
- package/lib/access-control-values.js.map +1 -1
- package/lib/access-control.d.ts +7 -8
- package/lib/access-control.d.ts.map +1 -1
- package/lib/access-control.js +5 -0
- package/lib/access-control.js.map +1 -1
- package/lib/analytics.d.ts +6 -7
- package/lib/analytics.d.ts.map +1 -1
- package/lib/api-contract/components.generated.json +2553 -1046
- package/lib/api-contract/engine.d.ts +65 -0
- package/lib/api-contract/engine.d.ts.map +1 -0
- package/lib/api-contract/engine.js +569 -0
- package/lib/api-contract/engine.js.map +1 -0
- package/lib/api-contract/index.d.ts +21 -182
- package/lib/api-contract/index.d.ts.map +1 -1
- package/lib/api-contract/index.js +13 -551
- package/lib/api-contract/index.js.map +1 -1
- package/lib/api-schemas/access-control.d.ts +33 -10
- package/lib/api-schemas/access-control.d.ts.map +1 -1
- package/lib/api-schemas/access-control.js +7 -6
- package/lib/api-schemas/access-control.js.map +1 -1
- package/lib/api-schemas/account.d.ts +5 -9
- package/lib/api-schemas/account.d.ts.map +1 -1
- package/lib/api-schemas/account.js +0 -7
- package/lib/api-schemas/account.js.map +1 -1
- package/lib/api-schemas/agent-budget.d.ts +15 -0
- package/lib/api-schemas/agent-budget.d.ts.map +1 -0
- package/lib/api-schemas/agent-budget.js +55 -0
- package/lib/api-schemas/agent-budget.js.map +1 -0
- package/lib/api-schemas/agent-runs.d.ts +1525 -100
- package/lib/api-schemas/agent-runs.d.ts.map +1 -1
- package/lib/api-schemas/agent-runs.js +60 -28
- package/lib/api-schemas/agent-runs.js.map +1 -1
- package/lib/api-schemas/apikey.d.ts +11 -10
- package/lib/api-schemas/apikey.d.ts.map +1 -1
- package/lib/api-schemas/apikey.js.map +1 -1
- package/lib/api-schemas/app-lifecycle.d.ts +13 -0
- package/lib/api-schemas/app-lifecycle.d.ts.map +1 -1
- package/lib/api-schemas/app-lifecycle.js +4 -0
- package/lib/api-schemas/app-lifecycle.js.map +1 -1
- package/lib/api-schemas/app-runtime.d.ts +1821 -108
- package/lib/api-schemas/app-runtime.d.ts.map +1 -1
- package/lib/api-schemas/audit-trail.d.ts +275 -0
- package/lib/api-schemas/audit-trail.d.ts.map +1 -1
- package/lib/api-schemas/audit-trail.js +156 -1
- package/lib/api-schemas/audit-trail.js.map +1 -1
- package/lib/api-schemas/content.d.ts +665 -30
- package/lib/api-schemas/content.d.ts.map +1 -1
- package/lib/api-schemas/content.js +47 -0
- package/lib/api-schemas/content.js.map +1 -1
- package/lib/api-schemas/cost-analytics.d.ts +19 -0
- package/lib/api-schemas/cost-analytics.d.ts.map +1 -1
- package/lib/api-schemas/cost-analytics.js +38 -2
- package/lib/api-schemas/cost-analytics.js.map +1 -1
- package/lib/api-schemas/document-processing.d.ts +381 -18
- package/lib/api-schemas/document-processing.d.ts.map +1 -1
- package/lib/api-schemas/environment.d.ts +0 -5
- package/lib/api-schemas/environment.d.ts.map +1 -1
- package/lib/api-schemas/environment.js +0 -1
- package/lib/api-schemas/environment.js.map +1 -1
- package/lib/api-schemas/events.d.ts +1143 -54
- package/lib/api-schemas/events.d.ts.map +1 -1
- package/lib/api-schemas/group.d.ts +2 -1
- package/lib/api-schemas/group.d.ts.map +1 -1
- package/lib/api-schemas/group.js.map +1 -1
- package/lib/api-schemas/index.d.ts +2 -0
- package/lib/api-schemas/index.d.ts.map +1 -1
- package/lib/api-schemas/index.js +2 -0
- package/lib/api-schemas/index.js.map +1 -1
- package/lib/api-schemas/inference-profile.d.ts +5017 -0
- package/lib/api-schemas/inference-profile.d.ts.map +1 -0
- package/lib/api-schemas/inference-profile.js +135 -0
- package/lib/api-schemas/inference-profile.js.map +1 -0
- package/lib/api-schemas/integrations.d.ts +6 -6
- package/lib/api-schemas/interaction.d.ts +34391 -703
- package/lib/api-schemas/interaction.d.ts.map +1 -1
- package/lib/api-schemas/interaction.js +34 -1
- package/lib/api-schemas/interaction.js.map +1 -1
- package/lib/api-schemas/invites.d.ts +10 -9
- package/lib/api-schemas/invites.d.ts.map +1 -1
- package/lib/api-schemas/invites.js.map +1 -1
- package/lib/api-schemas/process.d.ts +653 -34
- package/lib/api-schemas/process.d.ts.map +1 -1
- package/lib/api-schemas/process.js +66 -0
- package/lib/api-schemas/process.js.map +1 -1
- package/lib/api-schemas/project-configuration.d.ts +1323 -62
- package/lib/api-schemas/project-configuration.d.ts.map +1 -1
- package/lib/api-schemas/project-configuration.js +15 -1
- package/lib/api-schemas/project-configuration.js.map +1 -1
- package/lib/api-schemas/project.d.ts +2031 -116
- package/lib/api-schemas/project.d.ts.map +1 -1
- package/lib/api-schemas/project.js +9 -1
- package/lib/api-schemas/project.js.map +1 -1
- package/lib/api-schemas/quota.d.ts +7 -6
- package/lib/api-schemas/quota.d.ts.map +1 -1
- package/lib/api-schemas/quota.js.map +1 -1
- package/lib/api-schemas/registry.d.ts +28171 -36096
- package/lib/api-schemas/registry.d.ts.map +1 -1
- package/lib/api-schemas/registry.js +77 -30
- package/lib/api-schemas/registry.js.map +1 -1
- package/lib/api-schemas/run-budget.d.ts +16 -0
- package/lib/api-schemas/run-budget.d.ts.map +1 -0
- package/lib/api-schemas/run-budget.js +22 -0
- package/lib/api-schemas/run-budget.js.map +1 -0
- package/lib/api-schemas/secrets.d.ts +0 -71
- package/lib/api-schemas/secrets.d.ts.map +1 -1
- package/lib/api-schemas/secrets.js +0 -73
- package/lib/api-schemas/secrets.js.map +1 -1
- package/lib/api-schemas/store.d.ts +6388 -338
- package/lib/api-schemas/store.d.ts.map +1 -1
- package/lib/api-schemas/store.js +11 -0
- package/lib/api-schemas/store.js.map +1 -1
- package/lib/api-schemas/user.d.ts +9 -8
- package/lib/api-schemas/user.d.ts.map +1 -1
- package/lib/api-schemas/user.js.map +1 -1
- package/lib/api-schemas/view-execution.d.ts +8 -0
- package/lib/api-schemas/view-execution.d.ts.map +1 -1
- package/lib/api-schemas/views.d.ts +14 -0
- package/lib/api-schemas/views.d.ts.map +1 -1
- package/lib/api-schemas/workflow-runs.d.ts +335 -12
- package/lib/api-schemas/workflow-runs.d.ts.map +1 -1
- package/lib/api-schemas/workflow-runs.js +2 -0
- package/lib/api-schemas/workflow-runs.js.map +1 -1
- package/lib/apikey.d.ts +30 -25
- package/lib/apikey.d.ts.map +1 -1
- package/lib/apikey.js.map +1 -1
- package/lib/apps.d.ts +116 -121
- package/lib/apps.d.ts.map +1 -1
- package/lib/apps.js.map +1 -1
- package/lib/audit-trail.d.ts +30 -19
- package/lib/audit-trail.d.ts.map +1 -1
- package/lib/audit-trail.js +6 -0
- package/lib/audit-trail.js.map +1 -1
- package/lib/browser-credentials.d.ts +11 -12
- package/lib/browser-credentials.d.ts.map +1 -1
- package/lib/common.d.ts +17 -24
- package/lib/common.d.ts.map +1 -1
- package/lib/content-query.d.ts +3 -4
- package/lib/content-query.d.ts.map +1 -1
- package/lib/cost-analytics.d.ts +13 -21
- package/lib/cost-analytics.d.ts.map +1 -1
- package/lib/cost-analytics.js +0 -7
- package/lib/cost-analytics.js.map +1 -1
- package/lib/data-platform.d.ts +79 -88
- package/lib/data-platform.d.ts.map +1 -1
- package/lib/data-platform.js.map +1 -1
- package/lib/delegation.d.ts +4 -5
- package/lib/delegation.d.ts.map +1 -1
- package/lib/email.d.ts +15 -17
- package/lib/email.d.ts.map +1 -1
- package/lib/email.js.map +1 -1
- package/lib/embeddings.d.ts +25 -26
- package/lib/embeddings.d.ts.map +1 -1
- package/lib/environment.d.ts +16 -17
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js.map +1 -1
- package/lib/facets.d.ts +3 -4
- package/lib/facets.d.ts.map +1 -1
- package/lib/group.d.ts +6 -7
- package/lib/group.d.ts.map +1 -1
- package/lib/group.js.map +1 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/inference-profile.d.ts +17 -0
- package/lib/inference-profile.d.ts.map +1 -0
- package/lib/inference-profile.js +2 -0
- package/lib/inference-profile.js.map +1 -0
- package/lib/integrations.d.ts +19 -20
- package/lib/integrations.d.ts.map +1 -1
- package/lib/integrations.js.map +1 -1
- package/lib/interaction.d.ts +68 -73
- package/lib/interaction.d.ts.map +1 -1
- package/lib/interaction.js.map +1 -1
- package/lib/meters.d.ts +4 -5
- package/lib/meters.d.ts.map +1 -1
- package/lib/oauth-server.d.ts +40 -41
- package/lib/oauth-server.d.ts.map +1 -1
- package/lib/oauth.d.ts +9 -10
- package/lib/oauth.d.ts.map +1 -1
- package/lib/payload.d.ts +11 -14
- package/lib/payload.d.ts.map +1 -1
- package/lib/pending-asks.d.ts +8 -13
- package/lib/pending-asks.d.ts.map +1 -1
- package/lib/pending-asks.js +0 -4
- package/lib/pending-asks.js.map +1 -1
- package/lib/platform-event.d.ts +48 -71
- package/lib/platform-event.d.ts.map +1 -1
- package/lib/platform-event.js.map +1 -1
- package/lib/principal-context.d.ts +3 -3
- package/lib/principal-context.d.ts.map +1 -1
- package/lib/project.d.ts +42 -43
- package/lib/project.d.ts.map +1 -1
- package/lib/project.js.map +1 -1
- package/lib/prompt.d.ts +14 -16
- package/lib/prompt.d.ts.map +1 -1
- package/lib/prompt.js.map +1 -1
- package/lib/query.d.ts +10 -13
- package/lib/query.d.ts.map +1 -1
- package/lib/rate-limiter.d.ts +7 -11
- package/lib/rate-limiter.d.ts.map +1 -1
- package/lib/refs.d.ts +2 -3
- package/lib/refs.d.ts.map +1 -1
- package/lib/refs.js.map +1 -1
- package/lib/roles/types.d.ts +5 -6
- package/lib/roles/types.d.ts.map +1 -1
- package/lib/roles/types.js.map +1 -1
- package/lib/run-budget.d.ts +4 -0
- package/lib/run-budget.d.ts.map +1 -0
- package/lib/run-budget.js +2 -0
- package/lib/run-budget.js.map +1 -0
- package/lib/runs.d.ts +9 -10
- package/lib/runs.d.ts.map +1 -1
- package/lib/secrets.d.ts +10 -21
- package/lib/secrets.d.ts.map +1 -1
- package/lib/store/agent-approval.d.ts +4 -5
- package/lib/store/agent-approval.d.ts.map +1 -1
- package/lib/store/agent-approval.js.map +1 -1
- package/lib/store/agent-run.d.ts +50 -52
- package/lib/store/agent-run.d.ts.map +1 -1
- package/lib/store/agent-run.js.map +1 -1
- package/lib/store/collections.d.ts +11 -12
- package/lib/store/collections.d.ts.map +1 -1
- package/lib/store/collections.js.map +1 -1
- package/lib/store/conversation-state.d.ts +14 -8
- package/lib/store/conversation-state.d.ts.map +1 -1
- package/lib/store/doc-analyzer.d.ts +7 -8
- package/lib/store/doc-analyzer.d.ts.map +1 -1
- package/lib/store/dsl-workflow.d.ts +10 -12
- package/lib/store/dsl-workflow.d.ts.map +1 -1
- package/lib/store/dsl-workflow.js.map +1 -1
- package/lib/store/duration.d.ts +12 -0
- package/lib/store/duration.d.ts.map +1 -0
- package/lib/store/duration.js +2 -0
- package/lib/store/duration.js.map +1 -0
- package/lib/store/grounded-extraction.d.ts +6 -7
- package/lib/store/grounded-extraction.d.ts.map +1 -1
- package/lib/store/intake-policy-schema.generated.d.ts.map +1 -1
- package/lib/store/intake-policy-schema.generated.js +143 -2
- package/lib/store/intake-policy-schema.generated.js.map +1 -1
- package/lib/store/process.d.ts +70 -69
- package/lib/store/process.d.ts.map +1 -1
- package/lib/store/process.js.map +1 -1
- package/lib/store/rendering.d.ts +6 -12
- package/lib/store/rendering.d.ts.map +1 -1
- package/lib/store/rendering.js +0 -5
- package/lib/store/rendering.js.map +1 -1
- package/lib/store/signals.d.ts +43 -0
- package/lib/store/signals.d.ts.map +1 -1
- package/lib/store/signals.js +6 -1
- package/lib/store/signals.js.map +1 -1
- package/lib/store/store.d.ts +92 -91
- package/lib/store/store.d.ts.map +1 -1
- package/lib/store/store.js.map +1 -1
- package/lib/store/task.d.ts +10 -14
- package/lib/store/task.d.ts.map +1 -1
- package/lib/store/workflow.d.ts +35 -41
- package/lib/store/workflow.d.ts.map +1 -1
- package/lib/store/workflow.js.map +1 -1
- package/lib/sts-token-types.d.ts +14 -19
- package/lib/sts-token-types.d.ts.map +1 -1
- package/lib/tools.d.ts +12 -13
- package/lib/tools.d.ts.map +1 -1
- package/lib/transient-tokens.d.ts +3 -3
- package/lib/transient-tokens.d.ts.map +1 -1
- package/lib/user.d.ts +16 -20
- package/lib/user.d.ts.map +1 -1
- package/lib/user.js.map +1 -1
- package/lib/vertesia-common.js +2 -2
- package/lib/vertesia-common.js.map +1 -1
- package/lib/views.d.ts +61 -64
- package/lib/views.d.ts.map +1 -1
- package/lib/views.js.map +1 -1
- package/lib/wire-types.generated.d.ts +11227 -0
- package/lib/wire-types.generated.d.ts.map +1 -0
- package/lib/wire-types.generated.js +4 -0
- package/lib/wire-types.generated.js.map +1 -0
- package/lib/workflow-analytics.d.ts +21 -19
- package/lib/workflow-analytics.d.ts.map +1 -1
- package/lib/workflow-analytics.js +2 -2
- package/lib/workflow-analytics.js.map +1 -1
- package/package.json +16 -11
- package/src/access-control-values.ts +2 -2
- package/src/access-control.ts +7 -15
- package/src/analytics.ts +6 -13
- package/src/api-contract/components.generated.json +2553 -1046
- package/src/api-contract/engine.ts +641 -0
- package/src/api-contract/index.ts +30 -661
- package/src/api-schemas/access-control.ts +18 -16
- package/src/api-schemas/account.ts +5 -12
- package/src/api-schemas/agent-budget.ts +60 -0
- package/src/api-schemas/agent-runs.ts +63 -27
- package/src/api-schemas/apikey.ts +11 -10
- package/src/api-schemas/app-lifecycle.ts +5 -0
- package/src/api-schemas/audit-trail.ts +175 -1
- package/src/api-schemas/content.ts +55 -0
- package/src/api-schemas/cost-analytics.ts +42 -2
- package/src/api-schemas/environment.ts +0 -1
- package/src/api-schemas/group.ts +2 -1
- package/src/api-schemas/index.ts +2 -0
- package/src/api-schemas/inference-profile.ts +151 -0
- package/src/api-schemas/interaction.ts +40 -1
- package/src/api-schemas/invites.ts +10 -9
- package/src/api-schemas/process.ts +78 -2
- package/src/api-schemas/project-configuration.ts +19 -1
- package/src/api-schemas/project.ts +19 -10
- package/src/api-schemas/quota.ts +7 -6
- package/src/api-schemas/registry.ts +164 -62
- package/src/api-schemas/run-budget.ts +23 -0
- package/src/api-schemas/secrets.ts +0 -84
- package/src/api-schemas/store.ts +12 -0
- package/src/api-schemas/user.ts +9 -8
- package/src/api-schemas/workflow-runs.ts +2 -0
- package/src/apikey.ts +30 -39
- package/src/apps.ts +116 -239
- package/src/audit-trail.ts +37 -35
- package/src/browser-credentials.ts +11 -23
- package/src/common.ts +17 -37
- package/src/content-query.ts +3 -4
- package/src/cost-analytics.ts +13 -35
- package/src/data-platform.ts +79 -168
- package/src/delegation.ts +4 -9
- package/src/email.ts +15 -29
- package/src/embeddings.ts +25 -51
- package/src/environment.ts +16 -33
- package/src/facets.ts +3 -4
- package/src/group.ts +6 -13
- package/src/index.ts +23 -0
- package/src/inference-profile.ts +20 -0
- package/src/integrations.ts +21 -41
- package/src/interaction.ts +68 -137
- package/src/meters.ts +4 -9
- package/src/oauth-server.ts +40 -81
- package/src/oauth.ts +9 -19
- package/src/payload.ts +11 -25
- package/src/pending-asks.ts +8 -22
- package/src/platform-event.ts +48 -122
- package/src/principal-context.ts +3 -3
- package/src/project.ts +43 -86
- package/src/prompt.ts +14 -31
- package/src/query.ts +10 -20
- package/src/rate-limiter.ts +7 -19
- package/src/refs.ts +2 -3
- package/src/roles/types.ts +5 -11
- package/src/run-budget.ts +4 -0
- package/src/runs.ts +9 -19
- package/src/secrets.ts +10 -42
- package/src/store/agent-approval.ts +4 -9
- package/src/store/agent-run.ts +53 -99
- package/src/store/collections.ts +11 -23
- package/src/store/conversation-state.ts +15 -15
- package/src/store/doc-analyzer.ts +7 -15
- package/src/store/dsl-workflow.ts +10 -23
- package/src/store/duration.ts +45 -0
- package/src/store/grounded-extraction.ts +6 -13
- package/src/store/intake-policy-schema.generated.ts +148 -2
- package/src/store/process.ts +70 -140
- package/src/store/rendering.ts +6 -19
- package/src/store/signals.ts +48 -0
- package/src/store/store.ts +92 -181
- package/src/store/task.ts +10 -24
- package/src/store/workflow.ts +35 -71
- package/src/sts-token-types.ts +14 -33
- package/src/tools.ts +12 -25
- package/src/transient-tokens.ts +3 -3
- package/src/user.ts +16 -40
- package/src/views.ts +61 -125
- package/src/wire-types.generated.ts +11888 -0
- package/src/workflow-analytics.ts +21 -19
- package/src/agent-resources.test.ts +0 -89
- package/src/api-contract/components.contract.test.ts +0 -99
- package/src/api-schemas/access-control.contract.test.ts +0 -209
- package/src/api-schemas/account-apikey.test.ts +0 -60
- package/src/api-schemas/account.contract.test.ts +0 -319
- package/src/api-schemas/adapter-edge-cases.test.ts +0 -499
- package/src/api-schemas/agent-communication.contract.test.ts +0 -90
- package/src/api-schemas/agent-history.contract.test.ts +0 -42
- package/src/api-schemas/agent-runs.contract.test.ts +0 -240
- package/src/api-schemas/api-discriminators.test.ts +0 -185
- package/src/api-schemas/apikey.contract.test.ts +0 -270
- package/src/api-schemas/app-delete-summary.contract.test.ts +0 -67
- package/src/api-schemas/bulk-operation.contract.test.ts +0 -39
- package/src/api-schemas/bundle-isolation.test.ts +0 -44
- package/src/api-schemas/commands.contract.test.ts +0 -51
- package/src/api-schemas/cost-analytics.contract.test.ts +0 -64
- package/src/api-schemas/dashboard.contract.test.ts +0 -138
- package/src/api-schemas/data-store.contract.test.ts +0 -97
- package/src/api-schemas/document-processing.contract.test.ts +0 -76
- package/src/api-schemas/emit-json-schema.test.ts +0 -36
- package/src/api-schemas/environment.contract.test.ts +0 -202
- package/src/api-schemas/group.contract.test.ts +0 -205
- package/src/api-schemas/indexing.contract.test.ts +0 -122
- package/src/api-schemas/interaction.contract.test.ts +0 -307
- package/src/api-schemas/invites.contract.test.ts +0 -268
- package/src/api-schemas/llumiverse.contract.test.ts +0 -179
- package/src/api-schemas/mcp-api-key.contract.test.ts +0 -125
- package/src/api-schemas/oauth.contract.test.ts +0 -181
- package/src/api-schemas/parameters.test.ts +0 -402
- package/src/api-schemas/process.contract.test.ts +0 -115
- package/src/api-schemas/project.contract.test.ts +0 -472
- package/src/api-schemas/pruning-limits.test.ts +0 -189
- package/src/api-schemas/quota.contract.test.ts +0 -147
- package/src/api-schemas/registry-groups.test.ts +0 -99
- package/src/api-schemas/response-pruning.test.ts +0 -260
- package/src/api-schemas/store.contract.test.ts +0 -378
- package/src/api-schemas/sts.contract.test.ts +0 -112
- package/src/api-schemas/user.contract.test.ts +0 -197
- package/src/api-schemas/views.contract.test.ts +0 -112
- package/src/api-schemas/zeno-response-contracts.test.ts +0 -380
- package/src/apps.test.ts +0 -89
- package/src/oauth-scopes.test.ts +0 -15
- package/src/roles.test.ts +0 -33
- package/src/schema-for-extraction.test.ts +0 -191
- package/src/store/agent-approval.test.ts +0 -18
- package/src/store/content-type-editing.test.ts +0 -17
- package/src/store/ms.d.ts +0 -42
- package/src/store/schedule.test.ts +0 -59
- package/src/store/workflow.test.ts +0 -640
- package/src/versions.test.ts +0 -13
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
OAuthAuthorizationServerMetadataSchema,
|
|
4
|
-
OAuthDeviceAuthorizationResponseSchema,
|
|
5
|
-
OAuthGrantStatusSchema,
|
|
6
|
-
OAuthTokenResponseSchema,
|
|
7
|
-
} from './oauth-server.js';
|
|
8
|
-
import { ApiSchemaComponents, validateApiRequest, validateApiResponse } from './registry.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* The OAuth contracts, pinned where converting them changed what the document says or where the
|
|
12
|
-
* conversion had a choice to make.
|
|
13
|
-
*
|
|
14
|
-
* The provider/client/grant closure is 31 components and almost all of it re-emits byte-identically;
|
|
15
|
-
* what is asserted here is the handful of places it does not, plus the request/response secret split
|
|
16
|
-
* that is the reason these components are shaped the way they are.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
describe('the OAuth grant status filter', () => {
|
|
20
|
-
/**
|
|
21
|
-
* `?status=` takes the grant statuses plus `all`, and the query schema spells all four out rather
|
|
22
|
-
* than composing them, because a union would reach the document as an `anyOf` of a `$ref` and a
|
|
23
|
-
* `const` once the parameter is expanded. That leaves two lists that have to agree, so this is
|
|
24
|
-
* the assertion that they do: adding a fourth grant status without adding it to the filter would
|
|
25
|
-
* publish a status the caller cannot filter for.
|
|
26
|
-
*/
|
|
27
|
-
it('offers every grant status, plus the all sentinel', () => {
|
|
28
|
-
expect(statusEnumOf('ListOAuthGrantsQuery')).toEqual([...OAuthGrantStatusSchema.options, 'all']);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('is the same list on the bulk revoke body as on the listing query', () => {
|
|
32
|
-
// Revoking in bulk selects grants exactly the way listing them does — a filter that meant
|
|
33
|
-
// something different from the preview the caller looked at is the failure mode here.
|
|
34
|
-
expect(statusEnumOf('BulkRevokeOAuthGrantsPayload')).toEqual(statusEnumOf('ListOAuthGrantsQuery'));
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('rejects a status that is neither, rather than listing everything', () => {
|
|
38
|
-
expect(validateApiRequest('BulkRevokeOAuthGrantsPayload', { status: 'pending' }).valid).toBe(false);
|
|
39
|
-
expect(validateApiRequest('BulkRevokeOAuthGrantsPayload', { status: 'all' }).valid).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('the OAuth provider secret boundary', () => {
|
|
44
|
-
it('accepts a client secret on create', () => {
|
|
45
|
-
expect(
|
|
46
|
-
validateApiRequest('CreateOAuthProviderPayload', {
|
|
47
|
-
name: 'acme',
|
|
48
|
-
display_name: 'Acme',
|
|
49
|
-
client_id: 'client-123',
|
|
50
|
-
client_secret: 'shhh',
|
|
51
|
-
}),
|
|
52
|
-
).toMatchObject({ valid: true });
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('refuses to publish one on the way back out', () => {
|
|
56
|
-
// The read shape says whether a secret exists and nothing more. `additionalProperties: false`
|
|
57
|
-
// is what turns a mapper that spread the document into a failed response instead of a leak.
|
|
58
|
-
const response = {
|
|
59
|
-
id: '68b1779130afe5403a1589ba',
|
|
60
|
-
name: 'acme',
|
|
61
|
-
display_name: 'Acme',
|
|
62
|
-
project: '69d4762f24d3048c99149d0b',
|
|
63
|
-
client_id: 'client-123',
|
|
64
|
-
has_client_secret: true,
|
|
65
|
-
use_pkce: true,
|
|
66
|
-
created_at: '2026-01-01T00:00:00.000Z',
|
|
67
|
-
updated_at: '2026-01-01T00:00:00.000Z',
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
expect(validateApiResponse('OAuthProvider', response)).toMatchObject({ valid: true });
|
|
71
|
-
expect(validateApiResponse('OAuthProvider', { ...response, client_secret: 'shhh' }).valid).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('publishes the client secret on the create response only', () => {
|
|
75
|
-
// The one call that generates it returns it once; every later read of the same client gets
|
|
76
|
-
// `client_secret_configured`. Two components rather than one optional field is what keeps
|
|
77
|
-
// that visible in the document.
|
|
78
|
-
const client = {
|
|
79
|
-
client_id: 'vts_abc',
|
|
80
|
-
client_name: 'Acme CLI',
|
|
81
|
-
client_type: 'public',
|
|
82
|
-
redirect_uris: ['https://acme.example/callback'],
|
|
83
|
-
grant_types: ['authorization_code'],
|
|
84
|
-
response_types: ['code'],
|
|
85
|
-
token_endpoint_auth_method: 'none',
|
|
86
|
-
allowed_scopes: ['openid'],
|
|
87
|
-
registration_source: 'admin',
|
|
88
|
-
status: 'active',
|
|
89
|
-
project_binding_mode: 'user_select',
|
|
90
|
-
created_at: '2026-01-01T00:00:00.000Z',
|
|
91
|
-
updated_at: '2026-01-01T00:00:00.000Z',
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
expect(validateApiResponse('OAuthClientCreateResponse', { ...client, client_secret: 'vss_x' })).toMatchObject({
|
|
95
|
-
valid: true,
|
|
96
|
-
});
|
|
97
|
-
expect(validateApiResponse('OAuthClient', { ...client, client_secret: 'vss_x' }).valid).toBe(false);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('UpdateOAuthProviderPayload', () => {
|
|
102
|
-
it('accepts an empty body and a single-field body, and still rejects a typo', () => {
|
|
103
|
-
expect(validateApiRequest('UpdateOAuthProviderPayload', {}).valid).toBe(true);
|
|
104
|
-
expect(validateApiRequest('UpdateOAuthProviderPayload', { display_name: 'Acme' }).valid).toBe(true);
|
|
105
|
-
expect(validateApiRequest('UpdateOAuthProviderPayload', { displayName: 'Acme' }).valid).toBe(false);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('OAuth protocol response parsing', () => {
|
|
110
|
-
it('parses complete authorization-server metadata and ignores registered extensions', () => {
|
|
111
|
-
const result = OAuthAuthorizationServerMetadataSchema.safeParse({
|
|
112
|
-
issuer: 'https://sts.example.com',
|
|
113
|
-
authorization_endpoint: 'https://cloud.example.com/oauth/authorize',
|
|
114
|
-
token_endpoint: 'https://sts.example.com/oauth/token',
|
|
115
|
-
jwks_uri: 'https://sts.example.com/.well-known/jwks.json',
|
|
116
|
-
response_types_supported: ['code'],
|
|
117
|
-
grant_types_supported: ['authorization_code'],
|
|
118
|
-
code_challenge_methods_supported: ['S256'],
|
|
119
|
-
token_endpoint_auth_methods_supported: ['none'],
|
|
120
|
-
scopes_supported: ['openid'],
|
|
121
|
-
extension_parameter: 'ignored',
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
expect(result.success).toBe(true);
|
|
125
|
-
if (result.success) {
|
|
126
|
-
expect(result.data).not.toHaveProperty('extension_parameter');
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('rejects incomplete device and token responses', () => {
|
|
131
|
-
expect(OAuthDeviceAuthorizationResponseSchema.safeParse({ device_code: 'device' }).success).toBe(false);
|
|
132
|
-
expect(
|
|
133
|
-
OAuthTokenResponseSchema.safeParse({
|
|
134
|
-
access_token: 'token',
|
|
135
|
-
token_type: 'Basic',
|
|
136
|
-
expires_in: 3600,
|
|
137
|
-
}).success,
|
|
138
|
-
).toBe(false);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('accepts RFC 6749 token responses with an omitted scope and case-insensitive bearer type', () => {
|
|
142
|
-
expect(
|
|
143
|
-
OAuthTokenResponseSchema.safeParse({
|
|
144
|
-
access_token: 'token',
|
|
145
|
-
token_type: 'bearer',
|
|
146
|
-
expires_in: 3600,
|
|
147
|
-
}).success,
|
|
148
|
-
).toBe(true);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('accepts OAuth token response extensions without widening the inferred contract', () => {
|
|
152
|
-
const result = OAuthTokenResponseSchema.safeParse({
|
|
153
|
-
access_token: 'token',
|
|
154
|
-
token_type: 'Bearer',
|
|
155
|
-
expires_in: 3600,
|
|
156
|
-
scope: 'openid',
|
|
157
|
-
extension_parameter: true,
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
expect(result.success).toBe(true);
|
|
161
|
-
if (result.success) {
|
|
162
|
-
expect(result.data).not.toHaveProperty('extension_parameter');
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* A component's `status` enum, as the registry emits it.
|
|
169
|
-
*
|
|
170
|
-
* Read off the component rather than off a generated document: generating one needs the scanner,
|
|
171
|
-
* which is a package this one does not depend on. The expansion of a query component into parameters
|
|
172
|
-
* inlines exactly this, and has its own tests in `parameters.test.ts`.
|
|
173
|
-
*/
|
|
174
|
-
function statusEnumOf(component: 'ListOAuthGrantsQuery' | 'BulkRevokeOAuthGrantsPayload'): string[] {
|
|
175
|
-
const emitted = ApiSchemaComponents[component] as { properties?: Record<string, { enum?: unknown }> } | undefined;
|
|
176
|
-
const status = emitted?.properties?.status;
|
|
177
|
-
if (!Array.isArray(status?.enum)) {
|
|
178
|
-
throw new Error(`${component} does not publish status as an inline enum`);
|
|
179
|
-
}
|
|
180
|
-
return status.enum as string[];
|
|
181
|
-
}
|
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { type JsonObject, toOpenApiComponents } from './adapter.js';
|
|
3
|
-
import { normalizeParameters } from './parameters.js';
|
|
4
|
-
import { ApiSchemaComponents, normalizeApiParameters, validateApiRequest } from './registry.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Fixtures are LOCAL rather than added to the registry, on purpose. `ApiSchemaComponents` is what the
|
|
8
|
-
* OpenAPI document publishes, so registering a component to exercise a coercion rule would put it in
|
|
9
|
-
* the spec. Everything below drives the pure normalizer against hand-built components; the two tests
|
|
10
|
-
* at the bottom then bind to the real registry to prove the published component is the one used.
|
|
11
|
-
*/
|
|
12
|
-
const COMPONENTS: Record<string, JsonObject> = toOpenApiComponents(
|
|
13
|
-
{
|
|
14
|
-
Params: {
|
|
15
|
-
type: 'object',
|
|
16
|
-
properties: {
|
|
17
|
-
name: { type: 'string' },
|
|
18
|
-
limit: { type: 'integer' },
|
|
19
|
-
ratio: { type: 'number' },
|
|
20
|
-
enabled: { type: 'boolean' },
|
|
21
|
-
level: { type: 'string', enum: ['account', 'project'] },
|
|
22
|
-
tags: { type: 'array', items: { type: 'string' } },
|
|
23
|
-
ids: { type: 'array', items: { type: 'integer' } },
|
|
24
|
-
// What zod emits for `z.union([z.string(), z.array(z.string())])`, and what the scanner
|
|
25
|
-
// publishes as a single `type: array` parameter with `explode: true`.
|
|
26
|
-
single_or_many: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }] },
|
|
27
|
-
statuses: { type: 'array', items: { $ref: '#/components/schemas/Status' } },
|
|
28
|
-
// The same union shape as `single_or_many`, over an enum: what `ListTasksQuery`
|
|
29
|
-
// declares for `status`.
|
|
30
|
-
status_or_many: {
|
|
31
|
-
anyOf: [
|
|
32
|
-
{ $ref: '#/components/schemas/Status' },
|
|
33
|
-
{ type: 'array', items: { $ref: '#/components/schemas/Status' } },
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
// An enum with a comma inside a member, which is what makes splitting ambiguous.
|
|
37
|
-
separators: { type: 'array', items: { type: 'string', enum: ['a,b', 'c'] } },
|
|
38
|
-
nullable_count: { anyOf: [{ type: 'integer' }, { type: 'null' }] },
|
|
39
|
-
listed_type: { type: ['integer', 'null'] },
|
|
40
|
-
freeform: {},
|
|
41
|
-
status: { $ref: '#/components/schemas/Status' },
|
|
42
|
-
},
|
|
43
|
-
required: ['name'],
|
|
44
|
-
},
|
|
45
|
-
Status: { type: 'string', enum: ['on', 'off'] },
|
|
46
|
-
Headers: {
|
|
47
|
-
type: 'object',
|
|
48
|
-
properties: { 'X-Request-Id': { type: 'string' }, 'x-retry-count': { type: 'integer' } },
|
|
49
|
-
},
|
|
50
|
-
NotAnObject: { type: 'string' },
|
|
51
|
-
},
|
|
52
|
-
{},
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
function normalize(raw: Record<string, string | string[] | undefined>, component = 'Params') {
|
|
56
|
-
return normalizeParameters(component, raw, 'query', COMPONENTS[component], COMPONENTS);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
describe('required and optional values', () => {
|
|
60
|
-
it('omits a parameter that did not arrive, leaving requiredness to the component', () => {
|
|
61
|
-
// The distinction that matters: omitted, not present-as-undefined. Validating `undefined`
|
|
62
|
-
// against `type: string` would report a type error where the contract says "absent".
|
|
63
|
-
expect(normalize({ name: 'acme' }).value).toEqual({ name: 'acme' });
|
|
64
|
-
expect('limit' in normalize({ name: 'acme' }).value).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('treats an explicitly undefined transport value as absence too', () => {
|
|
68
|
-
// `ctx.headers` exposes this shape for a header that was not sent.
|
|
69
|
-
expect(normalize({ name: 'acme', limit: undefined }).value).toEqual({ name: 'acme' });
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('produces an empty object when every declared parameter is optional and none arrived', () => {
|
|
73
|
-
expect(normalizeParameters('Headers', {}, 'query', COMPONENTS.Headers, COMPONENTS).value).toEqual({});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('leaves a missing required parameter for the component to reject, rather than inventing a message', () => {
|
|
77
|
-
// The normalizer never rejects: it omits `name` and returns. That the omission is an error is
|
|
78
|
-
// the component's own statement, which keeps one contract in charge of requiredness.
|
|
79
|
-
expect(() => normalize({ limit: '5' })).not.toThrow();
|
|
80
|
-
expect(normalize({ limit: '5' }).value).toEqual({ limit: 5 });
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('keeps an empty string as a value for a string parameter', () => {
|
|
84
|
-
// `?name=` is a present, empty value. A `type: string` can hold it, so it passes through and
|
|
85
|
-
// the component decides — inventing absence here would let the server read a URL the schema
|
|
86
|
-
// does not describe.
|
|
87
|
-
expect(normalize({ name: '' }).value).toEqual({ name: '' });
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe('scalar coercion', () => {
|
|
92
|
-
it.each([
|
|
93
|
-
['integer', 'limit', '25', 25],
|
|
94
|
-
['integer with exponent', 'limit', '1e3', 1000],
|
|
95
|
-
['integer, zero-padded', 'limit', '007', 7],
|
|
96
|
-
['negative integer', 'limit', '-3', -3],
|
|
97
|
-
['number', 'ratio', '1.5', 1.5],
|
|
98
|
-
['number, leading dot', 'ratio', '.5', 0.5],
|
|
99
|
-
['number, explicit plus', 'ratio', '+2', 2],
|
|
100
|
-
['boolean true', 'enabled', 'true', true],
|
|
101
|
-
['boolean false', 'enabled', 'false', false],
|
|
102
|
-
['boolean spelled as 1', 'enabled', '1', true],
|
|
103
|
-
['boolean spelled as 0', 'enabled', '0', false],
|
|
104
|
-
['string is untouched', 'name', '25', '25'],
|
|
105
|
-
['enum is untouched', 'level', 'account', 'account'],
|
|
106
|
-
['a schema with no type is untouched', 'freeform', 'anything', 'anything'],
|
|
107
|
-
])('coerces %s', (_label, key, raw, expected) => {
|
|
108
|
-
expect(normalize({ [key]: raw }).value[key]).toBe(expected);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('coerces through a $ref to a hoisted enum', () => {
|
|
112
|
-
// Reached through the adapter's own reference resolution, so what is coerced towards and what
|
|
113
|
-
// the spec publishes are read off the same node.
|
|
114
|
-
expect(normalize({ status: 'on' }).value.status).toBe('on');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it.each([
|
|
118
|
-
['a nullable integer union', 'nullable_count'],
|
|
119
|
-
['a type list containing null', 'listed_type'],
|
|
120
|
-
])('coerces %s as its non-null type', (_label, key) => {
|
|
121
|
-
expect(normalize({ [key]: '4' }).value[key]).toBe(4);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('invalid coercion is left for the component to report', () => {
|
|
126
|
-
it.each([
|
|
127
|
-
['non-numeric text', 'limit', 'abc'],
|
|
128
|
-
['an empty value for a number', 'limit', ''],
|
|
129
|
-
['whitespace around a number', 'limit', ' 1'],
|
|
130
|
-
['hex', 'limit', '0x10'],
|
|
131
|
-
['Infinity', 'ratio', 'Infinity'],
|
|
132
|
-
['NaN', 'ratio', 'NaN'],
|
|
133
|
-
['a trailing exponent', 'limit', '1e'],
|
|
134
|
-
['a boolean spelled as yes', 'enabled', 'yes'],
|
|
135
|
-
['an empty value for a boolean', 'enabled', ''],
|
|
136
|
-
['a capitalised boolean', 'enabled', 'True'],
|
|
137
|
-
])('leaves %s as the original text', (_label, key, raw) => {
|
|
138
|
-
const { value } = normalize({ name: 'acme', [key]: raw });
|
|
139
|
-
// Left as text rather than coerced to NaN or a guess: the component then reports a type error
|
|
140
|
-
// naming this parameter, which is a better message than anything invented here.
|
|
141
|
-
expect(value[key]).toBe(raw);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('does not truncate a non-integral value for an integer parameter', () => {
|
|
145
|
-
// Coerced to a number and handed on, so `type: integer` rejects it. `parseInt` would have
|
|
146
|
-
// silently produced 1 and accepted a request the document says is invalid.
|
|
147
|
-
expect(normalize({ name: 'a', limit: '1.5' }).value.limit).toBe(1.5);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
describe('arrays and repeated values', () => {
|
|
152
|
-
it('wraps a single occurrence, matching the published explode: true', () => {
|
|
153
|
-
expect(normalize({ name: 'a', tags: 'x' }).value.tags).toEqual(['x']);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('collects repeated occurrences in arrival order', () => {
|
|
157
|
-
expect(normalize({ name: 'a', tags: ['x', 'y'] }).value.tags).toEqual(['x', 'y']);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('coerces each item', () => {
|
|
161
|
-
expect(normalize({ name: 'a', ids: ['1', '2'] }).value.ids).toEqual([1, 2]);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('leaves an uncoercible item in place rather than dropping it', () => {
|
|
165
|
-
// Dropping would silently shorten the array; the component reports the bad item by index.
|
|
166
|
-
expect(normalize({ name: 'a', ids: ['1', 'x'] }).value.ids).toEqual([1, 'x']);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('reads a string-or-array union as an array, the shape the spec publishes', () => {
|
|
170
|
-
// The scanner collapses this union into one `type: array` parameter with `explode: true`, so
|
|
171
|
-
// reading the scalar variant would hand the handler a bare string where the spec promised an
|
|
172
|
-
// array.
|
|
173
|
-
expect(normalize({ name: 'a', single_or_many: 'x' }).value.single_or_many).toEqual(['x']);
|
|
174
|
-
expect(normalize({ name: 'a', single_or_many: ['x', 'y'] }).value.single_or_many).toEqual(['x', 'y']);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('hands repeated occurrences of a single-valued parameter on as an array, so the component rejects them', () => {
|
|
178
|
-
// `?level=account&level=project` is ambiguous and nothing published says which wins. Passing the
|
|
179
|
-
// array through makes the component report a type error instead of the server silently picking
|
|
180
|
-
// one — which is what several hand-written handlers do today.
|
|
181
|
-
expect(normalize({ name: 'a', level: ['account', 'project'] }).value.level).toEqual(['account', 'project']);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Comma-splitting applies to every array parameter, not only to enums.
|
|
187
|
-
*
|
|
188
|
-
* `explode: true` — what the document publishes — describes repeated keys and says nothing about
|
|
189
|
-
* commas, so this is something the server accepts BEYOND what it promises. It does so because
|
|
190
|
-
* `@vertesia/client` joins every array parameter with `,`, which means every SDK in the field sends
|
|
191
|
-
* the comma form, and because it is what all the hand-written readers this layer replaced already
|
|
192
|
-
* did: `readArray`, `parseTagsFilter`, `parseSourcesQuery`, and the agent-run search's `sort`,
|
|
193
|
-
* `tags` and `categories`.
|
|
194
|
-
*/
|
|
195
|
-
describe('comma-joined values, for an array parameter', () => {
|
|
196
|
-
it('splits a plain string array, which is what most filters are', () => {
|
|
197
|
-
// The regression this pins: with splitting limited to enums, `@vertesia/client`'s
|
|
198
|
-
// `sort: ['lol:sideways', 'started_at:desc']` arrived as ONE clause `lol:sideways,started_at:desc`,
|
|
199
|
-
// so the agent-run search sorted on an unmapped field and returned an arbitrary order.
|
|
200
|
-
expect(normalize({ name: 'a', tags: 'x,y' }).value.tags).toEqual(['x', 'y']);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('splits one occurrence into several values', () => {
|
|
204
|
-
expect(normalize({ name: 'a', statuses: 'on,off' }).value.statuses).toEqual(['on', 'off']);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('splits through the single-or-many union, which is how ListTasksQuery declares status', () => {
|
|
208
|
-
expect(normalize({ name: 'a', status_or_many: 'on,off' }).value.status_or_many).toEqual(['on', 'off']);
|
|
209
|
-
expect(normalize({ name: 'a', status_or_many: 'on' }).value.status_or_many).toEqual(['on']);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('composes with repetition rather than replacing it', () => {
|
|
213
|
-
// Both spellings mean the same list, so mixing them is not a third case to decide.
|
|
214
|
-
expect(normalize({ name: 'a', statuses: ['on,off', 'on'] }).value.statuses).toEqual(['on', 'off', 'on']);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('leaves an unknown member in place for the enum to reject', () => {
|
|
218
|
-
// Not dropped: the component reports `bogus` by index, so the caller learns which value was
|
|
219
|
-
// wrong rather than getting a silently shorter filter.
|
|
220
|
-
expect(normalize({ name: 'a', statuses: 'on,bogus' }).value.statuses).toEqual(['on', 'bogus']);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('cannot express a value that contains a comma — the cost of the rule above', () => {
|
|
224
|
-
// `'a,b'` is a declared member of this enum, and it is now unreachable: the caller has no way
|
|
225
|
-
// to escape the separator. Pinned rather than fixed because it is not a new limit — every
|
|
226
|
-
// reader this layer replaced split unconditionally — and because no published parameter has
|
|
227
|
-
// a comma-bearing member today. It is the reason a parameter whose values may legitimately
|
|
228
|
-
// contain commas must not be declared as an array of plain strings.
|
|
229
|
-
expect(normalize({ name: 'a', separators: 'a,b' }).value.separators).toEqual(['a', 'b']);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
describe('undeclared parameters', () => {
|
|
234
|
-
it('reports undeclared query keys without removing or rejecting anything', () => {
|
|
235
|
-
const { value, undeclared } = normalize({ name: 'a', sort: 'asc', page: '2' });
|
|
236
|
-
expect(undeclared).toEqual(['sort', 'page']);
|
|
237
|
-
// Not in the validated copy, so the component never sees them: whether they are an error is
|
|
238
|
-
// the endpoint's policy, not the component's.
|
|
239
|
-
expect(value).toEqual({ name: 'a' });
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('reports nothing when every key is declared', () => {
|
|
243
|
-
expect(normalize({ name: 'a', limit: '1' }).undeclared).toEqual([]);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('does not report a declared key that arrived as undefined', () => {
|
|
247
|
-
expect(normalize({ name: 'a', limit: undefined }).undeclared).toEqual([]);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('never reports undeclared headers, however many arrive', () => {
|
|
251
|
-
// Structural, not a policy: every request carries headers no endpoint declares, so a caller
|
|
252
|
-
// that rejected on this list could only ever reject correct requests.
|
|
253
|
-
const { undeclared } = normalizeParameters(
|
|
254
|
-
'Headers',
|
|
255
|
-
{ accept: '*/*', 'user-agent': 'curl/8', 'x-datadog-trace-id': '1', authorization: 'Bearer x' },
|
|
256
|
-
'header',
|
|
257
|
-
COMPONENTS.Headers,
|
|
258
|
-
COMPONENTS,
|
|
259
|
-
);
|
|
260
|
-
expect(undeclared).toEqual([]);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
describe('header name casing', () => {
|
|
265
|
-
function headers(raw: Record<string, string | string[] | undefined>) {
|
|
266
|
-
return normalizeParameters('Headers', raw, 'header', COMPONENTS.Headers, COMPONENTS);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
it('matches a declared mixed-case name against the lowercased name Node delivers', () => {
|
|
270
|
-
// HTTP field names are case-insensitive and Node has already lowercased them, so a component
|
|
271
|
-
// spelling `X-Request-Id` would never match a real request without this.
|
|
272
|
-
expect(headers({ 'x-request-id': 'abc' }).value).toEqual({ 'X-Request-Id': 'abc' });
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('keys the copy by the declared spelling, which is what the component validates', () => {
|
|
276
|
-
expect(Object.keys(headers({ 'x-request-id': 'abc' }).value)).toEqual(['X-Request-Id']);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('coerces a lowercase-declared header too', () => {
|
|
280
|
-
expect(headers({ 'x-retry-count': '2' }).value['x-retry-count']).toBe(2);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('does not case-fold query names, where case is significant', () => {
|
|
284
|
-
// Query parameter names are octet-exact; folding them would make `?Name=` and `?name=` the
|
|
285
|
-
// same parameter, which no part of the document says.
|
|
286
|
-
const { value, undeclared } = normalize({ Name: 'a' });
|
|
287
|
-
expect(value).toEqual({});
|
|
288
|
-
expect(undeclared).toEqual(['Name']);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
describe('the request is never mutated', () => {
|
|
293
|
-
it('leaves the raw object untouched, including values it coerced', () => {
|
|
294
|
-
// Koa memoizes `ctx.query` per query string and hands the same object to every later reader,
|
|
295
|
-
// so coercing in place would retype values for middleware that never opted in.
|
|
296
|
-
const raw = { name: 'acme', limit: '25', enabled: 'true', tags: 'x' };
|
|
297
|
-
const snapshot = structuredClone(raw);
|
|
298
|
-
const { value } = normalize(raw);
|
|
299
|
-
expect(raw).toEqual(snapshot);
|
|
300
|
-
expect(value).not.toBe(raw);
|
|
301
|
-
expect(value.limit).toBe(25);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it('does not alias an array value into the copy', () => {
|
|
305
|
-
const tags = ['x', 'y'];
|
|
306
|
-
const { value } = normalize({ name: 'a', tags });
|
|
307
|
-
expect(value.tags).not.toBe(tags);
|
|
308
|
-
expect(tags).toEqual(['x', 'y']);
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
describe('declaration errors', () => {
|
|
313
|
-
it('refuses a component that is not an object schema', () => {
|
|
314
|
-
// Cannot be expanded into named parameters, and the scanner would not publish one either.
|
|
315
|
-
// Failing is the only honest option: reaching the handler with an unchecked query while the
|
|
316
|
-
// spec advertises a checked one is the drift this whole arrangement exists to prevent.
|
|
317
|
-
expect(() => normalize({}, 'NotAnObject')).toThrow(/not an object schema with properties/);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it('refuses a component that does not exist', () => {
|
|
321
|
-
expect(() => normalizeParameters('Missing', {}, 'query', undefined, COMPONENTS)).toThrow(
|
|
322
|
-
/not an object schema with properties/,
|
|
323
|
-
);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe('inherited members are not read as parameters', () => {
|
|
328
|
-
it('ignores a prototype member with a declared name', () => {
|
|
329
|
-
// A `hasOwn` check rather than a truthiness test: without it a component declaring `toString`
|
|
330
|
-
// would receive a function.
|
|
331
|
-
const components: Record<string, JsonObject> = {
|
|
332
|
-
Proto: { type: 'object', properties: { toString: { type: 'string' } } },
|
|
333
|
-
};
|
|
334
|
-
expect(normalizeParameters('Proto', {}, 'query', components.Proto, components).value).toEqual({});
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
describe('bound to the published registry', () => {
|
|
339
|
-
it('normalizes against the same component object the document publishes', () => {
|
|
340
|
-
// The property this slice exists for: one declaration drives the published parameter and the
|
|
341
|
-
// runtime check. `ApiKeyListQuery` is the component `ListApiKeys` declares.
|
|
342
|
-
expect(normalizeApiParameters('ApiKeyListQuery', { level: 'account' }, 'query').value).toEqual({
|
|
343
|
-
level: 'account',
|
|
344
|
-
});
|
|
345
|
-
expect(ApiSchemaComponents.ApiKeyListQuery.properties).toMatchObject({
|
|
346
|
-
level: { enum: ['account', 'project'] },
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it('produces a copy the registry validator accepts, and rejects a value outside the enum', () => {
|
|
351
|
-
// End to end through the real validators, which is what the enforcer runs.
|
|
352
|
-
const good = normalizeApiParameters('ApiKeyListQuery', { level: 'project' }, 'query');
|
|
353
|
-
expect(validateApiRequest('ApiKeyListQuery', good.value)).toMatchObject({ valid: true });
|
|
354
|
-
|
|
355
|
-
const bad = normalizeApiParameters('ApiKeyListQuery', { level: 'bogus' }, 'query');
|
|
356
|
-
expect(validateApiRequest('ApiKeyListQuery', bad.value)).toMatchObject({ valid: false });
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it('accepts an absent optional parameter', () => {
|
|
360
|
-
const empty = normalizeApiParameters('ApiKeyListQuery', {}, 'query');
|
|
361
|
-
expect(empty.value).toEqual({});
|
|
362
|
-
expect(validateApiRequest('ApiKeyListQuery', empty.value)).toMatchObject({ valid: true });
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* `GET /tasks` is the endpoint the enum rule exists for. `TaskApi.list` builds its status
|
|
367
|
-
* parameter as `query.status.join(',')`, so every SDK in the field sends the comma form; these
|
|
368
|
-
* assertions are what stops enforcement from turning our own client's multi-status listing into
|
|
369
|
-
* a 400.
|
|
370
|
-
*/
|
|
371
|
-
it('accepts every spelling of the ListTasksQuery status parameter', () => {
|
|
372
|
-
for (const raw of ['pending,completed', ['pending', 'completed'], ['pending,completed']]) {
|
|
373
|
-
const { value } = normalizeApiParameters('ListTasksQuery', { status: raw }, 'query');
|
|
374
|
-
expect(value.status).toEqual(['pending', 'completed']);
|
|
375
|
-
expect(validateApiRequest('ListTasksQuery', value)).toMatchObject({ valid: true });
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it('coerces the ListTasksQuery paging parameters, and rejects text that is not a number', () => {
|
|
380
|
-
const good = normalizeApiParameters('ListTasksQuery', { limit: '25', offset: '50' }, 'query');
|
|
381
|
-
expect(good.value).toEqual({ limit: 25, offset: 50 });
|
|
382
|
-
expect(validateApiRequest('ListTasksQuery', good.value)).toMatchObject({ valid: true });
|
|
383
|
-
|
|
384
|
-
// `parseInt('abc')` reached Mongo as `NaN` before this, which disabled the page size cap.
|
|
385
|
-
const bad = normalizeApiParameters('ListTasksQuery', { limit: 'abc' }, 'query');
|
|
386
|
-
expect(validateApiRequest('ListTasksQuery', bad.value)).toMatchObject({ valid: false });
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it('rejects a status outside the published enum', () => {
|
|
390
|
-
const { value } = normalizeApiParameters('ListTasksQuery', { status: 'pending,bogus' }, 'query');
|
|
391
|
-
expect(validateApiRequest('ListTasksQuery', value)).toMatchObject({ valid: false });
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('leaves an undeclared ListTasksQuery parameter out of the validated copy rather than rejecting', () => {
|
|
395
|
-
// `GET /tasks` does not set `rejectUndeclaredQuery`, so a stale `?sort=` keeps working. The
|
|
396
|
-
// undeclared list is reported for the endpoint to act on; the component never sees the key.
|
|
397
|
-
const { value, undeclared } = normalizeApiParameters('ListTasksQuery', { sort: 'asc' }, 'query');
|
|
398
|
-
expect(undeclared).toEqual(['sort']);
|
|
399
|
-
expect(value).toEqual({});
|
|
400
|
-
expect(validateApiRequest('ListTasksQuery', value)).toMatchObject({ valid: true });
|
|
401
|
-
});
|
|
402
|
-
});
|