1package authoring23import (4 "bytes"5 "context"6 "os"7 "path/filepath"8 "strings"9 "testing"10)1112func TestBuildReplaysExactEvidenceFromEarlierAtlas(t *testing.T) {13 projectDir := t.TempDir()14 for _, name := range []string{15 "sample-region.atlas-project",16 "sample-region.geojson",17 "sample-region-routes.geojson",18 "sample-region-areas.geojson",19 "sample-region-raster.ppm",20 "sample-region-symbol.svg",21 } {22 data, err := os.ReadFile(filepath.Join("..", "..", "examples", name))23 if err != nil {24 t.Fatal(err)25 }26 if err := os.WriteFile(filepath.Join(projectDir, name), data, 0o644); err != nil {27 t.Fatal(err)28 }29 }30 projectPath := filepath.Join(projectDir, "sample-region.atlas-project")31 cacheDir := filepath.Join(t.TempDir(), "cache")32 libraryDir := filepath.Join(t.TempDir(), "library")33 first, err := Build(context.Background(), BuildOptions{34 ProjectPath: projectPath, CacheDir: cacheDir, LibraryDir: libraryDir,35 })36 if err != nil {37 t.Fatal(err)38 }39 firstBytes, err := os.ReadFile(first.Path)40 if err != nil {41 t.Fatal(err)42 }4344 featurePath := filepath.Join(projectDir, "sample-region.geojson")45 changed, err := os.ReadFile(featurePath)46 if err != nil {47 t.Fatal(err)48 }49 changed = []byte(strings.Replace(string(changed), "Sample Marker", "Updated Marker", 1))50 if err := os.WriteFile(featurePath, changed, 0o644); err != nil {51 t.Fatal(err)52 }53 second, err := Build(context.Background(), BuildOptions{54 ProjectPath: projectPath, CacheDir: cacheDir, LibraryDir: libraryDir,55 })56 if err != nil {57 t.Fatal(err)58 }59 if second.Path == first.Path {60 t.Fatal("changed evidence did not produce a distinct Atlas build")61 }6263 replayed, err := Build(context.Background(), BuildOptions{64 ProjectPath: projectPath, CacheDir: cacheDir, LibraryDir: libraryDir,65 ReplayPath: first.Path,66 })67 if err != nil {68 t.Fatal(err)69 }70 replayedBytes, err := os.ReadFile(replayed.Path)71 if err != nil {72 t.Fatal(err)73 }74 if replayed.Path != first.Path || !replayed.Present || !bytes.Equal(replayedBytes, firstBytes) {75 t.Fatalf("exact replay differs: first=%s replayed=%s present=%v", first.Path, replayed.Path, replayed.Present)76 }77}7879func TestReplayAllowsMappingOnlyProjectChangeButRejectsAcquisitionDrift(t *testing.T) {80 policy := strings.Repeat("d", 64)81 request := PlannedRequest{Request: Request{82 ID: strings.Repeat("a", 64), Kind: RequestFeatures, Source: "sample-places",83 Adapter: "geojson", AdapterVersion: "geojson/v1", License: "Example data", Attribution: "Sample Region contributors",84 }}85 plan := Plan{Project: "sample-region", ProjectDigest: policy, Requests: []PlannedRequest{request}}86 selection := EvidenceSelection{87 Format: BuildReceiptFormat, Project: "sample-region", ProjectDigest: strings.Repeat("e", 64),88 Captures: []SelectedCapture{{89 Request: request.ID, Acquisition: request.ID, Source: request.Source, SHA256: strings.Repeat("b", 64),90 Adapter: request.Adapter, AdapterVersion: request.AdapterVersion,91 Length: 1, MediaType: "application/geo+json", CapturedAt: "2026-08-08T12:00:00Z",92 License: request.License, Attribution: request.Attribution,93 }},94 }95 data, _, err := buildReceipt(plan, map[string]Capture{request.ID: {96 RequestHash: request.ID, Source: request.Source, CapturedAt: selection.Captures[0].CapturedAt,97 Body: BlobRef{SHA256: selection.Captures[0].SHA256, Length: 1, MediaType: selection.Captures[0].MediaType},98 }})99 if err != nil {100 t.Fatal(err)101 }102 decoded, err := decodeEvidenceSelection(data)103 if err != nil || decoded.ProjectDigest != policy {104 t.Fatalf("current policy receipt = %+v, %v", decoded, err)105 }106 selection.Captures[0].Request = strings.Repeat("c", 64)107 if _, err := replayCaptures(plan, selection, OpenCache(t.TempDir())); err == nil || !strings.Contains(err.Error(), "does not select") {108 t.Fatalf("replay acquisition drift = %v", err)109 }110}111112func TestSampleRegionReplayIsByteIdenticalAcrossFreshLibraries(t *testing.T) {113 projectPath := filepath.Join("..", "..", "examples", "sample-region.atlas-project")114 cacheDir := filepath.Join(t.TempDir(), "cache")115 first, err := Build(context.Background(), BuildOptions{116 ProjectPath: projectPath, CacheDir: cacheDir, LibraryDir: filepath.Join(t.TempDir(), "first-library"),117 })118 if err != nil {119 t.Fatal(err)120 }121 second, err := Build(context.Background(), BuildOptions{122 ProjectPath: projectPath, CacheDir: cacheDir, LibraryDir: filepath.Join(t.TempDir(), "second-library"),123 ReplayPath: first.Path,124 })125 if err != nil {126 t.Fatal(err)127 }128 firstBytes, err := os.ReadFile(first.Path)129 if err != nil {130 t.Fatal(err)131 }132 secondBytes, err := os.ReadFile(second.Path)133 if err != nil {134 t.Fatal(err)135 }136 if first.Present || second.Present || filepath.Base(first.Path) != filepath.Base(second.Path) || !bytes.Equal(firstBytes, secondBytes) {137 t.Fatalf("fresh-library replay differs: first=%s second=%s", first.Path, second.Path)138 }139}140141func TestBuildRefusesACorruptExistingStampTarget(t *testing.T) {142 projectPath := filepath.Join("..", "..", "examples", "sample-region.atlas-project")143 cacheDir := filepath.Join(t.TempDir(), "cache")144 libraryDir := filepath.Join(t.TempDir(), "library")145 first, err := Build(context.Background(), BuildOptions{146 ProjectPath: projectPath, CacheDir: cacheDir, LibraryDir: libraryDir,147 })148 if err != nil {149 t.Fatal(err)150 }151 file, err := os.OpenFile(first.Path, os.O_APPEND|os.O_WRONLY, 0)152 if err != nil {153 t.Fatal(err)154 }155 if _, err := file.WriteString("changed container bytes"); err != nil {156 file.Close()157 t.Fatal(err)158 }159 if err := file.Close(); err != nil {160 t.Fatal(err)161 }162 if _, err := Build(context.Background(), BuildOptions{163 ProjectPath: projectPath, CacheDir: cacheDir, LibraryDir: libraryDir, Offline: true,164 }); err == nil || !strings.Contains(err.Error(), "existing") {165 t.Fatalf("corrupt stamp target = %v", err)166 }167}