Skip to content

Commit b536473

Browse files
committed
added endtoendtests
1 parent f0c0a54 commit b536473

3 files changed

Lines changed: 245 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.functions;
2+
3+
import com.microsoft.durabletask.TaskEntity;
4+
import com.microsoft.durabletask.TaskEntityOperation;
5+
6+
/**
7+
* A simple counter entity for e2e testing.
8+
*/
9+
public class CounterEntity extends TaskEntity<Integer> {
10+
11+
public int add(int input) {
12+
this.state += input;
13+
return this.state;
14+
}
15+
16+
public int get() {
17+
return this.state;
18+
}
19+
20+
public void reset() {
21+
this.state = 0;
22+
}
23+
24+
@Override
25+
protected Integer initializeState(TaskEntityOperation operation) {
26+
return 0;
27+
}
28+
29+
@Override
30+
protected Class<Integer> getStateType() {
31+
return Integer.class;
32+
}
33+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package com.functions;
2+
3+
import com.microsoft.azure.functions.*;
4+
import com.microsoft.azure.functions.annotation.*;
5+
import com.microsoft.durabletask.*;
6+
import com.microsoft.durabletask.azurefunctions.DurableClientContext;
7+
import com.microsoft.durabletask.azurefunctions.DurableClientInput;
8+
import com.microsoft.durabletask.azurefunctions.DurableEntityTrigger;
9+
import com.microsoft.durabletask.azurefunctions.DurableOrchestrationTrigger;
10+
11+
import java.util.Optional;
12+
13+
/**
14+
* Azure Functions for entity e2e tests.
15+
* <p>
16+
* Tests:
17+
* <ul>
18+
* <li>{@code callEntity} — orchestrator calls entity and returns result</li>
19+
* <li>{@code signalEntity} — orchestrator signals entity (fire-and-forget), then calls to verify</li>
20+
* <li>{@code signalAndCallEntity} — orchestrator signals entity to add, then calls to get updated value</li>
21+
* </ul>
22+
*/
23+
public class EntityFunctions {
24+
25+
// ─── Entity trigger ───
26+
27+
@FunctionName("Counter")
28+
public String counterEntity(
29+
@DurableEntityTrigger(name = "req") String req) {
30+
return EntityRunner.loadAndRun(req, CounterEntity::new);
31+
}
32+
33+
// ─── Orchestrations ───
34+
35+
/**
36+
* Orchestration that calls the counter entity "add" operation and returns the result.
37+
* Input: JSON with entityKey and addValue fields.
38+
*/
39+
@FunctionName("CallEntityOrchestration")
40+
public int callEntityOrchestration(
41+
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
42+
EntityPayload input = ctx.getInput(EntityPayload.class);
43+
EntityInstanceId entityId = new EntityInstanceId("counter", input.entityKey);
44+
return ctx.callEntity(entityId, "add", input.addValue, Integer.class).await();
45+
}
46+
47+
/**
48+
* Orchestration that signals the counter entity to "add" then calls "get" to verify.
49+
* This tests both signalEntity (fire-and-forget) and callEntity (request-response).
50+
*/
51+
@FunctionName("SignalThenCallEntityOrchestration")
52+
public int signalThenCallEntityOrchestration(
53+
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
54+
EntityPayload input = ctx.getInput(EntityPayload.class);
55+
EntityInstanceId entityId = new EntityInstanceId("counter", input.entityKey);
56+
57+
// Signal — fire-and-forget
58+
ctx.signalEntity(entityId, "add", input.addValue);
59+
60+
// Call — request-response (should see the updated state)
61+
return ctx.callEntity(entityId, "get", null, Integer.class).await();
62+
}
63+
64+
/**
65+
* Orchestration that calls "get" on a fresh counter entity (tests zero-initialized state).
66+
*/
67+
@FunctionName("CallEntityGetOrchestration")
68+
public int callEntityGetOrchestration(
69+
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
70+
String entityKey = ctx.getInput(String.class);
71+
EntityInstanceId entityId = new EntityInstanceId("counter", entityKey);
72+
return ctx.callEntity(entityId, "get", null, Integer.class).await();
73+
}
74+
75+
// ─── HTTP triggers ───
76+
77+
/**
78+
* POST /api/StartCallEntityOrchestration?key={key}&value={value}
79+
*/
80+
@FunctionName("StartCallEntityOrchestration")
81+
public HttpResponseMessage startCallEntityOrchestration(
82+
@HttpTrigger(name = "req", methods = {HttpMethod.POST},
83+
authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
84+
@DurableClientInput(name = "durableContext") DurableClientContext durableContext,
85+
final ExecutionContext context) {
86+
String key = request.getQueryParameters().getOrDefault("key", "e2e-call-" + System.currentTimeMillis());
87+
int value = Integer.parseInt(request.getQueryParameters().getOrDefault("value", "5"));
88+
89+
DurableTaskClient client = durableContext.getClient();
90+
EntityPayload payload = new EntityPayload(key, value);
91+
String instanceId = client.scheduleNewOrchestrationInstance("CallEntityOrchestration", payload);
92+
context.getLogger().info("Started CallEntityOrchestration: " + instanceId);
93+
return durableContext.createCheckStatusResponse(request, instanceId);
94+
}
95+
96+
/**
97+
* POST /api/StartSignalThenCallEntityOrchestration?key={key}&value={value}
98+
*/
99+
@FunctionName("StartSignalThenCallEntityOrchestration")
100+
public HttpResponseMessage startSignalThenCallEntityOrchestration(
101+
@HttpTrigger(name = "req", methods = {HttpMethod.POST},
102+
authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
103+
@DurableClientInput(name = "durableContext") DurableClientContext durableContext,
104+
final ExecutionContext context) {
105+
String key = request.getQueryParameters().getOrDefault("key", "e2e-signal-" + System.currentTimeMillis());
106+
int value = Integer.parseInt(request.getQueryParameters().getOrDefault("value", "10"));
107+
108+
DurableTaskClient client = durableContext.getClient();
109+
EntityPayload payload = new EntityPayload(key, value);
110+
String instanceId = client.scheduleNewOrchestrationInstance("SignalThenCallEntityOrchestration", payload);
111+
context.getLogger().info("Started SignalThenCallEntityOrchestration: " + instanceId);
112+
return durableContext.createCheckStatusResponse(request, instanceId);
113+
}
114+
115+
/**
116+
* POST /api/StartCallEntityGetOrchestration?key={key}
117+
*/
118+
@FunctionName("StartCallEntityGetOrchestration")
119+
public HttpResponseMessage startCallEntityGetOrchestration(
120+
@HttpTrigger(name = "req", methods = {HttpMethod.POST},
121+
authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
122+
@DurableClientInput(name = "durableContext") DurableClientContext durableContext,
123+
final ExecutionContext context) {
124+
String key = request.getQueryParameters().getOrDefault("key", "e2e-get-" + System.currentTimeMillis());
125+
126+
DurableTaskClient client = durableContext.getClient();
127+
String instanceId = client.scheduleNewOrchestrationInstance("CallEntityGetOrchestration", key);
128+
context.getLogger().info("Started CallEntityGetOrchestration: " + instanceId);
129+
return durableContext.createCheckStatusResponse(request, instanceId);
130+
}
131+
132+
// ─── Helpers ───
133+
134+
/**
135+
* Payload for entity orchestrations.
136+
*/
137+
public static class EntityPayload {
138+
public String entityKey;
139+
public int addValue;
140+
141+
public EntityPayload() {
142+
}
143+
144+
public EntityPayload(String entityKey, int addValue) {
145+
this.entityKey = entityKey;
146+
this.addValue = addValue;
147+
}
148+
}
149+
}

endtoendtests/src/test/java/com/functions/EndToEndTests.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,69 @@ public void VersionedSubOrchestrationTests(String version) throws InterruptedExc
411411
}
412412
}
413413

414+
// ─── Entity tests ───
415+
416+
/**
417+
* Tests callEntity: orchestrator calls counter entity "add" and returns the result.
418+
* This is the key scenario for the trigger-binding-path fix where entity responses
419+
* arrive as EventRaised with a ResponseMessage JSON wrapper.
420+
*/
421+
@Test
422+
public void callEntityTest() throws InterruptedException {
423+
Set<String> continueStates = new HashSet<>();
424+
continueStates.add("Pending");
425+
continueStates.add("Running");
426+
Response response = post("/api/StartCallEntityOrchestration?key=call-test-1&value=5");
427+
JsonPath jsonPath = response.jsonPath();
428+
String statusQueryGetUri = jsonPath.get("statusQueryGetUri");
429+
boolean completed = pollingCheck(statusQueryGetUri, "Completed", continueStates, Duration.ofSeconds(30));
430+
assertTrue(completed, "CallEntityOrchestration should complete");
431+
432+
Response statusResponse = get(statusQueryGetUri);
433+
int output = statusResponse.jsonPath().get("output");
434+
assertEquals(5, output, "Counter entity should return the added value");
435+
}
436+
437+
/**
438+
* Tests signalEntity + callEntity: orchestrator signals counter entity to "add",
439+
* then calls "get" to verify the updated state.
440+
*/
441+
@Test
442+
public void signalThenCallEntityTest() throws InterruptedException {
443+
Set<String> continueStates = new HashSet<>();
444+
continueStates.add("Pending");
445+
continueStates.add("Running");
446+
Response response = post("/api/StartSignalThenCallEntityOrchestration?key=signal-test-1&value=10");
447+
JsonPath jsonPath = response.jsonPath();
448+
String statusQueryGetUri = jsonPath.get("statusQueryGetUri");
449+
boolean completed = pollingCheck(statusQueryGetUri, "Completed", continueStates, Duration.ofSeconds(30));
450+
assertTrue(completed, "SignalThenCallEntityOrchestration should complete");
451+
452+
Response statusResponse = get(statusQueryGetUri);
453+
int output = statusResponse.jsonPath().get("output");
454+
assertEquals(10, output, "Counter entity should return the signaled value after get");
455+
}
456+
457+
/**
458+
* Tests callEntity on a fresh entity: orchestrator calls "get" on a new counter entity,
459+
* which should return zero (the initial state).
460+
*/
461+
@Test
462+
public void callEntityGetInitialStateTest() throws InterruptedException {
463+
Set<String> continueStates = new HashSet<>();
464+
continueStates.add("Pending");
465+
continueStates.add("Running");
466+
Response response = post("/api/StartCallEntityGetOrchestration?key=get-test-" + System.currentTimeMillis());
467+
JsonPath jsonPath = response.jsonPath();
468+
String statusQueryGetUri = jsonPath.get("statusQueryGetUri");
469+
boolean completed = pollingCheck(statusQueryGetUri, "Completed", continueStates, Duration.ofSeconds(30));
470+
assertTrue(completed, "CallEntityGetOrchestration should complete");
471+
472+
Response statusResponse = get(statusQueryGetUri);
473+
int output = statusResponse.jsonPath().get("output");
474+
assertEquals(0, output, "Fresh counter entity should return initial state of 0");
475+
}
476+
414477
private boolean pollingCheck(String statusQueryGetUri,
415478
String expectedState,
416479
Set<String> continueStates,

0 commit comments

Comments
 (0)