233 lines
6.9 KiB
Go
233 lines
6.9 KiB
Go
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)
|
|
}
|