@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.
- package/LICENSE +21 -21
- package/README.md +413 -413
- package/dist/src/runner/isolated-loader.mjs +91 -91
- package/dist/src/runner/worker-loader.mjs +49 -49
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/GGBundleTest.ts +89 -89
- package/src/GGTest.ts +318 -318
- package/src/GGTestContext.ts +74 -74
- package/src/GGTestRunner.ts +308 -308
- package/src/GGTestRuntime.ts +265 -265
- package/src/GGTestRuntimeWorker.ts +159 -159
- package/src/GGTestSharedRef.ts +116 -116
- package/src/GGTestkitExtensionsDiscovery.ts +26 -26
- package/src/IGGLocalDiscoveryServer.ts +16 -16
- package/src/callOn/GGCallOnSelector.ts +61 -61
- package/src/callOn/GGContractClass.implement.ts +43 -43
- package/src/callOn/GGTestActionForLocatorOnCall.ts +134 -134
- package/src/callOn/TestableIPC.ts +81 -81
- package/src/callOn/callOn.ts +224 -224
- package/src/callOn/registerOnCallHandler.ts +123 -123
- package/src/index-node.ts +64 -64
- package/src/mockable/GGMockable.ts +22 -22
- package/src/mockable/GGMockableCall.ts +45 -45
- package/src/mockable/GGMockableIPC.ts +20 -20
- package/src/mockable/GGMockableInterceptor.ts +44 -44
- package/src/mockable/GGMockableInterceptorsServer.ts +69 -69
- package/src/mockable/mockable.ts +71 -71
- package/src/runner/InlineRunner.ts +47 -47
- package/src/runner/IsolatedRunner.ts +179 -179
- package/src/runner/RuntimeRunner.ts +15 -15
- package/src/runner/WorkerRunner.ts +179 -179
- package/src/runner/isolated-loader.mjs +91 -91
- package/src/runner/worker-loader.mjs +49 -49
- package/src/testers/GGCallInterceptor.ts +224 -224
- package/src/testers/GGMockWith.ts +92 -92
- package/src/testers/GGSpyWith.ts +115 -115
- package/src/testers/GGTestAction.ts +332 -332
- package/src/testers/GGTestComponent.ts +16 -16
- package/src/testers/GGTestSelector.ts +223 -223
- package/src/testers/IGGTestInterceptor.ts +10 -10
- package/src/testers/IGGTestWith.ts +15 -15
- package/src/testers/RuntimeSelector.ts +151 -151
- package/src/utils/GGExpectations.ts +78 -78
- package/src/utils/GGTestError.ts +36 -36
- package/src/utils/captureStack.ts +53 -53
package/src/callOn/callOn.ts
CHANGED
|
@@ -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
|
+
}
|