@memberjunction/server 2.117.0 → 2.119.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +10 -0
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/generated/generated.d.ts +653 -6
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +4033 -52
- package/dist/generated/generated.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +3 -4
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +32 -176
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts +41 -0
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -0
- package/dist/resolvers/RunTestResolver.js +349 -0
- package/dist/resolvers/RunTestResolver.js.map +1 -0
- package/package.json +41 -40
- package/src/agents/skip-sdk.ts +10 -0
- package/src/generated/generated.ts +2557 -41
- package/src/resolvers/RunAIAgentResolver.ts +42 -292
- package/src/resolvers/RunTestResolver.ts +369 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Resolver,
|
|
3
|
+
Mutation,
|
|
4
|
+
Query,
|
|
5
|
+
Arg,
|
|
6
|
+
Ctx,
|
|
7
|
+
ObjectType,
|
|
8
|
+
Field,
|
|
9
|
+
PubSub,
|
|
10
|
+
PubSubEngine,
|
|
11
|
+
ID,
|
|
12
|
+
Int
|
|
13
|
+
} from 'type-graphql';
|
|
14
|
+
import { AppContext, UserPayload } from '../types.js';
|
|
15
|
+
import { LogError, LogStatus } from '@memberjunction/core';
|
|
16
|
+
import { TestEngine } from '@memberjunction/testing-engine';
|
|
17
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
18
|
+
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
19
|
+
|
|
20
|
+
// ===== GraphQL Types =====
|
|
21
|
+
|
|
22
|
+
@ObjectType()
|
|
23
|
+
export class TestRunResult {
|
|
24
|
+
@Field()
|
|
25
|
+
success: boolean;
|
|
26
|
+
|
|
27
|
+
@Field({ nullable: true })
|
|
28
|
+
errorMessage?: string;
|
|
29
|
+
|
|
30
|
+
@Field({ nullable: true })
|
|
31
|
+
executionTimeMs?: number;
|
|
32
|
+
|
|
33
|
+
@Field()
|
|
34
|
+
result: string; // JSON serialized TestRunResult
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@ObjectType()
|
|
38
|
+
export class TestSuiteRunResult {
|
|
39
|
+
@Field()
|
|
40
|
+
success: boolean;
|
|
41
|
+
|
|
42
|
+
@Field({ nullable: true })
|
|
43
|
+
errorMessage?: string;
|
|
44
|
+
|
|
45
|
+
@Field({ nullable: true })
|
|
46
|
+
executionTimeMs?: number;
|
|
47
|
+
|
|
48
|
+
@Field()
|
|
49
|
+
result: string; // JSON serialized TestSuiteRunResult
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@ObjectType()
|
|
53
|
+
export class TestExecutionProgress {
|
|
54
|
+
@Field()
|
|
55
|
+
currentStep: string;
|
|
56
|
+
|
|
57
|
+
@Field(() => Int)
|
|
58
|
+
percentage: number;
|
|
59
|
+
|
|
60
|
+
@Field()
|
|
61
|
+
message: string;
|
|
62
|
+
|
|
63
|
+
@Field({ nullable: true })
|
|
64
|
+
testName?: string;
|
|
65
|
+
|
|
66
|
+
@Field({ nullable: true })
|
|
67
|
+
driverType?: string;
|
|
68
|
+
|
|
69
|
+
@Field({ nullable: true })
|
|
70
|
+
oracleEvaluation?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@ObjectType()
|
|
74
|
+
export class TestExecutionStreamMessage {
|
|
75
|
+
@Field(() => ID)
|
|
76
|
+
sessionId: string;
|
|
77
|
+
|
|
78
|
+
@Field(() => ID)
|
|
79
|
+
testRunId: string;
|
|
80
|
+
|
|
81
|
+
@Field()
|
|
82
|
+
type: 'progress' | 'oracle_eval' | 'complete' | 'error';
|
|
83
|
+
|
|
84
|
+
@Field({ nullable: true })
|
|
85
|
+
progress?: TestExecutionProgress;
|
|
86
|
+
|
|
87
|
+
@Field()
|
|
88
|
+
timestamp: Date;
|
|
89
|
+
|
|
90
|
+
// Not a GraphQL field - used internally
|
|
91
|
+
testRun?: any;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ===== Resolver =====
|
|
95
|
+
|
|
96
|
+
@Resolver()
|
|
97
|
+
export class RunTestResolver extends ResolverBase {
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Execute a single test
|
|
101
|
+
*/
|
|
102
|
+
@Mutation(() => TestRunResult)
|
|
103
|
+
async RunTest(
|
|
104
|
+
@Arg('testId') testId: string,
|
|
105
|
+
@Arg('verbose', { nullable: true }) verbose: boolean = true,
|
|
106
|
+
@Arg('environment', { nullable: true }) environment?: string,
|
|
107
|
+
@PubSub() pubSub?: PubSubEngine,
|
|
108
|
+
@Ctx() { userPayload }: AppContext = {} as AppContext
|
|
109
|
+
): Promise<TestRunResult> {
|
|
110
|
+
const startTime = Date.now();
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const user = this.GetUserFromPayload(userPayload);
|
|
114
|
+
if (!user) {
|
|
115
|
+
throw new Error('User context required');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
LogStatus(`[RunTestResolver] Starting test execution: ${testId}`);
|
|
119
|
+
|
|
120
|
+
// Get singleton instance
|
|
121
|
+
const engine = TestEngine.Instance;
|
|
122
|
+
|
|
123
|
+
// Configure engine (loads driver and oracle registries)
|
|
124
|
+
await engine.Config(verbose, user);
|
|
125
|
+
|
|
126
|
+
// Create progress callback if we have pubSub
|
|
127
|
+
const progressCallback = pubSub ?
|
|
128
|
+
this.createProgressCallback(pubSub, userPayload, testId) :
|
|
129
|
+
undefined;
|
|
130
|
+
|
|
131
|
+
// Run the test
|
|
132
|
+
const options = {
|
|
133
|
+
verbose,
|
|
134
|
+
environment,
|
|
135
|
+
progressCallback
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const result = await engine.RunTest(testId, options, user);
|
|
139
|
+
|
|
140
|
+
// Handle both single result and array of results (RepeatCount > 1)
|
|
141
|
+
let finalResult;
|
|
142
|
+
let allPassed = true;
|
|
143
|
+
|
|
144
|
+
if (Array.isArray(result)) {
|
|
145
|
+
// Multiple iterations - check if all passed
|
|
146
|
+
allPassed = result.every(r => r.status === 'Passed');
|
|
147
|
+
|
|
148
|
+
// For GraphQL, return summary information
|
|
149
|
+
// The full array is serialized in the result field
|
|
150
|
+
finalResult = {
|
|
151
|
+
testRunId: result[0]?.testRunId || '',
|
|
152
|
+
status: allPassed ? 'Passed' as const : 'Failed' as const
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Publish completion for each iteration
|
|
156
|
+
if (pubSub) {
|
|
157
|
+
for (const iterationResult of result) {
|
|
158
|
+
this.publishComplete(pubSub, userPayload, iterationResult);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
LogStatus(`[RunTestResolver] Test completed: ${result.length} iterations, ${allPassed ? 'all passed' : 'some failed'} in ${Date.now() - startTime}ms`);
|
|
163
|
+
} else {
|
|
164
|
+
// Single result
|
|
165
|
+
finalResult = result;
|
|
166
|
+
allPassed = result.status === 'Passed';
|
|
167
|
+
|
|
168
|
+
// Publish completion
|
|
169
|
+
if (pubSub && result.testRunId) {
|
|
170
|
+
this.publishComplete(pubSub, userPayload, result);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
LogStatus(`[RunTestResolver] Test completed: ${result.status} in ${Date.now() - startTime}ms`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const executionTime = Date.now() - startTime;
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
success: allPassed,
|
|
180
|
+
result: JSON.stringify(result), // Full result (single or array)
|
|
181
|
+
executionTimeMs: executionTime
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const executionTime = Date.now() - startTime;
|
|
186
|
+
const errorMsg = (error as Error).message;
|
|
187
|
+
|
|
188
|
+
LogError(`[RunTestResolver] Test execution failed: ${errorMsg}`);
|
|
189
|
+
|
|
190
|
+
// Publish error
|
|
191
|
+
if (pubSub) {
|
|
192
|
+
this.publishError(pubSub, userPayload, testId, errorMsg);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
errorMessage: errorMsg,
|
|
198
|
+
result: JSON.stringify({}),
|
|
199
|
+
executionTimeMs: executionTime
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Execute a test suite
|
|
206
|
+
*/
|
|
207
|
+
@Mutation(() => TestSuiteRunResult)
|
|
208
|
+
async RunTestSuite(
|
|
209
|
+
@Arg('suiteId') suiteId: string,
|
|
210
|
+
@Arg('verbose', { nullable: true }) verbose: boolean = true,
|
|
211
|
+
@Arg('environment', { nullable: true }) environment?: string,
|
|
212
|
+
@Arg('parallel', { nullable: true }) parallel: boolean = false,
|
|
213
|
+
@PubSub() pubSub?: PubSubEngine,
|
|
214
|
+
@Ctx() { userPayload }: AppContext = {} as AppContext
|
|
215
|
+
): Promise<TestSuiteRunResult> {
|
|
216
|
+
const startTime = Date.now();
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const user = this.GetUserFromPayload(userPayload);
|
|
220
|
+
if (!user) {
|
|
221
|
+
throw new Error('User context required');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
LogStatus(`[RunTestResolver] Starting suite execution: ${suiteId}`);
|
|
225
|
+
|
|
226
|
+
const engine = TestEngine.Instance;
|
|
227
|
+
await engine.Config(verbose, user);
|
|
228
|
+
|
|
229
|
+
// Create progress callback
|
|
230
|
+
const progressCallback = pubSub ?
|
|
231
|
+
this.createProgressCallback(pubSub, userPayload, suiteId) :
|
|
232
|
+
undefined;
|
|
233
|
+
|
|
234
|
+
const options = {
|
|
235
|
+
verbose,
|
|
236
|
+
environment,
|
|
237
|
+
parallel,
|
|
238
|
+
progressCallback
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const result = await engine.RunSuite(suiteId, options, user);
|
|
242
|
+
|
|
243
|
+
const executionTime = Date.now() - startTime;
|
|
244
|
+
|
|
245
|
+
LogStatus(`[RunTestResolver] Suite completed: ${result.totalTests} tests in ${executionTime}ms`);
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
success: result.status === 'Completed',
|
|
249
|
+
result: JSON.stringify(result),
|
|
250
|
+
executionTimeMs: executionTime
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
} catch (error) {
|
|
254
|
+
const executionTime = Date.now() - startTime;
|
|
255
|
+
const errorMsg = (error as Error).message;
|
|
256
|
+
|
|
257
|
+
LogError(`[RunTestResolver] Suite execution failed: ${errorMsg}`);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
success: false,
|
|
261
|
+
errorMessage: errorMsg,
|
|
262
|
+
result: JSON.stringify({}),
|
|
263
|
+
executionTimeMs: executionTime
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Query to check if a test is currently running
|
|
270
|
+
*/
|
|
271
|
+
@Query(() => Boolean)
|
|
272
|
+
async IsTestRunning(
|
|
273
|
+
@Arg('testId') testId: string,
|
|
274
|
+
@Ctx() { userPayload }: AppContext = {} as AppContext
|
|
275
|
+
): Promise<boolean> {
|
|
276
|
+
// TODO: Implement running test tracking
|
|
277
|
+
// For now, return false
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ===== Progress Callbacks =====
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Create progress callback for test execution
|
|
285
|
+
*/
|
|
286
|
+
private createProgressCallback(
|
|
287
|
+
pubSub: PubSubEngine,
|
|
288
|
+
userPayload: UserPayload,
|
|
289
|
+
testId: string
|
|
290
|
+
) {
|
|
291
|
+
return (progress: {
|
|
292
|
+
step: string;
|
|
293
|
+
percentage: number;
|
|
294
|
+
message: string;
|
|
295
|
+
metadata?: any;
|
|
296
|
+
}) => {
|
|
297
|
+
LogStatus(`[RunTestResolver] Progress: ${progress.step} - ${progress.percentage}%`);
|
|
298
|
+
|
|
299
|
+
// Get test run from metadata
|
|
300
|
+
const testRun = progress.metadata?.testRun;
|
|
301
|
+
|
|
302
|
+
const progressMsg: TestExecutionStreamMessage = {
|
|
303
|
+
sessionId: userPayload.sessionId || '',
|
|
304
|
+
testRunId: testRun?.ID || testId,
|
|
305
|
+
type: 'progress',
|
|
306
|
+
testRun: testRun ? testRun.GetAll() : undefined,
|
|
307
|
+
progress: {
|
|
308
|
+
currentStep: progress.step,
|
|
309
|
+
percentage: progress.percentage,
|
|
310
|
+
message: progress.message,
|
|
311
|
+
testName: progress.metadata?.testName,
|
|
312
|
+
driverType: progress.metadata?.driverType,
|
|
313
|
+
oracleEvaluation: progress.metadata?.oracleType
|
|
314
|
+
},
|
|
315
|
+
timestamp: new Date()
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
this.publishProgress(pubSub, progressMsg, userPayload);
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private publishProgress(pubSub: PubSubEngine, data: TestExecutionStreamMessage, userPayload: UserPayload) {
|
|
323
|
+
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
324
|
+
message: JSON.stringify({
|
|
325
|
+
resolver: 'RunTestResolver',
|
|
326
|
+
type: 'TestExecutionProgress',
|
|
327
|
+
status: 'ok',
|
|
328
|
+
data,
|
|
329
|
+
}),
|
|
330
|
+
sessionId: userPayload.sessionId,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private publishComplete(pubSub: PubSubEngine, userPayload: UserPayload, result: any) {
|
|
335
|
+
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
336
|
+
message: JSON.stringify({
|
|
337
|
+
resolver: 'RunTestResolver',
|
|
338
|
+
type: 'TestExecutionComplete',
|
|
339
|
+
status: 'ok',
|
|
340
|
+
data: {
|
|
341
|
+
sessionId: userPayload.sessionId,
|
|
342
|
+
testRunId: result.testRunId,
|
|
343
|
+
type: 'complete',
|
|
344
|
+
result: JSON.stringify(result),
|
|
345
|
+
timestamp: new Date()
|
|
346
|
+
},
|
|
347
|
+
}),
|
|
348
|
+
sessionId: userPayload.sessionId,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private publishError(pubSub: PubSubEngine, userPayload: UserPayload, testId: string, errorMsg: string) {
|
|
353
|
+
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
354
|
+
message: JSON.stringify({
|
|
355
|
+
resolver: 'RunTestResolver',
|
|
356
|
+
type: 'TestExecutionError',
|
|
357
|
+
status: 'error',
|
|
358
|
+
data: {
|
|
359
|
+
sessionId: userPayload.sessionId,
|
|
360
|
+
testRunId: testId,
|
|
361
|
+
type: 'error',
|
|
362
|
+
errorMessage: errorMsg,
|
|
363
|
+
timestamp: new Date()
|
|
364
|
+
},
|
|
365
|
+
}),
|
|
366
|
+
sessionId: userPayload.sessionId,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|