Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions test/e2e/Apps/BasicPython/orchestration_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@

import json

import azure.functions as func
import azure.durable_functions as df
import azure.functions as func

bp = df.Blueprint()


@bp.route(route="GetAllInstances", methods=["GET", "POST"])
@bp.durable_client_input(client_name="client")
async def get_all_instances(req: func.HttpRequest, client: df.DurableOrchestrationClient):
async def get_all_instances(req: func.HttpRequest, client):
try:
instances = await client.get_status_all()
# This would not be necessary if we implemnted __str__ for DurableOrchestrationStatus using to_json under the hood
# This would not be necessary if we implemnted __str__ for
# DurableOrchestrationStatus using to_json under the hood
Comment on lines +19 to +20
instances = json.dumps([i.to_json() for i in instances])
response = func.HttpResponse(
instances,
Expand All @@ -35,15 +36,38 @@ async def get_all_instances(req: func.HttpRequest, client: df.DurableOrchestrati

@bp.route(route="GetRunningInstances", methods=["GET", "POST"])
@bp.durable_client_input(client_name="client")
async def get_running_instances(req: func.HttpRequest, client: df.DurableOrchestrationClient):
async def get_running_instances(req: func.HttpRequest, client):
try:
filter_statuses = [
df.OrchestrationRuntimeStatus.Running,
df.OrchestrationRuntimeStatus.Pending,
df.OrchestrationRuntimeStatus.ContinuedAsNew
]
instances = await client.get_status_by(runtime_status=filter_statuses)
# This would not be necessary if we implemnted __str__ for DurableOrchestrationStatus using to_json under the hood
# This would not be necessary if we implemnted __str__ for
# DurableOrchestrationStatus using to_json under the hood
instances = json.dumps([i.to_json() for i in instances])
response = func.HttpResponse(
instances,
status_code=200,
mimetype="application/json"
)
return response
except Exception as ex:
response = func.HttpResponse(
str(ex),
status_code=400,
mimetype="text/plain"
)
Comment on lines +56 to +61
return response


@bp.route(route="GetInstancesByPrefix", methods=["GET", "POST"])
@bp.durable_client_input(client_name="client")
async def get_instances_by_prefix(req: func.HttpRequest, client):
try:
instance_id_prefix = req.params.get("instanceIdPrefix")
instances = await client.get_status_by(instance_id_prefix=instance_id_prefix)
instances = json.dumps([i.to_json() for i in instances])
response = func.HttpResponse(
instances,
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/Apps/BasicPython/purge_orchestration_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

@bp.route(route="PurgeOrchestrationHistory", methods=["GET", "POST"])
@bp.durable_client_input(client_name="client")
async def purge_history(req: func.HttpRequest, client: df.DurableOrchestrationClient):
async def purge_history(req: func.HttpRequest, client):
logging.info("Starting to purge instance histories")
try:
instance_id = req.params.get("instanceId")
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/Apps/BasicPython/suspend_resume_orchestration.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

@bp.route(route="SuspendInstance", methods=["GET", "POST"])
@bp.durable_client_input(client_name="client")
async def suspend_instance(req: func.HttpRequest, client: df.DurableOrchestrationClient):
async def suspend_instance(req: func.HttpRequest, client):
instance_id = req.params.get("instanceId")
suspend_reason = "Suspending the instance for test."
try:
Expand All @@ -29,7 +29,7 @@ async def suspend_instance(req: func.HttpRequest, client: df.DurableOrchestratio

@bp.route(route="ResumeInstance", methods=["GET", "POST"])
@bp.durable_client_input(client_name="client")
async def resume_instance(req: func.HttpRequest, client: df.DurableOrchestrationClient):
async def resume_instance(req: func.HttpRequest, client):
instance_id = req.params.get("instanceId")
resume_reason = "Resuming the instance for test."
try:
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/Apps/BasicPython/terminate_orchestration.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def simulated_work_activity(sleepms: int) -> str:

@bp.route(route="TerminateInstance", methods=["GET", "POST"])
@bp.durable_client_input(client_name="client")
async def terminate_instance(req: func.HttpRequest, client: df.DurableOrchestrationClient):
async def terminate_instance(req: func.HttpRequest, client):
instance_id = req.route_params.get("instanceId") or req.params.get("instanceId")
reason = "Long-running orchestration was terminated early."
try:
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/Apps/BasicPython/timeout_orchestration.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

@bp.route(route="TimeoutOrchestrator_HttpStart", methods=["GET", "POST"])
@bp.durable_client_input(client_name="client")
async def timer_http_start(req: func.HttpRequest, client: df.DurableOrchestrationClient):
async def timer_http_start(req: func.HttpRequest, client):
timeoutSeconds = req.params.get("timeoutSeconds")
if not timeoutSeconds or not str.isnumeric(timeoutSeconds):
return func.HttpResponse(
Expand Down
64 changes: 64 additions & 0 deletions test/e2e/Tests/Tests/OrchestrationQueryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Net;
using System.Net.Http;
using System.Text.Json.Nodes;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -68,6 +69,69 @@ public async Task ListRunningOrchestrations_ShouldContainRunningOrchestration()
await TryTerminateInstanceAsync(instanceId);
}
}

[Fact]
[Trait("Dotnet", "Skip")] // Query by instance ID prefix is only implemented in the Python E2E app today
[Trait("Node", "Skip")] // Query by instance ID prefix is only implemented in the Python E2E app today
[Trait("Java", "Skip")] // Query by instance ID prefix is only implemented in the Python E2E app today
[Trait("PowerShell", "Skip")] // PowerShell does not have an equivalent query API today
public async Task ListOrchestrations_ByInstanceIdPrefix_ShouldReturnOnlyMatchingInstances()
{
string prefix = $"query-prefix-{Guid.NewGuid():N}";
string[] matchingInstanceIds = [$"{prefix}-a", $"{prefix}-b"];
string nonMatchingInstanceId = $"other-prefix-{Guid.NewGuid():N}";

var startedInstanceIds = new List<string>();

try
{
foreach (string instanceId in matchingInstanceIds.Append(nonMatchingInstanceId))
{
using HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(
"StartOrchestration",
$"?orchestrationName=LongRunningOrchestrator&instanceId={Uri.EscapeDataString(instanceId)}");

Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);

string statusQueryGetUri = await DurableHelpers.ParseStatusQueryGetUriAsync(response);
await DurableHelpers.WaitForOrchestrationStateAsync(statusQueryGetUri, "Running", 30);
startedInstanceIds.Add(instanceId);
}

using HttpResponseMessage statusResponse = await HttpHelpers.InvokeHttpTrigger(
"GetInstancesByPrefix",
$"?instanceIdPrefix={Uri.EscapeDataString(prefix)}");

Assert.Equal(HttpStatusCode.OK, statusResponse.StatusCode);

string? statusResponseMessage = await statusResponse.Content.ReadAsStringAsync();
Assert.NotNull(statusResponseMessage);

JsonNode? statusResponseJsonNode = JsonNode.Parse(statusResponseMessage);
Assert.NotNull(statusResponseJsonNode);

var returnedInstanceIds = statusResponseJsonNode
.AsArray()
.Select(x => x?["InstanceId"]?.ToString() ?? x?["instanceId"]?.ToString())
.Where(x => !string.IsNullOrEmpty(x))
.ToHashSet();

foreach (string matchingInstanceId in matchingInstanceIds)
{
Assert.Contains(matchingInstanceId, returnedInstanceIds);
}

Assert.DoesNotContain(nonMatchingInstanceId, returnedInstanceIds);
}
finally
{
foreach (string instanceId in startedInstanceIds)
{
await TryTerminateInstanceAsync(instanceId);
}
}
}

private static async Task<bool> TryTerminateInstanceAsync(string instanceId)
{
try
Expand Down
Loading