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 @@ -61,7 +61,7 @@ Track progress of emulator policy handler implementation. Each policy needs a ha
| ⬜ | RateLimitByKey | RateLimitByKeyHandler.cs | No-op + callbacks | `emulator/rate-limit-by-key` |
| ✅ | RewriteUri | RewriteUriHandler.cs | Context mutation | `emulator/rewrite-uri` |
| ⬜ | SendRequest | SendRequestHandler.cs | External service mock | `emulator/send-request` |
| | SetBackendService | SetBackendServiceHandler.cs | Context mutation | `emulator/set-backend-service` |
| | SetBackendService | SetBackendServiceHandler.cs | Context mutation | `emulator/set-backend-service` |
| ⬜ | ValidateJwt | ValidateJwtHandler.cs | Validation + short-circuit | `emulator/validate-jwt` |

## Existing Handlers Missing Tests Only
Expand Down
5 changes: 4 additions & 1 deletion src/Testing/Document/TestDocumentExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
Expand Down 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 BackendStore SetupBackendStore(this TestDocument document) =>
document.Context.BackendStore;

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

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

public class BadRuntimeConfigurationException(string message) : Exception(message)
{
public required string Policy { get; init; }
}
6 changes: 6 additions & 0 deletions src/Testing/Emulator/Data/Backend.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

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

public record Backend(string Id, string Url);
29 changes: 29 additions & 0 deletions src/Testing/Emulator/Data/BackendStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;

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

public class BackendStore
{
private readonly Dictionary<string, Backend> _backends = new();

public BackendStore Add(string id, string url)
{
var backend = new Backend(id, url);
Add(backend);
return this;
}

public void Add(Backend backend)
{
if (!_backends.TryAdd(backend.Id, backend))
{
throw new Exception($"Backend with id '{backend.Id}' already exists.");
}
}

public bool TryGet(string id, [NotNullWhen(true)] out Backend? backend) =>
_backends.TryGetValue(id, out backend);
}
31 changes: 30 additions & 1 deletion src/Testing/Emulator/Policies/SetBackendServiceHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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 @@ -17,6 +18,34 @@ internal class SetBackendServiceHandler : PolicyHandler<SetBackendServiceConfig>

protected override void Handle(GatewayContext context, SetBackendServiceConfig config)
{
throw new NotImplementedException();
if (config.BaseUrl is not null)
{
SetServiceUrl(context, config.BaseUrl);
}
else if (config.BackendId is not null)
{
if (!context.BackendStore.TryGet(config.BackendId, out var backend))
{
throw new BadRuntimeConfigurationException(
$"Backend with id '{config.BackendId}' could not be found.")
{
Policy = PolicyName
};
}

SetServiceUrl(context, backend.Url);
}
}

private static void SetServiceUrl(GatewayContext context, string url)
{
var uri = new Uri(url);
context.Api.ServiceUrl = new MockUrl
{
Scheme = uri.Scheme,
Host = uri.Host,
Port = uri.Port.ToString(),
Path = uri.AbsolutePath
};
}
}
1 change: 1 addition & 0 deletions src/Testing/Emulator/SectionContextProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public static SectionContextProxy<TSection> Create(GatewayContext expressionCont
}
catch (FinishSectionProcessingException) { throw; }
catch (PolicyException) { throw; }
catch (BadRuntimeConfigurationException) { throw; }
catch (Exception e)
{
throw new PolicyException(e) { Policy = targetMethod.Name, Section = _sectionName, PolicyArgs = args };
Expand Down
3 changes: 2 additions & 1 deletion src/Testing/GatewayContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
Expand All @@ -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 BackendStore BackendStore = new();
internal readonly RateLimitStore RateLimitStore = new();

public GatewayContext()
Expand Down
141 changes: 141 additions & 0 deletions test/Test.Testing/Emulator/Policies/SetBackendServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

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;

namespace Test.Emulator.Emulator.Policies;

[TestClass]
public class SetBackendServiceTests
{
class SetBackendServiceWithBaseUrl : IDocument
{
public void Inbound(IInboundContext context)
{
context.SetBackendService(new SetBackendServiceConfig
{
BaseUrl = "https://new-backend.example.com/api"
});
}

public void Backend(IBackendContext context)
{
context.SetBackendService(new SetBackendServiceConfig
{
BaseUrl = "https://backend-section.example.com"
});
}

public void Outbound(IOutboundContext context)
{
context.SetBackendService(new SetBackendServiceConfig
{
BaseUrl = "https://outbound-backend.example.com"
});
}

public void OnError(IOnErrorContext context)
{
context.SetBackendService(new SetBackendServiceConfig
{
BaseUrl = "https://error-backend.example.com"
});
}
}

class SetBackendServiceWithBackendId : IDocument
{
public void Inbound(IInboundContext context)
{
context.SetBackendService(new SetBackendServiceConfig
{
BackendId = "my-backend"
});
}
}

[TestMethod]
public void SetBackendService_BaseUrl_Inbound()
{
var test = new SetBackendServiceWithBaseUrl().AsTestDocument();

test.RunInbound();

test.Context.Api.ServiceUrl.Scheme.Should().Be("https");
test.Context.Api.ServiceUrl.Host.Should().Be("new-backend.example.com");
test.Context.Api.ServiceUrl.Path.Should().Be("/api");
}

[TestMethod]
public void SetBackendService_BaseUrl_Backend()
{
var test = new SetBackendServiceWithBaseUrl().AsTestDocument();

test.RunBackend();

test.Context.Api.ServiceUrl.Host.Should().Be("backend-section.example.com");
}

[TestMethod]
public void SetBackendService_BaseUrl_Outbound()
{
var test = new SetBackendServiceWithBaseUrl().AsTestDocument();

test.RunOutbound();

test.Context.Api.ServiceUrl.Host.Should().Be("outbound-backend.example.com");
}

[TestMethod]
public void SetBackendService_BaseUrl_OnError()
{
var test = new SetBackendServiceWithBaseUrl().AsTestDocument();

test.RunOnError();

test.Context.Api.ServiceUrl.Host.Should().Be("error-backend.example.com");
}

[TestMethod]
public void SetBackendService_BackendId_ResolvesFromStore()
{
var test = new SetBackendServiceWithBackendId().AsTestDocument();
test.SetupBackendStore().Add("my-backend", "https://resolved-backend.example.com/v2");

test.RunInbound();

test.Context.Api.ServiceUrl.Host.Should().Be("resolved-backend.example.com");
test.Context.Api.ServiceUrl.Path.Should().Be("/v2");
}

[TestMethod]
public void SetBackendService_BackendId_NotFound_Throws()
{
var test = new SetBackendServiceWithBackendId().AsTestDocument();

var act = () => test.RunInbound();

act.Should().Throw<BadRuntimeConfigurationException>()
.Which.Message.Should().Contain("my-backend");
}

[TestMethod]
public void SetBackendService_Callback()
{
var test = new SetBackendServiceWithBaseUrl().AsTestDocument();
var callbackExecuted = false;
test.SetupInbound().SetBackendService().WithCallback((_, config) =>
{
callbackExecuted = true;
config.BaseUrl.Should().Be("https://new-backend.example.com/api");
});

test.RunInbound();

callbackExecuted.Should().BeTrue();
test.Context.Api.ServiceUrl.Host.Should().NotBe("new-backend.example.com");
}
}
Loading