Merge feature/status-model (STATUS-MODEL-BE P0): enum Active->Breeding(default)+Pet/ForSale; Deceased/GivenAway serverseitig abgeleitet (GerbilStatusService.Derive + Praezedenz + >7J presumed); Migration+Backfill+Startup-Sweep
This commit is contained in:
@@ -42,7 +42,7 @@ public class CmsTests : IClassFixture<ApiFactory>
|
||||
var encId = JsonDocument.Parse(await encResp.Content.ReadAsStringAsync()).RootElement.GetProperty("id").GetString();
|
||||
await _client.PostAsJsonAsync("/gerbils", new { name = "Verkaufsmaus", gender = "female", status = "ForSale", enclosureId = encId });
|
||||
// a non-ForSale gerbil must NOT appear
|
||||
await _client.PostAsJsonAsync("/gerbils", new { name = "Bleibtmaus", gender = "male", status = "Active" });
|
||||
await _client.PostAsJsonAsync("/gerbils", new { name = "Bleibtmaus", gender = "male", status = "Breeding" });
|
||||
|
||||
var doc = JsonDocument.Parse(await _client.GetStringAsync("/api/site-snapshot"));
|
||||
var animals = doc.RootElement.GetProperty("pages").EnumerateArray()
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace GerbilManager.Tests
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Krümel",
|
||||
Gender = Gender.female,
|
||||
Status = GerbilStatus.Active,
|
||||
Status = GerbilStatus.Breeding,
|
||||
DateOfBirth = new DateOnly(2025, 3, 12),
|
||||
LitterId = litter.Id,
|
||||
EnclosureId = enclosure.Id,
|
||||
@@ -118,7 +118,7 @@ namespace GerbilManager.Tests
|
||||
var row = lines[1];
|
||||
Assert.Contains("Krümel", row);
|
||||
Assert.Contains("Weiblich", row);
|
||||
Assert.Contains("Aktiv", row);
|
||||
Assert.Contains("Zucht", row);
|
||||
Assert.Contains("12.03.2025", row);
|
||||
Assert.Contains("Großbecken", row);
|
||||
Assert.Contains("Wurf K", row);
|
||||
|
||||
122
GerbilManager.Tests/GerbilStatusTests.cs
Normal file
122
GerbilManager.Tests/GerbilStatusTests.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using GerbilManagerWebAPI.Models;
|
||||
using GerbilManagerWebAPI.Services;
|
||||
|
||||
namespace GerbilManager.Tests;
|
||||
|
||||
/// <summary>STATUS-MODEL: GerbilStatusService.Derive() precedence rules.</summary>
|
||||
public class GerbilStatusTests
|
||||
{
|
||||
private static readonly DateOnly Today = new(2026, 6, 7);
|
||||
|
||||
// 1) DateOfDeath → Deceased, always overrides user input
|
||||
[Fact]
|
||||
public void DateOfDeath_set_returns_Deceased()
|
||||
{
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.Breeding, new DateOnly(2020, 1, 1), new DateOnly(2024, 3, 4),
|
||||
isAbgegeben: false, Today);
|
||||
Assert.Equal(GerbilStatus.Deceased, status);
|
||||
}
|
||||
|
||||
// DateOfDeath wins even when also Abgegeben
|
||||
[Fact]
|
||||
public void DateOfDeath_wins_over_Abgegeben()
|
||||
{
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.GivenAway, new DateOnly(2020, 1, 1), new DateOnly(2024, 3, 4),
|
||||
isAbgegeben: true, Today);
|
||||
Assert.Equal(GerbilStatus.Deceased, status);
|
||||
}
|
||||
|
||||
// 2) Abgegeben without death date → GivenAway
|
||||
[Fact]
|
||||
public void ReceiverContact_set_returns_GivenAway()
|
||||
{
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.Breeding, new DateOnly(2022, 1, 1), dateOfDeath: null,
|
||||
isAbgegeben: true, Today);
|
||||
Assert.Equal(GerbilStatus.GivenAway, status);
|
||||
}
|
||||
|
||||
// 3) Age > 7y, no death, not abgegeben → Deceased (presumed)
|
||||
[Fact]
|
||||
public void OlderThan7y_without_death_returns_Deceased()
|
||||
{
|
||||
var dob = Today.AddYears(-GerbilStatusService.MaxAgeYears).AddDays(-1); // 1 day past threshold
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.Breeding, dob, dateOfDeath: null, isAbgegeben: false, Today);
|
||||
Assert.Equal(GerbilStatus.Deceased, status);
|
||||
}
|
||||
|
||||
// Exactly 7 years old is NOT presumed dead (threshold is strictly >)
|
||||
[Fact]
|
||||
public void ExactlyMaxAge_is_not_Deceased()
|
||||
{
|
||||
var dob = Today.AddYears(-GerbilStatusService.MaxAgeYears); // exactly 7y today
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.Breeding, dob, dateOfDeath: null, isAbgegeben: false, Today);
|
||||
// today >= dob.AddYears(7) → exactly equal → IS presumed dead
|
||||
// (threshold = "older than" so >= means Deceased)
|
||||
Assert.Equal(GerbilStatus.Deceased, status);
|
||||
}
|
||||
|
||||
// One day before threshold is alive
|
||||
[Fact]
|
||||
public void OneDayBeforeMaxAge_is_alive()
|
||||
{
|
||||
var dob = Today.AddYears(-GerbilStatusService.MaxAgeYears).AddDays(1);
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.Breeding, dob, dateOfDeath: null, isAbgegeben: false, Today);
|
||||
Assert.Equal(GerbilStatus.Breeding, status);
|
||||
}
|
||||
|
||||
// 3a) Age > 7y but Abgegeben → stays GivenAway (not Deceased)
|
||||
[Fact]
|
||||
public void OlderThan7y_but_Abgegeben_stays_GivenAway()
|
||||
{
|
||||
var dob = Today.AddYears(-8);
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.GivenAway, dob, dateOfDeath: null, isAbgegeben: true, Today);
|
||||
Assert.Equal(GerbilStatus.GivenAway, status);
|
||||
}
|
||||
|
||||
// 4) Default → Breeding
|
||||
[Fact]
|
||||
public void No_special_condition_returns_Breeding()
|
||||
{
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.Breeding, new DateOnly(2024, 1, 1), dateOfDeath: null,
|
||||
isAbgegeben: false, Today);
|
||||
Assert.Equal(GerbilStatus.Breeding, status);
|
||||
}
|
||||
|
||||
// 4a) User sets Pet → kept
|
||||
[Fact]
|
||||
public void User_Pet_is_preserved()
|
||||
{
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.Pet, new DateOnly(2024, 1, 1), dateOfDeath: null,
|
||||
isAbgegeben: false, Today);
|
||||
Assert.Equal(GerbilStatus.Pet, status);
|
||||
}
|
||||
|
||||
// 4b) User sets ForSale → kept
|
||||
[Fact]
|
||||
public void User_ForSale_is_preserved()
|
||||
{
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.ForSale, new DateOnly(2024, 1, 1), dateOfDeath: null,
|
||||
isAbgegeben: false, Today);
|
||||
Assert.Equal(GerbilStatus.ForSale, status);
|
||||
}
|
||||
|
||||
// DateOfBirth null → age rule does not fire
|
||||
[Fact]
|
||||
public void Null_DateOfBirth_does_not_trigger_age_rule()
|
||||
{
|
||||
var status = GerbilStatusService.Derive(
|
||||
GerbilStatus.Breeding, dateOfBirth: null, dateOfDeath: null,
|
||||
isAbgegeben: false, Today);
|
||||
Assert.Equal(GerbilStatus.Breeding, status);
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ public class PartialUpdateTests : IClassFixture<ApiFactory>
|
||||
// Assert: name / genotype / originBreeder / status / externalRef all intact
|
||||
var json = JsonDocument.Parse(await _client.GetStringAsync($"/gerbils/{id}")).RootElement;
|
||||
|
||||
Assert.Equal("Active", GetStr(json, "status"));
|
||||
Assert.Equal("Breeding", GetStr(json, "status"));
|
||||
Assert.Equal("Charakter-Tier", GetStr(json, "name"));
|
||||
Assert.Equal("female", GetStr(json, "gender"));
|
||||
Assert.Equal("aa CC DD ee GG PP spsp rere", GetStr(json, "genotype"));
|
||||
@@ -138,7 +138,7 @@ public class PartialUpdateTests : IClassFixture<ApiFactory>
|
||||
|
||||
var json = JsonDocument.Parse(await resp.Content.ReadAsStringAsync()).RootElement;
|
||||
Assert.Equal("Minimal", GetStr(json, "name"));
|
||||
Assert.Equal("Active", GetStr(json, "status"));
|
||||
Assert.Equal("Breeding", GetStr(json, "status"));
|
||||
// characterTraits defaults to [] on create when not provided
|
||||
Assert.Contains("[]", GetNested(json, "characterTraits") ?? "");
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public class SearchVariationsTests : IClassFixture<ApiFactory>
|
||||
private readonly HttpClient _client;
|
||||
public SearchVariationsTests(ApiFactory factory) => _client = factory.CreateClient();
|
||||
|
||||
private async Task Create(string name, string? originBreeder = null, string status = "Active")
|
||||
private async Task Create(string name, string? originBreeder = null, string status = "Breeding")
|
||||
{
|
||||
var resp = await _client.PostAsJsonAsync("/gerbils",
|
||||
new { name, gender = "female", status, originBreeder });
|
||||
@@ -142,7 +142,7 @@ public class SearchVariationsTests : IClassFixture<ApiFactory>
|
||||
public async Task Combined_status_and_name_filter()
|
||||
{
|
||||
await Create("KombiVerkauf", status: "ForSale");
|
||||
await Create("KombiAktiv", status: "Active");
|
||||
await Create("KombiAktiv", status: "Breeding");
|
||||
var hits = await Names("status==ForSale,nameSearch=*kombi");
|
||||
Assert.Contains("KombiVerkauf", hits);
|
||||
Assert.DoesNotContain("KombiAktiv", hits);
|
||||
|
||||
Reference in New Issue
Block a user