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);
}
}