The fact sink was a second adapter over the same backend. INoteStore gains CreateAsync (DocumentExistsException on a taken path); the raw note's shape, slug and collision numbering live in DepositFactUseCase; GiteaRepository only moves text. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
72 lines
3.1 KiB
C#
72 lines
3.1 KiB
C#
using Microsoft.Extensions.Time.Testing;
|
|
using Wiki.Application.Facts;
|
|
using Wiki.Core.Model;
|
|
|
|
namespace Wiki.Application.Tests;
|
|
|
|
public class DepositFactUseCaseTests
|
|
{
|
|
private static readonly RawSource Fact = new(
|
|
Title: "UDM intercepts DNS: a follow-up",
|
|
Source: "https://example.com/thread#5",
|
|
Content: "The UDM answers every UDP/53 query itself, independent of what upstream is configured elsewhere.",
|
|
ObservedAt: new DateTimeOffset(2026, 9, 20, 14, 30, 0, TimeSpan.Zero),
|
|
Tags: ["dns", "udm"]);
|
|
|
|
private static readonly Identity By = new("agent-x", IdentityKind.Machine);
|
|
private static readonly DateTimeOffset Now = new(2026, 9, 23, 8, 0, 0, TimeSpan.Zero);
|
|
private const string ExpectedPath = "raw/2026-09-23-udm-intercepts-dns-a-follow-up.md";
|
|
|
|
private const string ExpectedText =
|
|
"---\n" +
|
|
"title: \"UDM intercepts DNS: a follow-up\"\n" +
|
|
"type: raw\n" +
|
|
"source: \"https://example.com/thread#5\"\n" +
|
|
"captured: 2026-09-23\n" +
|
|
"captured_by: agent-x\n" +
|
|
"observed: 2026-09-20T14:30:00+00:00\n" +
|
|
"tags: [dns, udm]\n" +
|
|
"---\n" +
|
|
"\n" +
|
|
"The UDM answers every UDP/53 query itself, independent of what upstream is configured elsewhere.\n";
|
|
|
|
private readonly FakeStore _store = new();
|
|
private DepositFactUseCase UseCase => new(_store, new FakeTimeProvider(Now));
|
|
|
|
[Fact]
|
|
public async Task WritesTheRawNoteAtTheDatedSlugPath()
|
|
{
|
|
var receipt = await UseCase.DepositAsync(Fact, By, CancellationToken.None);
|
|
Assert.Equal(ExpectedPath, receipt.Path);
|
|
Assert.Equal(Now, receipt.StoredAt);
|
|
Assert.Equal(ExpectedText, _store.Files[ExpectedPath]);
|
|
Assert.Equal(("raw: UDM intercepts DNS: a follow-up", ExpectedPath), _store.Created.Single());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NumbersThePathWhileItCollides()
|
|
{
|
|
_store.Files[ExpectedPath] = "already here";
|
|
_store.Files["raw/2026-09-23-udm-intercepts-dns-a-follow-up-2.md"] = "also here";
|
|
var receipt = await UseCase.DepositAsync(Fact, By, CancellationToken.None);
|
|
Assert.Equal("raw/2026-09-23-udm-intercepts-dns-a-follow-up-3.md", receipt.Path);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("", "source", "content long enough to pass the minimum length check, easily.")]
|
|
[InlineData("title", "", "content long enough to pass the minimum length check, easily.")]
|
|
[InlineData("title", "source", "too short")]
|
|
public async Task RejectsAnInvalidFactBeforeTouchingTheStore(string title, string source, string content)
|
|
{
|
|
var invalid = new RawSource(title, source, content, Now, []);
|
|
await Assert.ThrowsAsync<ArgumentException>(() => UseCase.DepositAsync(invalid, By, CancellationToken.None));
|
|
Assert.Empty(_store.Created);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Talos v1.13.10: aes256k / kernel 6.18!", "talos-v1-13-10-aes256k-kernel-6-18")]
|
|
[InlineData(" ", "untitled")]
|
|
[InlineData("Ünïcödé näme", "n-c-d-n-me")]
|
|
public void SlugifiesTitles(string title, string slug) => Assert.Equal(slug, DepositFactUseCase.Slugify(title));
|
|
}
|