@fuzdev/fuz_app 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/actions/action_codegen.d.ts.map +1 -1
  2. package/dist/auth/account_routes.d.ts +30 -0
  3. package/dist/auth/account_routes.d.ts.map +1 -1
  4. package/dist/auth/account_routes.js +44 -9
  5. package/dist/auth/admin_routes.d.ts.map +1 -1
  6. package/dist/auth/admin_routes.js +33 -2
  7. package/dist/auth/audit_log_routes.d.ts +2 -1
  8. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  9. package/dist/auth/audit_log_routes.js +11 -2
  10. package/dist/auth/audit_log_schema.d.ts +1 -1
  11. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  12. package/dist/auth/audit_log_schema.js +3 -1
  13. package/dist/auth/permit_queries.d.ts +19 -0
  14. package/dist/auth/permit_queries.d.ts.map +1 -1
  15. package/dist/auth/permit_queries.js +21 -0
  16. package/dist/auth/request_context.d.ts +10 -0
  17. package/dist/auth/request_context.d.ts.map +1 -1
  18. package/dist/auth/request_context.js +14 -0
  19. package/dist/hono_context.d.ts +7 -0
  20. package/dist/hono_context.d.ts.map +1 -1
  21. package/dist/realtime/sse_auth_guard.d.ts +23 -3
  22. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  23. package/dist/realtime/sse_auth_guard.js +38 -2
  24. package/dist/realtime/subscriber_registry.d.ts +62 -17
  25. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  26. package/dist/realtime/subscriber_registry.js +64 -21
  27. package/dist/server/validate_nginx.d.ts.map +1 -1
  28. package/dist/server/validate_nginx.js +61 -7
  29. package/dist/testing/admin_integration.d.ts.map +1 -1
  30. package/dist/testing/admin_integration.js +8 -8
  31. package/dist/testing/app_server.d.ts +9 -0
  32. package/dist/testing/app_server.d.ts.map +1 -1
  33. package/dist/testing/app_server.js +4 -3
  34. package/dist/testing/data_exposure.d.ts.map +1 -1
  35. package/dist/testing/data_exposure.js +1 -20
  36. package/dist/testing/error_coverage.d.ts +93 -27
  37. package/dist/testing/error_coverage.d.ts.map +1 -1
  38. package/dist/testing/error_coverage.js +160 -67
  39. package/dist/testing/integration.d.ts.map +1 -1
  40. package/dist/testing/integration.js +6 -6
  41. package/dist/testing/integration_helpers.d.ts +17 -0
  42. package/dist/testing/integration_helpers.d.ts.map +1 -1
  43. package/dist/testing/integration_helpers.js +31 -0
  44. package/dist/testing/round_trip.d.ts.map +1 -1
  45. package/dist/testing/round_trip.js +41 -55
  46. package/dist/testing/sse_round_trip.d.ts +64 -0
  47. package/dist/testing/sse_round_trip.d.ts.map +1 -0
  48. package/dist/testing/sse_round_trip.js +241 -0
  49. package/package.json +1 -1
@@ -1,52 +1,116 @@
1
1
  import './assert_dev_env.js';
2
+ /**
3
+ * Error reachability coverage tracking.
4
+ *
5
+ * Tracks which declared error statuses (and specific error codes) are
6
+ * actually exercised in tests. `ErrorCoverageCollector` records status
7
+ * codes (optionally with body `error` codes) observed during test runs,
8
+ * then `assert_error_coverage` compares against declared error schemas
9
+ * to find uncovered error paths — reporting per-code when the declared
10
+ * schema is a literal or enum, per-status otherwise.
11
+ *
12
+ * @module
13
+ */
14
+ import { z } from 'zod';
2
15
  import type { RouteSpec } from '../http/route_spec.js';
3
16
  /**
4
- * Tracks which route × status combinations have been exercised in tests.
17
+ * Extract declared error code values from an error response schema.
18
+ *
19
+ * Recognizes schemas shaped like `z.object({error: z.literal(...)})` or
20
+ * `z.object({error: z.enum([...])})` (incl. `looseObject`/`strictObject`).
21
+ * Returns the set of declared code values, or `null` if the schema doesn't
22
+ * expose a literal/enum `error` field (e.g., bare `ApiError` with `z.string()`).
5
23
  *
6
- * Use `record()` to log an observed status, or `assert_and_record()` to
7
- * combine response validation with tracking. After all tests, call
8
- * `uncovered()` to find declared error statuses never exercised.
24
+ * Used by coverage reporting to split a single declared status into per-code
25
+ * rows when the route's error schema names specific codes.
26
+ */
27
+ export declare const extract_declared_error_codes: (schema: z.ZodType) => Array<string> | null;
28
+ /** Uncovered entry — either a status-level row (no `code`) or a specific-code row. */
29
+ export interface UncoveredEntry {
30
+ method: string;
31
+ path: string;
32
+ status: number;
33
+ /** Declared code value missing, when the status's error schema names specific codes. */
34
+ code?: string;
35
+ }
36
+ /** Options controlling which routes/statuses are considered for coverage. */
37
+ export interface CoverageFilterOptions {
38
+ /** Routes to skip, in `'METHOD /path'` format. */
39
+ ignore_routes?: Array<string>;
40
+ /** HTTP status codes to skip. */
41
+ ignore_statuses?: Array<number>;
42
+ }
43
+ /**
44
+ * Tracks which route × status (and route × status × code) combinations have
45
+ * been exercised in tests.
46
+ *
47
+ * Use `record()` to log an observed status (optionally with the body's `error`
48
+ * code), or `assert_and_record()` to combine response validation with tracking
49
+ * (auto-extracts `body.error` from the response when present).
50
+ * After all tests, call `uncovered()` to find declared error paths never
51
+ * exercised.
52
+ *
53
+ * An observation recorded without a code still satisfies "any-code" coverage
54
+ * requirements for the same status — i.e., if a caller records just the status,
55
+ * all declared codes for that status are considered covered. Per-code tracking
56
+ * is additive: callers who know the body's `error` value should pass it to get
57
+ * precise per-code gap reporting on routes with literal/enum error schemas.
9
58
  */
10
59
  export declare class ErrorCoverageCollector {
11
- /** Observed route × status keys: `"METHOD /spec-path:STATUS"`. */
60
+ /**
61
+ * Observed keys: `"METHOD /spec-path:STATUS"` or `"METHOD /spec-path:STATUS:CODE"`.
62
+ *
63
+ * Both shapes coexist — the code-less key marks the status as covered at any
64
+ * code; a code-bearing key adds per-code precision.
65
+ */
12
66
  readonly observed: Set<string>;
13
67
  /**
14
- * Record an observed error status for a route.
68
+ * Record an observed error status (optionally with the body's `error` code) for a route.
15
69
  *
16
70
  * Resolves the concrete request path back to the spec template path
17
- * (e.g., `/api/accounts/abc` → `/api/accounts/:id`).
71
+ * (e.g., `/api/accounts/abc` → `/api/accounts/:id`). When `code` is provided,
72
+ * it is stored alongside the status for per-code coverage tracking.
18
73
  *
19
74
  * @param route_specs - route specs for path resolution
20
75
  * @param method - HTTP method
21
76
  * @param path - request path (may be concrete)
22
77
  * @param status - observed HTTP status code
78
+ * @param code - observed body `error` code (pass when the route's error
79
+ * schema declares specific codes via `z.literal` or `z.enum`)
23
80
  */
24
- record(route_specs: Array<RouteSpec>, method: string, path: string, status: number): void;
81
+ record(route_specs: Array<RouteSpec>, method: string, path: string, status: number, code?: string): void;
25
82
  /**
26
83
  * Validate a response against its route spec and record the status.
27
84
  *
28
- * Wraps `assert_response_matches_spec` and records the status code.
85
+ * Wraps `assert_response_matches_spec` and records the status code. For
86
+ * error responses, auto-extracts `body.error` from the JSON body (via a
87
+ * cloned response, so the original stream stays usable) and records it
88
+ * for per-code coverage. Pass an explicit `code` to override the
89
+ * auto-extracted value or when the body was already consumed.
29
90
  *
30
91
  * @param route_specs - route specs for schema lookup and path resolution
31
92
  * @param method - HTTP method
32
93
  * @param path - request path
33
94
  * @param response - the Response to validate and record
95
+ * @param code - observed body `error` code (override; if omitted and the
96
+ * response body is a JSON object with a string `error` field, that value
97
+ * is auto-extracted)
34
98
  */
35
- assert_and_record(route_specs: Array<RouteSpec>, method: string, path: string, response: Response): Promise<void>;
99
+ assert_and_record(route_specs: Array<RouteSpec>, method: string, path: string, response: Response, code?: string): Promise<void>;
36
100
  /**
37
- * Find declared error statuses that were never observed.
101
+ * Find declared error paths that were never observed.
38
102
  *
39
- * Computes the declared set from `merge_error_schemas` for each route spec,
40
- * then subtracts observed keys.
103
+ * Computes the declared set from `merge_error_schemas` for each route spec.
104
+ * For statuses whose error schema names specific codes (via `z.literal` or
105
+ * `z.enum`), reports per-code rows; otherwise reports one row per status.
106
+ * A status-only observation (no code) satisfies all declared codes for that
107
+ * status — the "any-code" rule.
41
108
  *
42
109
  * @param route_specs - route specs to check coverage against
43
- * @returns uncovered entries with method, path, and status
110
+ * @param options - exclusion configuration (skip routes or statuses)
111
+ * @returns uncovered entries with method, path, status, and optional code
44
112
  */
45
- uncovered(route_specs: Array<RouteSpec>): Array<{
46
- method: string;
47
- path: string;
48
- status: number;
49
- }>;
113
+ uncovered(route_specs: Array<RouteSpec>, options?: CoverageFilterOptions): Array<UncoveredEntry>;
50
114
  }
51
115
  /**
52
116
  * Default minimum error coverage threshold for the standard integration
@@ -55,20 +119,22 @@ export declare class ErrorCoverageCollector {
55
119
  */
56
120
  export declare const DEFAULT_INTEGRATION_ERROR_COVERAGE = 0.2;
57
121
  /** Options for `assert_error_coverage`. */
58
- export interface ErrorCoverageOptions {
122
+ export interface ErrorCoverageOptions extends CoverageFilterOptions {
59
123
  /** Minimum coverage ratio (0–1). Default `0` (informational only). */
60
124
  min_coverage?: number;
61
- /** Routes to skip, in `'METHOD /path'` format. */
62
- ignore_routes?: Array<string>;
63
- /** HTTP status codes to skip. */
64
- ignore_statuses?: Array<number>;
65
125
  }
66
126
  /**
67
127
  * Assert error coverage meets a minimum threshold.
68
128
  *
69
- * Computes the ratio of exercised error statuses to total declared error
70
- * statuses. When `min_coverage` is 0 (default), logs coverage info without
71
- * failing. When > 0, fails if coverage is below the threshold.
129
+ * Computes the ratio of exercised error paths to total declared error paths.
130
+ * For routes whose status error schema names specific codes (`z.literal` or
131
+ * `z.enum`), each declared code counts as one coverage path; for schemas
132
+ * without declared codes (`ApiError`/`z.string()`), the status counts as one
133
+ * path. A status-only observation covers all declared codes for that status
134
+ * (the "any-code" rule).
135
+ *
136
+ * When `min_coverage` is 0 (default), logs coverage info without failing.
137
+ * When > 0, fails if coverage is below the threshold.
72
138
  *
73
139
  * @param collector - the coverage collector with recorded observations
74
140
  * @param route_specs - route specs to check coverage against
@@ -1 +1 @@
1
- {"version":3,"file":"error_coverage.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/error_coverage.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAe7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAIrD;;;;;;GAMG;AACH,qBAAa,sBAAsB;IAClC,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IAE3C;;;;;;;;;;OAUG;IACH,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAMzF;;;;;;;;;OASG;IACG,iBAAiB,CACtB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,EAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,GAChB,OAAO,CAAC,IAAI,CAAC;IAKhB;;;;;;;;OAQG;IACH,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,CAAC;CAe/F;AAED;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,MAAM,CAAC;AAEtD,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACpC,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,aAAa,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,iCAAiC;IACjC,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GACjC,WAAW,sBAAsB,EACjC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,UAAU,oBAAoB,KAC5B,IA6CF,CAAC"}
1
+ {"version":3,"file":"error_coverage.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/error_coverage.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAIrD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,KAAK,CAAC,MAAM,CAAC,GAAG,IAWhF,CAAC;AAEF,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,wFAAwF;IACxF,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACrC,kDAAkD;IAClD,aAAa,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,iCAAiC;IACjC,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAChC;AAqDD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,sBAAsB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IAE3C;;;;;;;;;;;;;OAaG;IACH,MAAM,CACL,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,EAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,GACX,IAAI;IAUP;;;;;;;;;;;;;;;;OAgBG;IACG,iBAAiB,CACtB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,EAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,EAClB,IAAI,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC;IAgBhB;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAC,cAAc,CAAC;CAKhG;AAED;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,MAAM,CAAC;AAEtD,2CAA2C;AAC3C,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;IAClE,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAaD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,GACjC,WAAW,sBAAsB,EACjC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,UAAU,oBAAoB,KAC5B,IAqBF,CAAC"}
@@ -2,80 +2,183 @@ import './assert_dev_env.js';
2
2
  /**
3
3
  * Error reachability coverage tracking.
4
4
  *
5
- * Tracks which declared error statuses are actually exercised in tests.
6
- * `ErrorCoverageCollector` records status codes observed during test runs,
5
+ * Tracks which declared error statuses (and specific error codes) are
6
+ * actually exercised in tests. `ErrorCoverageCollector` records status
7
+ * codes (optionally with body `error` codes) observed during test runs,
7
8
  * then `assert_error_coverage` compares against declared error schemas
8
- * to find uncovered error paths.
9
+ * to find uncovered error paths — reporting per-code when the declared
10
+ * schema is a literal or enum, per-status otherwise.
9
11
  *
10
12
  * @module
11
13
  */
14
+ import { z } from 'zod';
12
15
  import { assert } from 'vitest';
13
16
  import { merge_error_schemas } from '../http/schema_helpers.js';
14
17
  import { find_route_spec, assert_response_matches_spec } from './integration_helpers.js';
15
18
  /**
16
- * Tracks which route × status combinations have been exercised in tests.
19
+ * Extract declared error code values from an error response schema.
17
20
  *
18
- * Use `record()` to log an observed status, or `assert_and_record()` to
19
- * combine response validation with tracking. After all tests, call
20
- * `uncovered()` to find declared error statuses never exercised.
21
+ * Recognizes schemas shaped like `z.object({error: z.literal(...)})` or
22
+ * `z.object({error: z.enum([...])})` (incl. `looseObject`/`strictObject`).
23
+ * Returns the set of declared code values, or `null` if the schema doesn't
24
+ * expose a literal/enum `error` field (e.g., bare `ApiError` with `z.string()`).
25
+ *
26
+ * Used by coverage reporting to split a single declared status into per-code
27
+ * rows when the route's error schema names specific codes.
28
+ */
29
+ export const extract_declared_error_codes = (schema) => {
30
+ if (!(schema instanceof z.ZodObject))
31
+ return null;
32
+ const error_field = schema.shape.error;
33
+ if (!error_field)
34
+ return null;
35
+ if (error_field instanceof z.ZodLiteral) {
36
+ return [...error_field.values].map(String);
37
+ }
38
+ if (error_field instanceof z.ZodEnum) {
39
+ return error_field.options.map(String);
40
+ }
41
+ return null;
42
+ };
43
+ /**
44
+ * Shared walk over declared error paths.
45
+ *
46
+ * Single source of truth for the route → status → code traversal used by
47
+ * both `uncovered()` and `assert_error_coverage`. Yields one entry per
48
+ * declared coverage path with a `covered` flag, applying the "any-code"
49
+ * rule (status-only observation covers all declared codes).
50
+ */
51
+ const walk_coverage = (collector, route_specs, options) => {
52
+ const ignore_routes = new Set(options?.ignore_routes);
53
+ const ignore_statuses = new Set(options?.ignore_statuses);
54
+ const entries = [];
55
+ for (const spec of route_specs) {
56
+ const route_key = `${spec.method} ${spec.path}`;
57
+ if (ignore_routes.has(route_key))
58
+ continue;
59
+ const merged = merge_error_schemas(spec);
60
+ if (!merged)
61
+ continue;
62
+ for (const status_str of Object.keys(merged)) {
63
+ const status = Number(status_str);
64
+ if (ignore_statuses.has(status))
65
+ continue;
66
+ const error_schema = merged[status];
67
+ if (!error_schema)
68
+ continue;
69
+ const status_key = `${spec.method} ${spec.path}:${status}`;
70
+ const status_observed = collector.observed.has(status_key);
71
+ const codes = extract_declared_error_codes(error_schema);
72
+ if (codes && codes.length > 0) {
73
+ for (const code of codes) {
74
+ const covered = status_observed || collector.observed.has(`${status_key}:${code}`);
75
+ entries.push({ method: spec.method, path: spec.path, status, code, covered });
76
+ }
77
+ }
78
+ else {
79
+ entries.push({ method: spec.method, path: spec.path, status, covered: status_observed });
80
+ }
81
+ }
82
+ }
83
+ return entries;
84
+ };
85
+ /**
86
+ * Tracks which route × status (and route × status × code) combinations have
87
+ * been exercised in tests.
88
+ *
89
+ * Use `record()` to log an observed status (optionally with the body's `error`
90
+ * code), or `assert_and_record()` to combine response validation with tracking
91
+ * (auto-extracts `body.error` from the response when present).
92
+ * After all tests, call `uncovered()` to find declared error paths never
93
+ * exercised.
94
+ *
95
+ * An observation recorded without a code still satisfies "any-code" coverage
96
+ * requirements for the same status — i.e., if a caller records just the status,
97
+ * all declared codes for that status are considered covered. Per-code tracking
98
+ * is additive: callers who know the body's `error` value should pass it to get
99
+ * precise per-code gap reporting on routes with literal/enum error schemas.
21
100
  */
22
101
  export class ErrorCoverageCollector {
23
- /** Observed route × status keys: `"METHOD /spec-path:STATUS"`. */
102
+ /**
103
+ * Observed keys: `"METHOD /spec-path:STATUS"` or `"METHOD /spec-path:STATUS:CODE"`.
104
+ *
105
+ * Both shapes coexist — the code-less key marks the status as covered at any
106
+ * code; a code-bearing key adds per-code precision.
107
+ */
24
108
  observed = new Set();
25
109
  /**
26
- * Record an observed error status for a route.
110
+ * Record an observed error status (optionally with the body's `error` code) for a route.
27
111
  *
28
112
  * Resolves the concrete request path back to the spec template path
29
- * (e.g., `/api/accounts/abc` → `/api/accounts/:id`).
113
+ * (e.g., `/api/accounts/abc` → `/api/accounts/:id`). When `code` is provided,
114
+ * it is stored alongside the status for per-code coverage tracking.
30
115
  *
31
116
  * @param route_specs - route specs for path resolution
32
117
  * @param method - HTTP method
33
118
  * @param path - request path (may be concrete)
34
119
  * @param status - observed HTTP status code
120
+ * @param code - observed body `error` code (pass when the route's error
121
+ * schema declares specific codes via `z.literal` or `z.enum`)
35
122
  */
36
- record(route_specs, method, path, status) {
123
+ record(route_specs, method, path, status, code) {
37
124
  const spec = find_route_spec(route_specs, method, path);
38
125
  const spec_path = spec ? spec.path : path;
39
- this.observed.add(`${method} ${spec_path}:${status}`);
126
+ const base_key = `${method} ${spec_path}:${status}`;
127
+ this.observed.add(base_key);
128
+ if (code !== undefined) {
129
+ this.observed.add(`${base_key}:${code}`);
130
+ }
40
131
  }
41
132
  /**
42
133
  * Validate a response against its route spec and record the status.
43
134
  *
44
- * Wraps `assert_response_matches_spec` and records the status code.
135
+ * Wraps `assert_response_matches_spec` and records the status code. For
136
+ * error responses, auto-extracts `body.error` from the JSON body (via a
137
+ * cloned response, so the original stream stays usable) and records it
138
+ * for per-code coverage. Pass an explicit `code` to override the
139
+ * auto-extracted value or when the body was already consumed.
45
140
  *
46
141
  * @param route_specs - route specs for schema lookup and path resolution
47
142
  * @param method - HTTP method
48
143
  * @param path - request path
49
144
  * @param response - the Response to validate and record
145
+ * @param code - observed body `error` code (override; if omitted and the
146
+ * response body is a JSON object with a string `error` field, that value
147
+ * is auto-extracted)
50
148
  */
51
- async assert_and_record(route_specs, method, path, response) {
149
+ async assert_and_record(route_specs, method, path, response, code) {
52
150
  await assert_response_matches_spec(route_specs, method, path, response);
53
- this.record(route_specs, method, path, response.status);
151
+ let resolved_code = code;
152
+ if (resolved_code === undefined && !response.ok && !response.bodyUsed) {
153
+ try {
154
+ const body = await response.clone().json();
155
+ if (body && typeof body.error === 'string') {
156
+ resolved_code = body.error;
157
+ }
158
+ }
159
+ catch {
160
+ // non-JSON body — no code to extract
161
+ }
162
+ }
163
+ this.record(route_specs, method, path, response.status, resolved_code);
54
164
  }
55
165
  /**
56
- * Find declared error statuses that were never observed.
166
+ * Find declared error paths that were never observed.
57
167
  *
58
- * Computes the declared set from `merge_error_schemas` for each route spec,
59
- * then subtracts observed keys.
168
+ * Computes the declared set from `merge_error_schemas` for each route spec.
169
+ * For statuses whose error schema names specific codes (via `z.literal` or
170
+ * `z.enum`), reports per-code rows; otherwise reports one row per status.
171
+ * A status-only observation (no code) satisfies all declared codes for that
172
+ * status — the "any-code" rule.
60
173
  *
61
174
  * @param route_specs - route specs to check coverage against
62
- * @returns uncovered entries with method, path, and status
175
+ * @param options - exclusion configuration (skip routes or statuses)
176
+ * @returns uncovered entries with method, path, status, and optional code
63
177
  */
64
- uncovered(route_specs) {
65
- const missing = [];
66
- for (const spec of route_specs) {
67
- const merged = merge_error_schemas(spec);
68
- if (!merged)
69
- continue;
70
- for (const status_str of Object.keys(merged)) {
71
- const status = Number(status_str);
72
- const key = `${spec.method} ${spec.path}:${status}`;
73
- if (!this.observed.has(key)) {
74
- missing.push({ method: spec.method, path: spec.path, status });
75
- }
76
- }
77
- }
78
- return missing;
178
+ uncovered(route_specs, options) {
179
+ return walk_coverage(this, route_specs, options)
180
+ .filter((entry) => !entry.covered)
181
+ .map(({ method, path, status, code }) => ({ method, path, status, ...(code && { code }) }));
79
182
  }
80
183
  }
81
184
  /**
@@ -84,12 +187,25 @@ export class ErrorCoverageCollector {
84
187
  * in the composable suites. Consumers should increase as their test suites mature.
85
188
  */
86
189
  export const DEFAULT_INTEGRATION_ERROR_COVERAGE = 0.2;
190
+ /**
191
+ * Format an uncovered entry for human-readable log output.
192
+ *
193
+ * Uses `status (code)` — spaces around the code make `:` unambiguous as
194
+ * the route_key / status separator.
195
+ */
196
+ const format_uncovered = (entry) => `${entry.method} ${entry.path} → ${entry.status}${entry.code ? ` (${entry.code})` : ''}`;
87
197
  /**
88
198
  * Assert error coverage meets a minimum threshold.
89
199
  *
90
- * Computes the ratio of exercised error statuses to total declared error
91
- * statuses. When `min_coverage` is 0 (default), logs coverage info without
92
- * failing. When > 0, fails if coverage is below the threshold.
200
+ * Computes the ratio of exercised error paths to total declared error paths.
201
+ * For routes whose status error schema names specific codes (`z.literal` or
202
+ * `z.enum`), each declared code counts as one coverage path; for schemas
203
+ * without declared codes (`ApiError`/`z.string()`), the status counts as one
204
+ * path. A status-only observation covers all declared codes for that status
205
+ * (the "any-code" rule).
206
+ *
207
+ * When `min_coverage` is 0 (default), logs coverage info without failing.
208
+ * When > 0, fails if coverage is below the threshold.
93
209
  *
94
210
  * @param collector - the coverage collector with recorded observations
95
211
  * @param route_specs - route specs to check coverage against
@@ -97,39 +213,16 @@ export const DEFAULT_INTEGRATION_ERROR_COVERAGE = 0.2;
97
213
  */
98
214
  export const assert_error_coverage = (collector, route_specs, options) => {
99
215
  const min_coverage = options?.min_coverage ?? 0;
100
- const ignore_routes = new Set(options?.ignore_routes);
101
- const ignore_statuses = new Set(options?.ignore_statuses);
102
- let total = 0;
103
- let covered = 0;
104
- const uncovered_entries = [];
105
- for (const spec of route_specs) {
106
- const route_key = `${spec.method} ${spec.path}`;
107
- if (ignore_routes.has(route_key))
108
- continue;
109
- const merged = merge_error_schemas(spec);
110
- if (!merged)
111
- continue;
112
- for (const status_str of Object.keys(merged)) {
113
- const status = Number(status_str);
114
- if (ignore_statuses.has(status))
115
- continue;
116
- total++;
117
- const key = `${spec.method} ${spec.path}:${status}`;
118
- if (collector.observed.has(key)) {
119
- covered++;
120
- }
121
- else {
122
- uncovered_entries.push(`${route_key} → ${status}`);
123
- }
124
- }
125
- }
216
+ const entries = walk_coverage(collector, route_specs, options);
217
+ const total = entries.length;
218
+ const uncovered_entries = entries.filter((e) => !e.covered);
219
+ const covered = total - uncovered_entries.length;
220
+ const uncovered_lines = uncovered_entries.map(format_uncovered);
126
221
  const ratio = total > 0 ? covered / total : 1;
127
222
  console.log(`[error coverage] ${covered}/${total} (${(ratio * 100).toFixed(1)}%)` +
128
- (uncovered_entries.length > 0
129
- ? `\n uncovered:\n ${uncovered_entries.join('\n ')}`
130
- : ''));
223
+ (uncovered_lines.length > 0 ? `\n uncovered:\n ${uncovered_lines.join('\n ')}` : ''));
131
224
  if (min_coverage > 0) {
132
225
  assert.ok(ratio >= min_coverage, `Error coverage ${(ratio * 100).toFixed(1)}% below threshold ${(min_coverage * 100).toFixed(1)}%` +
133
- `\n uncovered:\n ${uncovered_entries.join('\n ')}`);
226
+ `\n uncovered:\n ${uncovered_lines.join('\n ')}`);
134
227
  }
135
228
  };
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAgBjB;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAeD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IAo+CF,CAAC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAgBjB;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAeD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IAm/CF,CAAC"}
@@ -127,9 +127,9 @@ export const describe_standard_integration_tests = (options) => {
127
127
  }),
128
128
  });
129
129
  assert.strictEqual(res.status, 401);
130
- error_collector.record(test_app.route_specs, 'POST', login_route.path, 401);
131
- const body = await res.json();
130
+ const body = await res.clone().json();
132
131
  assert.strictEqual(body.error, 'invalid_credentials');
132
+ await error_collector.assert_and_record(test_app.route_specs, 'POST', login_route.path, res);
133
133
  });
134
134
  test('login with nonexistent user returns 401', async () => {
135
135
  const test_app = await create_test_app(build_test_app_options(options, get_db()));
@@ -148,9 +148,9 @@ export const describe_standard_integration_tests = (options) => {
148
148
  }),
149
149
  });
150
150
  assert.strictEqual(res.status, 401);
151
- error_collector.record(test_app.route_specs, 'POST', login_route.path, 401);
152
- const body = await res.json();
151
+ const body = await res.clone().json();
153
152
  assert.strictEqual(body.error, 'invalid_credentials');
153
+ await error_collector.assert_and_record(test_app.route_specs, 'POST', login_route.path, res);
154
154
  });
155
155
  test('login trims whitespace from username', async () => {
156
156
  const test_app = await create_test_app(build_test_app_options(options, get_db()));
@@ -821,9 +821,9 @@ export const describe_standard_integration_tests = (options) => {
821
821
  // third attempt should be rate-limited
822
822
  const limited_res = await make_bad_login();
823
823
  assert.strictEqual(limited_res.status, 429, 'Expected 429 after exceeding rate limit');
824
- error_collector.record(test_app.route_specs, 'POST', login_route.path, 429);
825
- const limited_body = await limited_res.json();
824
+ const limited_body = await limited_res.clone().json();
826
825
  assert.strictEqual(limited_body.error, 'rate_limit_exceeded');
826
+ await error_collector.assert_and_record(test_app.route_specs, 'POST', login_route.path, limited_res);
827
827
  // Retry-After header present
828
828
  const retry_after = limited_res.headers.get('Retry-After');
829
829
  assert.ok(retry_after, 'Expected Retry-After header on 429 response');
@@ -2,6 +2,7 @@ import './assert_dev_env.js';
2
2
  import type { RouteSpec, RouteMethod } from '../http/route_spec.js';
3
3
  import type { Keyring } from '../auth/keyring.js';
4
4
  import { type SessionOptions } from '../auth/session_cookie.js';
5
+ import type { TestApp, TestAccount } from './app_server.js';
5
6
  /**
6
7
  * Find a route spec matching the given method and path.
7
8
  *
@@ -99,4 +100,20 @@ export declare const collect_json_keys_recursive: (value: unknown) => Set<string
99
100
  * @param context - description for error messages
100
101
  */
101
102
  export declare const assert_no_sensitive_fields_in_json: (body: unknown, blocklist: ReadonlyArray<string>, context: string) => void;
103
+ /**
104
+ * Pick request headers matching a route spec's auth requirement.
105
+ *
106
+ * Maps `RouteAuth` onto a test account's credentials:
107
+ * - `none` — origin headers only
108
+ * - `authenticated` — the authed account's session cookie
109
+ * - `role: admin` — the admin account's session cookie
110
+ * - `role: <other>` — the test app's bootstrapped keeper session
111
+ * - `keeper` — the test app's daemon token
112
+ *
113
+ * @param spec - route spec to inspect
114
+ * @param test_app - the assembled test app (for bootstrapped credentials)
115
+ * @param authed_account - an account with no roles (for `authenticated` auth)
116
+ * @param admin_account - an account with `admin` role (for role-gated routes)
117
+ */
118
+ export declare const pick_auth_headers: (spec: RouteSpec, test_app: TestApp, authed_account: TestAccount, admin_account: TestAccount) => Record<string, string>;
102
119
  //# sourceMappingURL=integration_helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"integration_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAC,SAAS,EAAE,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAElE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAA8B,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE3F;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC3B,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,QAAQ,MAAM,EACd,MAAM,MAAM,KACV,SAAS,GAAG,SAad,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAC3B,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,QAAQ,MAAM,EACd,QAAQ,WAAW,KACjB,SAAS,GAAG,SAEd,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,UAAU,QAAQ,KAChB,OAAO,CAAC,IAAI,CAmDd,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GACtC,SAAS,OAAO,EAChB,iBAAiB,cAAc,CAAC,MAAM,CAAC,KACrC,OAAO,CAAC,MAAM,CAGhB,CAAC;AAuCF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,KAAK,CAAC,MAAM,CAQvF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,SAAS,MAAM,KACb,IAkBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oCAAoC,GAChD,UAAU,QAAQ,EAClB,MAAM;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,KACzB,IAUF,CAAC;AAIF,oEAAoE;AACpE,eAAO,MAAM,yBAAyB,EAAE,aAAa,CAAC,MAAM,CAAmC,CAAC;AAEhG,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,EAAE,aAAa,CAAC,MAAM,CAAgC,CAAC;AAE9F;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,OAAO,KAAG,GAAG,CAAC,MAAM,CAetE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,kCAAkC,GAC9C,MAAM,OAAO,EACb,WAAW,aAAa,CAAC,MAAM,CAAC,EAChC,SAAS,MAAM,KACb,IAKF,CAAC"}
1
+ {"version":3,"file":"integration_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAC,SAAS,EAAE,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAElE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAA8B,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE3F,OAAO,KAAK,EAAC,OAAO,EAAE,WAAW,EAAC,MAAM,iBAAiB,CAAC;AAE1D;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC3B,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,QAAQ,MAAM,EACd,MAAM,MAAM,KACV,SAAS,GAAG,SAad,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAC3B,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,QAAQ,MAAM,EACd,QAAQ,WAAW,KACjB,SAAS,GAAG,SAEd,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,UAAU,QAAQ,KAChB,OAAO,CAAC,IAAI,CAmDd,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GACtC,SAAS,OAAO,EAChB,iBAAiB,cAAc,CAAC,MAAM,CAAC,KACrC,OAAO,CAAC,MAAM,CAGhB,CAAC;AAuCF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,KAAK,CAAC,MAAM,CAQvF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,SAAS,MAAM,KACb,IAkBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oCAAoC,GAChD,UAAU,QAAQ,EAClB,MAAM;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,KACzB,IAUF,CAAC;AAIF,oEAAoE;AACpE,eAAO,MAAM,yBAAyB,EAAE,aAAa,CAAC,MAAM,CAAmC,CAAC;AAEhG,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,EAAE,aAAa,CAAC,MAAM,CAAgC,CAAC;AAE9F;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,OAAO,KAAG,GAAG,CAAC,MAAM,CAetE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,kCAAkC,GAC9C,MAAM,OAAO,EACb,WAAW,aAAa,CAAC,MAAM,CAAC,EAChC,SAAS,MAAM,KACb,IAKF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,SAAS,EACf,UAAU,OAAO,EACjB,gBAAgB,WAAW,EAC3B,eAAe,WAAW,KACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAcvB,CAAC"}
@@ -7,6 +7,7 @@ import './assert_dev_env.js';
7
7
  import { assert } from 'vitest';
8
8
  import { is_null_schema, merge_error_schemas } from '../http/schema_helpers.js';
9
9
  import { create_session_cookie_value } from '../auth/session_cookie.js';
10
+ import { ROLE_ADMIN } from '../auth/role_schema.js';
10
11
  /**
11
12
  * Find a route spec matching the given method and path.
12
13
  *
@@ -249,3 +250,33 @@ export const assert_no_sensitive_fields_in_json = (body, blocklist, context) =>
249
250
  assert.ok(!keys.has(field), `${context}: response contains blocklisted field '${field}'`);
250
251
  }
251
252
  };
253
+ /**
254
+ * Pick request headers matching a route spec's auth requirement.
255
+ *
256
+ * Maps `RouteAuth` onto a test account's credentials:
257
+ * - `none` — origin headers only
258
+ * - `authenticated` — the authed account's session cookie
259
+ * - `role: admin` — the admin account's session cookie
260
+ * - `role: <other>` — the test app's bootstrapped keeper session
261
+ * - `keeper` — the test app's daemon token
262
+ *
263
+ * @param spec - route spec to inspect
264
+ * @param test_app - the assembled test app (for bootstrapped credentials)
265
+ * @param authed_account - an account with no roles (for `authenticated` auth)
266
+ * @param admin_account - an account with `admin` role (for role-gated routes)
267
+ */
268
+ export const pick_auth_headers = (spec, test_app, authed_account, admin_account) => {
269
+ switch (spec.auth.type) {
270
+ case 'none':
271
+ return { host: 'localhost', origin: 'http://localhost:5173' };
272
+ case 'authenticated':
273
+ return authed_account.create_session_headers();
274
+ case 'role':
275
+ if (spec.auth.role === ROLE_ADMIN) {
276
+ return admin_account.create_session_headers();
277
+ }
278
+ return test_app.create_session_headers();
279
+ case 'keeper':
280
+ return test_app.create_daemon_token_headers();
281
+ }
282
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAc7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG9D,OAAO,EAAwB,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAO9D,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACpC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC,kDAAkD;IAClD,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IAqF9E,CAAC"}
1
+ {"version":3,"file":"round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAc7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG9D,OAAO,EAAwB,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAQ9D,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACpC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC,kDAAkD;IAClD,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IA6F9E,CAAC"}