Dons PayPal

This commit is contained in:
Paul Schneider 2026-11-02 10:40:29 +00:00
commit e3b4414ab1
Signed by: notazof
GPG key ID: AD4B9B545FBD7D12
45 changed files with 7476 additions and 9 deletions

View file

@ -0,0 +1,172 @@
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Security.Claims;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Yavsc.Abstract.Workflow;
using Yavsc.Api.Test.Fixtures;
using Yavsc.Models;
using Yavsc.Models.Relationship;
using Yavsc.Models.Workflow;
using Yavsc.Tests.Shared;
namespace Yavsc.Api.Test;
[Collection("Yavsc Api")]
public sealed class PerformerDonationSettingsTests(ApiWebServerFixture fixture) : IClassFixture<ApiWebServerFixture>
{
private const string Endpoint = "/api/v1/account/performer-profile";
private static CancellationToken Ct => TestContext.Current.CancellationToken;
private HttpClient Client(string? subject, string scope = "api")
{
var client = new HttpClient(new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
}) { BaseAddress = new Uri(fixture.BaseAddress) };
if (subject != null)
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
TestTokenIssuer.Issue(subject, scope, extraClaims: [new Claim(Yavsc.Constants.RoleClaimType, "Performer")]));
return client;
}
private async Task<string> CreatePerformerAsync(bool withProfile = true)
{
var id = "donation-settings-" + Guid.NewGuid().ToString("N");
using var scope = fixture.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
db.Users.Add(new ApplicationUser { Id = id, UserName = id, Email = id + "@example.test" });
if (withProfile) db.Performers.Add(new PerformerProfile
{
PerformerId = id, SIREN = "123456789", WebSite = "", Active = true,
DonationPayPalEmail = "initial@payments.example",
OrganizationAddress = new Location { Address = "Test" }
});
await db.SaveChangesAsync(Ct);
return id;
}
[Fact]
public async Task New_performer_can_set_a_recipient_when_creating_their_profile()
{
var id = await CreatePerformerAsync(withProfile: false);
using var client = Client(id);
var profile = await client.GetFromJsonAsync<PerformerProfileSettings>(Endpoint, Ct);
Assert.NotNull(profile);
Assert.Equal("", profile.DonationPayPalEmail);
profile.DonationPayPalEmail = "new@payments.example";
using var response = await client.PutAsJsonAsync(Endpoint, profile, Ct);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("new@payments.example",
(await client.GetFromJsonAsync<PerformerProfileSettings>(Endpoint, Ct))?.DonationPayPalEmail);
}
[Theory]
[InlineData(" new@payments.example ", "new@payments.example")]
[InlineData(" ", "")]
public async Task Recipient_is_trimmed_and_whitespace_disables_donations(string input, string expected)
{
var id = await CreatePerformerAsync();
using var client = Client(id);
using var response = await client.PutAsJsonAsync(Endpoint, new { donationPayPalEmail = input }, Ct);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(expected,
(await client.GetFromJsonAsync<PerformerProfileSettings>(Endpoint, Ct))?.DonationPayPalEmail);
}
[Fact]
public async Task Performer_can_read_update_and_disable_their_paypal_recipient_across_devices()
{
var id = await CreatePerformerAsync();
using var client = Client(id);
var profile = await client.GetFromJsonAsync<PerformerProfileSettings>(Endpoint, Ct);
Assert.NotNull(profile);
Assert.Equal("initial@payments.example", profile.DonationPayPalEmail);
using var read = await client.GetAsync(Endpoint, Ct);
Assert.True(read.Headers.CacheControl?.NoStore);
foreach (var email in new[] { "new@payments.example", "" })
{
profile.DonationPayPalEmail = email;
using var response = await client.PutAsJsonAsync(Endpoint, profile, Ct);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
using var otherDevice = Client(id);
var reloaded = await otherDevice.GetFromJsonAsync<PerformerProfileSettings>(Endpoint, Ct);
Assert.NotNull(reloaded);
Assert.Equal(email, reloaded.DonationPayPalEmail);
using var scope = fixture.Services.CreateScope();
var stored = await scope.ServiceProvider.GetRequiredService<ApplicationDbContext>()
.Performers.AsNoTracking().SingleAsync(p => p.PerformerId == id, Ct);
Assert.Equal(email.Length == 0 ? null : email, stored.DonationPayPalEmail);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Older_clients_omitting_or_sending_null_preserve_the_paypal_recipient(bool explicitNull)
{
var id = await CreatePerformerAsync();
using var client = Client(id);
using var response = explicitNull
? await client.PutAsJsonAsync(Endpoint, new { performerId = id, donationPayPalEmail = (string?)null }, Ct)
: await client.PutAsJsonAsync(Endpoint, new { performerId = id }, Ct);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var reloaded = await client.GetFromJsonAsync<PerformerProfileSettings>(Endpoint, Ct);
Assert.Equal("initial@payments.example", reloaded?.DonationPayPalEmail);
}
[Theory]
[InlineData("invalid")]
[InlineData("https://payments.example/donate")]
[InlineData("a@b@c.example")]
public async Task Invalid_recipient_returns_400_without_changing_the_profile(string email)
{
var id = await CreatePerformerAsync();
using var client = Client(id);
using var response = await client.PutAsJsonAsync(Endpoint,
new { performerId = id, donationPayPalEmail = email, webSite = "must-not-be-saved" }, Ct);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var profile = await client.GetFromJsonAsync<PerformerProfileSettings>(Endpoint, Ct);
Assert.Equal("initial@payments.example", profile?.DonationPayPalEmail);
Assert.Equal("", profile?.WebSite);
}
[Fact]
public async Task Too_long_recipient_returns_400_without_saving()
{
var id = await CreatePerformerAsync();
using var client = Client(id);
using var response = await client.PutAsJsonAsync(Endpoint,
new { donationPayPalEmail = new string('a', 128) + "@example.test" }, Ct);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Equal("initial@payments.example",
(await client.GetFromJsonAsync<PerformerProfileSettings>(Endpoint, Ct))?.DonationPayPalEmail);
}
[Fact]
public async Task Performer_cannot_write_another_accounts_recipient()
{
var owner = await CreatePerformerAsync();
var other = await CreatePerformerAsync();
using var client = Client(owner);
using var response = await client.PutAsJsonAsync(Endpoint,
new { performerId = other, donationPayPalEmail = "attacker@payments.example" }, Ct);
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
using var otherClient = Client(other);
Assert.Equal("initial@payments.example",
(await otherClient.GetFromJsonAsync<PerformerProfileSettings>(Endpoint, Ct))?.DonationPayPalEmail);
}
[Theory]
[InlineData(null, "api", HttpStatusCode.Unauthorized)]
[InlineData("anyone", "profile", HttpStatusCode.Forbidden)]
public async Task Read_and_write_require_authentication_and_api_scope(string? subject, string scope, HttpStatusCode expected)
{
using var client = Client(subject, scope);
using var read = await client.GetAsync(Endpoint, Ct);
using var write = await client.PutAsJsonAsync(Endpoint, new { donationPayPalEmail = "test@payments.example" }, Ct);
Assert.Equal(expected, read.StatusCode);
Assert.Equal(expected, write.StatusCode);
}
}