familynido/tests/FamilyNido.Tests/Integration/ScoresTests.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

110 lines
4.2 KiB
C#

using System.Net.Http.Json;
using FamilyNido.Domain.HouseholdTasks;
using FluentAssertions;
namespace FamilyNido.Tests.Integration;
/// <summary>
/// Coverage for the family scoreboard slices: leaderboard aggregation and
/// per-member totals (this week / this month / all time). Seeds tasks +
/// completions directly through EF so we don't need to drive the UI flow
/// for every fixture variation.
/// </summary>
public sealed class ScoresTests : IntegrationTestBase
{
public ScoresTests(IntegrationFixture fixture) : base(fixture) { }
[Fact]
public async Task Leaderboard_sums_points_per_member_for_the_range()
{
var handle = await WithDbAsync(db => TestSeed.SeedFamilyAsync(db, Fixture.Factory.Services));
await TestSeed.LoginAsync(Client, "dan@example.com");
var alice = await WithDbAsync(db => TestSeed.SeedMemberAsync(db, handle.Family.Id, "Alice"));
// Dan: 5 points (one task), Alice: 12 points (two tasks).
var today = DateOnly.FromDateTime(DateTime.UtcNow);
await WithDbAsync(async db =>
{
var t1 = new HouseholdTask
{
FamilyId = handle.Family.Id,
Title = "Tarea 5",
StartDate = today,
DueDate = today,
CreatedByMemberId = handle.Member.Id,
Points = 5,
};
var t2 = new HouseholdTask
{
FamilyId = handle.Family.Id,
Title = "Tarea 7",
StartDate = today,
DueDate = today,
CreatedByMemberId = handle.Member.Id,
Points = 7,
};
var t3 = new HouseholdTask
{
FamilyId = handle.Family.Id,
Title = "Tarea 5b",
StartDate = today,
DueDate = today,
CreatedByMemberId = handle.Member.Id,
Points = 5,
};
db.HouseholdTasks.AddRange(t1, t2, t3);
await db.SaveChangesAsync();
db.TaskCompletions.AddRange(
new TaskCompletion
{
TaskId = t1.Id, OccurrenceDate = today,
CompletedByMemberId = handle.Member.Id,
CompletedAt = DateTimeOffset.UtcNow,
},
new TaskCompletion
{
TaskId = t2.Id, OccurrenceDate = today,
CompletedByMemberId = alice.Id,
CompletedAt = DateTimeOffset.UtcNow,
},
new TaskCompletion
{
TaskId = t3.Id, OccurrenceDate = today,
CompletedByMemberId = alice.Id,
CompletedAt = DateTimeOffset.UtcNow,
});
await db.SaveChangesAsync();
});
var resp = await Client.GetAsync($"/api/scores/leaderboard?from={today:yyyy-MM-dd}&to={today:yyyy-MM-dd}");
var board = await ReadAsync<LeaderboardDto>(resp);
board!.Entries.Should().HaveCount(2);
board.Entries[0].MemberId.Should().Be(alice.Id);
board.Entries[0].Points.Should().Be(12);
board.Entries[0].CompletionCount.Should().Be(2);
board.Entries[1].MemberId.Should().Be(handle.Member.Id);
board.Entries[1].Points.Should().Be(5);
}
[Fact]
public async Task Member_score_returns_zeros_when_no_completions()
{
var handle = await WithDbAsync(db => TestSeed.SeedFamilyAsync(db, Fixture.Factory.Services));
await TestSeed.LoginAsync(Client, "dan@example.com");
var resp = await Client.GetAsync($"/api/scores/members/{handle.Member.Id}");
var score = await ReadAsync<MemberScoreDto>(resp);
score!.MemberId.Should().Be(handle.Member.Id);
score.ThisWeek.Should().Be(0);
score.ThisMonth.Should().Be(0);
score.AllTime.Should().Be(0);
}
private sealed record LeaderboardDto(string From, string To, List<ScoreboardEntry> Entries);
private sealed record ScoreboardEntry(Guid MemberId, int Points, int CompletionCount);
private sealed record MemberScoreDto(Guid MemberId, int ThisWeek, int ThisMonth, int AllTime);
}