@@ -1331,6 +1331,73 @@ void getLockedEntities_outsideCriticalSection_returnsEmpty() {
13311331 assertTrue (hasComplete , "Expected orchestration to complete" );
13321332 }
13331333
1334+ /**
1335+ * Regression test: In the Azure Functions trigger binding code path, entity lock grants
1336+ * arrive as EventRaised events (not EntityLockGranted proto events). The lock task's data
1337+ * type is AutoCloseable, which Jackson cannot instantiate because it's an interface.
1338+ * This test verifies that the orchestration completes successfully when the lock grant
1339+ * arrives via EventRaised (simulating the Azure Functions path).
1340+ */
1341+ @ Test
1342+ void lockEntities_lockGrantedViaEventRaised_succeeds () {
1343+ final String orchestratorName = "LockGrantedViaEventRaisedTest" ;
1344+ EntityInstanceId entityId = new EntityInstanceId ("Counter" , "c1" );
1345+
1346+ TaskOrchestrationExecutor executor = createExecutor (orchestratorName , ctx -> {
1347+ AutoCloseable lock = ctx .lockEntities (Arrays .asList (entityId )).await ();
1348+ assertTrue (ctx .isInCriticalSection ());
1349+ assertFalse (ctx .getLockedEntities ().isEmpty ());
1350+ try {
1351+ lock .close ();
1352+ } catch (Exception e ) {
1353+ throw new RuntimeException (e );
1354+ }
1355+ ctx .complete ("lock-via-event-raised" );
1356+ });
1357+
1358+ // First execution: orchestrator calls lockEntities, which produces a lock request action
1359+ List <HistoryEvent > pastEvents1 = Arrays .asList (
1360+ orchestratorStarted (),
1361+ executionStarted (orchestratorName , "null" ));
1362+ List <HistoryEvent > newEvents1 = Collections .singletonList (orchestratorCompleted ());
1363+
1364+ TaskOrchestratorResult result1 = executor .execute (pastEvents1 , newEvents1 , null );
1365+
1366+ // Extract the criticalSectionId from the lock request action
1367+ String criticalSectionId = null ;
1368+ try {
1369+ criticalSectionId = extractLockCriticalSectionId (result1 .getActions ());
1370+ } catch (Exception e ) {
1371+ fail ("Failed to extract criticalSectionId: " + e .getMessage ());
1372+ }
1373+ assertNotNull (criticalSectionId , "Expected a lock request action with criticalSectionId" );
1374+
1375+ // Second execution: replay with lock request in past, lock grant arrives as EventRaised
1376+ // (simulating the Azure Functions trigger binding path where DTFx sends lock grants
1377+ // as named events rather than proto EntityLockGranted events)
1378+ List <HistoryEvent > pastEvents2 = Arrays .asList (
1379+ orchestratorStarted (),
1380+ executionStarted (orchestratorName , "null" ),
1381+ eventSentEvent (0 ),
1382+ orchestratorCompleted ());
1383+ List <HistoryEvent > newEvents2 = Arrays .asList (
1384+ orchestratorStarted (),
1385+ eventRaisedEvent (criticalSectionId , "null" ),
1386+ orchestratorCompleted ());
1387+
1388+ TaskOrchestratorResult result2 = executor .execute (pastEvents2 , newEvents2 , null );
1389+
1390+ boolean hasComplete = false ;
1391+ for (OrchestratorAction action : result2 .getActions ()) {
1392+ if (action .hasCompleteOrchestration ()) {
1393+ String output = action .getCompleteOrchestration ().getResult ().getValue ();
1394+ assertEquals ("\" lock-via-event-raised\" " , output );
1395+ hasComplete = true ;
1396+ }
1397+ }
1398+ assertTrue (hasComplete , "Expected orchestration to complete after lock granted via EventRaised" );
1399+ }
1400+
13341401 @ Test
13351402 void lockEntities_varargs_producesLockAction () {
13361403 final String orchestratorName = "VarargsLockTest" ;
0 commit comments