@ruvyxa/testing 1.1.3 → 1.1.5
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 +21 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +51 -3
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
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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
|
-
//
|
|
76
|
-
|
|
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":"
|
|
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
|
+
"version": "1.1.5",
|
|
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.
|
|
52
|
+
"@ruvyxa/core": "^1.1.5"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@ruvyxa/core": "^1.1.
|
|
55
|
+
"@ruvyxa/core": "^1.1.5"
|
|
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
|
|
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
|
-
//
|
|
125
|
-
|
|
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
|
},
|