package harness
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/artifact"
"craftdesign.group/quarry/internal/chat"
"craftdesign.group/quarry/internal/events"
"craftdesign.group/quarry/internal/index"
"craftdesign.group/quarry/internal/ingest"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/semdiff"
"craftdesign.group/quarry/internal/web"
"craftdesign.group/quarry/internal/zitcli"
)
// artifactFoldServe builds the index the PRE-crosslink way — every
// structural artifact, manifests included, through fold() — and serves
// it. The reference implementation the crosslink path must equal.
func artifactFoldServe(t *testing.T, name, store, dataDir string) *httptest.Server {
t.Helper()
ix, err := index.Open(filepath.Join(dataDir, name+".db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { ix.Close() })
cli := zitcli.New(store)
var batch []index.Parsed
if _, err := cli.FramesEachStructural(zitcli.Cursor{}, func(a zitcli.Artifact) error {
art, perr := artifact.Parse(a.Bytes)
if perr != nil || art == nil {
return nil
}
batch = append(batch, index.Parsed{Hash: a.Hash, Art: art})
return nil
}); err != nil {
t.Fatal(err)
}
ingest.DeriveChanges(cli, batch)
if err := ix.FoldNow(batch); err != nil {
t.Fatal(err)
}
// The nav shows a Chat tab only where a chat plane runs; the
// crosslink side has one, so the reference must too, or the two
// pages differ on something that is not the fold.
ev, err := events.Start(filepath.Join(dataDir, name+"-nats"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(ev.Close)
room, err := chat.New(ev.Conn(), ev.JS())
if err != nil {
t.Fatal(err)
}
deps := api.Deps{Version: "0.0.0-harness", Repos: api.NewRepos(nil), Chat: room}
zc := cli
deps.Repos.Put(name, &api.Repo{Index: ix, Zit: cli,
Sync: func() (int, error) { return 0, nil },
Leaves: func() (map[string]bool, error) { return zc.Leaves(context.Background()) }})
r := ops.NewRegistry()
if err := api.RegisterAll(r, deps); err != nil {
t.Fatal(err)
}
apiRouter, err := api.Router(r, "0.0.0-harness", nil)
if err != nil {
t.Fatal(err)
}
m := http.NewServeMux()
m.Handle("/api/", apiRouter)
m.Handle("/", web.Handler(r, nil, nil))
srv := httptest.NewServer(m)
t.Cleanup(srv.Close)
return srv
}
// TestCrosslinkMatchesArtifactFold is the parity proof zit#109 demands:
// an index built from `zit crosslink` + non-manifest frames must be
// semantically identical to one built by folding every artifact — on
// pages, APIs, per-entry last-change data, and CHECK-IN search (the
// surface a missing search_doc silently broke once already).
func TestCrosslinkMatchesArtifactFold(t *testing.T) {
store := buildStore(t, "parityseed")
repos := map[string]string{"parity": store}
crosslink := ingestAndServe(t, repos, filepath.Join(t.TempDir(), "a"))
reference := artifactFoldServe(t, "parity", store, t.TempDir())
sf := []surface{
{"/r/parity/timeline", semdiff.HTML},
{"/api/v1/r/parity/timeline?y=ci", semdiff.JSON},
{"/r/parity/branch", semdiff.HTML},
{"/api/v1/r/parity/branch", semdiff.JSON},
{"/api/v1/r/parity/tree/trunk/", semdiff.JSON},
{"/api/v1/r/parity/search?q=parityseed&kind=checkin", semdiff.JSON},
{"/api/v1/r/parity/counts", semdiff.JSON},
}
var tl struct {
Entries []struct{ Hash string } `json:"entries"`
}
if err := json.Unmarshal(snapshot(t, reference, "/api/v1/r/parity/timeline?y=ci"), &tl); err != nil {
t.Fatal(err)
}
if len(tl.Entries) == 0 {
t.Fatal("reference has no check-ins")
}
for _, e := range tl.Entries {
sf = append(sf,
surface{"/r/parity/checkin/" + e.Hash, semdiff.HTML},
surface{"/api/v1/r/parity/checkin/" + e.Hash, semdiff.JSON})
}
for _, s := range sf {
a := snapshot(t, crosslink, s.path)
b := snapshot(t, reference, s.path)
diff, err := semdiff.Diff(s.kind, a, b)
if err != nil {
t.Fatalf("%s: %v", s.path, err)
}
if diff != "" {
t.Errorf("%s diverges:\n%s", s.path, diff)
}
}
// The search surface must also be NON-EMPTY on both sides, or an
// equal pair of empty answers proves nothing.
var hits struct {
Count int `json:"count"`
}
if err := json.Unmarshal(snapshot(t, crosslink, "/api/v1/r/parity/search?q=parityseed&kind=checkin"), &hits); err != nil {
t.Fatal(err)
}
if hits.Count == 0 {
t.Fatal("check-in search found nothing on the crosslink build")
}
}