Azure / azure-functions-durable-extension

Durable Task Framework extension for Azure Functions
MIT License
710 stars 263 forks source link

DurableTaskClientExtensions causes problems with mocks in unittests #2827

Closed JaapMosselman closed 1 month ago

JaapMosselman commented 1 month ago

Description

I have a HttpTrigger function which schedules a new orchestration. This function has a "[DurableClient] DurableTaskClient starter" input parameter. After scheduling the new orchestration, I use the starter.CreateCheckStatusResponse(req, instanceId) to return as response for the http request.

DurableTaskClient I can easily mock because it is an abstract class with abstract methods. But CreateCheckStatusResponse is an extension method in src/Worker.Extensions.DurableTask/DurableTaskClientExtensions.cs. When this is called with the DurableTaskClient mock, the GetQueryParams method will always return null, because the client is not a FunctionsDurableTaskClient. This gives a NullReferenceException, because the BuildUrl function in the SetHeadersAndGetPayload method tries to do a foreach on that null result from GetQueryParams.

Expected behavior

Some way to create a unittest for a function with uses the CreateCheckStatusResponse.

Actual behavior

Throws NullReferenceException

Relevant source code snippets

    private static object SetHeadersAndGetPayload(
        DurableTaskClient client, HttpRequestData request, HttpResponseData response, string instanceId)
    {
        static string BuildUrl(string url, params string?[] queryValues)
        {
            bool appended = false;
            foreach (string? query in queryValues)
            {
                if (!string.IsNullOrEmpty(query))
                {
                    url = url + (appended ? "&" : "?") + query;
                    appended = true;
                }
            }

            return url;
        }

        // TODO: To better support scenarios involving proxies or application gateways, this
        //       code should take the X-Forwarded-Host, X-Forwarded-Proto, and Forwarded HTTP
        //       request headers into consideration and generate the base URL accordingly.
        //       More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded.
        //       One potential workaround is to set ASPNETCORE_FORWARDEDHEADERS_ENABLED to true.
        string baseUrl = request.Url.GetLeftPart(UriPartial.Authority);
        string formattedInstanceId = Uri.EscapeDataString(instanceId);
        string instanceUrl = $"{baseUrl}/runtime/webhooks/durabletask/instances/{formattedInstanceId}";
        string? commonQueryParameters = GetQueryParams(client);
        response.Headers.Add("Location", BuildUrl(instanceUrl, commonQueryParameters));
        response.Headers.Add("Content-Type", "application/json");

        return new
        {
            id = instanceId,
            purgeHistoryDeleteUri = BuildUrl(instanceUrl, commonQueryParameters),
            sendEventPostUri = BuildUrl($"{instanceUrl}/raiseEvent/{{eventName}}", commonQueryParameters),
            statusQueryGetUri = BuildUrl(instanceUrl, commonQueryParameters),
            terminatePostUri = BuildUrl($"{instanceUrl}/terminate", "reason={{text}}", commonQueryParameters),
            suspendPostUri =  BuildUrl($"{instanceUrl}/suspend", "reason={{text}}", commonQueryParameters),
            resumePostUri =  BuildUrl($"{instanceUrl}/resume", "reason={{text}}", commonQueryParameters)
        };
    }

    private static ObjectSerializer GetObjectSerializer(HttpResponseData response)
    {
        return response.FunctionContext.InstanceServices.GetService<IOptions<WorkerOptions>>()?.Value?.Serializer
            ?? throw new InvalidOperationException("A serializer is not configured for the worker.");
    }

    private static string? GetQueryParams(DurableTaskClient client)
    {
        return client is FunctionsDurableTaskClient functions ? functions.QueryString : null;
    }

Known workarounds

None

App Details

JaapMosselman commented 1 month ago

Excuse me. My conclusion above was wrong. Closing this issue.