@@ -27,6 +27,11 @@ jest.mock("@open-api/generated", () => ({
2727 error : undefined ,
2828 response : { status : 200 , ok : true } ,
2929 } ) ,
30+ updateTask : jest . fn ( ) . mockResolvedValue ( {
31+ data : null ,
32+ error : undefined ,
33+ response : { status : 200 , ok : true } ,
34+ } ) ,
3035 } ,
3136} ) ) ;
3237
@@ -1459,3 +1464,142 @@ describe("V2 task chaining", () => {
14591464 expect ( mockUpdateTask ) . toHaveBeenCalledTimes ( 2 ) ;
14601465 } ) ;
14611466} ) ;
1467+
1468+ describe ( "v5/v4 backend detection" , ( ) => {
1469+ const makeTask = ( taskId : string , workflowInstanceId = "wf-1" ) : Task => ( {
1470+ taskId,
1471+ workflowInstanceId,
1472+ status : "IN_PROGRESS" ,
1473+ inputData : { } ,
1474+ } ) ;
1475+
1476+ const makeArgs = ( mockClient : Client ) : RunnerArgs => ( {
1477+ worker : {
1478+ taskDefName : "test" ,
1479+ execute : async ( ) => ( { outputData : { } , status : "COMPLETED" } ) ,
1480+ } ,
1481+ options : { pollInterval : 10 , domain : "" , concurrency : 1 , workerID : "worker-id" } ,
1482+ logger : mockLogger ,
1483+ client : mockClient ,
1484+ } ) ;
1485+
1486+ test ( "uses updateTaskV2 when server returns 200 (v5)" , async ( ) => {
1487+ const mockClient = createMockClient ( ) ;
1488+
1489+ ( TaskResource . batchPoll as jest . MockedFunction < typeof TaskResource . batchPoll > )
1490+ . mockResolvedValueOnce ( { data : [ makeTask ( "task-1" ) ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > )
1491+ . mockResolvedValue ( { data : [ ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > ) ;
1492+
1493+ // Default mock already returns 200 — no override needed
1494+
1495+ const runner = new TaskRunner ( makeArgs ( mockClient ) ) ;
1496+ activeRunners . push ( runner ) ;
1497+ runner . startPolling ( ) ;
1498+
1499+ await new Promise ( ( r ) => setTimeout ( ( ) => r ( true ) , 200 ) ) ;
1500+ runner . stopPolling ( ) ;
1501+
1502+ expect ( TaskResource . updateTaskV2 ) . toHaveBeenCalledTimes ( 1 ) ;
1503+ expect ( TaskResource . updateTask ) . not . toHaveBeenCalled ( ) ;
1504+ expect (
1505+ ( TaskRunner as unknown as { updateV2Available : boolean | null } ) . updateV2Available
1506+ ) . toBe ( true ) ;
1507+ } ) ;
1508+
1509+ test ( "falls back to updateTask when server returns 404 (v4 — endpoint absent)" , async ( ) => {
1510+ const mockClient = createMockClient ( ) ;
1511+
1512+ ( TaskResource . batchPoll as jest . MockedFunction < typeof TaskResource . batchPoll > )
1513+ . mockResolvedValueOnce ( { data : [ makeTask ( "task-1" ) ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > )
1514+ . mockResolvedValue ( { data : [ ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > ) ;
1515+
1516+ ( TaskResource . updateTaskV2 as jest . MockedFunction < typeof TaskResource . updateTaskV2 > )
1517+ . mockResolvedValue ( { data : null , error : undefined , response : { status : 404 , ok : false } } as Awaited < ReturnType < typeof TaskResource . updateTaskV2 > > ) ;
1518+
1519+ const runner = new TaskRunner ( makeArgs ( mockClient ) ) ;
1520+ activeRunners . push ( runner ) ;
1521+ runner . startPolling ( ) ;
1522+
1523+ await new Promise ( ( r ) => setTimeout ( ( ) => r ( true ) , 200 ) ) ;
1524+ runner . stopPolling ( ) ;
1525+
1526+ // Probed once, then fell back
1527+ expect ( TaskResource . updateTaskV2 ) . toHaveBeenCalledTimes ( 1 ) ;
1528+ expect ( TaskResource . updateTask ) . toHaveBeenCalledTimes ( 1 ) ;
1529+ expect (
1530+ ( TaskRunner as unknown as { updateV2Available : boolean | null } ) . updateV2Available
1531+ ) . toBe ( false ) ;
1532+ } ) ;
1533+
1534+ test ( "falls back to updateTask when server returns 405 (v4 — wrong method)" , async ( ) => {
1535+ const mockClient = createMockClient ( ) ;
1536+
1537+ ( TaskResource . batchPoll as jest . MockedFunction < typeof TaskResource . batchPoll > )
1538+ . mockResolvedValueOnce ( { data : [ makeTask ( "task-1" ) ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > )
1539+ . mockResolvedValue ( { data : [ ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > ) ;
1540+
1541+ ( TaskResource . updateTaskV2 as jest . MockedFunction < typeof TaskResource . updateTaskV2 > )
1542+ . mockResolvedValue ( { data : null , error : undefined , response : { status : 405 , ok : false } } as Awaited < ReturnType < typeof TaskResource . updateTaskV2 > > ) ;
1543+
1544+ const runner = new TaskRunner ( makeArgs ( mockClient ) ) ;
1545+ activeRunners . push ( runner ) ;
1546+ runner . startPolling ( ) ;
1547+
1548+ await new Promise ( ( r ) => setTimeout ( ( ) => r ( true ) , 200 ) ) ;
1549+ runner . stopPolling ( ) ;
1550+
1551+ expect ( TaskResource . updateTaskV2 ) . toHaveBeenCalledTimes ( 1 ) ;
1552+ expect ( TaskResource . updateTask ) . toHaveBeenCalledTimes ( 1 ) ;
1553+ expect (
1554+ ( TaskRunner as unknown as { updateV2Available : boolean | null } ) . updateV2Available
1555+ ) . toBe ( false ) ;
1556+ } ) ;
1557+
1558+ test ( "skips probe on subsequent tasks after v5 detection — uses updateTaskV2 directly" , async ( ) => {
1559+ const mockClient = createMockClient ( ) ;
1560+
1561+ ( TaskResource . batchPoll as jest . MockedFunction < typeof TaskResource . batchPoll > )
1562+ . mockResolvedValueOnce ( { data : [ makeTask ( "task-1" ) ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > )
1563+ . mockResolvedValueOnce ( { data : [ makeTask ( "task-2" ) ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > )
1564+ . mockResolvedValue ( { data : [ ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > ) ;
1565+
1566+ // Explicitly reset to 200: jest.clearAllMocks() preserves mockResolvedValue overrides
1567+ // from previous tests (e.g. the 405-fallback test), so we cannot rely on the module-level
1568+ // default here.
1569+ ( TaskResource . updateTaskV2 as jest . MockedFunction < typeof TaskResource . updateTaskV2 > )
1570+ . mockResolvedValue ( { data : null , error : undefined , response : { status : 200 , ok : true } } as Awaited < ReturnType < typeof TaskResource . updateTaskV2 > > ) ;
1571+
1572+ const runner = new TaskRunner ( makeArgs ( mockClient ) ) ;
1573+ activeRunners . push ( runner ) ;
1574+ runner . startPolling ( ) ;
1575+
1576+ await new Promise ( ( r ) => setTimeout ( ( ) => r ( true ) , 300 ) ) ;
1577+ runner . stopPolling ( ) ;
1578+
1579+ expect ( TaskResource . updateTaskV2 ) . toHaveBeenCalledTimes ( 2 ) ;
1580+ expect ( TaskResource . updateTask ) . not . toHaveBeenCalled ( ) ;
1581+ } ) ;
1582+
1583+ test ( "skips probe on subsequent tasks after v4 detection — uses updateTask directly" , async ( ) => {
1584+ const mockClient = createMockClient ( ) ;
1585+
1586+ ( TaskResource . batchPoll as jest . MockedFunction < typeof TaskResource . batchPoll > )
1587+ . mockResolvedValueOnce ( { data : [ makeTask ( "task-1" ) ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > )
1588+ . mockResolvedValueOnce ( { data : [ makeTask ( "task-2" ) ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > )
1589+ . mockResolvedValue ( { data : [ ] } as Awaited < ReturnType < typeof TaskResource . batchPoll > > ) ;
1590+
1591+ ( TaskResource . updateTaskV2 as jest . MockedFunction < typeof TaskResource . updateTaskV2 > )
1592+ . mockResolvedValue ( { data : null , error : undefined , response : { status : 405 , ok : false } } as Awaited < ReturnType < typeof TaskResource . updateTaskV2 > > ) ;
1593+
1594+ const runner = new TaskRunner ( makeArgs ( mockClient ) ) ;
1595+ activeRunners . push ( runner ) ;
1596+ runner . startPolling ( ) ;
1597+
1598+ await new Promise ( ( r ) => setTimeout ( ( ) => r ( true ) , 300 ) ) ;
1599+ runner . stopPolling ( ) ;
1600+
1601+ // Probe fires once for task-1, then task-2 goes directly to updateTask
1602+ expect ( TaskResource . updateTaskV2 ) . toHaveBeenCalledTimes ( 1 ) ;
1603+ expect ( TaskResource . updateTask ) . toHaveBeenCalledTimes ( 2 ) ;
1604+ } ) ;
1605+ } ) ;
0 commit comments