@pikku/core 0.12.52 → 0.12.53

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.
@@ -37,6 +37,20 @@ export interface WorkflowExpectEventuallyOptions extends WorkflowStepOptions {
37
37
  interval?: string | number
38
38
  }
39
39
 
40
+ /** Options for workflow.expectError() */
41
+ export interface WorkflowExpectErrorOptions extends WorkflowStepOptions {
42
+ /** Assert the error message matches (string = substring match). */
43
+ matches?: string | RegExp
44
+ }
45
+
46
+ /** Options for workflow.expectService() */
47
+ export interface WorkflowExpectServiceOptions extends WorkflowStepOptions {
48
+ /** Assert a recorded call's first argument deep-equals this value. */
49
+ calledWith?: unknown
50
+ /** Assert the exact number of matching calls. Default: at least one. */
51
+ times?: number
52
+ }
53
+
40
54
  /**
41
55
  * Type signature for workflow.do() RPC form - used by inspector
42
56
  */
@@ -381,6 +395,21 @@ export interface PikkuWorkflowWire {
381
395
  options?: WorkflowExpectEventuallyOptions
382
396
  ) => Promise<TOutput>
383
397
 
398
+ /** Error-path step (scenarios): succeeds only when the RPC throws; returns the message */
399
+ expectError: <TInput = any>(
400
+ stepName: string,
401
+ rpcName: string,
402
+ data: TInput,
403
+ options?: WorkflowExpectErrorOptions
404
+ ) => Promise<string>
405
+
406
+ /** Stub-assertion step (scenarios): asserts `service.method` was called on the target server */
407
+ expectService: (
408
+ stepName: string,
409
+ serviceMethod: string,
410
+ options?: WorkflowExpectServiceOptions
411
+ ) => Promise<void>
412
+
384
413
  /** Sleep for a duration */
385
414
  sleep: WorkflowWireSleep
386
415
 
@@ -60,6 +60,8 @@ import type {
60
60
  WorkflowServiceConfig,
61
61
  WorkflowStepOptions,
62
62
  WorkflowExpectEventuallyOptions,
63
+ WorkflowExpectErrorOptions,
64
+ WorkflowExpectServiceOptions,
63
65
  } from './workflow.types.js'
64
66
  import {
65
67
  continueGraph,
@@ -1099,12 +1101,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1099
1101
  })
1100
1102
  }
1101
1103
 
1102
- /**
1103
- * Start a new workflow run
1104
- * Automatically detects workflow type (DSL or graph) from meta and executes accordingly
1105
- * @param options.inline - If true, execute workflow directly without queue service
1106
- * @param options.startNode - Starting node ID for graph workflows (from wire config)
1107
- */
1108
1104
  /**
1109
1105
  * Start a new workflow run
1110
1106
  * Automatically detects workflow type (DSL or graph) from meta and executes accordingly
@@ -2229,6 +2225,104 @@ export abstract class PikkuWorkflowService implements WorkflowService {
2229
2225
  )
2230
2226
  },
2231
2227
 
2228
+ expectError: async (
2229
+ stepName: string,
2230
+ rpcName: string,
2231
+ data: any,
2232
+ options?: WorkflowExpectErrorOptions
2233
+ ) => {
2234
+ this.verifyStepName(stepName)
2235
+ const resolvedRpcName =
2236
+ addonNamespace && !rpcName.includes(':')
2237
+ ? `${addonNamespace}:${rpcName}`
2238
+ : rpcName
2239
+ return await this.inlineStep(
2240
+ runId,
2241
+ stepName,
2242
+ async () => {
2243
+ let result: any
2244
+ try {
2245
+ result = options?.actor
2246
+ ? await options.actor.invoke(resolvedRpcName, data)
2247
+ : await rpcService.rpcWithWire(resolvedRpcName, data, {})
2248
+ } catch (e: any) {
2249
+ const message = e?.message ?? String(e)
2250
+ if (options?.matches) {
2251
+ const matched =
2252
+ typeof options.matches === 'string'
2253
+ ? message.includes(options.matches)
2254
+ : options.matches.test(message)
2255
+ if (!matched) {
2256
+ throw new Error(
2257
+ `[workflow] expectError '${stepName}' ('${resolvedRpcName}') threw, but the message did not match ${options.matches}: ${message}`
2258
+ )
2259
+ }
2260
+ }
2261
+ return message
2262
+ }
2263
+ throw new Error(
2264
+ `[workflow] expectError '${stepName}' ('${resolvedRpcName}') expected an error but the call succeeded: ${JSON.stringify(result)?.slice(0, 300)}`
2265
+ )
2266
+ },
2267
+ options
2268
+ )
2269
+ },
2270
+
2271
+ expectService: async (
2272
+ stepName: string,
2273
+ serviceMethod: string,
2274
+ options?: WorkflowExpectServiceOptions
2275
+ ) => {
2276
+ this.verifyStepName(stepName)
2277
+ const [service, method] = serviceMethod.split('.')
2278
+ if (!service || !method) {
2279
+ throw new Error(
2280
+ `[workflow] expectService '${stepName}' needs 'service.method', got '${serviceMethod}'`
2281
+ )
2282
+ }
2283
+ await this.inlineStep(
2284
+ runId,
2285
+ stepName,
2286
+ async () => {
2287
+ const rpcName = 'console:getStubCalls'
2288
+ const calls: Array<{
2289
+ service: string
2290
+ method: string
2291
+ args: unknown[]
2292
+ }> = options?.actor
2293
+ ? await options.actor.invoke(rpcName, { service })
2294
+ : await rpcService.rpcWithWire(rpcName, { service }, {})
2295
+ const matching = (calls ?? []).filter(
2296
+ (c) =>
2297
+ c.service === service &&
2298
+ c.method === method &&
2299
+ (options?.calledWith === undefined ||
2300
+ JSON.stringify(c.args?.[0]) ===
2301
+ JSON.stringify(options.calledWith))
2302
+ )
2303
+ const expected = options?.times
2304
+ const ok =
2305
+ expected === undefined
2306
+ ? matching.length > 0
2307
+ : matching.length === expected
2308
+ if (!ok) {
2309
+ const seen =
2310
+ (calls ?? [])
2311
+ .map(
2312
+ (c) =>
2313
+ `${c.service}.${c.method}(${JSON.stringify(c.args?.[0])?.slice(0, 120) ?? ''})`
2314
+ )
2315
+ .join('\n ') || '(none)'
2316
+ throw new Error(
2317
+ `[workflow] expectService '${stepName}' expected ${expected ?? 'at least one'} call(s) to '${serviceMethod}'` +
2318
+ `${options?.calledWith !== undefined ? ` with ${JSON.stringify(options.calledWith)}` : ''}, found ${matching.length}. Recorded:\n ${seen}`
2319
+ )
2320
+ }
2321
+ },
2322
+ options
2323
+ )
2324
+ },
2325
+
2232
2326
  // Implement workflow.sleep()
2233
2327
  sleep: async (stepName: string, duration: string | number) => {
2234
2328
  this.verifyStepName(stepName)
@@ -8,6 +8,8 @@ export type { WorkflowService } from '../../services/workflow-service.js'
8
8
  export type {
9
9
  WorkflowStepOptions,
10
10
  WorkflowExpectEventuallyOptions,
11
+ WorkflowExpectErrorOptions,
12
+ WorkflowExpectServiceOptions,
11
13
  WorkflowWireDoRPC,
12
14
  WorkflowWireDoInline,
13
15
  WorkflowWireSleep,