@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
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Selector extension that adds targeted callOn to selectors.
|
|
3
|
-
*
|
|
4
|
-
* This allows explicitly targeting specific runtimes when multiple
|
|
5
|
-
* different runtime classes have the same service registered.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* const f = GGTest.startWorker({chain: ChainRuntime, weather: WeatherOnlyRuntime});
|
|
9
|
-
*
|
|
10
|
-
* // Both have WeatherService - explicitly target one:
|
|
11
|
-
* await f.chain.callOn(WeatherService).getWeather("Test").toMatchObject({...})
|
|
12
|
-
* await f.weather.callOn(WeatherService).getWeather("Test").toMatchObject({...})
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import {GGContext} from "@grest-ts/context";
|
|
16
|
-
import {GGTestSelector, GGTestSelectorExtension} from "../testers/GGTestSelector";
|
|
17
|
-
import {RuntimeConstructor} from "../testers/RuntimeSelector";
|
|
18
|
-
import {callOnTargeted, GGTestCallOn} from "./callOn";
|
|
19
|
-
import type {GGTestRuntime} from "../GGTestRuntime";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Type for the callable callOn extension.
|
|
23
|
-
* Can be called as a function to invoke targeted callOn.
|
|
24
|
-
*/
|
|
25
|
-
export interface GGCallOnSelectorCallable {
|
|
26
|
-
<T>(target: T, ctx?: GGContext): GGTestCallOn<T>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Extension that adds .callOn() as a callable to Selectors.
|
|
31
|
-
* Uses the selector's runtimes to target specific instances.
|
|
32
|
-
*
|
|
33
|
-
* Returns a callable that can be used directly: f.chain.callOn(WeatherService)
|
|
34
|
-
*/
|
|
35
|
-
export class GGCallOnSelector extends GGTestSelectorExtension {
|
|
36
|
-
|
|
37
|
-
public static readonly PROPERTY_NAME = "callOn";
|
|
38
|
-
|
|
39
|
-
constructor(runtimes: GGTestRuntime[]) {
|
|
40
|
-
super(runtimes);
|
|
41
|
-
// Return a callable function instead of this instance
|
|
42
|
-
const callable = <T>(target: T, ctx?: GGContext): GGTestCallOn<T> => {
|
|
43
|
-
return callOnTargeted(target, runtimes, ctx);
|
|
44
|
-
};
|
|
45
|
-
return callable as unknown as this;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Declaration merging to add 'callOn' to SelectorExtensions
|
|
50
|
-
declare module "@grest-ts/testkit" {
|
|
51
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
52
|
-
interface SelectorExtensions<T extends RuntimeConstructor[]> {
|
|
53
|
-
callOn: GGCallOnSelectorCallable;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Ensure RuntimeConstructor import is recognized (for declaration merging above)
|
|
58
|
-
export type _RuntimeConstructorRef = RuntimeConstructor;
|
|
59
|
-
|
|
60
|
-
// Register the extension
|
|
61
|
-
GGTestSelector.addExtension(GGCallOnSelector);
|
|
1
|
+
/**
|
|
2
|
+
* Selector extension that adds targeted callOn to selectors.
|
|
3
|
+
*
|
|
4
|
+
* This allows explicitly targeting specific runtimes when multiple
|
|
5
|
+
* different runtime classes have the same service registered.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const f = GGTest.startWorker({chain: ChainRuntime, weather: WeatherOnlyRuntime});
|
|
9
|
+
*
|
|
10
|
+
* // Both have WeatherService - explicitly target one:
|
|
11
|
+
* await f.chain.callOn(WeatherService).getWeather("Test").toMatchObject({...})
|
|
12
|
+
* await f.weather.callOn(WeatherService).getWeather("Test").toMatchObject({...})
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {GGContext} from "@grest-ts/context";
|
|
16
|
+
import {GGTestSelector, GGTestSelectorExtension} from "../testers/GGTestSelector";
|
|
17
|
+
import {RuntimeConstructor} from "../testers/RuntimeSelector";
|
|
18
|
+
import {callOnTargeted, GGTestCallOn} from "./callOn";
|
|
19
|
+
import type {GGTestRuntime} from "../GGTestRuntime";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Type for the callable callOn extension.
|
|
23
|
+
* Can be called as a function to invoke targeted callOn.
|
|
24
|
+
*/
|
|
25
|
+
export interface GGCallOnSelectorCallable {
|
|
26
|
+
<T>(target: T, ctx?: GGContext): GGTestCallOn<T>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extension that adds .callOn() as a callable to Selectors.
|
|
31
|
+
* Uses the selector's runtimes to target specific instances.
|
|
32
|
+
*
|
|
33
|
+
* Returns a callable that can be used directly: f.chain.callOn(WeatherService)
|
|
34
|
+
*/
|
|
35
|
+
export class GGCallOnSelector extends GGTestSelectorExtension {
|
|
36
|
+
|
|
37
|
+
public static readonly PROPERTY_NAME = "callOn";
|
|
38
|
+
|
|
39
|
+
constructor(runtimes: GGTestRuntime[]) {
|
|
40
|
+
super(runtimes);
|
|
41
|
+
// Return a callable function instead of this instance
|
|
42
|
+
const callable = <T>(target: T, ctx?: GGContext): GGTestCallOn<T> => {
|
|
43
|
+
return callOnTargeted(target, runtimes, ctx);
|
|
44
|
+
};
|
|
45
|
+
return callable as unknown as this;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Declaration merging to add 'callOn' to SelectorExtensions
|
|
50
|
+
declare module "@grest-ts/testkit" {
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
52
|
+
interface SelectorExtensions<T extends RuntimeConstructor[]> {
|
|
53
|
+
callOn: GGCallOnSelectorCallable;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Ensure RuntimeConstructor import is recognized (for declaration merging above)
|
|
58
|
+
export type _RuntimeConstructorRef = RuntimeConstructor;
|
|
59
|
+
|
|
60
|
+
// Register the extension
|
|
61
|
+
GGTestSelector.addExtension(GGCallOnSelector);
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Patches GGContractClass.implement() to auto-register contract instances in GGLocator.
|
|
3
|
-
*
|
|
4
|
-
* This enables callOn(ContractClass) to work without each protocol (HTTP, WebSocket, etc.)
|
|
5
|
-
* needing to manually register the contract instance.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {GGContractClass} from "@grest-ts/schema";
|
|
9
|
-
import {GGLocator, GGLocatorKey} from "@grest-ts/locator";
|
|
10
|
-
|
|
11
|
-
// Store the original implement method
|
|
12
|
-
const originalImplement = GGContractClass.prototype.implement;
|
|
13
|
-
|
|
14
|
-
export const LOCATOR_KEY_PREFIX_FOR_CONTRACT = "@contract:"
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Patched implement() that registers the returned client in GGLocator.
|
|
18
|
-
*/
|
|
19
|
-
GGContractClass.prototype.implement = function (
|
|
20
|
-
this: GGContractClass<any>,
|
|
21
|
-
instance: any,
|
|
22
|
-
options?: any
|
|
23
|
-
) {
|
|
24
|
-
const contractName = this.name;
|
|
25
|
-
|
|
26
|
-
// Get the client from original implement
|
|
27
|
-
const client = originalImplement.call(this, instance, options);
|
|
28
|
-
|
|
29
|
-
// Register in GGLocator for callOn(Contract) access
|
|
30
|
-
const scope = GGLocator.tryGetScope();
|
|
31
|
-
if (scope) {
|
|
32
|
-
const key = new GGLocatorKey<typeof client>(LOCATOR_KEY_PREFIX_FOR_CONTRACT + contractName);
|
|
33
|
-
if (scope.has(key)) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
`Contract '${contractName}' is already registered in this scope. ` +
|
|
36
|
-
`If you need multiple instances, use different contract names.`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
scope.set(key, client);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return client;
|
|
43
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Patches GGContractClass.implement() to auto-register contract instances in GGLocator.
|
|
3
|
+
*
|
|
4
|
+
* This enables callOn(ContractClass) to work without each protocol (HTTP, WebSocket, etc.)
|
|
5
|
+
* needing to manually register the contract instance.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {GGContractClass} from "@grest-ts/schema";
|
|
9
|
+
import {GGLocator, GGLocatorKey} from "@grest-ts/locator";
|
|
10
|
+
|
|
11
|
+
// Store the original implement method
|
|
12
|
+
const originalImplement = GGContractClass.prototype.implement;
|
|
13
|
+
|
|
14
|
+
export const LOCATOR_KEY_PREFIX_FOR_CONTRACT = "@contract:"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Patched implement() that registers the returned client in GGLocator.
|
|
18
|
+
*/
|
|
19
|
+
GGContractClass.prototype.implement = function (
|
|
20
|
+
this: GGContractClass<any>,
|
|
21
|
+
instance: any,
|
|
22
|
+
options?: any
|
|
23
|
+
) {
|
|
24
|
+
const contractName = this.name;
|
|
25
|
+
|
|
26
|
+
// Get the client from original implement
|
|
27
|
+
const client = originalImplement.call(this, instance, options);
|
|
28
|
+
|
|
29
|
+
// Register in GGLocator for callOn(Contract) access
|
|
30
|
+
const scope = GGLocator.tryGetScope();
|
|
31
|
+
if (scope) {
|
|
32
|
+
const key = new GGLocatorKey<typeof client>(LOCATOR_KEY_PREFIX_FOR_CONTRACT + contractName);
|
|
33
|
+
if (scope.has(key)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Contract '${contractName}' is already registered in this scope. ` +
|
|
36
|
+
`If you need multiple instances, use different contract names.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
scope.set(key, client);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return client;
|
|
43
|
+
};
|
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test action for invoking methods via GGLocator lookup.
|
|
3
|
-
*
|
|
4
|
-
* Used for:
|
|
5
|
-
* - @testable services (key: @testable:ClassName)
|
|
6
|
-
* - Direct contract calls (key: @contract:ContractName)
|
|
7
|
-
* - Custom GGLocatorKey lookups
|
|
8
|
-
*
|
|
9
|
-
* Extends GGTestAction to support .with() for mocks/spies and
|
|
10
|
-
* response expectations like .toMatchObject(), .toEqual().
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import {GGTestAction, GGTestActionConfig, tActionRawData} from "../testers/GGTestAction";
|
|
14
|
-
import {GG_TEST_RUNNER} from "../GGTestRunner";
|
|
15
|
-
import {SerializedContext, TestableInvokeResult, TestableIPC} from "./TestableIPC";
|
|
16
|
-
import {GGTestError} from "../utils/GGTestError";
|
|
17
|
-
import {GGContext} from "@grest-ts/context";
|
|
18
|
-
import type {GGTestRuntime} from "../GGTestRuntime";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Serialize a GGContext to a plain object for IPC transfer.
|
|
22
|
-
* Flattens the context hierarchy into a single object.
|
|
23
|
-
*/
|
|
24
|
-
function serializeContext(ctx: GGContext): SerializedContext {
|
|
25
|
-
const result: SerializedContext = {};
|
|
26
|
-
// Access the private values map via type assertion
|
|
27
|
-
const ctxAny = ctx as any;
|
|
28
|
-
if (ctxAny.values instanceof Map) {
|
|
29
|
-
for (const [key, value] of ctxAny.values) {
|
|
30
|
-
result[key] = value;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
// Also serialize parent context values (child values take precedence)
|
|
34
|
-
if (ctxAny.parent) {
|
|
35
|
-
const parentValues = serializeContext(ctxAny.parent);
|
|
36
|
-
return {...parentValues, ...result};
|
|
37
|
-
}
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Action for invoking a method via GGLocator lookup over IPC.
|
|
43
|
-
*
|
|
44
|
-
* @typeParam T - The expected return type of the method
|
|
45
|
-
*/
|
|
46
|
-
export class GGTestActionForLocatorOnCall<T> extends GGTestAction<T> {
|
|
47
|
-
|
|
48
|
-
private readonly keyName: string;
|
|
49
|
-
private readonly methodName: string;
|
|
50
|
-
private readonly args: any[];
|
|
51
|
-
private readonly targetRuntimes?: GGTestRuntime[];
|
|
52
|
-
|
|
53
|
-
constructor(ctx: GGContext, keyName: string, methodName: string, args: any[], targetRuntimes?: GGTestRuntime[]) {
|
|
54
|
-
const config: GGTestActionConfig = {
|
|
55
|
-
noResponse: false,
|
|
56
|
-
logData: {
|
|
57
|
-
message: `[${keyName}.${methodName}]`,
|
|
58
|
-
request: args.length > 0 ? args : undefined
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
super(ctx, config);
|
|
62
|
-
this.keyName = keyName;
|
|
63
|
-
this.methodName = methodName;
|
|
64
|
-
this.args = args;
|
|
65
|
-
this.targetRuntimes = targetRuntimes;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// -------------------------------------------------
|
|
69
|
-
// Action execution
|
|
70
|
-
// -------------------------------------------------
|
|
71
|
-
|
|
72
|
-
protected async executeAction(): Promise<tActionRawData> {
|
|
73
|
-
const runner = GG_TEST_RUNNER.get();
|
|
74
|
-
|
|
75
|
-
// Use target runtimes if provided, otherwise all runtimes
|
|
76
|
-
const searchRuntimes = this.targetRuntimes ?? runner.getAllRuntimes();
|
|
77
|
-
|
|
78
|
-
// Find runtimes that have this key registered
|
|
79
|
-
const candidates = searchRuntimes.filter(r => r.hasLocatorKey(this.keyName));
|
|
80
|
-
|
|
81
|
-
if (candidates.length === 0) {
|
|
82
|
-
// No runtime has this key - provide helpful error
|
|
83
|
-
const allRuntimes = runner.getAllRuntimes();
|
|
84
|
-
if (allRuntimes.length === 0) {
|
|
85
|
-
throw new GGTestError({
|
|
86
|
-
test: `No runtimes available to invoke ${this.keyName}.${this.methodName}`,
|
|
87
|
-
expected: "At least one runtime to be started",
|
|
88
|
-
received: "No runtimes found"
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
throw new GGTestError({
|
|
92
|
-
test: `Instance '${this.keyName}' not found in any runtime`,
|
|
93
|
-
expected: `A @testable instance registered with key '${this.keyName}'`,
|
|
94
|
-
received: `Key not registered in any of ${allRuntimes.length} runtime(s): ${allRuntimes.map(r => r.name).join(', ')}`
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (candidates.length > 1) {
|
|
99
|
-
// Check if all candidates are the same runtime class - if so, just use first one
|
|
100
|
-
const uniqueClassNames = new Set(candidates.map(r => r.className));
|
|
101
|
-
if (uniqueClassNames.size > 1) {
|
|
102
|
-
// Different runtime classes have this key - actual ambiguity
|
|
103
|
-
throw new GGTestError({
|
|
104
|
-
test: `Multiple different runtimes have '${this.keyName}' registered`,
|
|
105
|
-
expected: `Key '${this.keyName}' to be unique across different runtime classes, or use explicit runtime targeting`,
|
|
106
|
-
received: `Found in ${candidates.length} runtimes with different classes: ${[...uniqueClassNames].join(', ')}`
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
// Same runtime class - just use first instance (they're identical)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Exactly one runtime has this key - send IPC directly
|
|
113
|
-
const runtime = candidates[0];
|
|
114
|
-
const context = this.ctx ? serializeContext(this.ctx) : undefined;
|
|
115
|
-
|
|
116
|
-
const result: TestableInvokeResult = await runtime.sendCommand(TestableIPC.invoke, {
|
|
117
|
-
keyName: this.keyName,
|
|
118
|
-
methodName: this.methodName,
|
|
119
|
-
args: this.args,
|
|
120
|
-
context
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
if (result.success) {
|
|
124
|
-
return result.result as tActionRawData;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
throw new GGTestError({
|
|
128
|
-
test: `Error invoking ${this.keyName}.${this.methodName}`,
|
|
129
|
-
expected: "Method to execute successfully",
|
|
130
|
-
received: result.error || "Unknown error",
|
|
131
|
-
extra: result.stack
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Test action for invoking methods via GGLocator lookup.
|
|
3
|
+
*
|
|
4
|
+
* Used for:
|
|
5
|
+
* - @testable services (key: @testable:ClassName)
|
|
6
|
+
* - Direct contract calls (key: @contract:ContractName)
|
|
7
|
+
* - Custom GGLocatorKey lookups
|
|
8
|
+
*
|
|
9
|
+
* Extends GGTestAction to support .with() for mocks/spies and
|
|
10
|
+
* response expectations like .toMatchObject(), .toEqual().
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {GGTestAction, GGTestActionConfig, tActionRawData} from "../testers/GGTestAction";
|
|
14
|
+
import {GG_TEST_RUNNER} from "../GGTestRunner";
|
|
15
|
+
import {SerializedContext, TestableInvokeResult, TestableIPC} from "./TestableIPC";
|
|
16
|
+
import {GGTestError} from "../utils/GGTestError";
|
|
17
|
+
import {GGContext} from "@grest-ts/context";
|
|
18
|
+
import type {GGTestRuntime} from "../GGTestRuntime";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Serialize a GGContext to a plain object for IPC transfer.
|
|
22
|
+
* Flattens the context hierarchy into a single object.
|
|
23
|
+
*/
|
|
24
|
+
function serializeContext(ctx: GGContext): SerializedContext {
|
|
25
|
+
const result: SerializedContext = {};
|
|
26
|
+
// Access the private values map via type assertion
|
|
27
|
+
const ctxAny = ctx as any;
|
|
28
|
+
if (ctxAny.values instanceof Map) {
|
|
29
|
+
for (const [key, value] of ctxAny.values) {
|
|
30
|
+
result[key] = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Also serialize parent context values (child values take precedence)
|
|
34
|
+
if (ctxAny.parent) {
|
|
35
|
+
const parentValues = serializeContext(ctxAny.parent);
|
|
36
|
+
return {...parentValues, ...result};
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Action for invoking a method via GGLocator lookup over IPC.
|
|
43
|
+
*
|
|
44
|
+
* @typeParam T - The expected return type of the method
|
|
45
|
+
*/
|
|
46
|
+
export class GGTestActionForLocatorOnCall<T> extends GGTestAction<T> {
|
|
47
|
+
|
|
48
|
+
private readonly keyName: string;
|
|
49
|
+
private readonly methodName: string;
|
|
50
|
+
private readonly args: any[];
|
|
51
|
+
private readonly targetRuntimes?: GGTestRuntime[];
|
|
52
|
+
|
|
53
|
+
constructor(ctx: GGContext, keyName: string, methodName: string, args: any[], targetRuntimes?: GGTestRuntime[]) {
|
|
54
|
+
const config: GGTestActionConfig = {
|
|
55
|
+
noResponse: false,
|
|
56
|
+
logData: {
|
|
57
|
+
message: `[${keyName}.${methodName}]`,
|
|
58
|
+
request: args.length > 0 ? args : undefined
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
super(ctx, config);
|
|
62
|
+
this.keyName = keyName;
|
|
63
|
+
this.methodName = methodName;
|
|
64
|
+
this.args = args;
|
|
65
|
+
this.targetRuntimes = targetRuntimes;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// -------------------------------------------------
|
|
69
|
+
// Action execution
|
|
70
|
+
// -------------------------------------------------
|
|
71
|
+
|
|
72
|
+
protected async executeAction(): Promise<tActionRawData> {
|
|
73
|
+
const runner = GG_TEST_RUNNER.get();
|
|
74
|
+
|
|
75
|
+
// Use target runtimes if provided, otherwise all runtimes
|
|
76
|
+
const searchRuntimes = this.targetRuntimes ?? runner.getAllRuntimes();
|
|
77
|
+
|
|
78
|
+
// Find runtimes that have this key registered
|
|
79
|
+
const candidates = searchRuntimes.filter(r => r.hasLocatorKey(this.keyName));
|
|
80
|
+
|
|
81
|
+
if (candidates.length === 0) {
|
|
82
|
+
// No runtime has this key - provide helpful error
|
|
83
|
+
const allRuntimes = runner.getAllRuntimes();
|
|
84
|
+
if (allRuntimes.length === 0) {
|
|
85
|
+
throw new GGTestError({
|
|
86
|
+
test: `No runtimes available to invoke ${this.keyName}.${this.methodName}`,
|
|
87
|
+
expected: "At least one runtime to be started",
|
|
88
|
+
received: "No runtimes found"
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
throw new GGTestError({
|
|
92
|
+
test: `Instance '${this.keyName}' not found in any runtime`,
|
|
93
|
+
expected: `A @testable instance registered with key '${this.keyName}'`,
|
|
94
|
+
received: `Key not registered in any of ${allRuntimes.length} runtime(s): ${allRuntimes.map(r => r.name).join(', ')}`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (candidates.length > 1) {
|
|
99
|
+
// Check if all candidates are the same runtime class - if so, just use first one
|
|
100
|
+
const uniqueClassNames = new Set(candidates.map(r => r.className));
|
|
101
|
+
if (uniqueClassNames.size > 1) {
|
|
102
|
+
// Different runtime classes have this key - actual ambiguity
|
|
103
|
+
throw new GGTestError({
|
|
104
|
+
test: `Multiple different runtimes have '${this.keyName}' registered`,
|
|
105
|
+
expected: `Key '${this.keyName}' to be unique across different runtime classes, or use explicit runtime targeting`,
|
|
106
|
+
received: `Found in ${candidates.length} runtimes with different classes: ${[...uniqueClassNames].join(', ')}`
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// Same runtime class - just use first instance (they're identical)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Exactly one runtime has this key - send IPC directly
|
|
113
|
+
const runtime = candidates[0];
|
|
114
|
+
const context = this.ctx ? serializeContext(this.ctx) : undefined;
|
|
115
|
+
|
|
116
|
+
const result: TestableInvokeResult = await runtime.sendCommand(TestableIPC.invoke, {
|
|
117
|
+
keyName: this.keyName,
|
|
118
|
+
methodName: this.methodName,
|
|
119
|
+
args: this.args,
|
|
120
|
+
context
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (result.success) {
|
|
124
|
+
return result.result as tActionRawData;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw new GGTestError({
|
|
128
|
+
test: `Error invoking ${this.keyName}.${this.methodName}`,
|
|
129
|
+
expected: "Method to execute successfully",
|
|
130
|
+
received: result.error || "Unknown error",
|
|
131
|
+
extra: result.stack
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IPC protocol definitions for GGLocator-based service invocation.
|
|
3
|
-
*
|
|
4
|
-
* Defines the messages exchanged between test runner and runtime worker
|
|
5
|
-
* for invoking methods on registered instances (@testable, @contract, etc.).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {IPCClient, IPCServer} from "@grest-ts/ipc";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Serialized context data for IPC transfer.
|
|
12
|
-
* Contains flattened key-value pairs from GGContext.
|
|
13
|
-
*/
|
|
14
|
-
export type SerializedContext = Record<string, any>;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Payload for invoking a method on a registered instance.
|
|
18
|
-
*/
|
|
19
|
-
export interface TestableInvokePayload {
|
|
20
|
-
/** The GGLocatorKey name (e.g., "@testable:ServiceB", "@contract:ChainApi", or custom key) */
|
|
21
|
-
keyName: string;
|
|
22
|
-
/** The method name to invoke */
|
|
23
|
-
methodName: string;
|
|
24
|
-
/** Arguments to pass to the method (as array) */
|
|
25
|
-
args: any[];
|
|
26
|
-
/** Serialized GGContext data to restore on the worker side */
|
|
27
|
-
context?: SerializedContext;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Result from invoking a testable method.
|
|
32
|
-
*/
|
|
33
|
-
export interface TestableInvokeResult {
|
|
34
|
-
/** Whether the invocation succeeded */
|
|
35
|
-
success: boolean;
|
|
36
|
-
/** The return value from the method (if success) */
|
|
37
|
-
result?: any;
|
|
38
|
-
/** Error message (if failed) */
|
|
39
|
-
error?: string;
|
|
40
|
-
/** Error stack trace (if failed) */
|
|
41
|
-
stack?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Payload for registering locator keys from a runtime.
|
|
46
|
-
*/
|
|
47
|
-
export interface KeyRegistrationPayload {
|
|
48
|
-
/** Runtime ID sending the registration */
|
|
49
|
-
runtimeId: string;
|
|
50
|
-
/** All GGLocatorKey names registered in this runtime */
|
|
51
|
-
keys: string[];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* IPC endpoints for testable service invocation.
|
|
56
|
-
*/
|
|
57
|
-
export const TestableIPC = {
|
|
58
|
-
/**
|
|
59
|
-
* Messages FROM test server TO worker.
|
|
60
|
-
*/
|
|
61
|
-
client: {
|
|
62
|
-
/**
|
|
63
|
-
* Invoke a method on a testable service instance.
|
|
64
|
-
*/
|
|
65
|
-
invoke: IPCClient.defineRequest<TestableInvokePayload, TestableInvokeResult>("testable/invoke"),
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Messages FROM worker TO test server.
|
|
70
|
-
*/
|
|
71
|
-
server: {
|
|
72
|
-
/**
|
|
73
|
-
* Register all locator keys available in a runtime.
|
|
74
|
-
* Sent from worker to test runner after compose completes.
|
|
75
|
-
*/
|
|
76
|
-
registerKeys: IPCServer.defineRequest<KeyRegistrationPayload, void>("testable/register-keys"),
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
// Legacy alias for backwards compatibility
|
|
80
|
-
invoke: IPCClient.defineRequest<TestableInvokePayload, TestableInvokeResult>("testable/invoke"),
|
|
81
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* IPC protocol definitions for GGLocator-based service invocation.
|
|
3
|
+
*
|
|
4
|
+
* Defines the messages exchanged between test runner and runtime worker
|
|
5
|
+
* for invoking methods on registered instances (@testable, @contract, etc.).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {IPCClient, IPCServer} from "@grest-ts/ipc";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Serialized context data for IPC transfer.
|
|
12
|
+
* Contains flattened key-value pairs from GGContext.
|
|
13
|
+
*/
|
|
14
|
+
export type SerializedContext = Record<string, any>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Payload for invoking a method on a registered instance.
|
|
18
|
+
*/
|
|
19
|
+
export interface TestableInvokePayload {
|
|
20
|
+
/** The GGLocatorKey name (e.g., "@testable:ServiceB", "@contract:ChainApi", or custom key) */
|
|
21
|
+
keyName: string;
|
|
22
|
+
/** The method name to invoke */
|
|
23
|
+
methodName: string;
|
|
24
|
+
/** Arguments to pass to the method (as array) */
|
|
25
|
+
args: any[];
|
|
26
|
+
/** Serialized GGContext data to restore on the worker side */
|
|
27
|
+
context?: SerializedContext;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Result from invoking a testable method.
|
|
32
|
+
*/
|
|
33
|
+
export interface TestableInvokeResult {
|
|
34
|
+
/** Whether the invocation succeeded */
|
|
35
|
+
success: boolean;
|
|
36
|
+
/** The return value from the method (if success) */
|
|
37
|
+
result?: any;
|
|
38
|
+
/** Error message (if failed) */
|
|
39
|
+
error?: string;
|
|
40
|
+
/** Error stack trace (if failed) */
|
|
41
|
+
stack?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Payload for registering locator keys from a runtime.
|
|
46
|
+
*/
|
|
47
|
+
export interface KeyRegistrationPayload {
|
|
48
|
+
/** Runtime ID sending the registration */
|
|
49
|
+
runtimeId: string;
|
|
50
|
+
/** All GGLocatorKey names registered in this runtime */
|
|
51
|
+
keys: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* IPC endpoints for testable service invocation.
|
|
56
|
+
*/
|
|
57
|
+
export const TestableIPC = {
|
|
58
|
+
/**
|
|
59
|
+
* Messages FROM test server TO worker.
|
|
60
|
+
*/
|
|
61
|
+
client: {
|
|
62
|
+
/**
|
|
63
|
+
* Invoke a method on a testable service instance.
|
|
64
|
+
*/
|
|
65
|
+
invoke: IPCClient.defineRequest<TestableInvokePayload, TestableInvokeResult>("testable/invoke"),
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Messages FROM worker TO test server.
|
|
70
|
+
*/
|
|
71
|
+
server: {
|
|
72
|
+
/**
|
|
73
|
+
* Register all locator keys available in a runtime.
|
|
74
|
+
* Sent from worker to test runner after compose completes.
|
|
75
|
+
*/
|
|
76
|
+
registerKeys: IPCServer.defineRequest<KeyRegistrationPayload, void>("testable/register-keys"),
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// Legacy alias for backwards compatibility
|
|
80
|
+
invoke: IPCClient.defineRequest<TestableInvokePayload, TestableInvokeResult>("testable/invoke"),
|
|
81
|
+
};
|