Implement mcdoc v0.1.0: public documentation server
Single-binary Go server that fetches markdown from Gitea (mc org), renders to HTML with goldmark (GFM, chroma syntax highlighting, heading anchors), and serves a navigable read-only documentation site. Features: - Boot fetch with retry, webhook refresh, 15-minute poll fallback - In-memory cache with atomic per-repo swap - chi router with htmx partial responses for SPA-like navigation - HMAC-SHA256 webhook validation - Responsive CSS, TOC generation, priority doc ordering - $PORT env var support for MCP agent port assignment 33 tests across config, cache, render, and server packages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
165
internal/cache/cache_test.go
vendored
Normal file
165
internal/cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.wntrmute.dev/mc/mcdoc/internal/render"
|
||||
)
|
||||
|
||||
func TestSetAndGetRepo(t *testing.T) {
|
||||
c := New()
|
||||
|
||||
info := &RepoInfo{
|
||||
Name: "testrepo",
|
||||
Description: "A test repo",
|
||||
CommitSHA: "abc123",
|
||||
FetchedAt: time.Now(),
|
||||
Docs: []*Document{
|
||||
{Repo: "testrepo", URLPath: "README", Title: "README", HTML: "<p>hello</p>"},
|
||||
{Repo: "testrepo", URLPath: "ARCHITECTURE", Title: "Architecture", HTML: "<p>arch</p>"},
|
||||
},
|
||||
}
|
||||
c.SetRepo(info)
|
||||
|
||||
got, ok := c.GetRepo("testrepo")
|
||||
if !ok {
|
||||
t.Fatal("expected repo to exist")
|
||||
}
|
||||
if got.Name != "testrepo" {
|
||||
t.Fatalf("got name %q, want %q", got.Name, "testrepo")
|
||||
}
|
||||
if len(got.Docs) != 2 {
|
||||
t.Fatalf("got %d docs, want 2", len(got.Docs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDocument(t *testing.T) {
|
||||
c := New()
|
||||
c.SetRepo(&RepoInfo{
|
||||
Name: "r",
|
||||
Docs: []*Document{
|
||||
{Repo: "r", URLPath: "foo/bar", Title: "Bar", HTML: "<p>bar</p>"},
|
||||
},
|
||||
})
|
||||
|
||||
doc, ok := c.GetDocument("r", "foo/bar")
|
||||
if !ok {
|
||||
t.Fatal("expected document to exist")
|
||||
}
|
||||
if doc.Title != "Bar" {
|
||||
t.Fatalf("got title %q, want %q", doc.Title, "Bar")
|
||||
}
|
||||
|
||||
_, ok = c.GetDocument("r", "nonexistent")
|
||||
if ok {
|
||||
t.Fatal("expected document to not exist")
|
||||
}
|
||||
|
||||
_, ok = c.GetDocument("nonexistent", "foo/bar")
|
||||
if ok {
|
||||
t.Fatal("expected repo to not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListReposSorted(t *testing.T) {
|
||||
c := New()
|
||||
c.SetRepo(&RepoInfo{Name: "mcr"})
|
||||
c.SetRepo(&RepoInfo{Name: "abc"})
|
||||
c.SetRepo(&RepoInfo{Name: "mcp"})
|
||||
|
||||
repos := c.ListRepos()
|
||||
if len(repos) != 3 {
|
||||
t.Fatalf("got %d repos, want 3", len(repos))
|
||||
}
|
||||
if repos[0].Name != "abc" || repos[1].Name != "mcp" || repos[2].Name != "mcr" {
|
||||
t.Fatalf("unexpected order: %s, %s, %s", repos[0].Name, repos[1].Name, repos[2].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocSortOrder(t *testing.T) {
|
||||
c := New()
|
||||
c.SetRepo(&RepoInfo{
|
||||
Name: "r",
|
||||
Docs: []*Document{
|
||||
{URLPath: "CLAUDE"},
|
||||
{URLPath: "zebra"},
|
||||
{URLPath: "README"},
|
||||
{URLPath: "ARCHITECTURE"},
|
||||
{URLPath: "alpha"},
|
||||
{URLPath: "RUNBOOK"},
|
||||
},
|
||||
})
|
||||
|
||||
repo, _ := c.GetRepo("r")
|
||||
expected := []string{"README", "ARCHITECTURE", "RUNBOOK", "CLAUDE", "alpha", "zebra"}
|
||||
for i, doc := range repo.Docs {
|
||||
if doc.URLPath != expected[i] {
|
||||
t.Errorf("position %d: got %q, want %q", i, doc.URLPath, expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadyState(t *testing.T) {
|
||||
c := New()
|
||||
if c.IsReady() {
|
||||
t.Fatal("new cache should not be ready")
|
||||
}
|
||||
c.SetReady()
|
||||
if !c.IsReady() {
|
||||
t.Fatal("cache should be ready after SetReady")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCommitSHA(t *testing.T) {
|
||||
c := New()
|
||||
if sha := c.GetCommitSHA("nope"); sha != "" {
|
||||
t.Fatalf("expected empty sha, got %q", sha)
|
||||
}
|
||||
|
||||
c.SetRepo(&RepoInfo{Name: "r", CommitSHA: "abc123"})
|
||||
if sha := c.GetCommitSHA("r"); sha != "abc123" {
|
||||
t.Fatalf("expected abc123, got %q", sha)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAtomicRepoSwap(t *testing.T) {
|
||||
c := New()
|
||||
c.SetRepo(&RepoInfo{
|
||||
Name: "r",
|
||||
Docs: []*Document{{URLPath: "old", Title: "Old"}},
|
||||
})
|
||||
|
||||
c.SetRepo(&RepoInfo{
|
||||
Name: "r",
|
||||
Docs: []*Document{{URLPath: "new", Title: "New"}},
|
||||
})
|
||||
|
||||
repo, _ := c.GetRepo("r")
|
||||
if len(repo.Docs) != 1 || repo.Docs[0].URLPath != "new" {
|
||||
t.Fatal("expected atomic swap to replace docs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRepo(t *testing.T) {
|
||||
c := New()
|
||||
c.SetRepo(&RepoInfo{Name: "r"})
|
||||
c.RemoveRepo("r")
|
||||
|
||||
_, ok := c.GetRepo("r")
|
||||
if ok {
|
||||
t.Fatal("expected repo to be removed")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure Document's Headings field works with the render package type.
|
||||
func TestDocumentHeadingsType(t *testing.T) {
|
||||
doc := &Document{
|
||||
Headings: []render.Heading{
|
||||
{Level: 1, ID: "title", Text: "Title"},
|
||||
},
|
||||
}
|
||||
if doc.Headings[0].Text != "Title" {
|
||||
t.Fatal("unexpected heading text")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user