@ruvyxa/testing 1.1.3 → 1.1.4

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/dist/index.d.ts CHANGED
@@ -27,5 +27,25 @@ export interface MockCache extends MockControl<MockCacheCall> {
27
27
  delete(key: string): boolean;
28
28
  clear(): void;
29
29
  }
30
- export declare function mockCache(seed?: Readonly<Record<string, unknown>>): MockCache;
30
+ export interface MockCacheOptions {
31
+ /**
32
+ * Whether the double stands in for a cache used inside a request.
33
+ *
34
+ * The real builder throws for `scope('request')` outside one, so a suite
35
+ * exercising a loader that must not be request-scoped sets this to `false` and
36
+ * gets the production failure instead of a silent pass.
37
+ */
38
+ requestContext?: boolean;
39
+ }
40
+ /**
41
+ * A `cache()` double that refuses everything the real builder refuses.
42
+ *
43
+ * Every method used to be a plain assignment, so
44
+ * `cache('posts').ttl('5 minutes').get(async () => ({ published: new Date() }))`
45
+ * passed its test and threw twice in production — once on the duration, once on
46
+ * the value. The validators are imported from `@ruvyxa/core/server` rather than
47
+ * re-stated here: a helper whose whole job is parity cannot own a second copy of
48
+ * the rules it is checking.
49
+ */
50
+ export declare function mockCache(seed?: Readonly<Record<string, unknown>>, options?: MockCacheOptions): MockCache;
31
51
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,MAAM,EACP,MAAM,qBAAqB,CAAA;AAE5B,MAAM,WAAW,WAAW,CAAC,KAAK;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,CAAA;IAChC,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,aAAa,CAAC,CAAA;AAE9E,wBAAgB,UAAU,CAAC,OAAO,EAChC,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,EAC1E,QAAQ,GAAE,OAAO,CAAC,aAAa,CAAM,GACpC,UAAU,CAAC,OAAO,CAAC,CAsBrB;AAED,MAAM,WAAW,cAAc,CAAC,MAAM;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC/B;AAED,MAAM,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,IAAI,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,GACrE,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,GAAG;IACpC,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1C,CAAA;AAEH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,EACxC,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,EAClF,QAAQ,GAAE,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAM,GAC5C,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAgC7B;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,KAAK,EAAE,YAAY,GAAG,SAAS,CAAA;IAC/B,GAAG,EAAE,OAAO,CAAA;CACb;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW,CAAC,aAAa,CAAC;IAC3D,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;IAC3B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;IACtC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAC5B,KAAK,IAAI,IAAI,CAAA;CACd;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAAG,SAAS,CAwDjF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,MAAM,EACP,MAAM,qBAAqB,CAAA;AAE5B,MAAM,WAAW,WAAW,CAAC,KAAK;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,CAAA;IAChC,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,aAAa,CAAC,CAAA;AAE9E,wBAAgB,UAAU,CAAC,OAAO,EAChC,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,EAC1E,QAAQ,GAAE,OAAO,CAAC,aAAa,CAAM,GACpC,UAAU,CAAC,OAAO,CAAC,CAsBrB;AAED,MAAM,WAAW,cAAc,CAAC,MAAM;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC/B;AAED,MAAM,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,IAAI,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,GACrE,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,GAAG;IACpC,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1C,CAAA;AAEH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,EACxC,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,EAClF,QAAQ,GAAE,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAM,GAC5C,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAgC7B;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,KAAK,EAAE,YAAY,GAAG,SAAS,CAAA;IAC/B,GAAG,EAAE,OAAO,CAAA;CACb;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW,CAAC,aAAa,CAAC;IAC3D,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;IAC3B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;IACtC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAC5B,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,IAAI,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,EAC5C,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CAyEX"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { assertCacheKey, assertCacheSerializable, assertSharedCachePrivacy, normalizeCacheTags, parseTtl, } from '@ruvyxa/core/server';
1
2
  export function mockLoader(result, defaults = {}) {
2
3
  const calls = [];
3
4
  const callable = async (context = {}) => {
@@ -54,38 +55,66 @@ export function mockAction(result, defaults = {}) {
54
55
  },
55
56
  });
56
57
  }
57
- export function mockCache(seed = {}) {
58
+ /**
59
+ * A `cache()` double that refuses everything the real builder refuses.
60
+ *
61
+ * Every method used to be a plain assignment, so
62
+ * `cache('posts').ttl('5 minutes').get(async () => ({ published: new Date() }))`
63
+ * passed its test and threw twice in production — once on the duration, once on
64
+ * the value. The validators are imported from `@ruvyxa/core/server` rather than
65
+ * re-stated here: a helper whose whole job is parity cannot own a second copy of
66
+ * the rules it is checking.
67
+ */
68
+ export function mockCache(seed = {}, options = {}) {
58
69
  const values = new Map(Object.entries(seed));
59
70
  const calls = [];
71
+ const requestContext = options.requestContext ?? true;
60
72
  const callable = (key) => {
73
+ assertCacheKey(key);
61
74
  let ttl;
62
75
  let swr;
63
76
  let tags = [];
64
77
  let scope = 'deployment';
65
78
  const builder = {
66
79
  ttl(value) {
80
+ parseTtl(value);
67
81
  ttl = value;
68
82
  return builder;
69
83
  },
70
84
  swr(value) {
85
+ parseTtl(value);
71
86
  swr = value;
72
87
  return builder;
73
88
  },
74
89
  tags(...values) {
75
- // Code-unit order, matching `cache().tags()` in @ruvyxa/core.
76
- tags = [...new Set(values)].sort();
90
+ // Validated, de-duplicated, and code-unit ordered by @ruvyxa/core, so
91
+ // the recorded call carries the same tag identity a real entry would.
92
+ tags = normalizeCacheTags(values);
77
93
  return builder;
78
94
  },
79
95
  scope(value) {
96
+ if (value !== 'deployment' && value !== 'request') {
97
+ throw new TypeError('cache().scope() must be "deployment" or "request"');
98
+ }
80
99
  scope = value;
81
100
  return builder;
82
101
  },
83
102
  async get(producer) {
103
+ if (scope === 'request' && !requestContext) {
104
+ throw new Error('request-scoped cache used outside a request');
105
+ }
84
106
  const hit = scope === 'deployment' && values.has(key);
85
107
  calls.push({ key, ttl, swr, tags, scope, hit });
86
108
  if (hit)
87
109
  return values.get(key);
88
110
  const value = await producer();
111
+ // Order matters: the real builder awaits the producer before asserting
112
+ // on the value, and checks privacy only for the shared scope. Checking
113
+ // earlier, or checking privacy on a request-scoped read, would diverge
114
+ // in the other direction.
115
+ if (scope === 'deployment')
116
+ assertSharedCachePrivacy();
117
+ assertCacheSerializable(value);
89
118
  if (scope === 'deployment')
90
119
  values.set(key, value);
91
120
  return value;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruvyxa/testing",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Framework-native loader, action, and cache test doubles for Ruvyxa applications.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -49,10 +49,10 @@
49
49
  "access": "public"
50
50
  },
51
51
  "peerDependencies": {
52
- "@ruvyxa/core": "^1.1.3"
52
+ "@ruvyxa/core": "^1.1.4"
53
53
  },
54
54
  "devDependencies": {
55
- "@ruvyxa/core": "^1.1.3"
55
+ "@ruvyxa/core": "^1.1.4"
56
56
  },
57
57
  "exports": {
58
58
  ".": {
package/src/index.ts CHANGED
@@ -1,3 +1,10 @@
1
+ import {
2
+ assertCacheKey,
3
+ assertCacheSerializable,
4
+ assertSharedCachePrivacy,
5
+ normalizeCacheTags,
6
+ parseTtl,
7
+ } from '@ruvyxa/core/server'
1
8
  import type {
2
9
  ActionContext,
3
10
  CacheBuilder,
@@ -103,37 +110,78 @@ export interface MockCache extends MockControl<MockCacheCall> {
103
110
  clear(): void
104
111
  }
105
112
 
106
- export function mockCache(seed: Readonly<Record<string, unknown>> = {}): MockCache {
113
+ export interface MockCacheOptions {
114
+ /**
115
+ * Whether the double stands in for a cache used inside a request.
116
+ *
117
+ * The real builder throws for `scope('request')` outside one, so a suite
118
+ * exercising a loader that must not be request-scoped sets this to `false` and
119
+ * gets the production failure instead of a silent pass.
120
+ */
121
+ requestContext?: boolean
122
+ }
123
+
124
+ /**
125
+ * A `cache()` double that refuses everything the real builder refuses.
126
+ *
127
+ * Every method used to be a plain assignment, so
128
+ * `cache('posts').ttl('5 minutes').get(async () => ({ published: new Date() }))`
129
+ * passed its test and threw twice in production — once on the duration, once on
130
+ * the value. The validators are imported from `@ruvyxa/core/server` rather than
131
+ * re-stated here: a helper whose whole job is parity cannot own a second copy of
132
+ * the rules it is checking.
133
+ */
134
+ export function mockCache(
135
+ seed: Readonly<Record<string, unknown>> = {},
136
+ options: MockCacheOptions = {},
137
+ ): MockCache {
107
138
  const values = new Map(Object.entries(seed))
108
139
  const calls: MockCacheCall[] = []
140
+ const requestContext = options.requestContext ?? true
109
141
  const callable = (key: string): CacheBuilder => {
142
+ assertCacheKey(key)
110
143
  let ttl: string | undefined
111
144
  let swr: string | undefined
112
145
  let tags: readonly string[] = []
113
146
  let scope: 'deployment' | 'request' = 'deployment'
114
147
  const builder: CacheBuilder = {
115
148
  ttl(value) {
149
+ parseTtl(value)
116
150
  ttl = value
117
151
  return builder
118
152
  },
119
153
  swr(value) {
154
+ parseTtl(value)
120
155
  swr = value
121
156
  return builder
122
157
  },
123
158
  tags(...values) {
124
- // Code-unit order, matching `cache().tags()` in @ruvyxa/core.
125
- tags = [...new Set(values)].sort()
159
+ // Validated, de-duplicated, and code-unit ordered by @ruvyxa/core, so
160
+ // the recorded call carries the same tag identity a real entry would.
161
+ tags = normalizeCacheTags(values)
126
162
  return builder
127
163
  },
128
164
  scope(value) {
165
+ if (value !== 'deployment' && value !== 'request') {
166
+ throw new TypeError('cache().scope() must be "deployment" or "request"')
167
+ }
129
168
  scope = value
130
169
  return builder
131
170
  },
132
171
  async get<T>(producer: () => T | Promise<T>) {
172
+ if (scope === 'request' && !requestContext) {
173
+ throw new Error('request-scoped cache used outside a request')
174
+ }
133
175
  const hit = scope === 'deployment' && values.has(key)
134
176
  calls.push({ key, ttl, swr, tags, scope, hit })
135
177
  if (hit) return values.get(key) as T
136
178
  const value = await producer()
179
+ // Order matters: the real builder awaits the producer before asserting
180
+ // on the value, and checks privacy only for the shared scope. Checking
181
+ // earlier, or checking privacy on a request-scoped read, would diverge
182
+ // in the other direction.
183
+ if (scope === 'deployment') assertSharedCachePrivacy()
184
+ assertCacheSerializable(value)
137
185
  if (scope === 'deployment') values.set(key, value)
138
186
  return value
139
187
  },