diff --git a/.github/workflows/ci-cd.yml-archive b/.github/workflows/ci-cd.yml-archive index a71a25bb8..e52730ae0 100644 --- a/.github/workflows/ci-cd.yml-archive +++ b/.github/workflows/ci-cd.yml-archive @@ -72,13 +72,13 @@ jobs: id: build_solution shell: pwsh run: | - dotnet build ./DevBetterWeb.sln --configuration Release + dotnet build ./DevBetterWeb.slnx --configuration Release - name: Run unit tests id: run_unit_tests shell: pwsh run: | - dotnet test ./DevBetterWeb.sln --filter FullyQualifiedName!~Vimeo.Tests --configuration Release --no-build + dotnet test ./DevBetterWeb.slnx --filter FullyQualifiedName!~Vimeo.Tests --configuration Release --no-build - name: Publish WebApp id: publish_webapp shell: pwsh diff --git a/.github/workflows/repro-bug.yml b/.github/workflows/repro-bug.yml index b116ee75d..cf47a5aba 100644 --- a/.github/workflows/repro-bug.yml +++ b/.github/workflows/repro-bug.yml @@ -27,36 +27,57 @@ permissions: actions: read pull-requests: read +defaults: + run: + shell: bash + env: DOTNET_VERSION: 10.0.x + DOTNET_CONFIGURATION: Release # Set one or both. SOLUTION_FILE: DevBetterWeb.slnx PROJECT_FILE: src/DevBetterWeb.Web/DevBetterWeb.Web.csproj APP_URL: http://127.0.0.1:5010 - APP_STARTUP_COMMAND: dotnet run --project src/DevBetterWeb.Web/DevBetterWeb.Web.csproj --no-build --launch-profile "DevBetterWeb.Web - DEV" + APP_HEALTH_PATH: /health + APP_STARTUP_COMMAND: dotnet run --project src/DevBetterWeb.Web/DevBetterWeb.Web.csproj --configuration Release --no-build --no-launch-profile jobs: reproduce-bug: + # Only run on: + # - workflow_dispatch (maintainer-initiated), OR + # - pull_request labeled 'bug' from a branch in THIS repo (never a fork — + # we build & run PR code, so external PRs would be a code-execution risk). if: > - (github.event_name == 'pull_request' && github.event.label.name == 'bug') || - github.event_name == 'workflow_dispatch' + github.event_name == 'workflow_dispatch' || + (github.event_name == 'pull_request' && + github.event.label.name == 'bug' && + github.event.pull_request.head.repo.full_name == github.repository) runs-on: ubuntu-latest timeout-minutes: 30 concurrency: - group: repro-bug-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.inputs.issue_number || github.run_id }} + group: repro-bug-${{ github.event.pull_request.number || github.event.inputs.issue_number }} cancel-in-progress: true steps: - name: Resolve target context id: target - uses: actions/github-script@v7 + # Pinned to v7.0.1 commit SHA for supply-chain safety. + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: script: | const isDispatch = context.eventName === 'workflow_dispatch'; const inputs = context.payload.inputs || {}; + // Truncate untrusted text so it can't blow past env-var size limits + // or overwhelm downstream consumers. + const MAX_LEN = 8000; + const truncate = (s) => { + const v = (s || '').toString(); + return v.length > MAX_LEN ? v.slice(0, MAX_LEN) + '\n…[truncated]' : v; + }; + let issueNumber; let issueTitle; let issueBody; @@ -91,6 +112,12 @@ jobs: repo: context.repo.repo, pull_number: issueNumber }); + // Refuse to build code from a fork via dispatch as well. + const expected = `${context.repo.owner}/${context.repo.repo}`; + if (prResponse.data.head.repo.full_name !== expected) { + core.setFailed(`PR #${issueNumber} is from a fork; refusing to check out and build untrusted code.`); + return; + } checkoutRef = prResponse.data.head.sha; } else { checkoutRef = inputs.target_ref || 'main'; @@ -117,36 +144,53 @@ jobs: } core.exportVariable('ISSUE_NUMBER', String(issueNumber)); - core.exportVariable('ISSUE_TITLE', issueTitle); - core.exportVariable('ISSUE_BODY', issueBody); + core.exportVariable('ISSUE_TITLE', truncate(issueTitle)); + core.exportVariable('ISSUE_BODY', truncate(issueBody)); core.exportVariable('ISSUE_URL', issueUrl); core.exportVariable('REPO_NAME', context.repo.owner + '/' + context.repo.repo); core.setOutput('checkout_ref', checkoutRef); core.setOutput('issue_number', String(issueNumber)); - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ steps.target.outputs.checkout_ref }} + # Don't leave the GITHUB_TOKEN in .git/config — we only need to read. + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 with: node-version: 22 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 with: dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-v1 + - name: Initialize Playwright project + timeout-minutes: 5 run: | - npm init -y - npm install @playwright/test wait-on - npx playwright install --with-deps + set -euo pipefail + npm init -y >/dev/null + npm install --no-fund --no-audit @playwright/test wait-on + if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = "true" ]; then + npx playwright install-deps + else + npx playwright install --with-deps + fi - name: Restore .NET dependencies + timeout-minutes: 10 run: | + set -euo pipefail if [ -n "${SOLUTION_FILE}" ] && [ -f "${SOLUTION_FILE}" ]; then dotnet restore "${SOLUTION_FILE}" elif [ -n "${PROJECT_FILE}" ] && [ -f "${PROJECT_FILE}" ]; then @@ -156,28 +200,56 @@ jobs: fi - name: Build application + timeout-minutes: 15 run: | + set -euo pipefail if [ -n "${SOLUTION_FILE}" ] && [ -f "${SOLUTION_FILE}" ]; then - dotnet build "${SOLUTION_FILE}" --no-restore + dotnet build "${SOLUTION_FILE}" --configuration "${DOTNET_CONFIGURATION}" --no-restore elif [ -n "${PROJECT_FILE}" ] && [ -f "${PROJECT_FILE}" ]; then - dotnet build "${PROJECT_FILE}" --no-restore + dotnet build "${PROJECT_FILE}" --configuration "${DOTNET_CONFIGURATION}" --no-restore else - dotnet build --no-restore + dotnet build --configuration "${DOTNET_CONFIGURATION}" --no-restore fi - name: Start application run: | - ${APP_STARTUP_COMMAND} > app.log 2>&1 & + set -euo pipefail + export ASPNETCORE_URLS="${APP_URL}" + export ASPNETCORE_ENVIRONMENT="Development" + # setsid puts the app in its own process group so cleanup can kill + # the whole tree (dotnet often spawns child processes). + setsid bash -c "${APP_STARTUP_COMMAND} > app.log 2>&1" & echo $! > app.pid + echo "Started app with PGID $(cat app.pid)" - name: Wait for application startup + timeout-minutes: 3 run: | - npx wait-on "${APP_URL}" --timeout 120000 + set -uo pipefail + # Prefer a real health endpoint over a bare HTTP check; wait-on with + # http-get:// considers only 2xx as ready. + TARGET="http-get://127.0.0.1:5010${APP_HEALTH_PATH}" + if ! npx wait-on "${TARGET}" --timeout 120000 --interval 1000; then + echo "::warning::Health endpoint not ready at ${TARGET}; falling back to base URL." + if ! npx wait-on "http-get://127.0.0.1:5010/" --timeout 30000 --interval 1000; then + echo "::error::Application failed to become ready." + if [ -f app.pid ]; then + echo "Process status:" + ps -p "$(cat app.pid)" -f || true + fi + echo "Last 200 lines of app.log:" + tail -n 200 app.log || true + exit 1 + fi + fi - name: Generate reproduction script run: | + set -euo pipefail mkdir -p tests + # NOTE: ISSUE_TITLE / ISSUE_BODY are UNTRUSTED user input. + # Only log them — never pass them to page.goto(), eval(), or shell. cat << 'EOF' > tests/repro.spec.js const { test } = require('@playwright/test'); @@ -203,12 +275,12 @@ jobs: EOF - name: Run Playwright reproduction - run: | - npx playwright test --reporter=list + timeout-minutes: 10 + run: npx playwright test --reporter=list - name: Upload Playwright artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: repro-artifacts-${{ steps.target.outputs.issue_number }} path: | @@ -219,35 +291,35 @@ jobs: retention-days: 14 - name: Comment on issue or PR with artifact link - if: always() - uses: actions/github-script@v7 + if: always() && env.ISSUE_NUMBER != '' + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: script: | const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}` + `/actions/runs/${context.runId}`; - const body = ` - 🤖 Automated reproduction attempt completed. - - ## Target - #${process.env.ISSUE_NUMBER} - - ## Configuration - - Solution: \`${process.env.SOLUTION_FILE || '(not set)'}\` - - Project: \`${process.env.PROJECT_FILE || '(not set)'}\` - - App URL: \`${process.env.APP_URL}\` - - ## Artifacts - - 🎥 Playwright video - - 📷 Screenshots - - 🧭 Trace files - - 📄 Console output - - Download artifacts from the workflow run: - - ${runUrl} - `; + const body = [ + '🤖 Automated reproduction attempt completed.', + '', + '## Target', + `#${process.env.ISSUE_NUMBER}`, + '', + '## Configuration', + `- Solution: \`${process.env.SOLUTION_FILE || '(not set)'}\``, + `- Project: \`${process.env.PROJECT_FILE || '(not set)'}\``, + `- App URL: \`${process.env.APP_URL}\``, + '', + '## Artifacts', + '- 🎥 Playwright video', + '- 📷 Screenshots', + '- 🧭 Trace files', + '- 📄 Console output', + '', + 'Download artifacts from the workflow run:', + '', + runUrl + ].join('\n'); await github.rest.issues.createComment({ owner: context.repo.owner, @@ -260,5 +332,9 @@ jobs: if: always() run: | if [ -f app.pid ]; then - kill $(cat app.pid) || true + PGID="$(cat app.pid)" + # Kill the entire process group started via setsid. + kill -TERM -"${PGID}" 2>/dev/null || true + sleep 2 + kill -KILL -"${PGID}" 2>/dev/null || true fi diff --git a/DevBetterWeb.sln b/DevBetterWeb.sln deleted file mode 100644 index 105988411..000000000 Binary files a/DevBetterWeb.sln and /dev/null differ diff --git a/Dockerfile b/Dockerfile index d5e0dc09c..b231e7947 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG BUILD_CONFIGURATION=Debug -COPY *.sln . +COPY *.slnx . COPY . . RUN dotnet restore WORKDIR "/src/DevBetterWeb.Web" diff --git a/src/DevBetterWeb.Core/Services/CreateVideoService.cs b/src/DevBetterWeb.Core/Services/CreateVideoService.cs index 2d7208edc..d5be027b6 100644 --- a/src/DevBetterWeb.Core/Services/CreateVideoService.cs +++ b/src/DevBetterWeb.Core/Services/CreateVideoService.cs @@ -13,7 +13,6 @@ using NimblePros.Vimeo.VideoServices; using NimblePros.Vimeo.VideoTusService; using static DevBetterWeb.Core.Entities.Member; -using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext; namespace DevBetterWeb.Core.Services; public class CreateVideoService : ICreateVideoService diff --git a/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerEventService.cs b/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerEventService.cs index 2b5441ab9..a392aac82 100644 --- a/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerEventService.cs +++ b/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerEventService.cs @@ -21,7 +21,7 @@ public string GetSubscriptionId(string json) var invoice = stripeEvent.Data.Object as Invoice; if (invoice != null) { - return invoice.SubscriptionId; + return invoice.Parent?.SubscriptionDetails?.SubscriptionId ?? string.Empty; } var subscription = stripeEvent.Data.Object as Subscription; @@ -38,7 +38,7 @@ private string GetSubscriptionId(Stripe.Event stripeEvent) var invoice = stripeEvent.Data.Object as Invoice; if (invoice != null) { - return invoice.SubscriptionId; + return invoice.Parent?.SubscriptionDetails?.SubscriptionId ?? string.Empty; } var subscription = stripeEvent.Data.Object as Subscription; diff --git a/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerInvoiceService.cs b/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerInvoiceService.cs index de935cec9..69d971568 100644 --- a/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerInvoiceService.cs +++ b/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerInvoiceService.cs @@ -40,7 +40,7 @@ public string GetSubscriptionId(string json) var stripeEvent = EventUtility.ParseEvent(json); var invoice = stripeEvent.Data.Object as Invoice; - var subscriptionId = invoice!.Subscription.Id; + var subscriptionId = invoice!.Parent?.SubscriptionDetails?.SubscriptionId ?? string.Empty; return subscriptionId; } diff --git a/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerSubscriptionCreationService.cs b/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerSubscriptionCreationService.cs index 1c29219dc..1e0245029 100644 --- a/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerSubscriptionCreationService.cs +++ b/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerSubscriptionCreationService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using DevBetterWeb.Core; using DevBetterWeb.Core.Interfaces; using DevBetterWeb.Infrastructure.Interfaces; @@ -50,14 +51,14 @@ private IPaymentHandlerSubscriptionDTO CreateSubscription(string customerId, str }, }; - subscriptionOptions.AddExpand("latest_invoice.payment_intent"); + subscriptionOptions.AddExpand("latest_invoice.payments.data.payment.payment_intent"); var subscription = _subscriptionService.Create(subscriptionOptions); var id = subscription.Id; var status = subscription.Status; - var latestInvoicePaymentIntentStatus = subscription.LatestInvoice.PaymentIntent.Status; - var latestInvoicePaymentIntentClientSecret = subscription.LatestInvoice.PaymentIntent.ClientSecret; + var latestInvoicePaymentIntentStatus = subscription.LatestInvoice.Payments?.Data?.FirstOrDefault()?.Payment?.PaymentIntent?.Status ?? string.Empty; + var latestInvoicePaymentIntentClientSecret = subscription.LatestInvoice.ConfirmationSecret?.ClientSecret ?? string.Empty; var subscriptionDTO = new StripePaymentHandlerSubscriptionDTO(id, status, latestInvoicePaymentIntentStatus, latestInvoicePaymentIntentClientSecret); diff --git a/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerSubscriptionService.cs b/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerSubscriptionService.cs index 7eb53835b..5ef20c179 100644 --- a/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerSubscriptionService.cs +++ b/src/DevBetterWeb.Infrastructure/PaymentHandler/StripePaymentHandler/StripePaymentHandlerSubscriptionService.cs @@ -181,14 +181,14 @@ private BillingPeriod GetSubscriptionBillingInterval(Subscription subscription) private DateTime GetEndDate(Subscription subscription) { - DateTime endDate = subscription.CurrentPeriodEnd; + DateTime endDate = subscription.Items.Data[0].CurrentPeriodEnd; return endDate; } private DateTime GetStartDate(Subscription subscription) { - DateTime startDate = subscription.CurrentPeriodStart; + DateTime startDate = subscription.Items.Data[0].CurrentPeriodStart; return startDate; } diff --git a/src/DevBetterWeb.Web/Dockerfile b/src/DevBetterWeb.Web/Dockerfile index 127f647b9..76ba95f46 100644 --- a/src/DevBetterWeb.Web/Dockerfile +++ b/src/DevBetterWeb.Web/Dockerfile @@ -3,21 +3,21 @@ # # RUN COMMAND # docker run --name devbetterweb --rm -it -p 5000:80 web -FROM microsoft/dotnet:5.0-sdk AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /app -COPY *.sln . +COPY *.slnx . COPY . . WORKDIR /app/src/DevBetterWeb.Web RUN dotnet restore RUN dotnet publish -c Release -o out -FROM microsoft/dotnet:5.0-aspnetcore-runtime AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime WORKDIR /app COPY --from=build /app/src/DevBetterWeb.Web/out ./ # Optional: Set this here if not setting it from docker-compose.yml # ENV ASPNETCORE_ENVIRONMENT Development -ENTRYPOINT ["dotnet", "DevBetterWeb.Web.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "DevBetterWeb.Web.dll"] diff --git a/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/CustomerSubscriptionDeletedWebHook.cs b/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/CustomerSubscriptionDeletedWebHook.cs index 7fc9ba63a..d96991795 100644 --- a/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/CustomerSubscriptionDeletedWebHook.cs +++ b/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/CustomerSubscriptionDeletedWebHook.cs @@ -50,7 +50,7 @@ public override async Task HandleAsync(CancellationToken cancellat _logger.LogInformation($"Processing Stripe Event Type: {stripeEvent.Type}"); - if (stripeEvent.Type != Events.CustomerDeleted) + if (stripeEvent.Type != EventTypes.CustomerDeleted) { throw new Exception($"Unhandled Stripe event type {stripeEvent.Type}"); } diff --git a/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/CustomerSubscriptionUpdatedWebHook.cs b/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/CustomerSubscriptionUpdatedWebHook.cs index 842a233fe..4d0b23f30 100644 --- a/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/CustomerSubscriptionUpdatedWebHook.cs +++ b/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/CustomerSubscriptionUpdatedWebHook.cs @@ -57,7 +57,7 @@ public override async Task HandleAsync(CancellationToken cancellat _logger.LogInformation($"Processing Stripe Event Type: {stripeEvent.Type}"); - if (stripeEvent.Type != Events.CustomerUpdated) + if (stripeEvent.Type != EventTypes.CustomerUpdated) { throw new Exception($"Unhandled Stripe event type {stripeEvent.Type}"); } diff --git a/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/InvoicePaidWebHook.cs b/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/InvoicePaidWebHook.cs index 79d91ffa8..a06902a74 100644 --- a/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/InvoicePaidWebHook.cs +++ b/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/InvoicePaidWebHook.cs @@ -55,7 +55,7 @@ public override async Task HandleAsync(CancellationToken cancellat _logger.LogInformation($"Processing Stripe Event Type: {stripeEvent.Type}"); // Was InvoicePaymentSucceeded changed to InvoicePaid - if (stripeEvent.Type != Events.InvoicePaid) + if (stripeEvent.Type != EventTypes.InvoicePaid) { throw new Exception($"Unhandled Stripe event type {stripeEvent.Type}"); } diff --git a/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/PaymentIntentSucceededWebHook.cs b/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/PaymentIntentSucceededWebHook.cs index 2714a9dd9..988c49db4 100644 --- a/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/PaymentIntentSucceededWebHook.cs +++ b/src/DevBetterWeb.Web/Endpoints/StripeWebhookEndpoints/PaymentIntentSucceededWebHook.cs @@ -48,7 +48,7 @@ public override async Task HandleAsync(CancellationToken cancellat _logger.LogInformation($"Processing Stripe Event Type: {stripeEvent.Type}"); - if (stripeEvent.Type != Events.PaymentIntentSucceeded) + if (stripeEvent.Type != EventTypes.PaymentIntentSucceeded) { throw new Exception($"Unhandled Stripe event type {stripeEvent.Type}"); } diff --git a/src/DevBetterWeb.Web/MappingProfiles/InvoiceProfile.cs b/src/DevBetterWeb.Web/MappingProfiles/InvoiceProfile.cs index bf8907768..8f4796f97 100644 --- a/src/DevBetterWeb.Web/MappingProfiles/InvoiceProfile.cs +++ b/src/DevBetterWeb.Web/MappingProfiles/InvoiceProfile.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using AutoMapper; using DevBetterWeb.Web.Models; using Stripe; @@ -10,9 +10,9 @@ public InvoiceProfile() { CreateMap() .ForPath(dest => dest.IsPaid, - opt => opt.MapFrom(source => source.Paid)) + opt => opt.MapFrom(source => source.Status == "paid")) .ForPath(dest => dest.IsPaidOutOfBand, - opt => opt.MapFrom(source => source.PaidOutOfBand)) + opt => opt.MapFrom(source => false)) .ForPath(dest => dest.FinalizedAt, opt => opt.MapFrom(source => source.StatusTransitions.FinalizedAt)) .ForPath(dest => dest.PaidAt, diff --git a/src/DevBetterWeb.Web/Program.cs b/src/DevBetterWeb.Web/Program.cs index b9d78115d..0e8dc199e 100644 --- a/src/DevBetterWeb.Web/Program.cs +++ b/src/DevBetterWeb.Web/Program.cs @@ -26,7 +26,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using NimblePros.Metronome; using NimblePros.Vimeo.Extensions; using Serilog; @@ -103,8 +103,7 @@ builder.Services.AddStripeServices( builder.Configuration.GetSection("StripeOptions")["StripeSecretKey"]!); -var webProjectAssembly = typeof(Program).Assembly; -builder.Services.AddAutoMapper(webProjectAssembly); +builder.Services.AddAutoMapper(cfg => cfg.AddMaps(typeof(Program).Assembly)); builder.Services.AddMetronome(); @@ -153,6 +152,8 @@ builder.Services.AddHttpClient(); +builder.Services.AddHealthChecks(); + builder.Services.AddMvc() .AddControllersAsServices() .AddRazorRuntimeCompilation(); @@ -162,10 +163,14 @@ c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); }); -builder.Services.AddApplicationInsightsTelemetry(options => +var appInsightsConnectionString = builder.Configuration["APPINSIGHTS_CONNECTIONSTRING"]; +if (!string.IsNullOrWhiteSpace(appInsightsConnectionString)) { - options.ConnectionString = builder.Configuration["APPINSIGHTS_CONNECTIONSTRING"]; -}); + builder.Services.AddApplicationInsightsTelemetry(options => + { + options.ConnectionString = appInsightsConnectionString; + }); +} var app = builder.Build(); @@ -201,6 +206,8 @@ }); } +app.MapHealthChecks("/health"); + app.MapRazorPages(); app.UseStaticFiles(); diff --git a/tests/DevBetterWeb.Tests/Integration/Stripe/EventUtilityParseEvent.cs b/tests/DevBetterWeb.Tests/Integration/Stripe/EventUtilityParseEvent.cs index 093261516..504b5479b 100644 --- a/tests/DevBetterWeb.Tests/Integration/Stripe/EventUtilityParseEvent.cs +++ b/tests/DevBetterWeb.Tests/Integration/Stripe/EventUtilityParseEvent.cs @@ -11,10 +11,10 @@ public class EventUtilityParseEvent public void ParseJsonToSubscriptionId() { string json = System.IO.File.ReadAllText(_jsonFile); - var stripeEvent = EventUtility.ParseEvent(json); + var stripeEvent = EventUtility.ParseEvent(json, throwOnApiVersionMismatch: false); var invoice = (Invoice)stripeEvent.Data.Object; - string subscriptionId = invoice.SubscriptionId; + string subscriptionId = invoice.Parent?.SubscriptionDetails?.SubscriptionId ?? string.Empty; Assert.Equal("sub_K1hzaxOt9gb2TB", subscriptionId); } } diff --git a/tests/DevBetterWeb.Tests/Integration/Stripe/stripeJson1.json b/tests/DevBetterWeb.Tests/Integration/Stripe/stripeJson1.json index e4b9beb6b..46aaec7d5 100644 --- a/tests/DevBetterWeb.Tests/Integration/Stripe/stripeJson1.json +++ b/tests/DevBetterWeb.Tests/Integration/Stripe/stripeJson1.json @@ -162,7 +162,13 @@ "paid_at": 1628777276, "voided_at": null }, - "subscription": "sub_K1hzaxOt9gb2TB", + "parent": { + "type": "subscription_details", + "subscription_details": { + "subscription": "sub_K1hzaxOt9gb2TB", + "metadata": {} + } + }, "subtotal": 20000, "tax": null, "total": 20000,