Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/EmulatorPolicyChecklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Track progress of emulator policy handler implementation. Each policy needs a ha
| ⬜ | CacheStore | CacheStoreHandler.cs | Store interaction | `emulator/cache-store` |
| ⬜ | Cors | CorsHandler.cs | Context mutation | `emulator/cors` |
| ⬜ | EmitMetric | EmitMetricHandler.cs | No-op + callbacks | `emulator/emit-metric` |
| | ForwardRequest | ForwardRequestHandler.cs | External service mock | `emulator/forward-request` |
| | ForwardRequest | ForwardRequestHandler.cs | External service mock | `emulator/forward-request` |
| ⬜ | JsonP | JsonPHandler.cs | Context mutation | `emulator/jsonp` |
| ⬜ | JsonToXml | JsonToXmlHandle.cs | No-op + callbacks | `emulator/json-to-xml` |
| ⬜ | LlmEmitTokenMetric | LlmEmitTokenMetricHandler.cs | No-op + callbacks | `emulator/llm-emit-token-metric` |
Expand Down
3 changes: 3 additions & 0 deletions src/Testing/Document/TestDocumentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public static ResponseExampleStore SetupResponseExampleStore(this TestDocument d
public static LoggerStore SetupLoggerStore(this TestDocument document) =>
document.Context.LoggerStore;

public static ForwardRequestStore SetupForwardRequest(this TestDocument document) =>
document.Context.ForwardRequestStore;

public static RateLimitStore SetupRateLimitStore(this TestDocument document) =>
document.Context.RateLimitStore;
}
25 changes: 25 additions & 0 deletions src/Testing/Emulator/Data/ForwardRequestStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Data;

public class ForwardRequestStore
{
private readonly Queue<MockBackendResponse> _responses = new();
private MockBackendResponse? _default;

public ForwardRequestStore Returns(MockBackendResponse response)
{
_responses.Enqueue(response);
return this;
}

public ForwardRequestStore ReturnsDefault(MockBackendResponse response)
{
_default = response;
return this;
}

internal MockBackendResponse? GetNext() =>
_responses.Count > 0 ? _responses.Dequeue() : _default;
}
12 changes: 12 additions & 0 deletions src/Testing/Emulator/Data/MockBackendResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Data;

public record MockBackendResponse
{
public int StatusCode { get; init; } = 200;
public string StatusReason { get; init; } = "OK";
public Dictionary<string, string[]> Headers { get; init; } = new();
public string? Body { get; init; }
}
26 changes: 24 additions & 2 deletions src/Testing/Emulator/Policies/ForwardRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
using Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Expressions;

namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Policies;

Expand All @@ -12,6 +13,27 @@ internal class ForwardRequestHandler : PolicyHandlerOptionalParam<ForwardRequest

protected override void Handle(GatewayContext context, ForwardRequestConfig? config)
{
throw new NotImplementedException();
var mockResponse = context.ForwardRequestStore.GetNext();

if (mockResponse is null)
{
return;
}

context.Response = new MockResponse
{
StatusCode = mockResponse.StatusCode,
StatusReason = mockResponse.StatusReason,
};

foreach (var (key, values) in mockResponse.Headers)
{
context.Response.Headers[key] = values;
}

if (mockResponse.Body is not null)
{
context.Response.Body.Content = mockResponse.Body;
}
}
}
1 change: 1 addition & 0 deletions src/Testing/GatewayContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class GatewayContext : MockExpressionContext
internal readonly CacheStore CacheStore = new();
internal readonly ResponseExampleStore ResponseExampleStore = new();
internal readonly LoggerStore LoggerStore = new();
internal readonly ForwardRequestStore ForwardRequestStore = new();
internal readonly RateLimitStore RateLimitStore = new();

public GatewayContext()
Expand Down
93 changes: 89 additions & 4 deletions test/Test.Testing/Emulator/Policies/ForwardRequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
using Microsoft.Azure.ApiManagement.PolicyToolkit.Testing;
using Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Document;
using Microsoft.Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Data;

namespace Test.Emulator.Emulator.Policies;

Expand All @@ -18,18 +19,102 @@ public void Backend(IBackendContext context)
}
}

class ForwardRequestWithConfig : IDocument
{
public void Backend(IBackendContext context)
{
context.ForwardRequest(new ForwardRequestConfig { Timeout = 60 });
}
}

[TestMethod]
public void ForwardRequest_DefaultResponse()
{
var test = new SimpleForwardRequest().AsTestDocument();

test.RunBackend();

test.Context.Response.StatusCode.Should().Be(200);
}

[TestMethod]
public void ForwardRequest_MockedResponse()
{
var test = new SimpleForwardRequest().AsTestDocument();
test.SetupForwardRequest().ReturnsDefault(new MockBackendResponse
{
StatusCode = 201,
StatusReason = "Created",
Body = """{"id": 42}""",
Headers = { { "Content-Type", ["application/json"] } }
});

test.RunBackend();

test.Context.Response.StatusCode.Should().Be(201);
test.Context.Response.StatusReason.Should().Be("Created");
test.Context.Response.Body.Content.Should().Be("""{"id": 42}""");
test.Context.Response.Headers.Should().ContainKey("Content-Type");
}

[TestMethod]
public void ForwardRequest_SequentialResponses()
{
var test = new SimpleForwardRequest().AsTestDocument();
test.SetupForwardRequest()
.Returns(new MockBackendResponse { StatusCode = 200, Body = "first" })
.Returns(new MockBackendResponse { StatusCode = 201, Body = "second" });

test.RunBackend();
test.Context.Response.StatusCode.Should().Be(200);
test.Context.Response.Body.Content.Should().Be("first");

test.RunBackend();
test.Context.Response.StatusCode.Should().Be(201);
test.Context.Response.Body.Content.Should().Be("second");
}

[TestMethod]
public void ForwardRequest_ErrorResponse()
{
var test = new SimpleForwardRequest().AsTestDocument();
test.SetupForwardRequest().ReturnsDefault(new MockBackendResponse
{
StatusCode = 500,
StatusReason = "Internal Server Error",
Body = "backend error"
});

test.RunBackend();

test.Context.Response.StatusCode.Should().Be(500);
}

[TestMethod]
public void ForwardRequest_WithConfig()
{
var test = new ForwardRequestWithConfig().AsTestDocument();
test.SetupForwardRequest().ReturnsDefault(new MockBackendResponse { StatusCode = 204 });

test.RunBackend();

test.Context.Response.StatusCode.Should().Be(204);
}

[TestMethod]
public void ForwardRequest_Callback()
{
var test = new SimpleForwardRequest().AsTestDocument();
var executedCallback = false;
test.SetupBackend().ForwardRequest().WithCallback((_, _) =>
var callbackExecuted = false;
test.SetupBackend().ForwardRequest().WithCallback((context, _) =>
{
executedCallback = true;
callbackExecuted = true;
context.Response.StatusCode = 418;
});

test.RunBackend();

executedCallback.Should().BeTrue();
callbackExecuted.Should().BeTrue();
test.Context.Response.StatusCode.Should().Be(418);
}
}
Loading