VEU.WathBody/3-app/2-cli/Server.WatchBody.Web.Tests/ProgramStoreTests.cs

236 lines
7.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Kit.Helpers.SqlHelperAbstract;
using Server.WatchBody.Core.Dal.Src.Domain;
using Server.WatchBody.Core.Dal.Src.Repository;
using Server.WatchBody.Core.Programs;
using Xunit;
namespace Server.WatchBody.Web.Tests;
/// <summary>
/// Интеграционные тесты ProgramStore (specs m03 01-lifecycle,
/// 02-reading) против docker watchbody-pg. Каждый тест — свой атлет
/// (случайный ключ), fixture-данные не пересекаются с m02.
/// </summary>
[Collection("web")]
public class ProgramStoreTests(TestApp app)
{
private (ProgramStore Store, int AthleteId) NewStore()
{
var cs = new ConnectionString
{
Type = ConnectionStringType.Postgres,
Value = app.Config.DbConnectionString,
};
var athleteId = new AthleteRepository(cs).Insert(new Athlete
{
Name = "ProgramStore test",
ApiKeyHash = Server.WatchBody.Web.Services.ApiKeyHash.Of(Guid.NewGuid().ToString()),
});
var store = new ProgramStore(new ProgramRepository(cs), new ProgramVersionRepository(cs));
return (store, athleteId);
}
// --- EnsureProgram (spec 01 #1–2) ---
[Fact]
public void EnsureProgram_CreatesOnce()
{
var (store, athlete) = NewStore();
var first = store.EnsureProgram(athlete, "Программа v1");
var second = store.EnsureProgram(athlete, "Другое название");
Assert.Equal(first.Id, second.Id);
Assert.Equal("Программа v1", second.Title); // title не перезаписывается
}
// --- CreateDraft (spec 01 #3–9) ---
[Fact]
public void CreateDraft_FirstVersion_NoBasedOn()
{
var (store, athlete) = NewStore();
var v1 = store.CreateDraft(athlete, """{"weeks":[]}""", "user", "первая");
Assert.Equal(1, v1.Version);
Assert.Equal("draft", v1.Status);
Assert.Null(v1.BasedOnVersion);
}
[Fact]
public void CreateDraft_Second_BasedOnLatest()
{
var (store, athlete) = NewStore();
store.CreateDraft(athlete, """{"weeks":[]}""", "user", "первая");
var v2 = store.CreateDraft(athlete, """{"weeks":[1]}""", "user", "вторая");
Assert.Equal(2, v2.Version);
Assert.Equal(1, v2.BasedOnVersion);
}
[Fact]
public void CreateDraft_AuthorLlm_Rejected()
{
var (store, athlete) = NewStore();
Assert.Throws<ProgramStoreException>(
() => store.CreateDraft(athlete, "{}", "llm", ""));
}
[Fact]
public void CreateDraft_AuthorRules_Accepted()
{
var (store, athlete) = NewStore();
var v = store.CreateDraft(athlete, "{}", "rules", "правила");
Assert.Equal("rules", v.Author);
}
[Fact]
public void CreateDraft_BrokenChain_Rejected()
{
var (store, athlete) = NewStore();
Assert.Throws<ProgramStoreException>(
() => store.CreateDraft(athlete, "{}", "user", "", basedOnVersion: 5));
}
[Fact]
public void CreateDraft_EmptyBody_Rejected()
{
var (store, athlete) = NewStore();
Assert.Throws<ProgramStoreException>(() => store.CreateDraft(athlete, " ", "user", ""));
}
[Fact]
public void CreateDraft_TwoDrafts_VersionsGrow()
{
var (store, athlete) = NewStore();
var a = store.CreateDraft(athlete, "{}", "user", "a");
var b = store.CreateDraft(athlete, "{}", "user", "b");
Assert.Equal(1, a.Version);
Assert.Equal(2, b.Version);
Assert.All(store.ListVersions(athlete), v => Assert.Equal("draft", v.Status));
}
// --- Activate / Archive (spec 01 #10–16) ---
[Fact]
public void Activate_ShiftsPreviousToArchived()
{
var (store, athlete) = NewStore();
var v1 = store.CreateDraft(athlete, "{}", "user", "v1");
var v2 = store.CreateDraft(athlete, "{}", "user", "v2");
store.Activate(athlete, v1.Id);
Assert.Equal(v1.Id, store.GetActive(athlete)!.Id);
store.Activate(athlete, v2.Id);
var active = store.GetActive(athlete)!;
Assert.Equal(v2.Id, active.Id);
Assert.Equal("archived", store.GetVersion(athlete, v1.Id)!.Status);
}
[Fact]
public void Activate_FirstVersion_SingleActive()
{
var (store, athlete) = NewStore();
var v1 = store.CreateDraft(athlete, "{}", "user", "v1");
store.Activate(athlete, v1.Id);
Assert.Equal("active", store.GetActive(athlete)!.Status);
}
[Fact]
public void Activate_Archived_Rejected()
{
var (store, athlete) = NewStore();
var v1 = store.CreateDraft(athlete, "{}", "user", "v1");
var v2 = store.CreateDraft(athlete, "{}", "user", "v2");
store.Activate(athlete, v1.Id);
store.Activate(athlete, v2.Id);
Assert.Throws<ProgramStoreException>(() => store.Activate(athlete, v1.Id));
}
[Fact]
public void Activate_AlreadyActive_Rejected()
{
var (store, athlete) = NewStore();
var v1 = store.CreateDraft(athlete, "{}", "user", "v1");
store.Activate(athlete, v1.Id);
Assert.Throws<ProgramStoreException>(() => store.Activate(athlete, v1.Id));
}
[Fact]
public void Activate_ForeignVersion_Rejected()
{
var (store, athlete) = NewStore();
var (otherStore, otherAthlete) = NewStore();
var foreign = otherStore.CreateDraft(otherAthlete, "{}", "user", "чужая");
Assert.Throws<ProgramStoreException>(() => store.Activate(athlete, foreign.Id));
}
[Fact]
public void Archive_Active_ThenNoActive()
{
var (store, athlete) = NewStore();
var v1 = store.CreateDraft(athlete, "{}", "user", "v1");
store.Activate(athlete, v1.Id);
store.Archive(athlete, v1.Id);
Assert.Null(store.GetActive(athlete));
Assert.Throws<ProgramStoreException>(() => store.Archive(athlete, v1.Id)); // повтор
}
[Fact]
public void Archive_Draft_Rejected()
{
var (store, athlete) = NewStore();
var v1 = store.CreateDraft(athlete, "{}", "user", "v1");
Assert.Throws<ProgramStoreException>(() => store.Archive(athlete, v1.Id));
}
// --- чтение (spec 02 #1–7) ---
[Fact]
public void GetActive_NoProgram_Null()
{
var (store, athlete) = NewStore();
Assert.Null(store.GetActive(athlete));
}
[Fact]
public void GetActive_AllArchived_Null()
{
var (store, athlete) = NewStore();
var v1 = store.CreateDraft(athlete, """{"x":1}""", "user", "v1");
store.Activate(athlete, v1.Id);
store.Archive(athlete, v1.Id);
Assert.Null(store.GetActive(athlete));
}
[Fact]
public void ListVersions_Ordered_NoBody_Isolated()
{
var (store, athlete) = NewStore();
var (otherStore, otherAthlete) = NewStore();
for (var i = 0; i < 3; i++)
store.CreateDraft(athlete, "{\"n\":" + i + "}", "user", $"v{i + 1}");
otherStore.CreateDraft(otherAthlete, "{}", "user", "чужая");
var list = store.ListVersions(athlete);
Assert.Equal([1, 2, 3], list.Select(v => v.Version));
Assert.All(list, v => Assert.Equal(string.Empty, v.Body));
}
[Fact]
public void GetVersion_Own_WithBody_Foreign_Null()
{
var (store, athlete) = NewStore();
var (otherStore, otherAthlete) = NewStore();
var v = store.CreateDraft(athlete, """{"weeks":[2]}""", "user", "v1");
var foreign = otherStore.CreateDraft(otherAthlete, "{}", "user", "чужая");
Assert.True(System.Text.Json.JsonElement.DeepEquals(
System.Text.Json.JsonDocument.Parse("""{"weeks":[2]}""").RootElement,
System.Text.Json.JsonDocument.Parse(store.GetVersion(athlete, v.Id)!.Body).RootElement));
Assert.Null(store.GetVersion(athlete, foreign.Id));
}
}