using System.Net; using System.Net.Http.Json; using System.Text.Json; using GerbilManagerWebAPI.Inbox; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; namespace GerbilManager.Tests; /// INBOX-0 HTTP: sync endpoint imports (via a canned reader), status filter + triage, /// and the Gmail App Password is never returned by GET /api/mail-settings. public class RequestEndpointsTests : IClassFixture { private readonly ApiFactory _factory; public RequestEndpointsTests(ApiFactory factory) => _factory = factory; /// Test double for the IMAP reader: returns whatever the test stages, filtered by UID. private sealed class CannedReader : IGmailMailReader { public static List Messages { get; set; } = new(); public Task> FetchAsync(MailConnection c, uint sinceUid, CancellationToken ct = default) => Task.FromResult((IReadOnlyList)Messages.Where(m => m.Uid > sinceUid).ToList()); } private HttpClient ClientWithCannedReader() => _factory.WithWebHostBuilder(b => b.ConfigureTestServices(s => { s.RemoveAll(); s.AddScoped(); })).CreateClient(); [Fact] public async Task Sync_imports_then_triage_assigns_contact() { var client = ClientWithCannedReader(); CannedReader.Messages = new() { new("", null, null, null, "interessent@example.de", "Anna", "Suche Rennmäuse", "Hallo!", DateTimeOffset.UtcNow, Uid: 1001), }; // configure mail (otherwise sync reports MailNotConfigured) Assert.Equal(HttpStatusCode.NoContent, (await client.PutAsJsonAsync("/api/mail-settings", new { gmailAddress = "zucht@gmail.com", appPassword = "app-pw-xyz" })).StatusCode); var sync = await client.PostAsync("/api/requests/sync", null); Assert.Equal(HttpStatusCode.OK, sync.StatusCode); Assert.True(JsonDocument.Parse(await sync.Content.ReadAsStringAsync()).RootElement.GetProperty("imported").GetInt32() >= 1); // find the imported request via the status filter var listed = JsonDocument.Parse(await client.GetStringAsync("/api/requests?filter=status==New&pageSize=100")); var item = listed.RootElement.GetProperty("items").EnumerateArray() .Single(r => r.GetProperty("gmailMessageId").GetString() == ""); var reqId = item.GetProperty("id").GetString(); // a contact + triage var contactId = JsonDocument.Parse(await (await client.PostAsJsonAsync("/contacts", new { name = "Anna" })) .Content.ReadAsStringAsync()).RootElement.GetProperty("id").GetString(); var put = await client.PutAsJsonAsync($"/api/requests/{reqId}", new { status = "Assigned", assignedContactId = contactId }); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); var got = JsonDocument.Parse(await client.GetStringAsync($"/api/requests/{reqId}")).RootElement; Assert.Equal("Assigned", got.GetProperty("status").GetString()); Assert.Equal(contactId, got.GetProperty("assignedContactId").GetString()); } [Fact] public async Task MailSettings_stores_password_but_never_returns_it() { var client = _factory.CreateClient(); var put = await client.PutAsJsonAsync("/api/mail-settings", new { gmailAddress = "sekret@gmail.com", appPassword = "abcd efgh ijkl mnop" }); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); var json = await client.GetStringAsync("/api/mail-settings"); Assert.Contains("sekret@gmail.com", json); Assert.Contains("\"hasAppPassword\":true", json.Replace(" ", "")); Assert.DoesNotContain("abcd", json); // the app password must never leave the server Assert.DoesNotContain("appPassword", json); } }