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; /// /// Интеграционные тесты ProgramStore (specs m03 01-lifecycle, /// 02-reading) против docker watchbody-pg. Каждый тест — свой атлет /// (случайный ключ), fixture-данные не пересекаются с m02. /// [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( () => 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( () => store.CreateDraft(athlete, "{}", "user", "", basedOnVersion: 5)); } [Fact] public void CreateDraft_EmptyBody_Rejected() { var (store, athlete) = NewStore(); Assert.Throws(() => 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(() => 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(() => 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(() => 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(() => store.Archive(athlete, v1.Id)); // повтор } [Fact] public void Archive_Draft_Rejected() { var (store, athlete) = NewStore(); var v1 = store.CreateDraft(athlete, "{}", "user", "v1"); Assert.Throws(() => 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)); } }