familynido/tests/FamilyNido.Tests/Integration/PreferencesTests.cs
Pablo Fernández a308041d59 Initial commit
FamilyNido — a self-hosted PWA for a single household: shared calendar,
chores, meals, school agenda, health records and a family wall. One
instance per family, deployable with `docker compose` on any home
server.

Stack: .NET 10 (ASP.NET Core Minimal APIs) + EF Core 10 + PostgreSQL 16
on the backend, Angular 21 (standalone, signals, zoneless) + Tailwind
CSS v4 on the frontend, SignalR for realtime, optional OIDC alongside
local credentials, integration via a versioned `/api/v1/**` public API.

See README.md for the module overview and how to deploy.
2026-05-13 00:23:14 +02:00

107 lines
4 KiB
C#

using System.Net.Http.Json;
using FluentAssertions;
namespace FamilyNido.Tests.Integration;
/// <summary>
/// Coverage for /api/dashboard/preferences and /api/notifications/preferences.
/// Both follow the same shape: GET returns a reconciled view, PUT replaces.
/// </summary>
public sealed class PreferencesTests : IntegrationTestBase
{
public PreferencesTests(IntegrationFixture fixture) : base(fixture) { }
[Fact]
public async Task Dashboard_GET_returns_default_widget_order_when_user_has_no_row()
{
await WithDbAsync(db => TestSeed.SeedFamilyAsync(db, Fixture.Factory.Services));
await TestSeed.LoginAsync(Client, "dan@example.com");
var resp = await Client.GetAsync("/api/dashboard/preferences");
resp.IsSuccessStatusCode.Should().BeTrue();
var prefs = await ReadAsync<DashPrefsDto>(resp);
// The default order from DashboardWidgets.DefaultOrder.
prefs!.Widgets.Should().HaveCountGreaterThanOrEqualTo(8);
prefs.Widgets.Select(w => w.Id).Should().Contain([
"weather", "school", "agenda", "tasks", "calendar",
"meals", "wall", "scores", "birthdays",
]);
prefs.Widgets.Should().AllSatisfy(w => w.Visible.Should().BeTrue());
}
[Fact]
public async Task Dashboard_PUT_persists_the_layout_and_GET_reflects_it()
{
await WithDbAsync(db => TestSeed.SeedFamilyAsync(db, Fixture.Factory.Services));
await TestSeed.LoginAsync(Client, "dan@example.com");
var put = await Client.PutAsJsonAsync("/api/dashboard/preferences", new
{
widgets = new[]
{
new { id = "tasks", visible = true },
new { id = "weather", visible = false },
},
});
put.IsSuccessStatusCode.Should().BeTrue();
var get = await Client.GetAsync("/api/dashboard/preferences");
var prefs = await ReadAsync<DashPrefsDto>(get);
// The two ids the user sent are in the order they sent them; everything
// else is reconciled from the catalogue with Visible=true.
prefs!.Widgets[0].Id.Should().Be("tasks");
prefs.Widgets[0].Visible.Should().BeTrue();
prefs.Widgets[1].Id.Should().Be("weather");
prefs.Widgets[1].Visible.Should().BeFalse();
}
[Fact]
public async Task Notifications_GET_returns_defaults()
{
await WithDbAsync(db => TestSeed.SeedFamilyAsync(db, Fixture.Factory.Services));
await TestSeed.LoginAsync(Client, "dan@example.com");
var resp = await Client.GetAsync("/api/notifications/preferences");
resp.IsSuccessStatusCode.Should().BeTrue();
var prefs = await ReadAsync<NotifPrefsDto>(resp);
// Defaults: everything on (matches the persistence default).
prefs!.EmailEnabled.Should().BeTrue();
prefs.DigestEnabled.Should().BeTrue();
prefs.TaskAssignedEnabled.Should().BeTrue();
prefs.WallMentionEnabled.Should().BeTrue();
}
[Fact]
public async Task Notifications_PUT_persists_toggle_changes()
{
await WithDbAsync(db => TestSeed.SeedFamilyAsync(db, Fixture.Factory.Services));
await TestSeed.LoginAsync(Client, "dan@example.com");
var put = await Client.PutAsJsonAsync("/api/notifications/preferences", new
{
emailEnabled = true,
digestEnabled = false,
taskAssignedEnabled = true,
wallMentionEnabled = false,
});
put.IsSuccessStatusCode.Should().BeTrue();
var get = await Client.GetAsync("/api/notifications/preferences");
var prefs = await ReadAsync<NotifPrefsDto>(get);
prefs!.DigestEnabled.Should().BeFalse();
prefs.WallMentionEnabled.Should().BeFalse();
prefs.TaskAssignedEnabled.Should().BeTrue();
}
private sealed record DashPrefsDto(List<WidgetDto> Widgets);
private sealed record WidgetDto(string Id, bool Visible);
private sealed record NotifPrefsDto(
bool EmailEnabled,
bool DigestEnabled,
bool TaskAssignedEnabled,
bool WallMentionEnabled);
}