@grest-ts/testkit 0.0.6 → 0.0.8

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 (46) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +413 -413
  3. package/dist/src/runner/isolated-loader.mjs +91 -91
  4. package/dist/src/runner/worker-loader.mjs +49 -49
  5. package/dist/tsconfig.publish.tsbuildinfo +1 -1
  6. package/package.json +13 -13
  7. package/src/GGBundleTest.ts +89 -89
  8. package/src/GGTest.ts +318 -318
  9. package/src/GGTestContext.ts +74 -74
  10. package/src/GGTestRunner.ts +308 -308
  11. package/src/GGTestRuntime.ts +265 -265
  12. package/src/GGTestRuntimeWorker.ts +159 -159
  13. package/src/GGTestSharedRef.ts +116 -116
  14. package/src/GGTestkitExtensionsDiscovery.ts +26 -26
  15. package/src/IGGLocalDiscoveryServer.ts +16 -16
  16. package/src/callOn/GGCallOnSelector.ts +61 -61
  17. package/src/callOn/GGContractClass.implement.ts +43 -43
  18. package/src/callOn/GGTestActionForLocatorOnCall.ts +134 -134
  19. package/src/callOn/TestableIPC.ts +81 -81
  20. package/src/callOn/callOn.ts +224 -224
  21. package/src/callOn/registerOnCallHandler.ts +123 -123
  22. package/src/index-node.ts +64 -64
  23. package/src/mockable/GGMockable.ts +22 -22
  24. package/src/mockable/GGMockableCall.ts +45 -45
  25. package/src/mockable/GGMockableIPC.ts +20 -20
  26. package/src/mockable/GGMockableInterceptor.ts +44 -44
  27. package/src/mockable/GGMockableInterceptorsServer.ts +69 -69
  28. package/src/mockable/mockable.ts +71 -71
  29. package/src/runner/InlineRunner.ts +47 -47
  30. package/src/runner/IsolatedRunner.ts +179 -179
  31. package/src/runner/RuntimeRunner.ts +15 -15
  32. package/src/runner/WorkerRunner.ts +179 -179
  33. package/src/runner/isolated-loader.mjs +91 -91
  34. package/src/runner/worker-loader.mjs +49 -49
  35. package/src/testers/GGCallInterceptor.ts +224 -224
  36. package/src/testers/GGMockWith.ts +92 -92
  37. package/src/testers/GGSpyWith.ts +115 -115
  38. package/src/testers/GGTestAction.ts +332 -332
  39. package/src/testers/GGTestComponent.ts +16 -16
  40. package/src/testers/GGTestSelector.ts +223 -223
  41. package/src/testers/IGGTestInterceptor.ts +10 -10
  42. package/src/testers/IGGTestWith.ts +15 -15
  43. package/src/testers/RuntimeSelector.ts +151 -151
  44. package/src/utils/GGExpectations.ts +78 -78
  45. package/src/utils/GGTestError.ts +36 -36
  46. package/src/utils/captureStack.ts +53 -53
@@ -1,224 +1,224 @@
1
- /**
2
- * Unified callOn function for invoking methods on runtime instances.
3
- *
4
- * Works with:
5
- * - @testable classes: Direct service invocation via IPC
6
- * - GGLocatorKey: Direct instance lookup via IPC
7
- * - GGContractClass: Contract implementation lookup via IPC
8
- * - Contract holders (HTTP APIs, etc.): Delegated to their own factory
9
- *
10
- * @example
11
- * // Call @testable service
12
- * await callOn(MyService).doSomething("hello")
13
- *
14
- * // Call HTTP API (goes through HTTP transport)
15
- * await callOn(ChainApi).quickWeatherCheck({city: "NYC"})
16
- *
17
- * // Call contract directly (skips HTTP, uses IPC)
18
- * await callOn(ChainApiContract).quickWeatherCheck({city: "NYC"})
19
- *
20
- * // With context
21
- * await callOn(MyService, ctx).doSomething("hello")
22
- */
23
-
24
- import {GGLocatorKey} from "@grest-ts/locator";
25
- import {GGTestActionForLocatorOnCall} from "./GGTestActionForLocatorOnCall";
26
- import {LOCATOR_KEY_PREFIX_FOR_TESTABLE} from "@grest-ts/testkit-runtime";
27
- import {GGContractApiDefinition, GGContractClass, GGContractMethod} from "@grest-ts/schema";
28
- import {GGContext} from "@grest-ts/context";
29
- import {LOCATOR_KEY_PREFIX_FOR_CONTRACT} from "./GGContractClass.implement";
30
- import type {GGTestRuntime} from "../GGTestRuntime";
31
-
32
- // ============================================================================
33
- // Factory symbol for protocol-specific callOn support
34
- // ============================================================================
35
-
36
- /**
37
- * Symbol used by contract holders (HTTP APIs, WebSocket APIs, etc.) to provide
38
- * their own callOn proxy. This allows protocols to control how calls are made
39
- * without callOn needing to know about specific protocols.
40
- *
41
- * The factory can return anything - there's no constraint on the return type.
42
- * Each protocol fully controls what methods and types are available.
43
- */
44
- export const CALL_ON_FACTORY = Symbol.for("gg:callOnFactory");
45
-
46
- /**
47
- * Marker interface for targets that provide their own callOn proxy.
48
- * The factory can return any type - protocols have full control.
49
- */
50
- export interface GGCallOnFactory {
51
- [CALL_ON_FACTORY](ctx?: GGContext): unknown;
52
- }
53
-
54
- // ============================================================================
55
- // Type definitions
56
- // ============================================================================
57
-
58
- /**
59
- * Maps a class's async methods to GGLocatorLookupTestAction calls.
60
- * Used for @testable classes and GGLocatorKey lookups.
61
- */
62
- export type LocatorLookupAccess<T> = {
63
- [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R>
64
- ? (...args: A) => GGTestActionForLocatorOnCall<R>
65
- : never
66
- };
67
-
68
- /**
69
- * Maps contract methods to GGLocatorLookupTestAction calls.
70
- * Used for direct GGContractClass lookups via IPC.
71
- */
72
- export type ContractLocatorAccess<TContract> = {
73
- [K in keyof TContract]: TContract[K] extends GGContractMethod<infer Input, infer Output>
74
- ? Input extends undefined ? () => GGTestActionForLocatorOnCall<Output> : (data: Input) => GGTestActionForLocatorOnCall<Output>
75
- : never
76
- };
77
-
78
- export type GGTestCallOnCollection<T> = { [K in keyof T]: GGTestCallOn<T[K]> }
79
-
80
- /**
81
- * Resolves the return type for callOn(target):
82
- * - Factory targets → extract factory's return type (protocol controls everything)
83
- * - GGContractClass → ContractLocatorAccess (IPC)
84
- * - Constructor/GGLocatorKey → LocatorLookupAccess (IPC)
85
- * - Plain objects → recurse into properties
86
- */
87
- export type GGTestCallOn<T> =
88
- T extends { [CALL_ON_FACTORY]: (ctx?: GGContext) => infer R } ? R
89
- : T extends GGContractClass<infer TContract> ? ContractLocatorAccess<TContract>
90
- : T extends GGLocatorKey<infer Instance> ? LocatorLookupAccess<Instance>
91
- : T extends new (...args: any[]) => infer Instance ? LocatorLookupAccess<Instance>
92
- : T extends object ? { [K in keyof T]: GGTestCallOn<T[K]> }
93
- : never;
94
-
95
- // ============================================================================
96
- // The callOn function
97
- // ============================================================================
98
-
99
- /**
100
- * Check if target implements the CALL_ON_FACTORY interface.
101
- */
102
- function hasCallOnFactory(target: unknown): target is GGCallOnFactory {
103
- return target != null && typeof (target as any)[CALL_ON_FACTORY] === 'function';
104
- }
105
-
106
- /**
107
- * Check if target is a simple class (constructor function).
108
- */
109
- function isSimpleClass(value: unknown): value is new (...args: any[]) => any {
110
- return typeof value === 'function' && value.prototype !== undefined && value.prototype.constructor === value;
111
- }
112
-
113
- /**
114
- * Invoke methods on runtime instances via a unified interface.
115
- *
116
- * Resolution order:
117
- * 1. Factory symbol (HTTP APIs, WebSocket APIs provide their own)
118
- * 2. GGContractClass → IPC lookup via @contract: prefix
119
- * 3. GGLocatorKey → IPC lookup via key name
120
- * 4. Simple class → IPC lookup via @testable: prefix
121
- *
122
- * @example
123
- * await callOn(MyService, ctx).doSomething("hello")
124
- * await callOn(ChainApi, ctx).quickWeatherCheck({city: "NYC"})
125
- * await callOn(ChainApiContract, ctx).quickWeatherCheck({city: "NYC"})
126
- */
127
- export function callOn<T extends GGCallOnFactory>(target: T, ctx?: GGContext): GGTestCallOn<T>;
128
- export function callOn<T extends GGContractApiDefinition>(target: GGContractClass<T>, ctx?: GGContext): ContractLocatorAccess<T>;
129
- export function callOn<T>(target: GGLocatorKey<T>, ctx?: GGContext): LocatorLookupAccess<T>;
130
- export function callOn<T extends new (...args: any[]) => any>(target: T, ctx?: GGContext): LocatorLookupAccess<InstanceType<T>>;
131
- export function callOn<T extends object>(target: T, ctx?: GGContext): GGTestCallOn<T>;
132
- export function callOn(target: any, ctx?: GGContext): any {
133
- ctx ??= new GGContext("Test")
134
-
135
- if (hasCallOnFactory(target)) {
136
- return target[CALL_ON_FACTORY](ctx);
137
- }
138
-
139
- const keyName = resolveKeyName(target);
140
- return createCallOnProxy(keyName, ctx);
141
- }
142
-
143
- // ============================================================================
144
- // Helper functions for key resolution
145
- // ============================================================================
146
-
147
- /**
148
- * Resolve a target to its locator key name.
149
- * Handles GGContractClass, GGLocatorKey, and simple classes.
150
- */
151
- function resolveKeyName(target: any): string {
152
- if (target instanceof GGContractClass) {
153
- return LOCATOR_KEY_PREFIX_FOR_CONTRACT + target.name;
154
- }
155
- if (target instanceof GGLocatorKey) {
156
- return target.name;
157
- }
158
- if (isSimpleClass(target)) {
159
- return LOCATOR_KEY_PREFIX_FOR_TESTABLE + target.name;
160
- }
161
- throw new Error(`Unknown callOn target: ${target?.name ?? target?.constructor?.name ?? typeof target}`);
162
- }
163
-
164
- /**
165
- * Create a proxy that intercepts method calls and returns GGTestActionForLocatorOnCall.
166
- */
167
- function createCallOnProxy(keyName: string, ctx: GGContext, targetRuntimes?: GGTestRuntime[]): any {
168
- return new Proxy({}, {
169
- get(_, methodName: string) {
170
- return (...args: any[]) => new GGTestActionForLocatorOnCall<any>(ctx, keyName, methodName, args, targetRuntimes);
171
- }
172
- });
173
- }
174
-
175
- // ============================================================================
176
- // Targeted callOn - for explicit runtime selection
177
- // ============================================================================
178
-
179
- /**
180
- * Invoke methods on runtime instances, targeting specific runtimes.
181
- * Use this when you need to explicitly select which runtime to call
182
- * when multiple different runtime classes have the same service.
183
- *
184
- * @param target - The target to call (class, GGLocatorKey, or GGContractClass)
185
- * @param runtimes - The specific runtimes to target
186
- * @param ctx - Optional context for the call
187
- *
188
- * @example
189
- * const f = GGTest.startWorker({chain: ChainRuntime, weather: WeatherOnlyRuntime});
190
- * // Both have WeatherService - explicitly target one:
191
- * await f.chain.callOn(WeatherService).getWeather("Test")
192
- */
193
- export function callOnTargeted<T>(target: T, runtimes: GGTestRuntime[], ctx?: GGContext): GGTestCallOn<T>;
194
- export function callOnTargeted(target: any, runtimes: GGTestRuntime[], ctx?: GGContext): any {
195
- ctx ??= new GGContext("Test");
196
-
197
- // Factory targets don't support runtime targeting - they control their own routing
198
- if (hasCallOnFactory(target)) {
199
- return target[CALL_ON_FACTORY](ctx);
200
- }
201
-
202
- const keyName = resolveKeyName(target);
203
- return createCallOnProxy(keyName, ctx, runtimes);
204
- }
205
-
206
- /**
207
- * Process a collection of targets and create callOn proxies for each.
208
- * It can be a recursive object where all "testable things" are replaced with callOn(X) handlers.
209
- * {serviceA: ApiA, sub: { serviceB: ApiB}} -> {serviceA: callOn(ApiA), sub: { serviceB: callOn(ApiB)}}
210
- */
211
- export function callOnCollection<T extends object | any[]>(target: T, ctx?: GGContext): GGTestCallOnCollection<T> {
212
- const result: any = Array.isArray(target) ? [] : {};
213
- for (const key in target) {
214
- const value = target[key];
215
- if (value != null) {
216
- if (hasCallOnFactory(value) || value instanceof GGContractClass || value instanceof GGLocatorKey || isSimpleClass(value)) {
217
- result[key] = callOn(value as any, ctx);
218
- } else if (typeof value === 'object') {
219
- result[key] = callOnCollection(value, ctx);
220
- }
221
- }
222
- }
223
- return result;
224
- }
1
+ /**
2
+ * Unified callOn function for invoking methods on runtime instances.
3
+ *
4
+ * Works with:
5
+ * - @testable classes: Direct service invocation via IPC
6
+ * - GGLocatorKey: Direct instance lookup via IPC
7
+ * - GGContractClass: Contract implementation lookup via IPC
8
+ * - Contract holders (HTTP APIs, etc.): Delegated to their own factory
9
+ *
10
+ * @example
11
+ * // Call @testable service
12
+ * await callOn(MyService).doSomething("hello")
13
+ *
14
+ * // Call HTTP API (goes through HTTP transport)
15
+ * await callOn(ChainApi).quickWeatherCheck({city: "NYC"})
16
+ *
17
+ * // Call contract directly (skips HTTP, uses IPC)
18
+ * await callOn(ChainApiContract).quickWeatherCheck({city: "NYC"})
19
+ *
20
+ * // With context
21
+ * await callOn(MyService, ctx).doSomething("hello")
22
+ */
23
+
24
+ import {GGLocatorKey} from "@grest-ts/locator";
25
+ import {GGTestActionForLocatorOnCall} from "./GGTestActionForLocatorOnCall";
26
+ import {LOCATOR_KEY_PREFIX_FOR_TESTABLE} from "@grest-ts/testkit-runtime";
27
+ import {GGContractApiDefinition, GGContractClass, GGContractMethod} from "@grest-ts/schema";
28
+ import {GGContext} from "@grest-ts/context";
29
+ import {LOCATOR_KEY_PREFIX_FOR_CONTRACT} from "./GGContractClass.implement";
30
+ import type {GGTestRuntime} from "../GGTestRuntime";
31
+
32
+ // ============================================================================
33
+ // Factory symbol for protocol-specific callOn support
34
+ // ============================================================================
35
+
36
+ /**
37
+ * Symbol used by contract holders (HTTP APIs, WebSocket APIs, etc.) to provide
38
+ * their own callOn proxy. This allows protocols to control how calls are made
39
+ * without callOn needing to know about specific protocols.
40
+ *
41
+ * The factory can return anything - there's no constraint on the return type.
42
+ * Each protocol fully controls what methods and types are available.
43
+ */
44
+ export const CALL_ON_FACTORY = Symbol.for("gg:callOnFactory");
45
+
46
+ /**
47
+ * Marker interface for targets that provide their own callOn proxy.
48
+ * The factory can return any type - protocols have full control.
49
+ */
50
+ export interface GGCallOnFactory {
51
+ [CALL_ON_FACTORY](ctx?: GGContext): unknown;
52
+ }
53
+
54
+ // ============================================================================
55
+ // Type definitions
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Maps a class's async methods to GGLocatorLookupTestAction calls.
60
+ * Used for @testable classes and GGLocatorKey lookups.
61
+ */
62
+ export type LocatorLookupAccess<T> = {
63
+ [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R>
64
+ ? (...args: A) => GGTestActionForLocatorOnCall<R>
65
+ : never
66
+ };
67
+
68
+ /**
69
+ * Maps contract methods to GGLocatorLookupTestAction calls.
70
+ * Used for direct GGContractClass lookups via IPC.
71
+ */
72
+ export type ContractLocatorAccess<TContract> = {
73
+ [K in keyof TContract]: TContract[K] extends GGContractMethod<infer Input, infer Output>
74
+ ? Input extends undefined ? () => GGTestActionForLocatorOnCall<Output> : (data: Input) => GGTestActionForLocatorOnCall<Output>
75
+ : never
76
+ };
77
+
78
+ export type GGTestCallOnCollection<T> = { [K in keyof T]: GGTestCallOn<T[K]> }
79
+
80
+ /**
81
+ * Resolves the return type for callOn(target):
82
+ * - Factory targets → extract factory's return type (protocol controls everything)
83
+ * - GGContractClass → ContractLocatorAccess (IPC)
84
+ * - Constructor/GGLocatorKey → LocatorLookupAccess (IPC)
85
+ * - Plain objects → recurse into properties
86
+ */
87
+ export type GGTestCallOn<T> =
88
+ T extends { [CALL_ON_FACTORY]: (ctx?: GGContext) => infer R } ? R
89
+ : T extends GGContractClass<infer TContract> ? ContractLocatorAccess<TContract>
90
+ : T extends GGLocatorKey<infer Instance> ? LocatorLookupAccess<Instance>
91
+ : T extends new (...args: any[]) => infer Instance ? LocatorLookupAccess<Instance>
92
+ : T extends object ? { [K in keyof T]: GGTestCallOn<T[K]> }
93
+ : never;
94
+
95
+ // ============================================================================
96
+ // The callOn function
97
+ // ============================================================================
98
+
99
+ /**
100
+ * Check if target implements the CALL_ON_FACTORY interface.
101
+ */
102
+ function hasCallOnFactory(target: unknown): target is GGCallOnFactory {
103
+ return target != null && typeof (target as any)[CALL_ON_FACTORY] === 'function';
104
+ }
105
+
106
+ /**
107
+ * Check if target is a simple class (constructor function).
108
+ */
109
+ function isSimpleClass(value: unknown): value is new (...args: any[]) => any {
110
+ return typeof value === 'function' && value.prototype !== undefined && value.prototype.constructor === value;
111
+ }
112
+
113
+ /**
114
+ * Invoke methods on runtime instances via a unified interface.
115
+ *
116
+ * Resolution order:
117
+ * 1. Factory symbol (HTTP APIs, WebSocket APIs provide their own)
118
+ * 2. GGContractClass → IPC lookup via @contract: prefix
119
+ * 3. GGLocatorKey → IPC lookup via key name
120
+ * 4. Simple class → IPC lookup via @testable: prefix
121
+ *
122
+ * @example
123
+ * await callOn(MyService, ctx).doSomething("hello")
124
+ * await callOn(ChainApi, ctx).quickWeatherCheck({city: "NYC"})
125
+ * await callOn(ChainApiContract, ctx).quickWeatherCheck({city: "NYC"})
126
+ */
127
+ export function callOn<T extends GGCallOnFactory>(target: T, ctx?: GGContext): GGTestCallOn<T>;
128
+ export function callOn<T extends GGContractApiDefinition>(target: GGContractClass<T>, ctx?: GGContext): ContractLocatorAccess<T>;
129
+ export function callOn<T>(target: GGLocatorKey<T>, ctx?: GGContext): LocatorLookupAccess<T>;
130
+ export function callOn<T extends new (...args: any[]) => any>(target: T, ctx?: GGContext): LocatorLookupAccess<InstanceType<T>>;
131
+ export function callOn<T extends object>(target: T, ctx?: GGContext): GGTestCallOn<T>;
132
+ export function callOn(target: any, ctx?: GGContext): any {
133
+ ctx ??= new GGContext("Test")
134
+
135
+ if (hasCallOnFactory(target)) {
136
+ return target[CALL_ON_FACTORY](ctx);
137
+ }
138
+
139
+ const keyName = resolveKeyName(target);
140
+ return createCallOnProxy(keyName, ctx);
141
+ }
142
+
143
+ // ============================================================================
144
+ // Helper functions for key resolution
145
+ // ============================================================================
146
+
147
+ /**
148
+ * Resolve a target to its locator key name.
149
+ * Handles GGContractClass, GGLocatorKey, and simple classes.
150
+ */
151
+ function resolveKeyName(target: any): string {
152
+ if (target instanceof GGContractClass) {
153
+ return LOCATOR_KEY_PREFIX_FOR_CONTRACT + target.name;
154
+ }
155
+ if (target instanceof GGLocatorKey) {
156
+ return target.name;
157
+ }
158
+ if (isSimpleClass(target)) {
159
+ return LOCATOR_KEY_PREFIX_FOR_TESTABLE + target.name;
160
+ }
161
+ throw new Error(`Unknown callOn target: ${target?.name ?? target?.constructor?.name ?? typeof target}`);
162
+ }
163
+
164
+ /**
165
+ * Create a proxy that intercepts method calls and returns GGTestActionForLocatorOnCall.
166
+ */
167
+ function createCallOnProxy(keyName: string, ctx: GGContext, targetRuntimes?: GGTestRuntime[]): any {
168
+ return new Proxy({}, {
169
+ get(_, methodName: string) {
170
+ return (...args: any[]) => new GGTestActionForLocatorOnCall<any>(ctx, keyName, methodName, args, targetRuntimes);
171
+ }
172
+ });
173
+ }
174
+
175
+ // ============================================================================
176
+ // Targeted callOn - for explicit runtime selection
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Invoke methods on runtime instances, targeting specific runtimes.
181
+ * Use this when you need to explicitly select which runtime to call
182
+ * when multiple different runtime classes have the same service.
183
+ *
184
+ * @param target - The target to call (class, GGLocatorKey, or GGContractClass)
185
+ * @param runtimes - The specific runtimes to target
186
+ * @param ctx - Optional context for the call
187
+ *
188
+ * @example
189
+ * const f = GGTest.startWorker({chain: ChainRuntime, weather: WeatherOnlyRuntime});
190
+ * // Both have WeatherService - explicitly target one:
191
+ * await f.chain.callOn(WeatherService).getWeather("Test")
192
+ */
193
+ export function callOnTargeted<T>(target: T, runtimes: GGTestRuntime[], ctx?: GGContext): GGTestCallOn<T>;
194
+ export function callOnTargeted(target: any, runtimes: GGTestRuntime[], ctx?: GGContext): any {
195
+ ctx ??= new GGContext("Test");
196
+
197
+ // Factory targets don't support runtime targeting - they control their own routing
198
+ if (hasCallOnFactory(target)) {
199
+ return target[CALL_ON_FACTORY](ctx);
200
+ }
201
+
202
+ const keyName = resolveKeyName(target);
203
+ return createCallOnProxy(keyName, ctx, runtimes);
204
+ }
205
+
206
+ /**
207
+ * Process a collection of targets and create callOn proxies for each.
208
+ * It can be a recursive object where all "testable things" are replaced with callOn(X) handlers.
209
+ * {serviceA: ApiA, sub: { serviceB: ApiB}} -> {serviceA: callOn(ApiA), sub: { serviceB: callOn(ApiB)}}
210
+ */
211
+ export function callOnCollection<T extends object | any[]>(target: T, ctx?: GGContext): GGTestCallOnCollection<T> {
212
+ const result: any = Array.isArray(target) ? [] : {};
213
+ for (const key in target) {
214
+ const value = target[key];
215
+ if (value != null) {
216
+ if (hasCallOnFactory(value) || value instanceof GGContractClass || value instanceof GGLocatorKey || isSimpleClass(value)) {
217
+ result[key] = callOn(value as any, ctx);
218
+ } else if (typeof value === 'object') {
219
+ result[key] = callOnCollection(value, ctx);
220
+ }
221
+ }
222
+ }
223
+ return result;
224
+ }