using Wiki.Core.Model; using Wiki.Core.Ports; namespace Wiki.Contracts; /// Every adapter derives a test class from this and supplies a store over . public abstract class NoteStoreContract { /// A store whose current head exposes exactly . protected abstract Task CreateStoreAsync(); [Fact] public async Task HeadHasACommit() { var store = await CreateStoreAsync(); var head = await store.GetHeadAsync(CancellationToken.None); Assert.False(string.IsNullOrWhiteSpace(head.Commit)); } [Fact] public async Task ListsOnlyVaultDocumentsOfRequestedKinds() { var store = await CreateStoreAsync(); var head = await store.GetHeadAsync(CancellationToken.None); var all = await store.ListAsync(head.Commit, VaultFixture.AllKinds, CancellationToken.None); Assert.Equal( VaultFixture.Expected.OrderBy(e => e.Path), all.Select(r => (r.Kind, r.Slug, r.Path)).OrderBy(e => e.Path)); var wiki = await store.ListAsync(head.Commit, new HashSet { DocumentKind.Wiki }, CancellationToken.None); Assert.All(wiki, r => Assert.Equal(DocumentKind.Wiki, r.Kind)); Assert.Equal(2, wiki.Count); } [Fact] public async Task ContentHashIsStableAndDistinct() { var store = await CreateStoreAsync(); var head = await store.GetHeadAsync(CancellationToken.None); var a = await store.ListAsync(head.Commit, VaultFixture.AllKinds, CancellationToken.None); var b = await store.ListAsync(head.Commit, VaultFixture.AllKinds, CancellationToken.None); Assert.Equal(a.OrderBy(r => r.Path).Select(r => r.ContentHash), b.OrderBy(r => r.Path).Select(r => r.ContentHash)); Assert.All(a, r => Assert.False(string.IsNullOrWhiteSpace(r.ContentHash))); Assert.Equal(a.Count, a.Select(r => r.ContentHash).Distinct().Count()); } [Fact] public async Task ReadsExactText() { var store = await CreateStoreAsync(); var head = await store.GetHeadAsync(CancellationToken.None); var refs = await store.ListAsync(head.Commit, VaultFixture.AllKinds, CancellationToken.None); foreach (var r in refs) { var text = await store.ReadAsync(r, head.Commit, CancellationToken.None); Assert.Equal(VaultFixture.Files[r.Path], text); } } [Fact] public async Task CreatesANewDocumentAndRefusesAnExistingPath() { var store = await CreateStoreAsync(); var written = await store.CreateAsync("raw/2026-09-24-new.md", "---\ntitle: New\n---\n\nText.\n", "raw: New", CancellationToken.None); Assert.Equal("raw/2026-09-24-new.md", written.Path); Assert.False(string.IsNullOrWhiteSpace(written.Commit)); var head = await store.GetHeadAsync(CancellationToken.None); var refs = await store.ListAsync(head.Commit, VaultFixture.AllKinds, CancellationToken.None); var created = Assert.Single(refs, r => r.Path == "raw/2026-09-24-new.md"); Assert.Equal("---\ntitle: New\n---\n\nText.\n", await store.ReadAsync(created, head.Commit, CancellationToken.None)); await Assert.ThrowsAsync(() => store.CreateAsync("raw/2026-09-24-new.md", "other", "raw: New", CancellationToken.None)); await Assert.ThrowsAsync(() => store.CreateAsync("wiki/udm-dns.md", "other", "x", CancellationToken.None)); } [Fact] public async Task CreatesABinaryAndRefusesAnExistingPath() { var store = await CreateStoreAsync(); var bytes = new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 1, 2, 3 }; var written = await store.CreateAsync("raw/2026-09-24-new-1.jpg", bytes, "raw: New (attachment 1)", CancellationToken.None); Assert.Equal("raw/2026-09-24-new-1.jpg", written.Path); Assert.False(string.IsNullOrWhiteSpace(written.Commit)); await Assert.ThrowsAsync(() => store.CreateAsync("raw/2026-09-24-new-1.jpg", bytes, "x", CancellationToken.None)); } [Fact] public async Task HealthReportsOk() { var store = await CreateStoreAsync(); var health = await store.CheckAsync(CancellationToken.None); Assert.True(health.Ok, health.Detail); Assert.Equal(store.Name, health.Adapter); } }