Skip to content

Commit

Permalink
allow using named verifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
alexalok committed Dec 26, 2023
1 parent 8dbcf55 commit 079a457
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 84 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,5 @@ $RECYCLE.BIN/
# Mac desktop service store files
.DS_Store

_NCrunch*
_NCrunch*
.vs
146 changes: 102 additions & 44 deletions ReceiptVerifierEndpoint.Tests/ReceiptVerifierEndpointTests.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System.Text;
using System.Text.Json;
using AppleReceiptVerifierNET;
using AppleReceiptVerifierNET.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using ReceiptVerifierMiddlewareEndpoint;
using ReceiptVerifierMiddlewareEndpoint.Middlewares;
using Environment = AppleReceiptVerifierNET.Models.Environment;

namespace ReceiptVerifierEndpoint.Tests;

Expand All @@ -17,12 +16,21 @@ public class ReceiptVerifierEndpointTests
public async Task Ensure_Invoke_Async_Writes_Response_Body()
{
const string requestUrl = "/api/receiptInfo";
var options = new OptionsWrapper<ReceiptVerifierMiddlewareOptions>(new ReceiptVerifierMiddlewareOptions

var options = new OptionsWrapper<ReceiptVerifierMiddlewareOptions>(new ReceiptVerifierMiddlewareOptions
{
Path = requestUrl
});


var receiptVerifierMock = new Mock<IAppleReceiptVerifier>(MockBehavior.Strict);
receiptVerifierMock.Setup(v => v.VerifyReceiptAsync("abc", false))
.ReturnsAsync(new VerifyReceiptResponse(null, null, null, null, null, null!, 0)
{
RawJson = "abc"
});
var receiptVerifierEndpoint =
new ReceiptVerifierEndpointMiddleware(null!, options);

var context = new DefaultHttpContext
{
Request =
Expand All @@ -33,18 +41,12 @@ public async Task Ensure_Invoke_Async_Writes_Response_Body()
Response =
{
Body = new MemoryStream()
}
},
RequestServices = new ServiceCollection()
.AddTransient<IAppleReceiptVerifier>(_ => receiptVerifierMock.Object)
.BuildServiceProvider()
};

var receiptVerifierMock = new Mock<IAppleReceiptVerifier>(MockBehavior.Strict);
receiptVerifierMock.Setup(v => v.VerifyReceiptAsync("abc", false))
.ReturnsAsync(new VerifyReceiptResponse(null, null, null, null, null, null!, 0)
{
RawJson = "abc"
});
var receiptVerifierEndpoint =
new ReceiptVerifierEndpointMiddleware(null!, receiptVerifierMock.Object, options);


await receiptVerifierEndpoint.InvokeAsync(context);

var responseBody = context.Response.Body;
Expand All @@ -58,12 +60,21 @@ public async Task Ensure_Next_Delegate_Executes_If_Wrong_Request_Url()
{
const string requestUrl = "/api/receiptInfo";
const string wrongRequestUrl = "/receiptInfo";

var options = new OptionsWrapper<ReceiptVerifierMiddlewareOptions>(new ReceiptVerifierMiddlewareOptions
{
Path = requestUrl
});




var receiptVerifierMock = new Mock<IAppleReceiptVerifier>(MockBehavior.Strict);
receiptVerifierMock.Setup(v => v.VerifyReceiptAsync("abc", false))
.ReturnsAsync(new VerifyReceiptResponse(null, null, null, null, null, null!, 0)
{
RawJson = "abc"
});

var context = new DefaultHttpContext
{
Request =
Expand All @@ -74,25 +85,22 @@ public async Task Ensure_Next_Delegate_Executes_If_Wrong_Request_Url()
Response =
{
Body = new MemoryStream()
}
},
RequestServices = new ServiceCollection()
.AddTransient<IAppleReceiptVerifier>(_ => receiptVerifierMock.Object)
.BuildServiceProvider()
};

var receiptVerifierMock = new Mock<IAppleReceiptVerifier>(MockBehavior.Strict);
receiptVerifierMock.Setup(v => v.VerifyReceiptAsync("abc", false))
.ReturnsAsync(new VerifyReceiptResponse(null, null, null, null, null, null!, 0)
{
RawJson = "abc"
});

var requestDelegateMock = new Mock<RequestDelegate>();
requestDelegateMock.Setup(x => x(context));
var receiptVerifierEndpoint =
new ReceiptVerifierEndpointMiddleware(requestDelegateMock.Object, receiptVerifierMock.Object, options);
var receiptVerifierEndpoint =
new ReceiptVerifierEndpointMiddleware(requestDelegateMock.Object, options);

await receiptVerifierEndpoint.InvokeAsync(context);
requestDelegateMock.Verify(x=>x(context), Times.Once);

requestDelegateMock.Verify(x => x(context), Times.Once);
}

[Fact]
public async Task Ensure_Next_Delegate_Not_Executes_If_Request_Url_Is_Correct()
{
Expand All @@ -102,7 +110,15 @@ public async Task Ensure_Next_Delegate_Not_Executes_If_Request_Url_Is_Correct()
{
Path = requestUrl
});


var receiptVerifierMock = new Mock<IAppleReceiptVerifier>(MockBehavior.Strict);
receiptVerifierMock.Setup(v => v.VerifyReceiptAsync("abc", false))
.ReturnsAsync(new VerifyReceiptResponse(null, null, null, null, null, null!, 0)
{
RawJson = "abc"
});


var context = new DefaultHttpContext
{
Request =
Expand All @@ -113,22 +129,64 @@ public async Task Ensure_Next_Delegate_Not_Executes_If_Request_Url_Is_Correct()
Response =
{
Body = new MemoryStream()
}
},
RequestServices = new ServiceCollection()
.AddTransient<IAppleReceiptVerifier>(_ => receiptVerifierMock.Object)
.BuildServiceProvider()
};

var receiptVerifierMock = new Mock<IAppleReceiptVerifier>(MockBehavior.Strict);
receiptVerifierMock.Setup(v => v.VerifyReceiptAsync("abc", false))
.ReturnsAsync(new VerifyReceiptResponse(null, null, null, null, null, null!, 0)
{
RawJson = "abc"
});

var requestDelegateMock = new Mock<RequestDelegate>();
requestDelegateMock.Setup(x => x(context));
var receiptVerifierEndpoint =
new ReceiptVerifierEndpointMiddleware(requestDelegateMock.Object, receiptVerifierMock.Object, options);

var receiptVerifierEndpoint =
new ReceiptVerifierEndpointMiddleware(requestDelegateMock.Object, options);

await receiptVerifierEndpoint.InvokeAsync(context);

requestDelegateMock.Verify(x=>x(context), Times.Never);

requestDelegateMock.Verify(x => x(context), Times.Never);
}

[Fact]
public async Task Middleware_Uses_Named_Verifier()
{
const string verifierName = "test-verifier";
const string requestUrl = "/api/receiptInfo";

// Arrange
Mock<IAppleReceiptVerifier> verifierStub = new();
verifierStub.Setup(v => v.VerifyReceiptAsync("abc", false))
.ReturnsAsync(new VerifyReceiptResponseStub() { RawJson = "{}" });

Mock<IAppleReceiptVerifierResolver> resolverMock = new();
resolverMock.Setup(r => r.Resolve(verifierName)).Returns(verifierStub.Object);
RequestDelegate nextStub = (HttpContext ctx) => Task.CompletedTask;
OptionsWrapper<ReceiptVerifierMiddlewareOptions> options = new(new()
{
Path = requestUrl,
VerifierName = verifierName
});

ReceiptVerifierEndpointMiddleware middleware = new(nextStub, options);
HttpContext ctx = new DefaultHttpContext()
{
Request =
{
Path = requestUrl,
Body = new MemoryStream(Encoding.ASCII.GetBytes("{\"Receipt\":\"abc\"}"))
},
Response =
{
Body = new MemoryStream()
},
RequestServices = new ServiceCollection()
.AddScoped<IAppleReceiptVerifierResolver>(_ => resolverMock.Object)
.BuildServiceProvider()
};

// Act
await middleware.InvokeAsync(ctx);

// Assert
resolverMock.Verify(r => r.Resolve(verifierName), Times.Once);
}
}
14 changes: 14 additions & 0 deletions ReceiptVerifierEndpoint.Tests/VerifyReceiptResponseStub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using AppleReceiptVerifierNET.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ReceiptVerifierEndpoint.Tests;
internal record VerifyReceiptResponseStub : VerifyReceiptResponse
{
public VerifyReceiptResponseStub() : base(null, null, null, null, null, null!, 0)
{
}
}
6 changes: 6 additions & 0 deletions ReceiptVerifierEndpoint.lutconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<LUTConfig Version="1.0">
<Repository />
<ParallelBuilds>true</ParallelBuilds>
<ParallelTestRuns>true</ParallelTestRuns>
<TestCaseTimeout>180000</TestCaseTimeout>
</LUTConfig>
20 changes: 15 additions & 5 deletions ReceiptVerifierEndpoint/HostingExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using AppleReceiptVerifierNET;
using Microsoft.Extensions.DependencyInjection.Extensions;
using ReceiptVerifierEndpoint;
using Microsoft.Extensions.Options;
using ReceiptVerifierMiddlewareEndpoint.Middlewares;

namespace ReceiptVerifierMiddlewareEndpoint;
Expand All @@ -21,10 +21,20 @@ public static class HostingExtensions

public static IApplicationBuilder UseReceiptVerifierEndpointMiddleware(this IApplicationBuilder app)
{
var verifier = app.ApplicationServices.GetService<IAppleReceiptVerifier>();
if (verifier == null)
throw new InvalidOperationException("AppleReceiptVerifier not registered.");

var options = app.ApplicationServices
.GetRequiredService<IOptions<ReceiptVerifierMiddlewareOptions>>().Value;
using var scope = app.ApplicationServices.CreateScope();
if (options.VerifierName == null)
{
_ = scope.ServiceProvider.GetRequiredService<IAppleReceiptVerifier>();
}
else
{
var resolver = scope.ServiceProvider.GetRequiredService<IAppleReceiptVerifierResolver>();
_ = resolver.Resolve(options.VerifierName) ?? throw new InvalidOperationException(
$"AppleReceiptVerifier named {options.VerifierName} is not registered.");
}

return app.UseMiddleware<ReceiptVerifierEndpointMiddleware>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,32 @@ namespace ReceiptVerifierMiddlewareEndpoint.Middlewares;
public class ReceiptVerifierEndpointMiddleware
{
private readonly RequestDelegate _next;
readonly IAppleReceiptVerifier _receiptVerifier;
private readonly ReceiptVerifierMiddlewareOptions _options;
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);

public ReceiptVerifierEndpointMiddleware(RequestDelegate next, IAppleReceiptVerifier receiptVerifier, IOptions<ReceiptVerifierMiddlewareOptions> options)
public ReceiptVerifierEndpointMiddleware(RequestDelegate next, IOptions<ReceiptVerifierMiddlewareOptions> options)
{
_next = next;
_receiptVerifier = receiptVerifier;
_options = options.Value;
}

public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.Equals(_options.Path, StringComparison.InvariantCultureIgnoreCase))
{
var body = await JsonSerializer.DeserializeAsync<ReceiptDto>(context.Request.Body,
var body = await JsonSerializer.DeserializeAsync<ReceiptDto>(context.Request.Body,
SerializerOptions);
if (body == null)
{
{
context.Response.StatusCode = 400;
}
else
{
var receiptInfo = await _receiptVerifier.VerifyReceiptAsync(body.Receipt);
IAppleReceiptVerifier verifier = _options.VerifierName == null
? context.RequestServices.GetRequiredService<IAppleReceiptVerifier>()
: context.RequestServices.GetRequiredService<IAppleReceiptVerifierResolver>()
.Resolve(_options.VerifierName);
var receiptInfo = await verifier.VerifyReceiptAsync(body.Receipt);
await context.Response.WriteAsync(receiptInfo.RawJson);
}
}
Expand Down
55 changes: 27 additions & 28 deletions ReceiptVerifierEndpoint/ReceiptVerifierEndpoint.csproj
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<IsPackable>true</IsPackable>
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<IsPackable>true</IsPackable>
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
</PropertyGroup>

<PropertyGroup>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
<Copyright>Copyright © kej 2022</Copyright>
<Description>ReceiptVerifierMiddleware adds an endpoint to retrieve receipt info via AppleReceiptVerifier.NET</Description>
<Company>kej</Company>
<Authors>kej</Authors>
<Version>1.0.1</Version>
<PackageReleaseNotes>
</PackageReleaseNotes>
<PackageTags>App Store, in-app purchase, in-app subscription, verify receipt</PackageTags>
<RepositoryUrl>https://github.com/kingkej/ReceiptVerifierMiddlewareEndpoint</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/kingkej/ReceiptVerifierMiddlewareEndpoint</PackageProjectUrl>
<PackageId>ReceiptVerifierMiddlewareEndpoint</PackageId>
<RootNamespace>ReceiptVerifierMiddlewareEndpoint</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
<Copyright>Copyright © kej 2022</Copyright>
<Description>ReceiptVerifierMiddleware adds an endpoint to retrieve receipt info via AppleReceiptVerifier.NET</Description>
<Company>kej</Company>
<Authors>kej</Authors>
<Version>1.2.3</Version>
<PackageReleaseNotes>Allowed using named verifiers.</PackageReleaseNotes>
<PackageTags>App Store, in-app purchase, in-app subscription, verify receipt</PackageTags>
<RepositoryUrl>https://github.com/kingkej/ReceiptVerifierMiddlewareEndpoint</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/kingkej/ReceiptVerifierMiddlewareEndpoint</PackageProjectUrl>
<PackageId>ReceiptVerifierMiddlewareEndpoint</PackageId>
<RootNamespace>ReceiptVerifierMiddlewareEndpoint</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AppleReceiptVerifier.NET" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AppleReceiptVerifier.NET" Version="1.0.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ namespace ReceiptVerifierMiddlewareEndpoint;
public class ReceiptVerifierMiddlewareOptions
{
public string Path { get; set; } = null!;
public string? VerifierName { get; set; } = null!;
}

0 comments on commit 079a457

Please sign in to comment.