11// packages/jobs/test/handlers/continuous.test.ts
22
33import { describe , it , expect , beforeEach } from "vitest" ;
4- import { Effect , Layer , Schema , Duration } from "effect" ;
5- import { createTestRuntime , NoopTrackerLayer } from "@durable-effect/core" ;
6- import {
7- ContinuousHandler ,
8- ContinuousHandlerLayer ,
9- type ContinuousResponse ,
10- } from "../../src/handlers/continuous" ;
11- import { MetadataService , MetadataServiceLayer } from "../../src/services/metadata" ;
12- import { AlarmService , AlarmServiceLayer } from "../../src/services/alarm" ;
13- import { RegistryService , RegistryServiceLayer } from "../../src/services/registry" ;
14- import { JobExecutionServiceLayer } from "../../src/services/execution" ;
15- import { CleanupServiceLayer } from "../../src/services/cleanup" ;
16- import { RetryExecutorLayer } from "../../src/retry" ;
4+ import { Effect , Schema , Duration } from "effect" ;
5+ import { ContinuousHandler } from "../../src/handlers/continuous" ;
176import { Continuous } from "../../src/definitions/continuous" ;
18- import type { RuntimeJobRegistry } from "../../src/registry/typed" ;
7+ import {
8+ createTestRegistry ,
9+ createContinuousTestLayer ,
10+ runWithLayer ,
11+ runExitWithLayer ,
12+ } from "./test-utils" ;
1913
2014// =============================================================================
2115// Test Fixtures
@@ -98,86 +92,15 @@ const terminatingPrimitive = Continuous.make({
9892 } ) ,
9993} ) ;
10094
101- // Create test registry using object types (RuntimeJobRegistry)
102- const createTestRegistry = ( ) : RuntimeJobRegistry => ( {
103- continuous : {
104- "counter" : { ...counterPrimitive , name : "counter" } ,
105- "failing" : { ...failingPrimitive , name : "failing" } ,
106- "no-immediate" : { ...noImmediateStartPrimitive , name : "no-immediate" } ,
107- "terminating" : { ...terminatingPrimitive , name : "terminating" } ,
108- } as Record < string , any > ,
109- debounce : { } as Record < string , any > ,
110- workerPool : { } as Record < string , any > ,
111- task : { } as Record < string , any > ,
112- } ) ;
113-
114- // =============================================================================
115- // Test Helpers
116- // =============================================================================
117-
118- // Helper to run Effect with layer, bypassing strict R parameter checking
119- // This is needed because the test registry uses `as Record<string, any>` which
120- // causes `any` to leak into Effect R parameters
121- // eslint-disable-next-line @typescript-eslint/no-explicit-any
122- const runWithLayer = < A , E > (
123- effect : Effect . Effect < A , E , any > ,
124- layer : Layer . Layer < ContinuousHandler >
125- ) : Promise < A > =>
126- Effect . runPromise (
127- effect . pipe ( Effect . provide ( layer ) ) as Effect . Effect < A , E , never >
128- ) ;
129-
130- // eslint-disable-next-line @typescript-eslint/no-explicit-any
131- const runExitWithLayer = < A , E > (
132- effect : Effect . Effect < A , E , any > ,
133- layer : Layer . Layer < ContinuousHandler >
134- ) =>
135- Effect . runPromiseExit (
136- effect . pipe ( Effect . provide ( layer ) ) as Effect . Effect < A , E , never >
137- ) ;
138-
139- // =============================================================================
140- // Test Setup
141- // =============================================================================
142-
143- const createTestLayer = ( initialTime = 1000000 ) => {
144- const { layer : coreLayer , time, handles } = createTestRuntime ( "test-instance" , initialTime ) ;
145- const registry = createTestRegistry ( ) ;
146-
147- const servicesLayer = Layer . mergeAll (
148- MetadataServiceLayer ,
149- AlarmServiceLayer
150- ) . pipe (
151- Layer . provideMerge ( NoopTrackerLayer ) ,
152- Layer . provideMerge ( coreLayer )
153- ) ;
154-
155- // RetryExecutor depends on AlarmService from servicesLayer
156- const retryLayer = RetryExecutorLayer . pipe (
157- Layer . provideMerge ( servicesLayer )
158- ) ;
159-
160- // CleanupService depends on AlarmService and StorageAdapter
161- const cleanupLayer = CleanupServiceLayer . pipe (
162- Layer . provideMerge ( servicesLayer )
163- ) ;
164-
165- // JobExecutionService depends on RetryExecutor, CleanupService, and core adapters
166- const executionLayer = JobExecutionServiceLayer . pipe (
167- Layer . provideMerge ( retryLayer ) ,
168- Layer . provideMerge ( cleanupLayer ) ,
169- Layer . provideMerge ( coreLayer )
170- ) ;
171-
172- const handlerLayer = ContinuousHandlerLayer . pipe (
173- Layer . provideMerge ( RegistryServiceLayer ( registry ) ) ,
174- Layer . provideMerge ( servicesLayer ) ,
175- Layer . provideMerge ( retryLayer ) ,
176- Layer . provideMerge ( executionLayer )
177- ) as Layer . Layer < ContinuousHandler > ;
178-
179- return { layer : handlerLayer , time, handles, coreLayer } ;
180- } ;
95+ const createRegistry = ( ) =>
96+ createTestRegistry ( {
97+ continuous : {
98+ counter : { ...counterPrimitive , name : "counter" } ,
99+ failing : { ...failingPrimitive , name : "failing" } ,
100+ "no-immediate" : { ...noImmediateStartPrimitive , name : "no-immediate" } ,
101+ terminating : { ...terminatingPrimitive , name : "terminating" } ,
102+ } ,
103+ } ) ;
181104
182105// =============================================================================
183106// Tests
@@ -192,7 +115,8 @@ describe("ContinuousHandler", () => {
192115
193116 describe ( "start action" , ( ) => {
194117 it ( "creates a new instance and executes immediately by default" , async ( ) => {
195- const { layer } = createTestLayer ( 1000000 ) ;
118+ const registry = createRegistry ( ) ;
119+ const { layer } = createContinuousTestLayer ( registry , 1000000 ) ;
196120
197121 const result = await runWithLayer (
198122 Effect . gen ( function * ( ) {
@@ -222,7 +146,8 @@ describe("ContinuousHandler", () => {
222146 } ) ;
223147
224148 it ( "returns existing instance if already started" , async ( ) => {
225- const { layer } = createTestLayer ( ) ;
149+ const registry = createRegistry ( ) ;
150+ const { layer } = createContinuousTestLayer ( registry ) ;
226151
227152 const result = await runWithLayer (
228153 Effect . gen ( function * ( ) {
@@ -258,7 +183,8 @@ describe("ContinuousHandler", () => {
258183 } ) ;
259184
260185 it ( "respects startImmediately: false" , async ( ) => {
261- const { layer } = createTestLayer ( ) ;
186+ const registry = createRegistry ( ) ;
187+ const { layer } = createContinuousTestLayer ( registry ) ;
262188
263189 const result = await runWithLayer (
264190 Effect . gen ( function * ( ) {
@@ -284,7 +210,8 @@ describe("ContinuousHandler", () => {
284210
285211 describe ( "terminate action" , ( ) => {
286212 it ( "terminates a running instance and deletes all storage" , async ( ) => {
287- const { layer } = createTestLayer ( ) ;
213+ const registry = createRegistry ( ) ;
214+ const { layer } = createContinuousTestLayer ( registry ) ;
288215
289216 const result = await runWithLayer (
290217 Effect . gen ( function * ( ) {
@@ -330,7 +257,8 @@ describe("ContinuousHandler", () => {
330257 } ) ;
331258
332259 it ( "returns not_found for non-existent instance" , async ( ) => {
333- const { layer } = createTestLayer ( ) ;
260+ const registry = createRegistry ( ) ;
261+ const { layer } = createContinuousTestLayer ( registry ) ;
334262
335263 const result = await runWithLayer (
336264 Effect . gen ( function * ( ) {
@@ -352,7 +280,8 @@ describe("ContinuousHandler", () => {
352280 } ) ;
353281
354282 it ( "returns not_found when called twice (since first terminate deletes all storage)" , async ( ) => {
355- const { layer } = createTestLayer ( ) ;
283+ const registry = createRegistry ( ) ;
284+ const { layer } = createContinuousTestLayer ( registry ) ;
356285
357286 const result = await runWithLayer (
358287 Effect . gen ( function * ( ) {
@@ -396,7 +325,8 @@ describe("ContinuousHandler", () => {
396325
397326 describe ( "trigger action" , ( ) => {
398327 it ( "triggers immediate execution" , async ( ) => {
399- const { layer } = createTestLayer ( ) ;
328+ const registry = createRegistry ( ) ;
329+ const { layer } = createContinuousTestLayer ( registry ) ;
400330
401331 const result = await runWithLayer (
402332 Effect . gen ( function * ( ) {
@@ -431,7 +361,8 @@ describe("ContinuousHandler", () => {
431361 } ) ;
432362
433363 it ( "returns triggered: false for non-existent instance" , async ( ) => {
434- const { layer } = createTestLayer ( ) ;
364+ const registry = createRegistry ( ) ;
365+ const { layer } = createContinuousTestLayer ( registry ) ;
435366
436367 const result = await runWithLayer (
437368 Effect . gen ( function * ( ) {
@@ -451,7 +382,8 @@ describe("ContinuousHandler", () => {
451382 } ) ;
452383
453384 it ( "returns triggered: false for terminated instance" , async ( ) => {
454- const { layer } = createTestLayer ( ) ;
385+ const registry = createRegistry ( ) ;
386+ const { layer } = createContinuousTestLayer ( registry ) ;
455387
456388 const result = await runWithLayer (
457389 Effect . gen ( function * ( ) {
@@ -490,7 +422,8 @@ describe("ContinuousHandler", () => {
490422
491423 describe ( "status action" , ( ) => {
492424 it ( "returns status for running instance" , async ( ) => {
493- const { layer } = createTestLayer ( ) ;
425+ const registry = createRegistry ( ) ;
426+ const { layer } = createContinuousTestLayer ( registry ) ;
494427
495428 const result = await runWithLayer (
496429 Effect . gen ( function * ( ) {
@@ -520,7 +453,8 @@ describe("ContinuousHandler", () => {
520453 } ) ;
521454
522455 it ( "returns not_found for non-existent instance" , async ( ) => {
523- const { layer } = createTestLayer ( ) ;
456+ const registry = createRegistry ( ) ;
457+ const { layer } = createContinuousTestLayer ( registry ) ;
524458
525459 const result = await runWithLayer (
526460 Effect . gen ( function * ( ) {
@@ -542,7 +476,8 @@ describe("ContinuousHandler", () => {
542476
543477 describe ( "getState action" , ( ) => {
544478 it ( "returns current state" , async ( ) => {
545- const { layer } = createTestLayer ( ) ;
479+ const registry = createRegistry ( ) ;
480+ const { layer } = createContinuousTestLayer ( registry ) ;
546481
547482 const result = await runWithLayer (
548483 Effect . gen ( function * ( ) {
@@ -574,7 +509,8 @@ describe("ContinuousHandler", () => {
574509
575510 describe ( "handleAlarm" , ( ) => {
576511 it ( "executes on alarm and schedules next" , async ( ) => {
577- const { layer, time, handles } = createTestLayer ( 1000000 ) ;
512+ const registry = createRegistry ( ) ;
513+ const { layer, time, handles } = createContinuousTestLayer ( registry , 1000000 ) ;
578514
579515 await runWithLayer (
580516 Effect . gen ( function * ( ) {
@@ -609,7 +545,8 @@ describe("ContinuousHandler", () => {
609545 } ) ;
610546
611547 it ( "does nothing when terminated" , async ( ) => {
612- const { layer, time } = createTestLayer ( ) ;
548+ const registry = createRegistry ( ) ;
549+ const { layer, time } = createContinuousTestLayer ( registry ) ;
613550
614551 await runWithLayer (
615552 Effect . gen ( function * ( ) {
@@ -646,7 +583,8 @@ describe("ContinuousHandler", () => {
646583
647584 describe ( "error handling" , ( ) => {
648585 it ( "fails with ExecutionError when execute fails without retry config" , async ( ) => {
649- const { layer } = createTestLayer ( ) ;
586+ const registry = createRegistry ( ) ;
587+ const { layer } = createContinuousTestLayer ( registry ) ;
650588
651589 const resultExit = await runExitWithLayer (
652590 Effect . gen ( function * ( ) {
@@ -670,7 +608,8 @@ describe("ContinuousHandler", () => {
670608
671609 describe ( "ctx.terminate" , ( ) => {
672610 it ( "terminates on first run when condition met (purges state by default)" , async ( ) => {
673- const { layer } = createTestLayer ( ) ;
611+ const registry = createRegistry ( ) ;
612+ const { layer } = createContinuousTestLayer ( registry ) ;
674613
675614 const result = await runWithLayer (
676615 Effect . gen ( function * ( ) {
@@ -713,7 +652,8 @@ describe("ContinuousHandler", () => {
713652 } ) ;
714653
715654 it ( "terminates during alarm and stops further alarms" , async ( ) => {
716- const { layer, time, handles } = createTestLayer ( 1000000 ) ;
655+ const registry = createRegistry ( ) ;
656+ const { layer, time, handles } = createContinuousTestLayer ( registry , 1000000 ) ;
717657
718658 await runWithLayer (
719659 Effect . gen ( function * ( ) {
@@ -763,7 +703,8 @@ describe("ContinuousHandler", () => {
763703 } ) ;
764704
765705 it ( "trigger action returns terminated: true when terminate called" , async ( ) => {
766- const { layer } = createTestLayer ( ) ;
706+ const registry = createRegistry ( ) ;
707+ const { layer } = createContinuousTestLayer ( registry ) ;
767708
768709 const result = await runWithLayer (
769710 Effect . gen ( function * ( ) {
@@ -797,7 +738,8 @@ describe("ContinuousHandler", () => {
797738 } ) ;
798739
799740 it ( "trigger action returns triggered: false for terminated instance" , async ( ) => {
800- const { layer } = createTestLayer ( ) ;
741+ const registry = createRegistry ( ) ;
742+ const { layer } = createContinuousTestLayer ( registry ) ;
801743
802744 const result = await runWithLayer (
803745 Effect . gen ( function * ( ) {
@@ -830,7 +772,8 @@ describe("ContinuousHandler", () => {
830772 } ) ;
831773
832774 it ( "handleAlarm does nothing for terminated instance" , async ( ) => {
833- const { layer, time } = createTestLayer ( ) ;
775+ const registry = createRegistry ( ) ;
776+ const { layer, time } = createContinuousTestLayer ( registry ) ;
834777
835778 await runWithLayer (
836779 Effect . gen ( function * ( ) {
0 commit comments