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 / inspector_test.go
inspector_test.go 8.9K
1package workbench23import (4 "context"5 "encoding/json"6 "fmt"7 "io"8 "net/http"9 "net/url"10 "os"11 "path/filepath"12 "strings"13 "testing"14 "time"1516 "github.com/FelineStateMachine/atlas/internal/authoring"17)1819func representativeInspector(t *testing.T) (*Workbench, authoring.BuildResult) {20 t.Helper()21 root := t.TempDir()22 project, err := filepath.Abs(filepath.Join("..", "..", "examples", "sample-region"+authoring.ProjectExtension))23 if err != nil {24 t.Fatal(err)25 }26 targets := Targets{27 Atlas: "/opt/atlas", Dir: root, Project: project,28 Cache: filepath.Join(root, "cache"), Registry: filepath.Join(root, "library"),29 }30 result, err := authoring.Build(context.Background(), authoring.BuildOptions{31 ProjectPath: project, CacheDir: targets.Cache, LibraryDir: targets.Registry,32 })33 if err != nil {34 t.Fatal(err)35 }36 held, err := New(Options{Targets: targets})37 if err != nil {38 t.Fatal(err)39 }40 held.supervisor.last = supervisedRun{Name: "build", Artifact: result.Path, FinishedAt: time.Now()}41 return held, result42}4344func TestInspectorDescribesTheCompiledRepresentativeArtifact(t *testing.T) {45 held, _ := representativeInspector(t)46 artifact, err := held.openCompletedArtifact()47 if err != nil {48 t.Fatal(err)49 }50 page, err := inspectArtifact(artifact, "", "", 0)51 artifact.Close()52 if err != nil {53 t.Fatal(err)54 }55 if len(page.Worlds) != 1 || len(page.Worlds[0].FeatureSets) != 3 || page.Worlds[0].Features != 4 {56 t.Fatalf("compiled world summary = %+v", page.Worlds)57 }58 var places artifactFeatureSet59 for _, set := range page.Worlds[0].FeatureSets {60 if strings.HasSuffix(set.ID, "/set/places") {61 places = set62 }63 }64 if places.Geometry != "point" || places.FeatureCount != 2 || len(places.Properties) != 6 {65 t.Fatalf("compiled places contract = %+v", places)66 }67 wantKinds := map[string]bool{"bool": false, "int64": false, "float64": false, "string": false, "bytes": false, "id": false}68 for _, field := range places.Properties {69 wantKinds[field.Kind] = true70 }71 for kind, found := range wantKinds {72 if !found {73 t.Errorf("compiled places contract omits %s", kind)74 }75 }76 if page.Receipt == nil || len(page.Receipt.Captures) != 5 || page.ReceiptErr != "" {77 t.Fatalf("compiled receipt = %+v, error %q", page.Receipt, page.ReceiptErr)78 }7980 server := site(t, held)81 response, body := get(t, server, "/project/inspect")82 if response.StatusCode != http.StatusOK {83 t.Fatalf("inspector answered %d: %s", response.StatusCode, body)84 }85 wants(t, "compiled inspector", body,86 "Compiled artifact", "native semantic truth", "validated", "runtime union",87 "Sample Region", "CoordinateSpace", "Sample Places", "point", "Sample Routes", "path",88 "Sample Areas", "area", "Sample Background", "Presentation", "Executable replay receipt")8990 selected := places.Features[0]91 query := url.Values{"set": {places.ID}, "feature": {selected.ID}}92 response, body = get(t, server, "/project/inspect?"+query.Encode())93 if response.StatusCode != http.StatusOK {94 t.Fatalf("selected feature answered %d: %s", response.StatusCode, body)95 }96 wants(t, "compiled feature facts", body, "Feature facts", selected.Title, "typed values", "observed relationships", "within", "provenance records")97}9899func TestInspectorPaginatesFeatureSummaries(t *testing.T) {100 targets := projectTargets(t)101 if err := os.MkdirAll(targets.Registry, 0o755); err != nil {102 t.Fatal(err)103 }104 features := make([]featureSpec, 31)105 for index := range features {106 features[index] = featureSpec{id: int64(index + 1), title: fmt.Sprintf("Sample feature %02d", index+1)}107 }108 path := bundleSpec{109 slug: "sample-region", title: "Sample Region",110 worlds: []worldSpec{{slug: "sample-region", title: "Sample Region", features: features}},111 }.write(t, targets.Registry)112 held, err := New(Options{Targets: targets})113 if err != nil {114 t.Fatal(err)115 }116 held.supervisor.last = supervisedRun{Name: "build", Artifact: path, FinishedAt: time.Now()}117 server := site(t, held)118 response, body := get(t, server, "/project/inspect?set=sample-region%2Fset%2Fmarkers")119 if response.StatusCode != http.StatusOK {120 t.Fatalf("first page answered %d: %s", response.StatusCode, body)121 }122 wants(t, "first feature page", body, "Features (0–25 of 31)", "Sample feature 01", "Sample feature 25", "Next")123 if strings.Contains(body, "Sample feature 26") {124 t.Fatal("first feature page rendered beyond its 25-item budget")125 }126 response, body = get(t, server, "/project/inspect?set=sample-region%2Fset%2Fmarkers&offset=25")127 if response.StatusCode != http.StatusOK {128 t.Fatalf("second page answered %d: %s", response.StatusCode, body)129 }130 wants(t, "second feature page", body, "Features (25–31 of 31)", "Sample feature 26", "Sample feature 31", "Previous")131}132133func TestCompletedArtifactBoundaryRequiresSuccessAndRejectsLinks(t *testing.T) {134 targets := projectTargets(t)135 if err := os.MkdirAll(targets.Registry, 0o755); err != nil {136 t.Fatal(err)137 }138 held, err := New(Options{Targets: targets})139 if err != nil {140 t.Fatal(err)141 }142 for name, run := range map[string]supervisedRun{143 "running": {Name: "build", Running: true, Artifact: filepath.Join(targets.Registry, "running.atlas")},144 "failed": {Name: "build", Failed: true, FinishedAt: time.Now(), Artifact: filepath.Join(targets.Registry, "failed.atlas")},145 "unfinished": {Name: "build", Artifact: filepath.Join(targets.Registry, "unfinished.atlas")},146 "other run": {Name: "diff", FinishedAt: time.Now(), Artifact: filepath.Join(targets.Registry, "other.atlas")},147 } {148 t.Run(name, func(t *testing.T) {149 held.supervisor.last = run150 if _, err := held.openCompletedArtifact(); err == nil || !strings.Contains(err.Error(), "no successful completed") {151 t.Fatalf("boundary error = %v", err)152 }153 })154 }155156 outside := filepath.Join(targets.Dir, "outside.atlas")157 if err := os.WriteFile(outside, []byte("outside"), 0o644); err != nil {158 t.Fatal(err)159 }160 link := filepath.Join(targets.Registry, "link.atlas")161 if err := os.Symlink(outside, link); err != nil {162 t.Fatal(err)163 }164 held.supervisor.last = supervisedRun{Name: "build", FinishedAt: time.Now(), Artifact: link}165 if _, err := held.openCompletedArtifact(); err == nil || !strings.Contains(err.Error(), "symbolic link") {166 t.Fatalf("symlink error = %v", err)167 }168169 directory := filepath.Join(targets.Registry, "directory.atlas")170 if err := os.Mkdir(directory, 0o755); err != nil {171 t.Fatal(err)172 }173 held.supervisor.last = supervisedRun{Name: "build", FinishedAt: time.Now(), Artifact: directory}174 if _, err := held.openCompletedArtifact(); err == nil || !strings.Contains(err.Error(), "regular file") {175 t.Fatalf("directory error = %v", err)176 }177178 held.supervisor.last = supervisedRun{Name: "build", FinishedAt: time.Now(), Artifact: outside}179 if _, err := held.openCompletedArtifact(); err == nil || !strings.Contains(err.Error(), "outside") {180 t.Fatalf("outside-library error = %v", err)181 }182}183184func TestCompletedArtifactKeepsTheValidatedFileOpen(t *testing.T) {185 held, result := representativeInspector(t)186 want, err := os.ReadFile(result.Path)187 if err != nil {188 t.Fatal(err)189 }190 artifact, err := held.openCompletedArtifact()191 if err != nil {192 t.Fatal(err)193 }194 defer artifact.Close()195 if err := os.Rename(result.Path, result.Path+".moved"); err != nil {196 t.Fatal(err)197 }198 if err := os.WriteFile(result.Path, []byte("replacement"), 0o644); err != nil {199 t.Fatal(err)200 }201 if _, err := artifact.source.Seek(0, io.SeekStart); err != nil {202 t.Fatal(err)203 }204 got, err := io.ReadAll(artifact.source)205 if err != nil {206 t.Fatal(err)207 }208 if string(got) != string(want) {209 t.Fatal("validated handle followed the replaced pathname")210 }211}212213func TestInspectorReceiptDecoderIsStrictAndContained(t *testing.T) {214 hashA := strings.Repeat("a", 64)215 hashB := strings.Repeat("b", 64)216 selection := authoring.EvidenceSelection{217 Format: authoring.BuildReceiptFormat, Project: "sample-region", ProjectDigest: hashA,218 Captures: []authoring.SelectedCapture{{219 Request: hashA, Acquisition: hashB, Source: "sample-source", SHA256: hashA,220 Adapter: "geojson", AdapterVersion: "geojson/v1",221 Length: 4, MediaType: "application/geo+json", CapturedAt: "2026-08-08T12:00:00Z",222 License: "Example data", Attribution: "Sample Region contributors",223 }},224 }225 data, err := json.Marshal(selection)226 if err != nil {227 t.Fatal(err)228 }229 if _, err := inspectEvidenceSelection(data); err != nil {230 t.Fatalf("valid receipt refused: %v", err)231 }232 for name, mutate := range map[string]func([]byte) []byte{233 "unknown field": func(data []byte) []byte { return append(data[:len(data)-1], []byte(`,"surprise":true}`)...) },234 "trailing data": func(data []byte) []byte { return append(data, []byte(` {}`)...) },235 "uppercase hash": func(data []byte) []byte {236 return []byte(strings.Replace(string(data), hashA, strings.ToUpper(hashA), 1))237 },238 "bad time": func(data []byte) []byte {239 return []byte(strings.Replace(string(data), "2026-08-08T12:00:00Z", "sometime", 1))240 },241 } {242 t.Run(name, func(t *testing.T) {243 if _, err := inspectEvidenceSelection(mutate(append([]byte(nil), data...))); err == nil {244 t.Fatal("invalid receipt was accepted")245 }246 })247 }248}