package bundle import ( "os" "path/filepath" "reflect" "strings" "testing" "time" ) func TestWriteBundleCreatesCompleteBundle(t *testing.T) { sourceRoot := t.TempDir() outputRoot := filepath.Join(t.TempDir(), "bundle") writeFile(t, sourceRoot, "report.md", "# Report\n") writeFile(t, sourceRoot, "summary.txt", "Summary\n") created := time.Date(2026, 5, 30, 11, 10, 0, 0, time.UTC) manifest, err := WriteBundle(WriteBundleOptions{ Root: outputRoot, ID: "reports.writer", Created: created, Files: []BundleFile{ {SourcePath: filepath.Join(sourceRoot, "summary.txt"), Path: "nested/summary.txt"}, {SourcePath: filepath.Join(sourceRoot, "report.md"), Path: "report.md"}, }, }) if err != nil { t.Fatalf("WriteBundle() error = %v", err) } if !manifest.Created.Equal(created) { t.Fatalf("created = %s, want %s", manifest.Created, created) } if paths := manifestPaths(manifest); !reflect.DeepEqual(paths, []string{"nested/summary.txt", "report.md"}) { t.Fatalf("paths = %v, want caller order", paths) } if got := readFile(t, outputRoot, "nested/summary.txt"); got != "Summary\n" { t.Fatalf("nested summary = %q", got) } if got := readFile(t, outputRoot, "report.md"); got != "# Report\n" { t.Fatalf("report = %q", got) } loaded, err := LoadManifest(outputRoot) if err != nil { t.Fatalf("LoadManifest() error = %v", err) } if !reflect.DeepEqual(loaded, manifest) { t.Fatalf("loaded manifest = %#v, want %#v", loaded, manifest) } if err := ValidateBundle(outputRoot, loaded); err != nil { t.Fatalf("ValidateBundle() error = %v", err) } } func TestWriteBundleDefaultsCreated(t *testing.T) { sourceRoot := t.TempDir() outputRoot := filepath.Join(t.TempDir(), "bundle") writeFile(t, sourceRoot, "report.md", "# Report\n") before := time.Now().UTC() manifest, err := WriteBundle(WriteBundleOptions{ Root: outputRoot, ID: "reports.created", Files: []BundleFile{ {SourcePath: filepath.Join(sourceRoot, "report.md"), Path: "report.md"}, }, }) if err != nil { t.Fatalf("WriteBundle() error = %v", err) } after := time.Now().UTC() if manifest.Created.Before(before) || manifest.Created.After(after) { t.Fatalf("created = %s, want between %s and %s", manifest.Created, before, after) } } func TestWriteBundleRejectsDuplicatePaths(t *testing.T) { sourceRoot := t.TempDir() writeFile(t, sourceRoot, "report.md", "# Report\n") _, err := WriteBundle(WriteBundleOptions{ Root: filepath.Join(t.TempDir(), "bundle"), ID: "reports.duplicate", Files: []BundleFile{ {SourcePath: filepath.Join(sourceRoot, "report.md"), Path: "report.md"}, {SourcePath: filepath.Join(sourceRoot, "report.md"), Path: "report.md"}, }, }) if err == nil || !strings.Contains(err.Error(), "duplicates") { t.Fatalf("WriteBundle() error = %v, want duplicate path error", err) } } func TestWriteBundleRejectsSymlinkSource(t *testing.T) { sourceRoot := t.TempDir() writeFile(t, sourceRoot, "target.md", "# Report\n") linkPath := filepath.Join(sourceRoot, "link.md") if err := os.Symlink("target.md", linkPath); err != nil { t.Skipf("symlink unavailable: %v", err) } _, err := WriteBundle(WriteBundleOptions{ Root: filepath.Join(t.TempDir(), "bundle"), ID: "reports.symlink", Files: []BundleFile{ {SourcePath: linkPath, Path: "report.md"}, }, }) if err == nil || !strings.Contains(err.Error(), "regular file") { t.Fatalf("WriteBundle() error = %v, want regular file error", err) } } func TestWriteBundleDoesNotOverwriteByDefault(t *testing.T) { sourceRoot := t.TempDir() outputRoot := filepath.Join(t.TempDir(), "bundle") writeFile(t, sourceRoot, "report.md", "old\n") if _, err := WriteBundle(WriteBundleOptions{ Root: outputRoot, ID: "reports.old", Files: []BundleFile{ {SourcePath: filepath.Join(sourceRoot, "report.md"), Path: "report.md"}, }, }); err != nil { t.Fatalf("initial WriteBundle() error = %v", err) } writeFile(t, sourceRoot, "report.md", "new\n") _, err := WriteBundle(WriteBundleOptions{ Root: outputRoot, ID: "reports.new", Files: []BundleFile{ {SourcePath: filepath.Join(sourceRoot, "report.md"), Path: "report.md"}, }, }) if err == nil || !strings.Contains(err.Error(), "already exists") { t.Fatalf("WriteBundle() error = %v, want exists error", err) } if got := readFile(t, outputRoot, "report.md"); got != "old\n" { t.Fatalf("report = %q, want old content", got) } } func TestWriteBundleOverwritesExistingRoot(t *testing.T) { sourceRoot := t.TempDir() outputRoot := filepath.Join(t.TempDir(), "bundle") writeFile(t, sourceRoot, "old.md", "old\n") if _, err := WriteBundle(WriteBundleOptions{ Root: outputRoot, ID: "reports.old", Files: []BundleFile{ {SourcePath: filepath.Join(sourceRoot, "old.md"), Path: "old.md"}, }, }); err != nil { t.Fatalf("initial WriteBundle() error = %v", err) } writeFile(t, sourceRoot, "new.md", "new\n") manifest, err := WriteBundle(WriteBundleOptions{ Root: outputRoot, ID: "reports.new", Overwrite: true, Files: []BundleFile{ {SourcePath: filepath.Join(sourceRoot, "new.md"), Path: "new.md"}, }, }) if err != nil { t.Fatalf("WriteBundle(overwrite) error = %v", err) } if manifest.ID != "reports.new" { t.Fatalf("manifest id = %q, want reports.new", manifest.ID) } if _, err := os.Stat(filepath.Join(outputRoot, "old.md")); !os.IsNotExist(err) { t.Fatalf("old file stat error = %v, want not exist", err) } if got := readFile(t, outputRoot, "new.md"); got != "new\n" { t.Fatalf("new file = %q", got) } if err := ValidateBundle(outputRoot, manifest); err != nil { t.Fatalf("ValidateBundle() error = %v", err) } } func TestWriteBundleKeepsExistingRootWhenReplacementBuildFails(t *testing.T) { sourceRoot := t.TempDir() outputRoot := filepath.Join(t.TempDir(), "bundle") writeFile(t, sourceRoot, "report.md", "old\n") oldManifest, err := WriteBundle(WriteBundleOptions{ Root: outputRoot, ID: "reports.old", Files: []BundleFile{ {SourcePath: filepath.Join(sourceRoot, "report.md"), Path: "report.md"}, }, }) if err != nil { t.Fatalf("initial WriteBundle() error = %v", err) } _, err = WriteBundle(WriteBundleOptions{ Root: outputRoot, ID: "reports.new", Overwrite: true, Files: []BundleFile{ {SourcePath: filepath.Join(sourceRoot, "missing.md"), Path: "report.md"}, }, }) if err == nil { t.Fatal("WriteBundle(overwrite) error = nil, want missing source error") } loaded, err := LoadManifest(outputRoot) if err != nil { t.Fatalf("LoadManifest() error = %v", err) } if !reflect.DeepEqual(loaded, oldManifest) { t.Fatalf("loaded manifest = %#v, want old manifest %#v", loaded, oldManifest) } if got := readFile(t, outputRoot, "report.md"); got != "old\n" { t.Fatalf("report = %q, want old content", got) } } func readFile(t *testing.T, root, relative string) string { t.Helper() data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(relative))) if err != nil { t.Fatalf("read %s: %v", relative, err) } return string(data) }