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));
}
}