@grest-ts/testkit 0.0.6 → 0.0.7
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 +12 -12
- 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,123 +1,123 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker-side handler for GGLocator-based service invocation.
|
|
3
|
-
*
|
|
4
|
-
* Receives IPC requests from tests to invoke methods on registered instances
|
|
5
|
-
* (@testable, @contract, etc.) and returns the results.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {GGLocator, GGLocatorKey} from "@grest-ts/locator";
|
|
9
|
-
import {GGLog} from "@grest-ts/logger";
|
|
10
|
-
import {SerializedContext, TestableIPC, TestableInvokePayload, TestableInvokeResult} from "./TestableIPC";
|
|
11
|
-
import {GGTestRuntimeWorker} from "../GGTestRuntimeWorker";
|
|
12
|
-
import {GGContext} from "@grest-ts/context";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Context for logging
|
|
16
|
-
*/
|
|
17
|
-
const LOG_CONTEXT = {name: "GGLocatorWorkerHandler"};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Deserialize context data into a GGContext.
|
|
21
|
-
* Directly populates the internal values map.
|
|
22
|
-
*/
|
|
23
|
-
function deserializeContext(data: SerializedContext): GGContext {
|
|
24
|
-
const ctx = new GGContext("ipc-context");
|
|
25
|
-
// Access internal values map and populate directly
|
|
26
|
-
const ctxAny = ctx as any;
|
|
27
|
-
for (const [keyName, value] of Object.entries(data)) {
|
|
28
|
-
ctxAny.values.set(keyName, value);
|
|
29
|
-
}
|
|
30
|
-
return ctx;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Register the locator lookup handler on the worker.
|
|
35
|
-
* Called during worker initialization.
|
|
36
|
-
*/
|
|
37
|
-
export function registerOnCallHandler(worker: GGTestRuntimeWorker): void {
|
|
38
|
-
worker.onIpcRequest(TestableIPC.invoke, async (payload: TestableInvokePayload): Promise<TestableInvokeResult> => {
|
|
39
|
-
const {keyName, methodName, args, context} = payload;
|
|
40
|
-
|
|
41
|
-
GGLog.debug(LOG_CONTEXT, `Invoking ${keyName}.${methodName}`, {args, hasContext: !!context});
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
// Look up the instance in the current scope
|
|
45
|
-
const scope = GGLocator.tryGetScope();
|
|
46
|
-
if (!scope) {
|
|
47
|
-
return {
|
|
48
|
-
success: false,
|
|
49
|
-
error: `No GGLocator scope available - is the runtime running?`
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const key = new GGLocatorKey<any>(keyName);
|
|
54
|
-
const instance = scope.tryGet(key);
|
|
55
|
-
|
|
56
|
-
if (!instance) {
|
|
57
|
-
return {
|
|
58
|
-
success: false,
|
|
59
|
-
error: `Instance '${keyName}' not found in GGLocator. ` +
|
|
60
|
-
`Make sure the class is decorated with @testable or registered during compose().`
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Check if method exists
|
|
65
|
-
const method = instance[methodName];
|
|
66
|
-
if (typeof method !== 'function') {
|
|
67
|
-
return {
|
|
68
|
-
success: false,
|
|
69
|
-
error: `Method '${methodName}' not found on instance '${keyName}'. ` +
|
|
70
|
-
`Available methods: ${getMethodNames(instance).join(', ')}`
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Invoke the method, optionally within the provided context
|
|
75
|
-
let result: any;
|
|
76
|
-
if (context) {
|
|
77
|
-
const ctx = deserializeContext(context);
|
|
78
|
-
result = await ctx.run(() => method.apply(instance, args));
|
|
79
|
-
} else {
|
|
80
|
-
result = await method.apply(instance, args);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
GGLog.debug(LOG_CONTEXT, `Invocation ${keyName}.${methodName} completed`, {result});
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
success: true,
|
|
87
|
-
result
|
|
88
|
-
};
|
|
89
|
-
} catch (error: any) {
|
|
90
|
-
GGLog.error(LOG_CONTEXT, `Error invoking ${keyName}.${methodName}`, error);
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
success: false,
|
|
94
|
-
error: error.message || String(error),
|
|
95
|
-
stack: error.stack
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Get method names from an instance for error messages.
|
|
103
|
-
*/
|
|
104
|
-
function getMethodNames(instance: any): string[] {
|
|
105
|
-
const names: string[] = [];
|
|
106
|
-
let proto = Object.getPrototypeOf(instance);
|
|
107
|
-
|
|
108
|
-
while (proto && proto !== Object.prototype) {
|
|
109
|
-
const propNames = Object.getOwnPropertyNames(proto)
|
|
110
|
-
.filter(name => {
|
|
111
|
-
if (name === 'constructor') return false;
|
|
112
|
-
try {
|
|
113
|
-
return typeof proto[name] === 'function';
|
|
114
|
-
} catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
names.push(...propNames);
|
|
119
|
-
proto = Object.getPrototypeOf(proto);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return [...new Set(names)].sort();
|
|
123
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Worker-side handler for GGLocator-based service invocation.
|
|
3
|
+
*
|
|
4
|
+
* Receives IPC requests from tests to invoke methods on registered instances
|
|
5
|
+
* (@testable, @contract, etc.) and returns the results.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {GGLocator, GGLocatorKey} from "@grest-ts/locator";
|
|
9
|
+
import {GGLog} from "@grest-ts/logger";
|
|
10
|
+
import {SerializedContext, TestableIPC, TestableInvokePayload, TestableInvokeResult} from "./TestableIPC";
|
|
11
|
+
import {GGTestRuntimeWorker} from "../GGTestRuntimeWorker";
|
|
12
|
+
import {GGContext} from "@grest-ts/context";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Context for logging
|
|
16
|
+
*/
|
|
17
|
+
const LOG_CONTEXT = {name: "GGLocatorWorkerHandler"};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Deserialize context data into a GGContext.
|
|
21
|
+
* Directly populates the internal values map.
|
|
22
|
+
*/
|
|
23
|
+
function deserializeContext(data: SerializedContext): GGContext {
|
|
24
|
+
const ctx = new GGContext("ipc-context");
|
|
25
|
+
// Access internal values map and populate directly
|
|
26
|
+
const ctxAny = ctx as any;
|
|
27
|
+
for (const [keyName, value] of Object.entries(data)) {
|
|
28
|
+
ctxAny.values.set(keyName, value);
|
|
29
|
+
}
|
|
30
|
+
return ctx;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Register the locator lookup handler on the worker.
|
|
35
|
+
* Called during worker initialization.
|
|
36
|
+
*/
|
|
37
|
+
export function registerOnCallHandler(worker: GGTestRuntimeWorker): void {
|
|
38
|
+
worker.onIpcRequest(TestableIPC.invoke, async (payload: TestableInvokePayload): Promise<TestableInvokeResult> => {
|
|
39
|
+
const {keyName, methodName, args, context} = payload;
|
|
40
|
+
|
|
41
|
+
GGLog.debug(LOG_CONTEXT, `Invoking ${keyName}.${methodName}`, {args, hasContext: !!context});
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// Look up the instance in the current scope
|
|
45
|
+
const scope = GGLocator.tryGetScope();
|
|
46
|
+
if (!scope) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error: `No GGLocator scope available - is the runtime running?`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const key = new GGLocatorKey<any>(keyName);
|
|
54
|
+
const instance = scope.tryGet(key);
|
|
55
|
+
|
|
56
|
+
if (!instance) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
error: `Instance '${keyName}' not found in GGLocator. ` +
|
|
60
|
+
`Make sure the class is decorated with @testable or registered during compose().`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check if method exists
|
|
65
|
+
const method = instance[methodName];
|
|
66
|
+
if (typeof method !== 'function') {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: `Method '${methodName}' not found on instance '${keyName}'. ` +
|
|
70
|
+
`Available methods: ${getMethodNames(instance).join(', ')}`
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Invoke the method, optionally within the provided context
|
|
75
|
+
let result: any;
|
|
76
|
+
if (context) {
|
|
77
|
+
const ctx = deserializeContext(context);
|
|
78
|
+
result = await ctx.run(() => method.apply(instance, args));
|
|
79
|
+
} else {
|
|
80
|
+
result = await method.apply(instance, args);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
GGLog.debug(LOG_CONTEXT, `Invocation ${keyName}.${methodName} completed`, {result});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
result
|
|
88
|
+
};
|
|
89
|
+
} catch (error: any) {
|
|
90
|
+
GGLog.error(LOG_CONTEXT, `Error invoking ${keyName}.${methodName}`, error);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: error.message || String(error),
|
|
95
|
+
stack: error.stack
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get method names from an instance for error messages.
|
|
103
|
+
*/
|
|
104
|
+
function getMethodNames(instance: any): string[] {
|
|
105
|
+
const names: string[] = [];
|
|
106
|
+
let proto = Object.getPrototypeOf(instance);
|
|
107
|
+
|
|
108
|
+
while (proto && proto !== Object.prototype) {
|
|
109
|
+
const propNames = Object.getOwnPropertyNames(proto)
|
|
110
|
+
.filter(name => {
|
|
111
|
+
if (name === 'constructor') return false;
|
|
112
|
+
try {
|
|
113
|
+
return typeof proto[name] === 'function';
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
names.push(...propNames);
|
|
119
|
+
proto = Object.getPrototypeOf(proto);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return [...new Set(names)].sort();
|
|
123
|
+
}
|
package/src/index-node.ts
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @grest-ts/testkit - Component testing library
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// Core test framework
|
|
6
|
-
import path from "path";
|
|
7
|
-
import {fileURLToPath} from "url";
|
|
8
|
-
import {WorkerRunner} from "./runner/WorkerRunner";
|
|
9
|
-
import {IsolatedRunner} from "./runner/IsolatedRunner";
|
|
10
|
-
|
|
11
|
-
export * from './GGTest'
|
|
12
|
-
export * from './GGTestRunner'
|
|
13
|
-
export * from './IGGLocalDiscoveryServer'
|
|
14
|
-
export * from './GGTestRuntime'
|
|
15
|
-
export * from './testers/GGTestComponent'
|
|
16
|
-
export * from './mockable/GGMockable'
|
|
17
|
-
export * from './mockable/GGMockableCall'
|
|
18
|
-
export * from './mockable/mockable'
|
|
19
|
-
export * from './GGTestContext'
|
|
20
|
-
|
|
21
|
-
// Mockable component server (import triggers factory registration)
|
|
22
|
-
export * from './mockable/GGMockableInterceptorsServer'
|
|
23
|
-
|
|
24
|
-
// Testable - direct service invocation from tests
|
|
25
|
-
export * from './callOn/callOn'
|
|
26
|
-
export * from './callOn/GGTestActionForLocatorOnCall'
|
|
27
|
-
export * from './callOn/GGCallOnSelector'
|
|
28
|
-
|
|
29
|
-
// Contract registration - patches GGContractClass.implement() to auto-register
|
|
30
|
-
import './callOn/GGContractClass.implement'
|
|
31
|
-
|
|
32
|
-
// Control channel for runtime config updates
|
|
33
|
-
export * from './GGTestRuntimeWorker'
|
|
34
|
-
|
|
35
|
-
// Test utilities - core infrastructure
|
|
36
|
-
export * from './testers/IGGTestWith'
|
|
37
|
-
export * from './testers/GGTestAction'
|
|
38
|
-
export * from './testers/GGMockWith'
|
|
39
|
-
export * from './testers/GGSpyWith'
|
|
40
|
-
export * from './testers/GGCallInterceptor'
|
|
41
|
-
export * from './utils/GGExpectations'
|
|
42
|
-
export * from './utils/GGTestError'
|
|
43
|
-
export {captureStackSourceFile} from './utils/captureStack'
|
|
44
|
-
export * from './GGTestSharedRef'
|
|
45
|
-
|
|
46
|
-
// Production bundle DCE verification
|
|
47
|
-
export * from './GGBundleTest'
|
|
48
|
-
|
|
49
|
-
// Mockable interceptor
|
|
50
|
-
export * from './mockable/GGMockableInterceptor'
|
|
51
|
-
|
|
52
|
-
// Selector system for runtime access
|
|
53
|
-
export * from './testers/GGTestSelector'
|
|
54
|
-
export * from './testers/RuntimeSelector'
|
|
55
|
-
|
|
56
|
-
// Worker runner (path configured by @grest-ts/testkit-vitest)
|
|
57
|
-
export * from './runner/WorkerRunner'
|
|
58
|
-
|
|
59
|
-
export type * from "./testers/IGGTestWith";
|
|
60
|
-
export type * from "./testers/IGGTestInterceptor";
|
|
61
|
-
|
|
62
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
63
|
-
WorkerRunner.setWorkerLoaderPath(path.join(__dirname, 'runner', 'worker-loader.mjs'));
|
|
64
|
-
IsolatedRunner.setIsolatedLoaderPath(path.join(__dirname, 'runner', 'isolated-loader.mjs'));
|
|
1
|
+
/**
|
|
2
|
+
* @grest-ts/testkit - Component testing library
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Core test framework
|
|
6
|
+
import path from "path";
|
|
7
|
+
import {fileURLToPath} from "url";
|
|
8
|
+
import {WorkerRunner} from "./runner/WorkerRunner";
|
|
9
|
+
import {IsolatedRunner} from "./runner/IsolatedRunner";
|
|
10
|
+
|
|
11
|
+
export * from './GGTest'
|
|
12
|
+
export * from './GGTestRunner'
|
|
13
|
+
export * from './IGGLocalDiscoveryServer'
|
|
14
|
+
export * from './GGTestRuntime'
|
|
15
|
+
export * from './testers/GGTestComponent'
|
|
16
|
+
export * from './mockable/GGMockable'
|
|
17
|
+
export * from './mockable/GGMockableCall'
|
|
18
|
+
export * from './mockable/mockable'
|
|
19
|
+
export * from './GGTestContext'
|
|
20
|
+
|
|
21
|
+
// Mockable component server (import triggers factory registration)
|
|
22
|
+
export * from './mockable/GGMockableInterceptorsServer'
|
|
23
|
+
|
|
24
|
+
// Testable - direct service invocation from tests
|
|
25
|
+
export * from './callOn/callOn'
|
|
26
|
+
export * from './callOn/GGTestActionForLocatorOnCall'
|
|
27
|
+
export * from './callOn/GGCallOnSelector'
|
|
28
|
+
|
|
29
|
+
// Contract registration - patches GGContractClass.implement() to auto-register
|
|
30
|
+
import './callOn/GGContractClass.implement'
|
|
31
|
+
|
|
32
|
+
// Control channel for runtime config updates
|
|
33
|
+
export * from './GGTestRuntimeWorker'
|
|
34
|
+
|
|
35
|
+
// Test utilities - core infrastructure
|
|
36
|
+
export * from './testers/IGGTestWith'
|
|
37
|
+
export * from './testers/GGTestAction'
|
|
38
|
+
export * from './testers/GGMockWith'
|
|
39
|
+
export * from './testers/GGSpyWith'
|
|
40
|
+
export * from './testers/GGCallInterceptor'
|
|
41
|
+
export * from './utils/GGExpectations'
|
|
42
|
+
export * from './utils/GGTestError'
|
|
43
|
+
export {captureStackSourceFile} from './utils/captureStack'
|
|
44
|
+
export * from './GGTestSharedRef'
|
|
45
|
+
|
|
46
|
+
// Production bundle DCE verification
|
|
47
|
+
export * from './GGBundleTest'
|
|
48
|
+
|
|
49
|
+
// Mockable interceptor
|
|
50
|
+
export * from './mockable/GGMockableInterceptor'
|
|
51
|
+
|
|
52
|
+
// Selector system for runtime access
|
|
53
|
+
export * from './testers/GGTestSelector'
|
|
54
|
+
export * from './testers/RuntimeSelector'
|
|
55
|
+
|
|
56
|
+
// Worker runner (path configured by @grest-ts/testkit-vitest)
|
|
57
|
+
export * from './runner/WorkerRunner'
|
|
58
|
+
|
|
59
|
+
export type * from "./testers/IGGTestWith";
|
|
60
|
+
export type * from "./testers/IGGTestInterceptor";
|
|
61
|
+
|
|
62
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
63
|
+
WorkerRunner.setWorkerLoaderPath(path.join(__dirname, 'runner', 'worker-loader.mjs'));
|
|
64
|
+
IsolatedRunner.setIsolatedLoaderPath(path.join(__dirname, 'runner', 'isolated-loader.mjs'));
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Marker interface for external API services that can be mocked in tests.
|
|
3
|
-
* When a class implements this interface, the test framework can intercept
|
|
4
|
-
* all method calls and provide mock responses.
|
|
5
|
-
*
|
|
6
|
-
* Examples: REST APIs, GraphQL clients, third-party SDKs
|
|
7
|
-
*/
|
|
8
|
-
export interface GGMockableExternalApi {
|
|
9
|
-
// Marker interface - no methods required
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Marker interface for database/storage services that can be mocked in tests.
|
|
14
|
-
* When a class implements this interface, the test framework can intercept
|
|
15
|
-
* all method calls and provide mock responses.
|
|
16
|
-
*
|
|
17
|
-
* Examples: Database clients, cache services, file storage
|
|
18
|
-
*/
|
|
19
|
-
export interface GGMockableDatabaseApi {
|
|
20
|
-
// Marker interface - no methods required
|
|
21
|
-
}
|
|
22
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Marker interface for external API services that can be mocked in tests.
|
|
3
|
+
* When a class implements this interface, the test framework can intercept
|
|
4
|
+
* all method calls and provide mock responses.
|
|
5
|
+
*
|
|
6
|
+
* Examples: REST APIs, GraphQL clients, third-party SDKs
|
|
7
|
+
*/
|
|
8
|
+
export interface GGMockableExternalApi {
|
|
9
|
+
// Marker interface - no methods required
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Marker interface for database/storage services that can be mocked in tests.
|
|
14
|
+
* When a class implements this interface, the test framework can intercept
|
|
15
|
+
* all method calls and provide mock responses.
|
|
16
|
+
*
|
|
17
|
+
* Examples: Database clients, cache services, file storage
|
|
18
|
+
*/
|
|
19
|
+
export interface GGMockableDatabaseApi {
|
|
20
|
+
// Marker interface - no methods required
|
|
21
|
+
}
|
|
22
|
+
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import {GG_TEST_RUNTIME_WORKER} from "../GGTestRuntimeWorker";
|
|
2
|
-
import {CALL_THROUGH} from "./GGMockableInterceptorsServer";
|
|
3
|
-
import {GGMockableIPC} from "./GGMockableIPC";
|
|
4
|
-
|
|
5
|
-
const MOCKABLE_WRAPPED = Symbol('GGMockableWrapped');
|
|
6
|
-
|
|
7
|
-
export function GGMockableCall(cls: any, methodName: string, nameMapping: string[]): void {
|
|
8
|
-
// Skip if already wrapped (happens in INLINE mode with multiple runtime instances)
|
|
9
|
-
if (cls.prototype[methodName]?.[MOCKABLE_WRAPPED]) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const originalMethod = cls.prototype[methodName];
|
|
14
|
-
const wrappedMethod = async function (this: any, ...inputArgs: any[]) {
|
|
15
|
-
const worker = GG_TEST_RUNTIME_WORKER.get();
|
|
16
|
-
|
|
17
|
-
const args: any = {}
|
|
18
|
-
for (let i = 0; i < inputArgs.length; i++) {
|
|
19
|
-
if (!nameMapping[i]) break;
|
|
20
|
-
args[nameMapping[i]] = inputArgs[i];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const result = await worker.ipcClient.sendFrameworkRequest(GGMockableIPC.testServer.call, {
|
|
24
|
-
className: cls.name,
|
|
25
|
-
methodName: methodName,
|
|
26
|
-
callArgs: args
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
if (result === CALL_THROUGH) {
|
|
30
|
-
const realResult = await originalMethod.apply(this, inputArgs);
|
|
31
|
-
await worker.ipcClient.sendFrameworkRequest(GGMockableIPC.testServer.spyResult, {
|
|
32
|
-
className: cls.name,
|
|
33
|
-
methodName: methodName,
|
|
34
|
-
callResult: realResult
|
|
35
|
-
});
|
|
36
|
-
return realResult;
|
|
37
|
-
} else {
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Mark as wrapped and assign to prototype
|
|
43
|
-
(wrappedMethod as any)[MOCKABLE_WRAPPED] = true;
|
|
44
|
-
cls.prototype[methodName] = wrappedMethod;
|
|
45
|
-
}
|
|
1
|
+
import {GG_TEST_RUNTIME_WORKER} from "../GGTestRuntimeWorker";
|
|
2
|
+
import {CALL_THROUGH} from "./GGMockableInterceptorsServer";
|
|
3
|
+
import {GGMockableIPC} from "./GGMockableIPC";
|
|
4
|
+
|
|
5
|
+
const MOCKABLE_WRAPPED = Symbol('GGMockableWrapped');
|
|
6
|
+
|
|
7
|
+
export function GGMockableCall(cls: any, methodName: string, nameMapping: string[]): void {
|
|
8
|
+
// Skip if already wrapped (happens in INLINE mode with multiple runtime instances)
|
|
9
|
+
if (cls.prototype[methodName]?.[MOCKABLE_WRAPPED]) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const originalMethod = cls.prototype[methodName];
|
|
14
|
+
const wrappedMethod = async function (this: any, ...inputArgs: any[]) {
|
|
15
|
+
const worker = GG_TEST_RUNTIME_WORKER.get();
|
|
16
|
+
|
|
17
|
+
const args: any = {}
|
|
18
|
+
for (let i = 0; i < inputArgs.length; i++) {
|
|
19
|
+
if (!nameMapping[i]) break;
|
|
20
|
+
args[nameMapping[i]] = inputArgs[i];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const result = await worker.ipcClient.sendFrameworkRequest(GGMockableIPC.testServer.call, {
|
|
24
|
+
className: cls.name,
|
|
25
|
+
methodName: methodName,
|
|
26
|
+
callArgs: args
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (result === CALL_THROUGH) {
|
|
30
|
+
const realResult = await originalMethod.apply(this, inputArgs);
|
|
31
|
+
await worker.ipcClient.sendFrameworkRequest(GGMockableIPC.testServer.spyResult, {
|
|
32
|
+
className: cls.name,
|
|
33
|
+
methodName: methodName,
|
|
34
|
+
callResult: realResult
|
|
35
|
+
});
|
|
36
|
+
return realResult;
|
|
37
|
+
} else {
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Mark as wrapped and assign to prototype
|
|
43
|
+
(wrappedMethod as any)[MOCKABLE_WRAPPED] = true;
|
|
44
|
+
cls.prototype[methodName] = wrappedMethod;
|
|
45
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import {IPCServer} from "@grest-ts/ipc";
|
|
2
|
-
|
|
3
|
-
export interface MockableCallPayload {
|
|
4
|
-
className: string;
|
|
5
|
-
methodName: string;
|
|
6
|
-
callArgs: any;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface MockableSpyResultPayload {
|
|
10
|
-
className: string;
|
|
11
|
-
methodName: string;
|
|
12
|
-
callResult: any;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const GGMockableIPC = {
|
|
16
|
-
testServer: {
|
|
17
|
-
call: IPCServer.defineRequest<MockableCallPayload, any>("mockable/call"),
|
|
18
|
-
spyResult: IPCServer.defineRequest<MockableSpyResultPayload, any>("mockable/spy-result"),
|
|
19
|
-
}
|
|
20
|
-
}
|
|
1
|
+
import {IPCServer} from "@grest-ts/ipc";
|
|
2
|
+
|
|
3
|
+
export interface MockableCallPayload {
|
|
4
|
+
className: string;
|
|
5
|
+
methodName: string;
|
|
6
|
+
callArgs: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface MockableSpyResultPayload {
|
|
10
|
+
className: string;
|
|
11
|
+
methodName: string;
|
|
12
|
+
callResult: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const GGMockableIPC = {
|
|
16
|
+
testServer: {
|
|
17
|
+
call: IPCServer.defineRequest<MockableCallPayload, any>("mockable/call"),
|
|
18
|
+
spyResult: IPCServer.defineRequest<MockableSpyResultPayload, any>("mockable/spy-result"),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import type {GGTestRunner} from "../GGTestRunner";
|
|
2
|
-
import {GGCallInterceptor, GGCallInterceptorConfig} from "../testers/GGCallInterceptor";
|
|
3
|
-
import {GGMockableInterceptorsServer} from "./GGMockableInterceptorsServer";
|
|
4
|
-
|
|
5
|
-
export interface MockableInterceptorConfig extends GGCallInterceptorConfig {
|
|
6
|
-
className: string;
|
|
7
|
-
methodName: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Interceptor for mocking/spying on mockable class methods.
|
|
12
|
-
* Registers with GGMockableInterceptorsServer which handles the IPC communication.
|
|
13
|
-
*
|
|
14
|
-
* For spy mode, the flow is two-phase:
|
|
15
|
-
* 1. mockable/call → onRequest() validates input, returns undefined (CALL_THROUGH)
|
|
16
|
-
* 2. mockable/spy-result → onResponse() validates output
|
|
17
|
-
*/
|
|
18
|
-
export class GGMockableInterceptor extends GGCallInterceptor {
|
|
19
|
-
|
|
20
|
-
public readonly className: string;
|
|
21
|
-
public readonly methodName: string;
|
|
22
|
-
|
|
23
|
-
constructor(test: GGTestRunner, config: MockableInterceptorConfig) {
|
|
24
|
-
super(test, config);
|
|
25
|
-
this.className = config.className;
|
|
26
|
-
this.methodName = config.methodName;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public getKey(): string {
|
|
30
|
-
return `${this.className}.${this.methodName}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
protected doRegister(): void {
|
|
34
|
-
this.test.getExtensionInstance(GGMockableInterceptorsServer).addInterceptor(this);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
protected doUnregister(): void {
|
|
38
|
-
this.test.getExtensionInstance(GGMockableInterceptorsServer).deleteInterceptor(this);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
protected parseResponseData(result: any): any {
|
|
42
|
-
return result;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
1
|
+
import type {GGTestRunner} from "../GGTestRunner";
|
|
2
|
+
import {GGCallInterceptor, GGCallInterceptorConfig} from "../testers/GGCallInterceptor";
|
|
3
|
+
import {GGMockableInterceptorsServer} from "./GGMockableInterceptorsServer";
|
|
4
|
+
|
|
5
|
+
export interface MockableInterceptorConfig extends GGCallInterceptorConfig {
|
|
6
|
+
className: string;
|
|
7
|
+
methodName: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Interceptor for mocking/spying on mockable class methods.
|
|
12
|
+
* Registers with GGMockableInterceptorsServer which handles the IPC communication.
|
|
13
|
+
*
|
|
14
|
+
* For spy mode, the flow is two-phase:
|
|
15
|
+
* 1. mockable/call → onRequest() validates input, returns undefined (CALL_THROUGH)
|
|
16
|
+
* 2. mockable/spy-result → onResponse() validates output
|
|
17
|
+
*/
|
|
18
|
+
export class GGMockableInterceptor extends GGCallInterceptor {
|
|
19
|
+
|
|
20
|
+
public readonly className: string;
|
|
21
|
+
public readonly methodName: string;
|
|
22
|
+
|
|
23
|
+
constructor(test: GGTestRunner, config: MockableInterceptorConfig) {
|
|
24
|
+
super(test, config);
|
|
25
|
+
this.className = config.className;
|
|
26
|
+
this.methodName = config.methodName;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public getKey(): string {
|
|
30
|
+
return `${this.className}.${this.methodName}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected doRegister(): void {
|
|
34
|
+
this.test.getExtensionInstance(GGMockableInterceptorsServer).addInterceptor(this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected doUnregister(): void {
|
|
38
|
+
this.test.getExtensionInstance(GGMockableInterceptorsServer).deleteInterceptor(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected parseResponseData(result: any): any {
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
}
|