Skip to content
quarry Artifact [d50ff490f1]
Login

Artifact [d50ff490f1]

Artifact d50ff490f130c590b34b3bfe7b1c9a7e8b218de94e7543d077f92a0eab24acae:


package web

import (
	"fmt"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/artifact"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ops"
)

func testRegistry(t *testing.T) *ops.Registry {
	t.Helper()
	// An empty (but real) index so entity lookups answer not-found
	// rather than "no store configured".
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: "9.9.9-test", Repos: map[string]*api.Repo{"test": {Index: ix}}}); err != nil {
		t.Fatal(err)
	}
	return r
}

func TestHomePageRendersThroughVersionOp(t *testing.T) {
	rec := httptest.NewRecorder()
	Handler(testRegistry(t)).ServeHTTP(rec, httptest.NewRequest("GET", "/", nil))
	if rec.Code != 200 {
		t.Fatalf("status %d", rec.Code)
	}
	body := rec.Body.String()
	if !strings.Contains(body, "quarry 9.9.9-test") {
		t.Fatalf("footer missing op-served version:\n%s", body)
	}
	if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/html") {
		t.Fatalf("content-type %q", ct)
	}
	if strings.Contains(body, "<script") {
		t.Fatal("no-JS baseline violated: script tag on the base page")
	}
	if !strings.Contains(body, "prefers-color-scheme") {
		t.Fatal("dark theme missing")
	}
}

func TestUnknownPathIs404(t *testing.T) {
	rec := httptest.NewRecorder()
	Handler(testRegistry(t)).ServeHTTP(rec, httptest.NewRequest("GET", "/nope", nil))
	if rec.Code != 404 {
		t.Fatalf("status %d", rec.Code)
	}
}

func TestThemed404Page(t *testing.T) {
	for _, path := range []string{"/nope", "/r/test/wiki/NoSuchPage", "/r/test/checkin/deadbeefdead", "/r/test/ticket/deadbeef"} {
		rec := httptest.NewRecorder()
		Handler(testRegistry(t)).ServeHTTP(rec, httptest.NewRequest("GET", path, nil))
		if rec.Code != 404 {
			t.Errorf("%s: status %d", path, rec.Code)
			continue
		}
		body := rec.Body.String()
		if !strings.HasPrefix(rec.Header().Get("Content-Type"), "text/html") {
			t.Errorf("%s: content-type %q — bare default 404?", path, rec.Header().Get("Content-Type"))
		}
		if !strings.Contains(body, "quarry 9.9.9-test") {
			t.Errorf("%s: 404 page missing version footer", path)
		}
		if !strings.Contains(body, `href="/"`) {
			t.Errorf("%s: 404 page has no way back into the site", path)
		}
	}
}

func TestPersistentNavOnEveryPage(t *testing.T) {
	// Ticket 010: every repo page carries that repo's nav and search
	// form; every page (fleet and 404 included) carries the site link.
	for _, path := range []string{"/r/test/timeline", "/r/test/wiki", "/r/test/ticket", "/r/test/search"} {
		rec := httptest.NewRecorder()
		Handler(testRegistry(t)).ServeHTTP(rec, httptest.NewRequest("GET", path, nil))
		body := rec.Body.String()
		for _, want := range []string{`<nav>`, `href="/r/test/timeline"`, `href="/r/test/wiki"`, `href="/r/test/ticket"`, `action="/r/test/search"`} {
			if !strings.Contains(body, want) {
				t.Errorf("%s: missing %s in header", path, want)
			}
		}
	}
	for _, path := range []string{"/", "/nope"} {
		rec := httptest.NewRecorder()
		Handler(testRegistry(t)).ServeHTTP(rec, httptest.NewRequest("GET", path, nil))
		if !strings.Contains(rec.Body.String(), `class="site" href="/"`) {
			t.Errorf("%s: missing site link", path)
		}
	}
}

func TestFleetPageListsRepos(t *testing.T) {
	rec := httptest.NewRecorder()
	Handler(testRegistry(t)).ServeHTTP(rec, httptest.NewRequest("GET", "/", nil))
	if rec.Code != 200 {
		t.Fatalf("status %d", rec.Code)
	}
	if !strings.Contains(rec.Body.String(), `href="/r/test/timeline"`) {
		t.Fatalf("fleet page missing repo link:\n%s", rec.Body.String())
	}
}

func TestTablesScrollInWrapper(t *testing.T) {
	// Ticket 120: wide tables scroll inside .tw, not the page.
	rec := httptest.NewRecorder()
	Handler(testRegistry(t)).ServeHTTP(rec, httptest.NewRequest("GET", "/r/test/timeline", nil))
	body := rec.Body.String()
	if strings.Contains(body, "<table>") && !strings.Contains(body, `<div class="tw"><table>`) {
		t.Fatalf("timeline table not wrapped:\n%s", body)
	}
}

func TestTimelineRendersAllRowsWithBranches(t *testing.T) {
	// Regression: a template field mismatch once truncated the page at
	// the first row — Execute's error was discarded. Render a POPULATED
	// timeline and assert every row, branch labels, and a complete page.
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	var batch []index.Parsed
	for i := 0; i < 5; i++ {
		h := strings.Repeat(fmt.Sprintf("%02d", i+1), 32)
		art := &artifact.Artifact{Type: artifact.Manifest, User: "dan",
			Date:    fmt.Sprintf("2026-08-15T0%d:00:00", i),
			Comment: fmt.Sprintf("commit %d", i)}
		if i == 0 {
			art.Tags = []artifact.Tag{{Name: "*branch", Target: "*", Value: "trunk"}}
		}
		if i > 0 {
			art.Parents = []string{strings.Repeat(fmt.Sprintf("%02d", i), 32)}
		}
		batch = append(batch, index.Parsed{Hash: h, Art: art})
	}
	if err := ix.Fold(batch); err != nil {
		t.Fatal(err)
	}
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: "9.9.9-test",
		Repos: map[string]*api.Repo{"test": {Index: ix}}}); err != nil {
		t.Fatal(err)
	}
	rec := httptest.NewRecorder()
	Handler(r).ServeHTTP(rec, httptest.NewRequest("GET", "/r/test/timeline", nil))
	body := rec.Body.String()
	if got := strings.Count(body, "<tr><td><a href=\"/r/test/checkin/"); got != 5 {
		t.Fatalf("rendered %d rows, want 5:\n%s", got, body)
	}
	if !strings.Contains(body, `href="/r/test/timeline?branch=trunk">trunk</a>`) {
		t.Fatalf("branch label missing:\n%s", body)
	}
	if !strings.HasSuffix(strings.TrimSpace(body), "</html>") {
		t.Fatal("page truncated — template error swallowed")
	}
}