@ionite/ecv 0.0.23-beta.20260729.1

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.
@@ -0,0 +1,1035 @@
1
+ import { ChildProcess } from "node:child_process";
2
+ /**
3
+ * Options controlling how readiness is polled.
4
+ */
5
+ interface WaitForReadyOptions {
6
+ /**
7
+ * Readiness path appended to the base URL. When omitted it is derived from {@link basePath}.
8
+ * @default "`${basePath}/readyz`" (i.e. `/api/ionite/readyz`)
9
+ */
10
+ readyPath?: string;
11
+ /**
12
+ * Base path the ionite app mounts the SDK handler under, used to derive {@link readyPath} when it is
13
+ * not given (ionite apps expose `${basePath}/readyz`). Ignored when `readyPath` is set explicitly.
14
+ * @default "/api/ionite"
15
+ */
16
+ basePath?: string;
17
+ /**
18
+ * Optional liveness path checked before readiness (e.g. "/api/livez").
19
+ * When set, an attempt only succeeds if liveness is OK and readiness is OK.
20
+ */
21
+ livenessPath?: string;
22
+ /**
23
+ * Maximum number of polling attempts.
24
+ * @default 30
25
+ */
26
+ maxRetries?: number;
27
+ /**
28
+ * Delay between attempts in milliseconds.
29
+ * @default 1000
30
+ */
31
+ retryDelayMs?: number;
32
+ /**
33
+ * Per-request abort timeout in milliseconds.
34
+ * @default 3000
35
+ */
36
+ requestTimeoutMs?: number;
37
+ }
38
+ /**
39
+ * Poll an application until its readiness endpoint responds OK.
40
+ */
41
+ declare function waitForReady(baseUrl: string, options?: WaitForReadyOptions): Promise<void>;
42
+ /**
43
+ * Options for {@link startAppUnderTest}.
44
+ */
45
+ interface StartAppOptions extends WaitForReadyOptions {
46
+ /** Executable to run (e.g. "bun"). */
47
+ command: string;
48
+ /** Arguments passed to the command (e.g. ["run", "api"]). */
49
+ args?: string[];
50
+ /** Working directory for the spawned process. */
51
+ cwd?: string;
52
+ /**
53
+ * Environment variables merged on top of `process.env`.
54
+ * Pass `ecv.env` here so the app connects to the ECV mock server.
55
+ */
56
+ env?: Record<string, string | undefined>;
57
+ /** Public base URL the app will be reachable at (used for readiness polling and returned to callers). */
58
+ baseUrl: string;
59
+ }
60
+ /**
61
+ * Handle for a spawned application under test.
62
+ */
63
+ interface AppUnderTest {
64
+ /** Base URL the app is reachable at. */
65
+ baseUrl: string;
66
+ /** The spawned child process. */
67
+ process: ChildProcess;
68
+ /** Stop the app (SIGTERM, then SIGKILL fallback if it is still running). */
69
+ stop(): Promise<void>;
70
+ }
71
+ /**
72
+ * Spawn an application under test, wait for it to become ready, and return a handle with a `stop()`.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const ecv = await startEcvServer();
77
+ * const app = await startAppUnderTest({
78
+ * command: 'bun',
79
+ * args: ['run', 'api'],
80
+ * env: { ...ecv.env, APP_BASE_URL: 'http://localhost:3544' },
81
+ * baseUrl: 'http://localhost:3544',
82
+ * // readyPath defaults to `${basePath}/readyz` (i.e. /api/ionite/readyz); override basePath if needed.
83
+ * });
84
+ * // ...run tests against app.baseUrl...
85
+ * await app.stop();
86
+ * ```
87
+ */
88
+ declare function startAppUnderTest(options: StartAppOptions): Promise<AppUnderTest>;
89
+ import { EnterpriseExtension, Ionite } from "@ionite/server";
90
+ declare function setVaultRedisUrl(url: string | undefined): void;
91
+ declare function getVaultRedisUrl(): string | undefined;
92
+ interface EcvServerHandle {
93
+ baseUrl: string;
94
+ port: number;
95
+ host: string;
96
+ env: {
97
+ ION_CONFIG_TYPE: "vault";
98
+ ION_VAULT_URL: string;
99
+ ION_VAULT_TOKEN: string;
100
+ ION_VAULT_PATH: string;
101
+ REDIS_URL?: string;
102
+ };
103
+ /**
104
+ * Narrow tenant config-locator pointer for seeding tenant rows / tenant-create requests. The root
105
+ * vault connection comes from `env` (ION_VAULT_*); a tenant only stores its config `path`.
106
+ */
107
+ configLocator: {
108
+ path: string;
109
+ };
110
+ }
111
+ /**
112
+ * Server configuration options
113
+ */
114
+ interface ServerOptions {
115
+ /**
116
+ * Port to listen on
117
+ * @default 3515
118
+ */
119
+ port?: number;
120
+ /**
121
+ * Host to bind to
122
+ * @default 'localhost'
123
+ */
124
+ host?: string;
125
+ /**
126
+ * Enable verbose logging
127
+ * @default false
128
+ */
129
+ verbose?: boolean;
130
+ /**
131
+ * Redis URL for vault secrets at `{ION_VAULT_PATH}/redis` (consumed by ionRedis).
132
+ * Falls back to `REDIS_URL` when omitted.
133
+ */
134
+ redisUrl?: string;
135
+ }
136
+ /**
137
+ * Start the ECV mock server
138
+ *
139
+ * @param options - Server configuration options
140
+ * @returns Promise that resolves with the server connection config when the server is listening
141
+ */
142
+ declare function startEcvServer(options?: ServerOptions): Promise<EcvServerHandle>;
143
+ /**
144
+ * Stop the ECV mock server
145
+ *
146
+ * @returns Promise that resolves when the server has stopped
147
+ */
148
+ declare function stopEcvServer(): Promise<void>;
149
+ /**
150
+ * ionite Validator Types
151
+ *
152
+ * These types define the configuration and result structures for
153
+ * ionite validation tests.
154
+ */
155
+ /**
156
+ * Test definition for Vitest-compatible test suites
157
+ */
158
+ interface TestDef {
159
+ name: string;
160
+ fn: () => Promise<void>;
161
+ }
162
+ /**
163
+ * Base configuration for all validation suites
164
+ */
165
+ interface ValidationConfig {
166
+ /**
167
+ * Base URL of the application under test.
168
+ * May be a getter so Vitest suites can resolve the URL after `beforeAll`.
169
+ * @example "http://localhost:3000"
170
+ */
171
+ baseUrl: string | (() => string);
172
+ /**
173
+ * Optional timeout for requests in milliseconds
174
+ * @default 5000
175
+ */
176
+ timeout?: number;
177
+ /**
178
+ * Whether to skip TLS certificate verification (for local testing)
179
+ * @default false
180
+ */
181
+ skipTlsVerify?: boolean;
182
+ /**
183
+ * Optional custom headers to include in all requests.
184
+ * May be a function (or async function) so that headers are resolved when the request runs
185
+ * (e.g. for workload auth token that is obtained in beforeAll).
186
+ */
187
+ headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
188
+ }
189
+ /**
190
+ * SSO Validation Configuration
191
+ */
192
+ interface SSOValidationConfig extends ValidationConfig {
193
+ /**
194
+ * Path to the login endpoint
195
+ * @default "/api/auth/login"
196
+ */
197
+ loginPath?: string;
198
+ /**
199
+ * Path to the callback endpoint (where the IdP redirects after auth)
200
+ * @default "/api/auth/callback"
201
+ */
202
+ callbackPath?: string;
203
+ /**
204
+ * Path to the user info endpoint
205
+ * @default "/api/auth/user"
206
+ */
207
+ userPath?: string;
208
+ /**
209
+ * Path to the logout endpoint
210
+ * @default "/api/auth/logout"
211
+ */
212
+ logoutPath?: string;
213
+ /**
214
+ * Path to the back-channel logout endpoint
215
+ * @default "/api/auth/logout/backchannel"
216
+ */
217
+ backChannelLogoutPath?: string;
218
+ /**
219
+ * Path to the token endpoint
220
+ * @default "/api/auth/token"
221
+ */
222
+ tokenPath?: string;
223
+ /**
224
+ * Path to the refresh endpoint
225
+ * @default "/api/auth/refresh"
226
+ */
227
+ refreshPath?: string;
228
+ /**
229
+ * Expected redirect location pattern for login
230
+ * Should match the IdP authorization URL
231
+ */
232
+ expectedAuthorizationUrlPattern?: RegExp;
233
+ /**
234
+ * Optional opt-in best-practice assertion: when set, a FAILED callback's `Location` header must
235
+ * match this pattern. Apps following the recommended pattern redirect login failures to an
236
+ * unauthenticated-reachable page carrying a generic error code (e.g. `/[?&]auth_error=login_failed/`).
237
+ * When unset, the failure path is only asserted to be a same-origin 302 (the SDK contract).
238
+ */
239
+ expectedCallbackErrorPattern?: RegExp;
240
+ /**
241
+ * Test user credentials for end-to-end SSO testing
242
+ * If not provided, only structural tests will run
243
+ */
244
+ testCredentials?: {
245
+ username: string;
246
+ password: string;
247
+ };
248
+ /**
249
+ * Expected fields returned by the authenticated user endpoint after a successful SSO flow.
250
+ * Useful for validating userStore.lookup-enriched profile data while preserving JWT-owned auth fields.
251
+ */
252
+ expectedUser?: {
253
+ id?: string;
254
+ userName?: string;
255
+ name?: string;
256
+ email?: string;
257
+ avatar?: string;
258
+ requireSso?: boolean;
259
+ };
260
+ }
261
+ /**
262
+ * CIAM Validation Configuration
263
+ *
264
+ * Validates the customer (CIAM) domain route contracts that are distinct from
265
+ * the workforce (SSO) domain: the customer info endpoint and magic-link issuance.
266
+ */
267
+ interface CIAMValidationConfig extends ValidationConfig {
268
+ /**
269
+ * Path to the customer info endpoint (CIAM equivalent of the SSO `userPath`).
270
+ * @default "/api/ionite/auth/customer"
271
+ */
272
+ customerPath?: string;
273
+ /**
274
+ * Path to the magic-link generation endpoint (workload-authenticated POST).
275
+ * @default "/api/ionite/magic-link"
276
+ */
277
+ magicLinkPath?: string;
278
+ /**
279
+ * Path to the magic-link login endpoint (GET with token).
280
+ * @default "/api/ionite/magic-link/login"
281
+ */
282
+ magicLinkLoginPath?: string;
283
+ /**
284
+ * Path to the CIAM logout endpoint (distinct from the SSO logout path).
285
+ * @default "/api/ionite/auth/customer/logout"
286
+ */
287
+ logoutPath?: string;
288
+ /**
289
+ * Former CIAM provider-initiated back-channel logout path. The endpoint has been
290
+ * removed (AUDIT_HISTORY.md §1.6); the validation suite probes this path to confirm it now
291
+ * returns 404. Customer logout is first-party / local session invalidation only.
292
+ * @default "/api/ionite/auth/customer/logout/backchannel"
293
+ */
294
+ backChannelLogoutPath?: string;
295
+ /**
296
+ * Path to the sanitized third-party provider discovery endpoint (GET).
297
+ * @default "/api/ionite/auth/customer/providers"
298
+ */
299
+ customerProvidersPath?: string;
300
+ /**
301
+ * Path to the third-party customer login endpoint (GET; `?provider=` selects).
302
+ * @default "/api/ionite/auth/customer/login"
303
+ */
304
+ customerLoginPath?: string;
305
+ }
306
+ /**
307
+ * IAM Validation Configuration
308
+ */
309
+ interface IAMValidationConfig extends ValidationConfig {
310
+ /**
311
+ * Path prefix for SCIM endpoints (used for both outgoing proxy and incoming handler)
312
+ * @default "/api/iam"
313
+ */
314
+ scimPath?: string;
315
+ /**
316
+ * Path for Groups Inbound handler (app's own SCIM server for receiving group provisioning)
317
+ * If not provided, defaults to `${scimPath}/Groups`
318
+ * @example "/api/iam/Groups"
319
+ */
320
+ groupsInboundPath?: string;
321
+ /**
322
+ * Ionite instance for workload authentication.
323
+ * Optional escape hatch: when provided, IAM tests use ionite workload token acquisition
324
+ * (requires workload to be configured). Can be a function that returns the instance
325
+ * (useful when instance is initialized in beforeAll).
326
+ *
327
+ * When omitted, IAM tests mint a workload token from the ECV mock server at `ecvUrl`.
328
+ */
329
+ ion?: Ionite | (() => Ionite | null);
330
+ /**
331
+ * URL of the ECV mock server used to mint a workload token for SCIM calls when `ion` is not provided.
332
+ * @default "http://localhost:3515"
333
+ */
334
+ ecvUrl?: string;
335
+ /**
336
+ * Test user data for CRUD operations
337
+ */
338
+ testUser?: {
339
+ userName: string;
340
+ displayName: string;
341
+ emails: Array<{
342
+ value: string;
343
+ primary?: boolean;
344
+ }>;
345
+ name?: {
346
+ givenName?: string;
347
+ familyName?: string;
348
+ };
349
+ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"?: EnterpriseExtension;
350
+ };
351
+ /**
352
+ * Test group data for CRUD operations
353
+ */
354
+ testGroup?: {
355
+ displayName: string;
356
+ externalId?: string;
357
+ };
358
+ }
359
+ /**
360
+ * Workload Validation Configuration
361
+ */
362
+ interface WorkloadValidationConfig extends ValidationConfig {
363
+ /**
364
+ * Path to the token validation endpoint
365
+ * @default "/api/workload/validate"
366
+ */
367
+ validatePath?: string;
368
+ /**
369
+ * Path to the JWKS endpoint
370
+ * @default "/api/workload/jwks"
371
+ */
372
+ jwksPath?: string;
373
+ /**
374
+ * A valid workload token for the positive-path validation tests. When not provided, one is
375
+ * minted from the ECV mock IdP at `{ecvUrl}/workload/token`; if that fails the tests FAIL
376
+ * (no silent skip).
377
+ */
378
+ validToken?: string;
379
+ /**
380
+ * URL of the ECV mock server for testing workload authentication
381
+ * Used for the whoami test to validate tokens independently
382
+ * @default "http://localhost:3515"
383
+ */
384
+ ecvUrl?: string;
385
+ }
386
+ /**
387
+ * Tenant validation scope.
388
+ *
389
+ * - `tenantManager`: full tenant management API (GET/DELETE/upsert by id)
390
+ * - `creation`: POST upsert only (async TMS / deployer-style servers)
391
+ */
392
+ type TenantValidationProfile = "tenantManager" | "creation";
393
+ /**
394
+ * Tenant Validation Configuration
395
+ */
396
+ interface TenantValidationConfig extends ValidationConfig {
397
+ /**
398
+ * Which tenant-management surface to validate.
399
+ * @default 'tenantManager'
400
+ */
401
+ profile?: TenantValidationProfile;
402
+ /**
403
+ * Path to the tenant creation endpoint
404
+ * @default "/api/tenant"
405
+ */
406
+ tenantPath?: string;
407
+ /**
408
+ * URL path where ESVS webhook server listens for tenant creation updates
409
+ * If not provided, webhook tests will be skipped
410
+ */
411
+ webhookPath?: string;
412
+ /**
413
+ * Test tenant data for tenant creation
414
+ */
415
+ testTenantData?: {
416
+ id: string;
417
+ companyId: string;
418
+ companyName: string;
419
+ environmentType: "POC" | "DEV" | "QA" | "PROD";
420
+ email?: string;
421
+ webhookUrl?: string;
422
+ callbackUrl?: string;
423
+ /** Required: create requests must carry a `configLocator` (see `CreateTenantRequest`). */
424
+ configLocator: {
425
+ /** Optional name of a secrets source declared on the root Ionite; omit to use the default `ion.secret`. */
426
+ source?: string;
427
+ /** Tenant-specific secret path within that source. */
428
+ path: string;
429
+ /** Optional poll interval (ms) for the tenant config subscription. */
430
+ ttl?: number;
431
+ };
432
+ };
433
+ }
434
+ /**
435
+ * Capability Catalog Validation Configuration.
436
+ *
437
+ * Validates the runtime readiness endpoint (`${basePath}/readyz`) and catalogs the application's
438
+ * capabilities for publishing to the ECI. Runs at CI/CD time; the readiness endpoint it reads is the
439
+ * runtime source of truth for per-module readiness and shares one capability definition
440
+ * (`ION_CAPABILITY_CATALOG`) with this suite so the SDK, ECV, and docs cannot drift.
441
+ */
442
+ interface CapabilityValidationConfig extends ValidationConfig {
443
+ /**
444
+ * Path to the readiness endpoint (`${basePath}/readyz`).
445
+ * @default "/api/ionite/readyz"
446
+ */
447
+ readyzPath?: string;
448
+ /**
449
+ * Path to the ECV/dev-only capability discovery endpoint (`${basePath}/capabilitiez`).
450
+ * @default "/api/ionite/capabilitiez"
451
+ */
452
+ capabilitiezPath?: string;
453
+ /**
454
+ * ECV mock server base URL used to mint workload tokens for authenticated capabilitiez requests.
455
+ * @default "http://localhost:3515"
456
+ */
457
+ ecvUrl?: string;
458
+ }
459
+ /**
460
+ * Authz / OAA Validation Configuration.
461
+ *
462
+ * Validates the Veza OAA projection (`ion.authz.oaa`). The in-process closure tests run with no
463
+ * dependencies; the OAA projection tests build an Ionite instance whose OAA client points
464
+ * at the ECV mock server's Veza receiver, so the real SDK OAA payloads can be decoded and asserted.
465
+ */
466
+ interface AuthzValidationConfig {
467
+ /**
468
+ * ECV mock server handle (or getter) from `startEcvServer`. Provides the OAA receiver base URL the
469
+ * projection tests target. When omitted, only the dependency-free closure tests run.
470
+ */
471
+ ecv?: EcvServerHandle | (() => EcvServerHandle);
472
+ /**
473
+ * Timeout (ms) applied to OAA readiness waits.
474
+ * @default 5000
475
+ */
476
+ timeout?: number;
477
+ }
478
+ /**
479
+ * Result of an individual validation test
480
+ */
481
+ interface ValidationResult {
482
+ /**
483
+ * Name of the test
484
+ */
485
+ name: string;
486
+ /**
487
+ * Whether the test passed
488
+ */
489
+ passed: boolean;
490
+ /**
491
+ * Error message if the test failed
492
+ */
493
+ error?: string;
494
+ /**
495
+ * Duration of the test in milliseconds
496
+ */
497
+ duration: number;
498
+ /**
499
+ * Additional details or diagnostics
500
+ */
501
+ details?: Record<string, unknown>;
502
+ }
503
+ /**
504
+ * Result of a validation suite
505
+ */
506
+ interface ValidationSuiteResult {
507
+ /**
508
+ * Name of the suite (e.g., "SSO", "IAM", "Workload")
509
+ */
510
+ suite: string;
511
+ /**
512
+ * Whether all tests in the suite passed
513
+ */
514
+ passed: boolean;
515
+ /**
516
+ * Individual test results
517
+ */
518
+ tests: ValidationResult[];
519
+ /**
520
+ * Total duration of the suite in milliseconds
521
+ */
522
+ duration: number;
523
+ /**
524
+ * Summary statistics
525
+ */
526
+ summary: {
527
+ total: number;
528
+ passed: number;
529
+ failed: number;
530
+ skipped: number;
531
+ };
532
+ }
533
+ /**
534
+ * Full validation report
535
+ */
536
+ interface ValidationReport {
537
+ /**
538
+ * Timestamp of the validation run
539
+ */
540
+ timestamp: Date;
541
+ /**
542
+ * Base URL tested
543
+ */
544
+ baseUrl: string;
545
+ /**
546
+ * Whether all suites passed
547
+ */
548
+ passed: boolean;
549
+ /**
550
+ * Individual suite results
551
+ */
552
+ suites: ValidationSuiteResult[];
553
+ /**
554
+ * Total duration of all tests
555
+ */
556
+ duration: number;
557
+ }
558
+ declare function createAuthzClosureTests(): {
559
+ tests: TestDef[];
560
+ };
561
+ /**
562
+ * OAA projection tests. They require a running ECV mock server (its Veza receiver records the decoded
563
+ * payloads) and validate that the real `ion.authz.oaa` client emits well-formed, correctly-encoded
564
+ * incremental and full-snapshot payloads for pushes, grants, revokes, and sync.
565
+ */
566
+ declare function createOaaProjectionTests(config: AuthzValidationConfig): TestDef[];
567
+ /**
568
+ * Workload OAuth-scope tests (require the ECV mock server for JWKS + token minting). They validate
569
+ * the two authorization sources for a workload: explicit `hasScope*` (token claims) and the
570
+ * config-gated `scopesAsEntitlements` auto-honor inside `has*`.
571
+ */
572
+ declare function createWorkloadScopeTests(config: AuthzValidationConfig): TestDef[];
573
+ /**
574
+ * Programmatic Authz/OAA validation runner. Always runs the in-process closure tests; when an ECV
575
+ * mock server handle is provided, also runs the OAA projection tests against its Veza receiver.
576
+ */
577
+ declare function validateAuthz(config?: AuthzValidationConfig): Promise<ValidationSuiteResult>;
578
+ /**
579
+ * Runs all Capability Catalog validation tests and returns a suite result whose details carry the
580
+ * capability catalog for publishing to the ECI.
581
+ */
582
+ declare function validateCapabilities(config: CapabilityValidationConfig): Promise<ValidationSuiteResult>;
583
+ /**
584
+ * Creates a Vitest-compatible test suite for capability catalog validation.
585
+ */
586
+ declare function createCapabilityTests(config: CapabilityValidationConfig): Array<TestDef>;
587
+ import { CapabilitiezManifest, CapabilityKey, Ionite as Ionite2 } from "@ionite/server";
588
+ interface DiscoveredECVConfig extends ValidationConfig {
589
+ /**
590
+ * ionite basePath used by the application under test.
591
+ * @default "/api/ionite"
592
+ */
593
+ basePath?: string;
594
+ /**
595
+ * Override the capabilitiez path when the endpoint is not mounted at `${basePath}/capabilitiez`.
596
+ */
597
+ capabilitiezPath?: string;
598
+ /**
599
+ * Opt-in best-practice assertion threaded into the SSO suite: when set, a FAILED callback's
600
+ * `Location` must match this pattern (e.g. `/[?&]auth_error=login_failed/`). See
601
+ * {@link SSOValidationConfig.expectedCallbackErrorPattern}.
602
+ */
603
+ expectedCallbackErrorPattern?: RegExp;
604
+ /**
605
+ * ECV mock server handle (from `startEcvServer`), or a getter returning it. Preferred over `ecvUrl`:
606
+ * the discovery and IAM token-minting URL is derived from `ecv.baseUrl`, so it can never drift from
607
+ * where the server started. A getter is supported because the handle is often created in `beforeAll`,
608
+ * after test collection.
609
+ */
610
+ ecv?: EcvServerHandle | (() => EcvServerHandle);
611
+ /**
612
+ * Ionite instance used by IAM tests to obtain workload-authenticated SCIM tokens.
613
+ * Optional escape hatch for real-IdP / custom-scope runs; when omitted, tokens are minted from the
614
+ * ECV mock server.
615
+ */
616
+ ion?: Ionite2 | (() => Ionite2 | null);
617
+ /**
618
+ * ECV mock server base URL used to mint workload tokens for authenticated discovery.
619
+ * Ignored when `ecv` is provided.
620
+ * @default "http://localhost:3515"
621
+ */
622
+ ecvUrl?: string;
623
+ }
624
+ declare function fetchCapabilitiezManifest(config: DiscoveredECVConfig): Promise<CapabilitiezManifest>;
625
+ /**
626
+ * A single discoverable ECV check. Entries are enumerated up front (so every possible check is
627
+ * visible in the test reporter) and gated at run time against the capabilitiez manifest. `alwaysRun`
628
+ * entries (in-process closure, config retry, manifest reachability) ignore capability gating.
629
+ */
630
+ interface EcvCatalogEntry {
631
+ /** Stable machine id (suite + name slug). */
632
+ id: string;
633
+ /** Display group: 'SSO' | 'CIAM' | 'IAM' | 'Workload' | 'Authz/OAA' | 'Authz closure' | 'Discovery' | 'Config retry policy'. */
634
+ suite: string;
635
+ /** Human-readable test name. */
636
+ name: string;
637
+ /** Capability gate; when omitted (or `alwaysRun`), the check is not gated. An array passes if ANY key is testable. */
638
+ capability?: CapabilityKey | CapabilityKey[];
639
+ /** Always run regardless of capabilitiez (in-process or infrastructure checks). */
640
+ alwaysRun?: boolean;
641
+ /** Execute the underlying assertion using the resolved manifest (present for capability-gated entries). */
642
+ run: (manifest?: CapabilitiezManifest) => Promise<void>;
643
+ }
644
+ /**
645
+ * Compute a skip reason for an entry's capability gate, or `undefined` when it should run. Prefers the
646
+ * manifest's own `reason` string so skips explain why a capability is not wired/testable.
647
+ */
648
+ declare function capabilitySkipReason(manifest: CapabilitiezManifest, capability?: CapabilityKey | CapabilityKey[]): string | undefined;
649
+ /**
650
+ * Build the full catalog of discoverable ECV checks. Names are enumerated synchronously so the
651
+ * complete list is visible at collection time; capability-gated entries resolve their shared factory
652
+ * instance lazily from the manifest at run time (preserving cross-test state such as created user ids).
653
+ */
654
+ declare function buildEcvCatalog(config: DiscoveredECVConfig, options?: {
655
+ configRetry?: boolean;
656
+ }): EcvCatalogEntry[];
657
+ /**
658
+ * Build discovered ECV tests as a flat `TestDef[]` for manual wiring.
659
+ *
660
+ * @deprecated Plain `TestDef`s cannot signal a real skip to the test reporter, so a capability that
661
+ * is not testable is reported as a pass — only mitigated here by a visible `console.warn` SKIP line.
662
+ * Prefer {@link defineDiscoveredECVSuite}, which reports each skipped capability via `ctx.skip(reason)`
663
+ * so the reporter distinguishes skipped from passed (AUDIT_HISTORY.md §4.10).
664
+ */
665
+ declare function createDiscoveredECVTests(config: DiscoveredECVConfig): Array<TestDef>;
666
+ /**
667
+ * Runs all CIAM validation tests
668
+ */
669
+ declare function validateCIAM(config: CIAMValidationConfig): Promise<ValidationSuiteResult>;
670
+ declare function createCIAMTests(config: CIAMValidationConfig): {
671
+ tests: Array<TestDef>;
672
+ };
673
+ /**
674
+ * Lifecycle hardening coverage (AUDIT_HISTORY.md §5.1/§5.2/§5.4): module-readiness re-arm on de-configuration,
675
+ * async/deadlock-guarded `beforeChange`, and robust `afterChange` error handling.
676
+ */
677
+ declare function createConfigLifecycleTests(): TestDef[];
678
+ /**
679
+ * Config-merge & secrets-boundary coverage (AUDIT_HISTORY.md §2.1/§2.2/§2.3/§4.3/§4.5): the ConfigSource owns
680
+ * credentials, unimplemented backends fail fast at apply, invalid/empty config payloads and locators
681
+ * are rejected, a configured `beforeChange` always re-applies, and declared-but-unconfigured secrets
682
+ * sources keep the module not-ready.
683
+ */
684
+ declare function createConfigBoundaryTests(): TestDef[];
685
+ declare function createConfigRetryTests(): TestDef[];
686
+ /**
687
+ * Canonical `ion:*` scope strings the ECV harness embeds in workload tokens so deny-by-default SDK
688
+ * endpoints (AUDIT_HISTORY.md §1.32) authorize via `scopesAsEntitlements`. They mirror the SDK-owned Permission
689
+ * ids so they can never drift from the gate.
690
+ */
691
+ /** All three SCIM scopes (read + user/group writes) — the IAM suite reads and writes with one token. */
692
+ declare const ION_IAM_SCOPES: string;
693
+ /** Scope required to read the capability manifest at the capabilitiez endpoint. */
694
+ declare const ION_CAPABILITIEZ_SCOPE: string;
695
+ /** Scope required to manage tenant records via the tenant-management endpoints. */
696
+ declare const ION_TENANT_SCOPE: string;
697
+ /**
698
+ * Get a workload token from the ECV mock server for use when calling the SDK's protected endpoints
699
+ * (tenant management, SCIM, capabilitiez). Used by tests so the call is authenticated with a workload
700
+ * identity.
701
+ *
702
+ * The SDK gates its protected endpoints deny-by-default (AUDIT_HISTORY.md §1.32). When the app under test enables
703
+ * `RemoteConfig.authz.scopesAsEntitlements` (the ECV mock vault does), the requested `scope` is honored
704
+ * as the matching entitlement — so pass the `ion:*` permission id(s) the endpoint requires (e.g.
705
+ * `ion:tenant:manage`, `ion:capabilitiez:read`, or the IAM scopes). Omit `scope` to mint an
706
+ * authenticated-but-unauthorized token (useful for asserting 403 denial).
707
+ *
708
+ * @param esvBaseUrl - Base URL of the ECV mock server (e.g. http://localhost:3515)
709
+ * @param scope - Space-delimited OAuth scope(s) to embed in the token (the SDK `ion:*` permission ids)
710
+ * @param clientId - Client ID for client_credentials grant (default: local-workload-client)
711
+ * @param clientSecret - Client secret (default: local-workload-secret)
712
+ * @returns The access token to use as Authorization: Bearer <token>
713
+ */
714
+ declare function getTenantAuthToken(esvBaseUrl?: string, scope?: string, clientId?: string, clientSecret?: string): Promise<string>;
715
+ declare function createHarnessSelfTests(): TestDef[];
716
+ /**
717
+ * Runs all IAM validation tests (core user tests + optional groups tests)
718
+ */
719
+ declare function validateIAM(config: IAMValidationConfig): Promise<ValidationSuiteResult>;
720
+ /**
721
+ * Creates Vitest-compatible test suite for IAM validation.
722
+ *
723
+ * Returns `{ tests, ext }` where:
724
+ * - `tests`: Core user management tests (always run)
725
+ * - `ext`: Extension methods for optional functionality:
726
+ * - `createGroupsOutboundTests()` - Tests app calling external IAM provider
727
+ * - `createGroupsInboundTests()` - Tests external IAM provider calling app
728
+ *
729
+ * @example
730
+ * ```ts
731
+ * describe('IAM', () => {
732
+ * const { tests, ext } = createIAMTests({ ... });
733
+ *
734
+ * // Core user tests
735
+ * tests.forEach(({ name, fn }) => it(name, fn));
736
+ *
737
+ * // Optional: Groups Outbound tests (app -> external IAM)
738
+ * describe('Groups Outbound', () => {
739
+ * ext.createGroupsOutboundTests().forEach(({ name, fn }) => it(name, fn));
740
+ * });
741
+ *
742
+ * // Optional: Groups Inbound tests (external IAM -> app)
743
+ * describe('Groups Inbound', () => {
744
+ * ext.createGroupsInboundTests().forEach(({ name, fn }) => it(name, fn));
745
+ * });
746
+ * });
747
+ * ```
748
+ */
749
+ declare function createIAMTests(config: IAMValidationConfig): {
750
+ tests: Array<TestDef>;
751
+ ext: {
752
+ createGroupsOutboundTests: () => Array<TestDef>;
753
+ createGroupsInboundTests: () => Array<TestDef>;
754
+ };
755
+ };
756
+ declare function createIamDeltaPatchTests(): {
757
+ tests: TestDef[];
758
+ };
759
+ /**
760
+ * Programmatic IAM delta validation runner (always in-process; no external server required).
761
+ */
762
+ declare function validateIamDelta(): Promise<ValidationSuiteResult>;
763
+ declare function createLfvCallbackTests(): {
764
+ tests: TestDef[];
765
+ };
766
+ declare function validateLfvDelivery(): Promise<ValidationSuiteResult>;
767
+ declare function createOtelTests(): {
768
+ tests: TestDef[];
769
+ };
770
+ declare function validateOtel(): Promise<ValidationSuiteResult>;
771
+ declare function createPerformanceTuningTests(): {
772
+ tests: TestDef[];
773
+ };
774
+ declare function validatePerformanceTuning(): Promise<ValidationSuiteResult>;
775
+ /**
776
+ * Validate the hardened protocol/error surface of the routed ionite handler. In-process; no running app.
777
+ */
778
+ declare function createProtocolSurfaceTests(): {
779
+ tests: TestDef[];
780
+ };
781
+ /**
782
+ * Run the protocol-surface validation suite and return a structured result list.
783
+ */
784
+ declare function validateProtocolSurface(): Promise<{
785
+ name: string;
786
+ passed: boolean;
787
+ error?: string;
788
+ }[]>;
789
+ declare function createRedisStoreTests(): {
790
+ tests: TestDef[];
791
+ };
792
+ declare function validateRedisStores(): Promise<ValidationSuiteResult>;
793
+ declare function createSecretsHardeningTests(): {
794
+ tests: TestDef[];
795
+ };
796
+ declare function validateSecretsHardening(): Promise<ValidationSuiteResult>;
797
+ /**
798
+ * Runs all SSO validation tests
799
+ */
800
+ declare function validateSSO(config: SSOValidationConfig): Promise<ValidationSuiteResult>;
801
+ /**
802
+ * Creates Vitest-compatible test suite for SSO validation
803
+ */
804
+ declare function createSSOTests(config: SSOValidationConfig): {
805
+ tests: Array<TestDef>;
806
+ ext: {
807
+ createJITTests: () => Array<TestDef>;
808
+ createLogoutTests: () => Array<TestDef>;
809
+ createBackChannelLogoutTests: () => Array<TestDef>;
810
+ };
811
+ };
812
+ declare function createSsoIdTokenVerificationTests(): {
813
+ tests: TestDef[];
814
+ };
815
+ declare function validateSsoIdTokenVerification(): Promise<ValidationSuiteResult>;
816
+ /**
817
+ * Runs all Tenant validation tests
818
+ */
819
+ declare function validateTenant(config: TenantValidationConfig): Promise<ValidationSuiteResult>;
820
+ /**
821
+ * Creates Vitest-compatible test suite for Tenant validation
822
+ */
823
+ declare function createTenantTests(config: TenantValidationConfig): Array<TestDef>;
824
+ declare function createTenantLifecycleTests(): {
825
+ tests: TestDef[];
826
+ };
827
+ declare function validateTenantLifecycle(): Promise<ValidationSuiteResult>;
828
+ type StandardSchemaV1 = {
829
+ readonly "~standard": {
830
+ readonly validate: (value: unknown) => {
831
+ value: unknown;
832
+ } | {
833
+ issues: ReadonlyArray<{
834
+ message: string;
835
+ path?: ReadonlyArray<unknown>;
836
+ }>;
837
+ } | Promise<{
838
+ value: unknown;
839
+ } | {
840
+ issues: ReadonlyArray<{
841
+ message: string;
842
+ path?: ReadonlyArray<unknown>;
843
+ }>;
844
+ }>;
845
+ };
846
+ };
847
+ /**
848
+ * Creates a fetch function with default configuration
849
+ */
850
+ declare function resolveBaseUrl(config: Pick<ValidationConfig, "baseUrl">): string;
851
+ declare function createFetcher(config: ValidationConfig): (path: string, options?: RequestInit) => Promise<Response>;
852
+ /**
853
+ * Wraps a test function to capture timing and errors
854
+ */
855
+ declare function runTest(name: string, testFn: () => Promise<undefined | {
856
+ details?: Record<string, unknown>;
857
+ }>): Promise<ValidationResult>;
858
+ /**
859
+ * Creates a skipped test result
860
+ */
861
+ declare function skipTest(name: string, reason: string): ValidationResult;
862
+ /**
863
+ * Assertion helper for tests
864
+ */
865
+ declare function assert(condition: boolean, message: string): asserts condition;
866
+ /**
867
+ * Assertion helper for equality
868
+ */
869
+ declare function assertEqual<T>(actual: T, expected: T, message?: string): void;
870
+ /**
871
+ * Assertion helper for validating data against a StandardSchemaV1 validator
872
+ *
873
+ * This is the preferred way to validate data when you have a validator available.
874
+ * The validator can be from valibot, zod, or any other library that implements StandardSchemaV1.
875
+ *
876
+ * @param data - The data to validate
877
+ * @param validator - A StandardSchemaV1 validator (e.g., from valibotValidators or zodValidators)
878
+ * @param message - Optional custom error message
879
+ *
880
+ * @example
881
+ * ```typescript
882
+ * import { valibotValidators } from '@ionite/valibot';
883
+ * const tenantValidator = valibotValidators.tenantResponse();
884
+ * assertValid(data, tenantValidator);
885
+ * ```
886
+ *
887
+ * @throws Error if validation fails
888
+ */
889
+ declare function assertValid(data: unknown, validator: StandardSchemaV1, message?: string): void;
890
+ declare function createValidatorParityTests(): {
891
+ tests: TestDef[];
892
+ };
893
+ declare function validateValidatorParity(): Promise<ValidationSuiteResult>;
894
+ declare function createVaultWebSocketTransportTests(): {
895
+ tests: TestDef[];
896
+ };
897
+ declare function validateVaultWebSocketTransport(): Promise<ValidationSuiteResult>;
898
+ /**
899
+ * Runs all Workload validation tests
900
+ */
901
+ declare function validateWorkload(config: WorkloadValidationConfig): Promise<ValidationSuiteResult>;
902
+ /**
903
+ * Creates Vitest-compatible test suite for Workload validation
904
+ */
905
+ declare function createWorkloadTests(config: WorkloadValidationConfig): Array<TestDef>;
906
+ import { Ionite as Ionite3 } from "@ionite/server";
907
+ /**
908
+ * Helper to create a complete Vitest test suite
909
+ *
910
+ * @example
911
+ * ```typescript
912
+ * import { describe, it } from 'vitest';
913
+ * import { defineECVTests } from '@ionite/ecv';
914
+ *
915
+ * defineECVTests(describe, it, {
916
+ * baseUrl: 'http://localhost:3000',
917
+ * sso: true,
918
+ * workload: true,
919
+ * });
920
+ * ```
921
+ */
922
+ declare function defineECVTests(describe: (name: string, fn: () => void) => void, it: (name: string, fn: () => Promise<void>) => void, config: {
923
+ baseUrl: string;
924
+ sso?: boolean | SSOValidationConfig;
925
+ ciam?: boolean | CIAMValidationConfig;
926
+ lfv?: boolean;
927
+ /**
928
+ * OpenTelemetry module validation. OPT-IN (`otel: true`): the suite starts a process-global
929
+ * `NodeSDK`, which registers global OTel providers/diagnostics for the whole test process, so it
930
+ * is off by default to avoid interfering with a consumer's own tracing/other suites. In-repo
931
+ * coverage still runs it via the discovered `capabilitiez` catalog (always-run).
932
+ */
933
+ otel?: boolean;
934
+ protocolSurface?: boolean;
935
+ harness?: boolean;
936
+ secretsHardening?: boolean;
937
+ redisStores?: boolean;
938
+ tenantLifecycle?: boolean;
939
+ vaultWebsocket?: boolean;
940
+ idTokenVerification?: boolean;
941
+ performanceTuning?: boolean;
942
+ validatorParity?: boolean;
943
+ iam?: IAMValidationConfig;
944
+ workload?: boolean | WorkloadValidationConfig;
945
+ tenant?: boolean | TenantValidationConfig;
946
+ capabilities?: boolean | CapabilityValidationConfig;
947
+ authz?: boolean | AuthzValidationConfig;
948
+ }): void;
949
+ /**
950
+ * Vitest-style lifecycle hooks passed into {@link defineDiscoveredECVSuite}.
951
+ *
952
+ * The helper takes hooks rather than importing `vitest` so the package stays framework-agnostic.
953
+ */
954
+ /**
955
+ * Minimal Vitest test-context shape used by discovered ECV tests. Vitest's real `TestContext`
956
+ * structurally satisfies this (its `skip(note?)` dynamically marks the running test as skipped).
957
+ */
958
+ interface EcvTestContext {
959
+ skip: (note?: string) => void;
960
+ }
961
+ interface DiscoveredECVSuiteHooks {
962
+ describe: (name: string, fn: () => void) => void;
963
+ it: (name: string, fn: (ctx: EcvTestContext) => Promise<void> | void) => void;
964
+ beforeAll: (fn: () => Promise<void> | void) => void;
965
+ afterAll: (fn: () => Promise<void> | void) => void;
966
+ }
967
+ /**
968
+ * Configuration for {@link defineDiscoveredECVSuite}.
969
+ */
970
+ interface DiscoveredECVSuiteConfig {
971
+ /**
972
+ * ECV mock server handle (or getter) from `startEcvServer`. The app's connection env is taken from
973
+ * `ecv.env`, and IAM/workload/capabilitiez token minting is derived from `ecv.baseUrl`.
974
+ */
975
+ ecv: EcvServerHandle | (() => EcvServerHandle);
976
+ /**
977
+ * The application under test to spawn. `ecv.env` is merged in automatically (your `app.env` wins on
978
+ * conflicts), so you only specify app-specific variables here.
979
+ */
980
+ app: StartAppOptions;
981
+ /**
982
+ * ionite basePath used by the application under test.
983
+ * @default "/api/ionite"
984
+ */
985
+ basePath?: string;
986
+ /** Timeout (ms) applied to discovered validation requests. */
987
+ timeout?: number;
988
+ /**
989
+ * Opt-in best-practice assertion: when set, a FAILED SSO callback's `Location` must match this
990
+ * pattern (e.g. `/[?&]auth_error=login_failed/`), proving the app surfaces login errors to the user.
991
+ */
992
+ expectedCallbackErrorPattern?: RegExp;
993
+ /**
994
+ * Register the config-retry policy tests alongside discovered tests.
995
+ * @default false
996
+ */
997
+ configRetry?: boolean;
998
+ /**
999
+ * Optional Ionite instance escape hatch for IAM token minting (real-IdP / custom scopes).
1000
+ * When omitted, IAM tokens are minted from the ECV mock server.
1001
+ */
1002
+ ion?: Ionite3 | (() => Ionite3 | null);
1003
+ }
1004
+ /**
1005
+ * Register a complete discovered-ECV suite: spawn the app under test, run config-retry (optional) and
1006
+ * capabilitiez-discovered module tests, and tear the app down afterwards.
1007
+ *
1008
+ * The ECV mock server (and any Redis) lifecycle stays with the caller, since not every project uses Redis.
1009
+ *
1010
+ * @example
1011
+ * ```ts
1012
+ * import { afterAll, beforeAll, describe, it } from 'vitest';
1013
+ * import { defineDiscoveredECVSuite, startEcvServer, stopEcvServer } from '@ionite/ecv';
1014
+ *
1015
+ * let ecv: Awaited<ReturnType<typeof startEcvServer>>;
1016
+ * beforeAll(async () => { ecv = await startEcvServer(); });
1017
+ * afterAll(async () => { await stopEcvServer(); });
1018
+ *
1019
+ * defineDiscoveredECVSuite(
1020
+ * { describe, it, beforeAll, afterAll },
1021
+ * {
1022
+ * ecv: () => ecv,
1023
+ * app: { command: 'bun', args: ['run', 'api'], baseUrl: 'http://localhost:3544' },
1024
+ * basePath: '/api/ionite',
1025
+ * configRetry: true,
1026
+ * },
1027
+ * );
1028
+ * ```
1029
+ */
1030
+ declare function defineDiscoveredECVSuite(hooks: DiscoveredECVSuiteHooks, config: DiscoveredECVSuiteConfig): void;
1031
+ /**
1032
+ * Print a validation report to console
1033
+ */
1034
+ declare function printReport(report: ValidationReport): void;
1035
+ export { waitForReady, validateWorkload, validateVaultWebSocketTransport, validateValidatorParity, validateTenantLifecycle, validateTenant, validateSsoIdTokenVerification, validateSecretsHardening, validateSSO, validateRedisStores, validateProtocolSurface, validatePerformanceTuning, validateOtel, validateLfvDelivery, validateIamDelta, validateIAM, validateCapabilities, validateCIAM, validateAuthz, stopEcvServer, startEcvServer, startAppUnderTest, skipTest, setVaultRedisUrl, runTest, resolveBaseUrl, printReport, getVaultRedisUrl, getTenantAuthToken, fetchCapabilitiezManifest, defineECVTests, defineDiscoveredECVSuite, createWorkloadTests, createWorkloadScopeTests, createVaultWebSocketTransportTests, createValidatorParityTests, createTenantTests, createTenantLifecycleTests, createSsoIdTokenVerificationTests, createSecretsHardeningTests, createSSOTests, createRedisStoreTests, createProtocolSurfaceTests, createPerformanceTuningTests, createOtelTests, createOaaProjectionTests, createLfvCallbackTests, createIamDeltaPatchTests, createIAMTests, createHarnessSelfTests, createFetcher, createDiscoveredECVTests, createConfigRetryTests, createConfigLifecycleTests, createConfigBoundaryTests, createCapabilityTests, createCIAMTests, createAuthzClosureTests, capabilitySkipReason, buildEcvCatalog, assertValid, assertEqual, assert, WorkloadValidationConfig, WaitForReadyOptions, ValidationSuiteResult, ValidationResult, ValidationReport, ValidationConfig, TestDef, TenantValidationProfile, TenantValidationConfig, StartAppOptions, SSOValidationConfig, ION_TENANT_SCOPE, ION_IAM_SCOPES, ION_CAPABILITIEZ_SCOPE, IAMValidationConfig, EcvTestContext, EcvServerHandle, EcvCatalogEntry, DiscoveredECVSuiteHooks, DiscoveredECVSuiteConfig, DiscoveredECVConfig, CapabilityValidationConfig, CIAMValidationConfig, AuthzValidationConfig, AppUnderTest };