package doc
import (
"context"
"fmt"
"sort"
"testing"
"time"
"sourcecraft.dev/bigbes/sr-ht-spec/core"
"sourcecraft.dev/bigbes/sr-ht-spec/gitx"
)
// Fixtures are real git objects, built in-process through gitx. Nothing here
// shells out to git and nothing reads a checkout — which is the point of the
// port: if these tests could be satisfied by a directory of files, they would
// not be testing the thing that changed.
var fxSpace = core.SpaceRef{Owner: "bigbes", Name: "rfcs"}
func fxSig(n int) gitx.Signature {
return gitx.Signature{
Name: "bigbes",
Email: "bigbes@gmail.com",
When: time.Date(2026, 7, 22, 12, 0, 0, 0, time.UTC).Add(time.Duration(n) * time.Minute),
}
}
// space creates an empty bare space under a temp repos root.
func space(t *testing.T) *gitx.Repo {
t.Helper()
repo, err := gitx.Create(context.Background(), t.TempDir(), fxSpace, gitx.CreateOptions{Owner: fxSig(0)})
if err != nil {
t.Fatalf("gitx.Create: %v", err)
}
return repo
}
// commit writes files onto a fresh proposal branch cut from base and returns
// the branch name. A proposal branch is used rather than the approved branch
// because the write path refuses the latter by design, and because reading a
// proposal branch is one of the three revisions the read plane must serve
// through this exact code path.
func commit(t *testing.T, repo *gitx.Repo, n int, base string, files map[string]string) string {
t.Helper()
ctx := context.Background()
branch, err := gitx.ProposalBranch(int64(n))
if err != nil {
t.Fatalf("ProposalBranch(%d): %v", n, err)
}
if _, err := repo.CreateProposalBranch(ctx, branch, base); err != nil {
t.Fatalf("CreateProposalBranch(%q, %q): %v", branch, base, err)
}
paths := make([]string, 0, len(files))
for p := range files {
paths = append(paths, p)
}
sort.Strings(paths)
writes := make([]gitx.Write, 0, len(files))
for _, p := range paths {
writes = append(writes, gitx.Write{Path: p, Content: []byte(files[p])})
}
meta := gitx.CommitMeta{
Message: fmt.Sprintf("fixture %d", n),
Author: fxSig(n),
Committer: fxSig(n),
}
if _, err := repo.CommitProposal(ctx, branch, writes, meta); err != nil {
t.Fatalf("CommitProposal(%q): %v", branch, err)
}
return branch
}
// archiveOf commits files into a fresh space and builds the archive of the
// result — the whole path from git objects to Archive.
//
// The read half is spelled out here rather than hidden behind a helper in this
// package, because this package no longer has one: service/ owns the one route
// from a revision to an Archive, and a convenience wrapper here would be a
// second one growing back.
func archiveOf(t *testing.T, files map[string]string) *Archive {
t.Helper()
repo := space(t)
rev := commit(t, repo, 1, repo.ApprovedBranch(), files)
return archiveAt(t, repo, rev)
}
// archiveAt reads one revision and builds its archive.
func archiveAt(t *testing.T, repo *gitx.Repo, rev string) *Archive {
t.Helper()
docs, err := repo.ListDocuments(context.Background(), rev)
if err != nil {
t.Fatalf("ListDocuments(%q): %v", rev, err)
}
return FromDocuments(fxSpace, rev, docs)
}
// mustPage looks a document up by path, failing the test when it is absent.
func mustPage(t *testing.T, a *Archive, path string) *Page {
t.Helper()
p, ok := a.ByPath(path)
if !ok {
var have []string
for _, p := range a.Pages {
have = append(have, p.Path)
}
t.Fatalf("document %q missing; archive holds %v", path, have)
}
return p
}