-
Notifications
You must be signed in to change notification settings - Fork 0
test(integration): add Payment + Shipping integration projects (real DB + Wolverine) #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # gitleaks configuration — extends the default ruleset rather than replacing it. | ||
| # The default rules (AWS keys, JWTs, private keys, .env values, etc.) stay active. | ||
| # | ||
| # How findings are actually suppressed in this repo: **inline `// gitleaks:allow` | ||
| # markers at the end of the offending line.** That mechanism is reliable across | ||
| # gitleaks 8.x. The `[[allowlists]]` block below was tried first but does NOT | ||
| # reliably suppress findings from extended-default rules in 8.24.x — the | ||
| # config loads (CI log shows "using gitleaks config from GITLEAKS_CONFIG env | ||
| # var") and the [extend] block works, but the allowlist's path+regex match | ||
| # doesn't fire against generic-api-key findings. Kept here as supplementary | ||
| # documentation of the project's fake-credential convention; the inline | ||
| # `gitleaks:allow` marker on the literal line is the load-bearing mechanism. | ||
| # | ||
| # See CLAUDE.md "Testing → fake credentials in test fixtures" for the | ||
| # rule + reference factories. | ||
|
|
||
| [extend] | ||
| useDefault = true | ||
|
|
||
| # Documentation of intent. The inline `gitleaks:allow` markers on the literal | ||
| # lines in each of the three factories are what actually suppresses the | ||
| # findings; this block is kept for the convention-encoding it carries. | ||
| [[allowlists]] | ||
| description = "Fake ASB connection string in integration test factories (documentation; primary suppressor is inline `gitleaks:allow`)" | ||
| paths = [ | ||
| '''tests/OrderService\.Tests\.Integration/OrderApiFactory\.cs''', | ||
| '''tests/PaymentService\.Tests\.Integration/PaymentApiFactory\.cs''', | ||
| '''tests/ShippingService\.Tests\.Integration/ShippingApiFactory\.cs''', | ||
| ] | ||
| regexes = [ | ||
| '''SharedAccessKey=[A-Za-z0-9+/=]{20,}''', | ||
| ] | ||
| regexTarget = "line" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
tests/PaymentService.Tests.Integration/PaymentApiFactory.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| using Microsoft.AspNetCore.Authentication; | ||
| using Microsoft.AspNetCore.Hosting; | ||
| using Microsoft.AspNetCore.Mvc.Testing; | ||
| using Microsoft.AspNetCore.TestHost; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using NSubstitute; | ||
| using PaymentService.Domain; | ||
| using Testcontainers.MsSql; | ||
| using Wolverine; | ||
| using Xunit; | ||
|
|
||
| namespace PaymentService.Tests.Integration; | ||
|
|
||
| /// <summary> | ||
| /// Boots the real PaymentService API in-process against a throwaway SQL Server container, with | ||
| /// Wolverine's external transports stubbed so the Acceptor + Gateway handlers and the | ||
| /// transactional outbox run locally against a real DB. | ||
| /// | ||
| /// <para> | ||
| /// <b>What this exercises that unit tests can't:</b> the split <c>ProcessPaymentHandler</c> | ||
| /// (Acceptor) → <c>PaymentProcessingRequested</c> → <c>PaymentProcessingRequestedHandler</c> | ||
| /// (Gateway) flow running over Wolverine's local queue + middleware chain (FluentValidation, | ||
| /// AutoApplyTransactions, ContextPropagation), the outbox staging the terminal event in the same | ||
| /// EF transaction as the entity write, EF migrations applying to real SQL Server, the | ||
| /// <c>RowVersion</c> concurrency token, and the OrderId-uniqueness idempotency guard. | ||
| /// </para> | ||
| /// <para> | ||
| /// <b>Why stub the transport instead of the ASB emulator:</b> the outbox-staging guarantee and | ||
| /// the handler logic are what unit tests can't reach. The internal <c>PaymentProcessingRequested</c> | ||
| /// message has no external routing, so it stays on the in-process local queue and is fully | ||
| /// exercised here; only the outbound <c>PaymentCompletedEvent</c>/<c>PaymentFailedEvent</c> wire | ||
| /// hop is stubbed. See <c>docs/STATUS.md</c>. | ||
| /// </para> | ||
| /// <para> | ||
| /// <b>Why a fake "messaging" connection string:</b> <c>Program.cs</c> calls | ||
| /// <c>UseAzureServiceBus(GetConnectionString("messaging")!)</c>, parsed eagerly at registration | ||
| /// even with external transports disabled — so it must be syntactically valid ASB. | ||
| /// </para> | ||
| /// <para> | ||
| /// <b>Why stub <c>IPaymentGateway</c>:</b> the Gateway handler calls the Stripe gateway. Tests | ||
| /// configure the stub to return success or failure to drive the Completed / Failed branches | ||
| /// without touching a real payment provider. | ||
| /// </para> | ||
| /// </summary> | ||
| public sealed class PaymentApiFactory : WebApplicationFactory<Program>, IAsyncLifetime | ||
| { | ||
| private readonly MsSqlContainer _sqlServer = | ||
| new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-latest").Build(); | ||
|
|
||
| /// <summary>The stub injected in place of <c>StripePaymentGateway</c>. Tests configure it.</summary> | ||
| public IPaymentGateway Gateway { get; } = Substitute.For<IPaymentGateway>(); | ||
|
|
||
| public async Task InitializeAsync() | ||
| { | ||
| await _sqlServer.StartAsync(); | ||
| } | ||
|
|
||
| Task IAsyncLifetime.DisposeAsync() => DisposeAsync().AsTask(); | ||
|
|
||
| public override async ValueTask DisposeAsync() | ||
| { | ||
| // Dispose the host BEFORE the SQL container so Wolverine's durable background agents | ||
| // (outbox dispatcher, recovery sweeper) stop polling the wolverine.* tables before the | ||
| // DB is torn down — otherwise their heartbeats hit "connection refused" and crash the | ||
| // test host AFTER all tests passed. Swallow the cancellation that a slow shutdown can | ||
| // surface (a delayed background-service stop isn't a correctness signal). See the | ||
| // matching note in OrderApiFactory. | ||
| try | ||
| { | ||
| await base.DisposeAsync(); | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| // Intentional swallow — TaskCanceledException derives from OperationCanceledException. | ||
| } | ||
|
|
||
| await _sqlServer.DisposeAsync(); | ||
| } | ||
|
|
||
| protected override void ConfigureWebHost(IWebHostBuilder builder) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
|
|
||
| // Disable Wolverine AutoProvision — against the fake ASB string it would hang trying to | ||
| // provision topics/subscriptions at startup. DisableAllExternalWolverineTransports below | ||
| // handles routing; this handles the broker-provisioning that runs earlier. | ||
| builder.UseSetting("Wolverine:AutoProvision", "false"); | ||
|
|
||
| // Real SQL Server for the payments DB + Wolverine outbox tables. | ||
| builder.UseSetting("ConnectionStrings:payments-db", _sqlServer.GetConnectionString()); | ||
|
|
||
| // Syntactically-valid ASB connection string — parsed eagerly, never used over the wire. | ||
| // SharedAccessKey base64-decodes to "fake-shared-key-for-testing-only". The inline | ||
| // `gitleaks:allow` marker on the literal line is the suppressor; .gitleaks.toml documents | ||
| // the project's convention but its [[allowlists]] block does not reliably override the | ||
| // extended-default `generic-api-key` rule in gitleaks 8.x. See CLAUDE.md. | ||
| builder.UseSetting( | ||
| "ConnectionStrings:messaging", | ||
| "Endpoint=sb://fake.servicebus.windows.net/;SharedAccessKeyName=fake;SharedAccessKey=ZmFrZS1zaGFyZWQta2V5LWZvci10ZXN0aW5nLW9ubHk="); // gitleaks:allow | ||
|
|
||
| builder.ConfigureTestServices(services => | ||
| { | ||
| // Always-succeeds auth so .RequireAuthorization() on /payments/process passes. | ||
| services.AddAuthentication(TestAuthHandler.SchemeName) | ||
| .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(TestAuthHandler.SchemeName, _ => { }); | ||
|
|
||
| // Stub the Stripe gateway — tests drive Completed/Failed via the configured result. | ||
| services.AddSingleton<IPaymentGateway>(Gateway); | ||
|
|
||
| // Disable external ASB listeners/senders. Outgoing events route to in-process stubs | ||
| // (still through the outbox + middleware chain); the internal PaymentProcessingRequested | ||
| // message stays on the local queue and runs end-to-end. | ||
| services.DisableAllExternalWolverineTransports(); | ||
| }); | ||
| } | ||
|
|
||
| /// <summary>Opens a DI scope — callers resolve <c>PaymentDbContext</c> for direct DB setup/assertions.</summary> | ||
| public AsyncServiceScope CreateDbScope() => Services.CreateAsyncScope(); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.