clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

feat/atlas-next-horizon
atlas / internal / workbench / workbench_test.go
workbench_test.go 11.7K
1package workbench23import (4 "html"5 "io"6 "net/http"7 "net/http/httptest"8 "strconv"9 "strings"10 "testing"1112 "github.com/FelineStateMachine/atlas/internal/enrich/maturity"13)1415// The library every page test reads: two builds of one volume, the newer one16// richer and carrying a ledger, and a second volume with a single build.17func testLibrary(t *testing.T) string {18 t.Helper()19 dir := t.TempDir()20 bundleSpec{21 slug: "hollowmere", title: "Hollowmere",22 createdAt: "2026-01-01T00:00:00Z", stamp: strings.Repeat("a", 64),23 worlds: []worldSpec{{24 slug: "overworld", title: "Overworld",25 features: []featureSpec{{id: 1, title: "Gate"}},26 }},27 }.write(t, dir)28 bundleSpec{29 slug: "hollowmere", title: "Hollowmere",30 createdAt: "2026-02-01T00:00:00Z", stamp: strings.Repeat("b", 64),31 worlds: []worldSpec{{32 slug: "overworld", title: "Overworld", icon: "icons/marker.png",33 features: []featureSpec{34 {id: 1, title: "Gate", description: "The way in, and out."},35 {id: 2, title: "Well"},36 {id: 3, title: "Chapel"},37 },38 merged: []map[string]any{{39 "source": "IGN Wiki",40 "slug": "ign",41 "donorFeatures": map[string]any{"point": 4},42 "added": 1,43 "matched": []map[string]any{{"d": 11, "w": 1, "px": 38, "e": true}},44 "held": []map[string]any{{"d": 12, "t": "Old Well", "why": "name 200px away"}},45 "alignment": "14 names, 3px residual",46 }},47 }},48 }.write(t, dir)49 bundleSpec{50 slug: "driftfen", title: "Driftfen",51 createdAt: "2026-03-01T00:00:00Z", stamp: strings.Repeat("c", 64),52 worlds: []worldSpec{{53 slug: "marsh", title: "Marsh",54 features: []featureSpec{{id: 7, title: "Reed Gate"}},55 }},56 }.write(t, dir)57 return dir58}5960func testWorkbench(t *testing.T, dir string) *Workbench {61 t.Helper()62 held, err := New(Options{63 Targets: Targets{Registry: dir},64 Sources: testSources(),65 Runtime: []byte("/* the vendored runtime */"),66 })67 if err != nil {68 t.Fatal(err)69 }70 return held71}7273func testSources() []Source {74 return []Source{75 {76 Name: "mapgenie", Label: "MapGenie",77 Attribution: "Maps and pin data by MapGenie and its contributors.",78 IDSpace: "native",79 },80 {81 Name: "ign", Label: "IGN Wiki",82 License: "Reproduced under IGN's terms.",83 IDSpace: "native", Crawlable: true, Pair: true,84 TargetHint: "an IGN wikimap as <objectSlug>/<mapSlug>, e.g. cyberpunk-2077/night-city",85 },86 {87 Name: "nasa-trek", Label: "NASA Trek",88 License: "Public domain.",89 Attribution: "Global mosaics from NASA's Solar System Treks.",90 IDSpace: "derived", Crawlable: true,91 TargetHint: "a body, e.g. mars",92 },93 }94}9596func site(t *testing.T, held *Workbench) *httptest.Server {97 t.Helper()98 server := httptest.NewServer(held)99 t.Cleanup(server.Close)100 return server101}102103func get(t *testing.T, server *httptest.Server, path string) (*http.Response, string) {104 t.Helper()105 response, err := server.Client().Get(server.URL + path)106 if err != nil {107 t.Fatal(err)108 }109 t.Cleanup(func() { response.Body.Close() })110 body, err := io.ReadAll(response.Body)111 if err != nil {112 t.Fatal(err)113 }114 // A page is read the way a person reads it: html/template escapes a plus115 // sign and an apostrophe, and a test that asserted on the escapes would be116 // asserting about the encoder rather than about the page.117 return response, html.UnescapeString(string(body))118}119120func wants(t *testing.T, page, body string, wanted ...string) {121 t.Helper()122 for _, want := range wanted {123 if !strings.Contains(body, want) {124 t.Errorf("the %s page misses %q", page, want)125 }126 }127}128129func TestTheLibraryPageHeadlinesEveryVolumeWithItsScore(t *testing.T) {130 dir := testLibrary(t)131 server := site(t, testWorkbench(t, dir))132133 response, body := get(t, server, "/")134 if response.StatusCode != http.StatusOK {135 t.Fatalf("the library page answered %d", response.StatusCode)136 }137 if got := response.Header.Get("Content-Security-Policy"); got != contentSecurityPolicy {138 t.Errorf("the page carries %q as its policy", got)139 }140 wants(t, "library", body, "Hollowmere", "hollowmere", "Driftfen", dir, "IGN Wiki")141142 // The figures are the serving build's, and the serving build is the newer143 // one: the score on the page is the score the enrich lane would compute.144 table, err := maturity.Points()145 if err != nil {146 t.Fatal(err)147 }148 held := &library{dir: dir, table: table}149 volumes, skipped, err := held.volumes()150 if err != nil {151 t.Fatal(err)152 }153 if len(skipped) != 0 {154 t.Fatalf("bundles were skipped: %v", skipped)155 }156 for _, volume := range volumes {157 wants(t, "library", body, strconv.Itoa(volume.Serving().Total))158 }159160 // Hollowmere's newer build is richer, and the movement says so.161 var hollowmere *volume162 for _, volume := range volumes {163 if volume.Slug == "hollowmere" {164 hollowmere = volume165 }166 }167 moved := hollowmere.Movement()168 if moved == nil || moved.Delta <= 0 {169 t.Fatalf("the richer build did not move the score: %+v", moved)170 }171 wants(t, "library", body, "+"+strconv.Itoa(moved.Delta))172}173174func TestTheServingBuildIsTheOneTheRegistryWouldFoldTo(t *testing.T) {175 table, err := maturity.Points()176 if err != nil {177 t.Fatal(err)178 }179 held := &library{dir: testLibrary(t), table: table}180 volumes, _, err := held.volumes()181 if err != nil {182 t.Fatal(err)183 }184 for _, volume := range volumes {185 if want := maturity.Serving(volume.Builds); volume.Serving() != want {186 t.Errorf("%s serves %s; the registry's fold picks %s",187 volume.Slug, volume.Serving().File, want.File)188 }189 }190}191192func TestTheMeasurementPageIsTheScoreThenTheDiagnosticsThenTheLedger(t *testing.T) {193 server := site(t, testWorkbench(t, testLibrary(t)))194195 response, body := get(t, server, "/volume/hollowmere")196 if response.StatusCode != http.StatusOK {197 t.Fatalf("the measurement page answered %d", response.StatusCode)198 }199 wants(t, "measurement", body,200 // the headline201 "points", "against the build before it",202 // the per-world breakdown203 "overworld", "collections",204 // the five axes, carried as diagnostics205 "annotation", "cartography", "structure", "icons", "conventions",206 "nothing gates on them",207 // the native provenance accounts, not a v3 merge-log projection208 "IGN Wiki", "Test Origin",209 // both builds, the newer marked210 "serving")211212 newer := strings.Index(body, strings.Repeat("b", 12))213 older := strings.Index(body, strings.Repeat("a", 12))214 if newer < 0 || older < 0 || newer > older {215 t.Errorf("the builds are not newest first (newer at %d, older at %d)", newer, older)216 }217218 if response, _ := get(t, server, "/volume/nowhere"); response.StatusCode != http.StatusNotFound {219 t.Errorf("an unknown volume answered %d", response.StatusCode)220 }221}222223func TestTheDiffPageIsHeadlinedByTheScoreDelta(t *testing.T) {224 dir := testLibrary(t)225 server := site(t, testWorkbench(t, dir))226227 table, err := maturity.Points()228 if err != nil {229 t.Fatal(err)230 }231 held := &library{dir: dir, table: table}232 volume, _, err := held.volumeBySlug("hollowmere")233 if err != nil {234 t.Fatal(err)235 }236 from, to := volume.Builds[1], volume.Builds[0]237238 response, body := get(t, server, "/volume/hollowmere/diff?a="+from.File+"&b="+to.File)239 if response.StatusCode != http.StatusOK {240 t.Fatalf("the diff page answered %d: %s", response.StatusCode, body)241 }242 moved := maturity.Compare(from, to)243 wants(t, "diff", body,244 "+"+strconv.Itoa(moved.Delta), "richer",245 // what moved, under the headline246 "Score by world", "Axes", "point features", "described share",247 // the features themselves248 "Well", "Chapel",249 // and the ledgers' agreement250 "Matched-pair stability")251252 // The reverse direction is the same comparison read the other way.253 _, reverse := get(t, server, "/volume/hollowmere/diff?a="+to.File+"&b="+from.File)254 wants(t, "reversed diff", reverse, strconv.Itoa(-moved.Delta), "poorer")255256 if response, _ := get(t, server, "/volume/hollowmere/diff?a=nowhere.atlas&b="+to.File); response.StatusCode != http.StatusBadRequest {257 t.Errorf("a diff naming a build that is not there answered %d", response.StatusCode)258 }259}260261func TestTheNavigationPointsAtTheSingleProjectInsteadOfLegacySources(t *testing.T) {262 server := site(t, testWorkbench(t, testLibrary(t)))263264 response, _ := get(t, server, "/sources")265 if response.StatusCode != http.StatusNotFound {266 t.Fatalf("the retired sources page answered %d", response.StatusCode)267 }268 _, body := get(t, server, "/")269 wants(t, "navigation", body, `href="/project"`, "Project")270 if strings.Contains(body, `href="/sources"`) || strings.Contains(body, `href="/operations"`) {271 t.Fatal("library navigation still exposes a legacy creation surface")272 }273}274275func TestTheAssetsAreTheWorkbenchsOwnAndTheRuntimeIsOptional(t *testing.T) {276 server := site(t, testWorkbench(t, testLibrary(t)))277278 response, body := get(t, server, "/assets/workbench.css")279 if response.StatusCode != http.StatusOK {280 t.Fatalf("the stylesheet answered %d", response.StatusCode)281 }282 if got := response.Header.Get("Content-Type"); !strings.HasPrefix(got, "text/css") {283 t.Errorf("the stylesheet arrived as %q", got)284 }285 wants(t, "stylesheet", body, "--accent", ".op-row")286287 if response, _ := get(t, server, "/assets/htmx.js"); response.StatusCode != http.StatusOK {288 t.Errorf("the runtime the wiring handed over answered %d", response.StatusCode)289 }290 if response, _ := get(t, server, "/assets/nothing.js"); response.StatusCode != http.StatusNotFound {291 t.Errorf("an asset nobody has answered %d", response.StatusCode)292 }293294 // A workbench mounted without a runtime is a working workbench.295 bare, err := New(Options{Targets: Targets{Registry: t.TempDir()}})296 if err != nil {297 t.Fatal(err)298 }299 bareSite := site(t, bare)300 if response, _ := get(t, bareSite, "/assets/htmx.js"); response.StatusCode != http.StatusNotFound {301 t.Errorf("a workbench with no runtime served one: %d", response.StatusCode)302 }303 if response, body := get(t, bareSite, "/"); response.StatusCode != http.StatusOK {304 t.Errorf("the library page of an empty registry answered %d: %s", response.StatusCode, body)305 }306}307308// TestTheMeasurementPageCarriesTheMeasuredScore holds the page to the number309// the measurement lane itself reports for the same bytes. The bundle is310// stated, not captured: what each score point is worth is the maturity311// package's own suite's business; this test's business is that the page312// prints that verdict rather than a stale or reformatted one, table version313// included. A stated bundle with prose, an icon and a ledger scores well314// clear of zero, so a page that went blank cannot pass by printing nothing.315func TestTheMeasurementPageCarriesTheMeasuredScore(t *testing.T) {316 dir := t.TempDir()317 path := bundleSpec{318 slug: "hollowmere", title: "Hollowmere",319 worlds: []worldSpec{{320 slug: "overworld", title: "Overworld", icon: "icons/marker.png",321 features: []featureSpec{322 {id: 1, title: "Gate", description: "The way in, and out."},323 {id: 2, title: "Well"},324 },325 merged: []map[string]any{{326 "source": "IGN Wiki", "slug": "ign",327 "donorFeatures": map[string]any{"point": 4},328 "added": 1,329 "matched": []map[string]any{{"d": 11, "w": 1, "px": 38, "e": true}},330 "alignment": "14 names, 3px residual",331 }},332 }},333 }.write(t, dir)334 table, err := maturity.Points()335 if err != nil {336 t.Fatal(err)337 }338 measured, err := maturity.Measure(path, table)339 if err != nil {340 t.Fatal(err)341 }342 if measured.Total <= 0 {343 t.Fatalf("the stated bundle scores %d; the page test needs a number that cannot be mistaken for a blank", measured.Total)344 }345346 server := site(t, testWorkbench(t, dir))347 response, body := get(t, server, "/volume/hollowmere")348 if response.StatusCode != http.StatusOK {349 t.Fatalf("the measurement page answered %d", response.StatusCode)350 }351 wants(t, "measurement", body, "Hollowmere", strconv.Itoa(measured.Total), "point table v"+strconv.Itoa(table.Version), "world")352353 _, library := get(t, server, "/")354 wants(t, "library", library, "Hollowmere", strconv.Itoa(measured.Total))355}