package storage import ( "context" "errors" "io" "testing" ) func TestValidatePath(t *testing.T) { valid := []string{ "report.md", "daily/report.md", "a-b_1.2/report.html", "manifest.json", StateFileName, "nested/manifest.json", "nested/" + StateFileName, } for _, path := range valid { t.Run("valid "+path, func(t *testing.T) { if err := ValidatePath(path); err != nil { t.Fatalf("ValidatePath(%q) error = %v", path, err) } }) } invalid := []string{ "", ".", "./report.md", "/absolute", "../outside", "nested/../outside", "nested/.", "nested/./file", "nested/", "nested//file", `nested\file`, } for _, path := range invalid { t.Run("invalid "+path, func(t *testing.T) { err := ValidatePath(path) if !IsInvalidPath(err) { t.Fatalf("ValidatePath(%q) error = %v, want invalid path", path, err) } }) } } func TestValidatePrefixAllowsRoot(t *testing.T) { if err := ValidatePrefix(""); err != nil { t.Fatalf("ValidatePrefix(\"\") error = %v", err) } if err := ValidatePrefix("a/.."); !IsInvalidPath(err) { t.Fatalf("ValidatePrefix traversal error = %v, want invalid path", err) } } func TestStatePath(t *testing.T) { tests := map[string]string{ "": StateFileName, "bundle": "bundle/" + StateFileName, } for bundlePath, want := range tests { t.Run(bundlePath, func(t *testing.T) { got, err := StatePath(bundlePath) if err != nil { t.Fatalf("StatePath(%q) error = %v", bundlePath, err) } if got != want { t.Fatalf("StatePath(%q) = %q, want %q", bundlePath, got, want) } }) } } func TestDisplayPath(t *testing.T) { tests := map[string]string{ "": ".", "bundle": "bundle", } for path, want := range tests { t.Run(path, func(t *testing.T) { if got := DisplayPath(path); got != want { t.Fatalf("DisplayPath(%q) = %q, want %q", path, got, want) } }) } } func TestManagedBundleTargets(t *testing.T) { tests := []struct { name string bundlePath string outputs []string want []string }{ { name: "root", outputs: []string{"report.html", "assets/style.css"}, want: []string{"report.html", "assets/style.css", StateFileName}, }, { name: "nested", bundlePath: "daily", outputs: []string{"report.html"}, want: []string{"daily/report.html", "daily/" + StateFileName}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ManagedBundleTargets(tt.bundlePath, tt.outputs) if err != nil { t.Fatalf("ManagedBundleTargets() error = %v", err) } if len(got) != len(tt.want) { t.Fatalf("targets = %v, want %v", got, tt.want) } for i := range got { if got[i] != tt.want[i] { t.Fatalf("targets = %v, want %v", got, tt.want) } } }) } } func TestManagedBundleTargetsRejectsInvalidOutputPath(t *testing.T) { if _, err := ManagedBundleTargets("bundle", []string{"../outside"}); !IsInvalidPath(err) { t.Fatalf("ManagedBundleTargets() error = %v, want invalid path", err) } } func TestManagedOutputTargetsOmitsStateFile(t *testing.T) { targets, err := ManagedOutputTargets("bundle", []string{"report.md", "nested/report.html"}) if err != nil { t.Fatalf("ManagedOutputTargets() error = %v", err) } want := []string{"bundle/report.md", "bundle/nested/report.html"} if len(targets) != len(want) { t.Fatalf("targets = %v, want %v", targets, want) } for index := range want { if targets[index] != want[index] { t.Fatalf("targets = %v, want %v", targets, want) } } } func TestListSortsEntries(t *testing.T) { backend := walkBackend{ entries: []Entry{ {Path: "z.txt", Type: EntryTypeFile}, {Path: "a.txt", Type: EntryTypeFile}, }, } entries, err := List(context.Background(), backend, "", WalkOptions{}) if err != nil { t.Fatalf("List() error = %v", err) } if got, want := []string{entries[0].Path, entries[1].Path}, []string{"a.txt", "z.txt"}; got[0] != want[0] || got[1] != want[1] { t.Fatalf("paths = %v, want %v", got, want) } } func TestTypedErrorPredicates(t *testing.T) { err := NewError(OpReadFile, "test", "missing", ErrNotFound, errors.New("missing")) if !IsNotFound(err) { t.Fatalf("IsNotFound(%v) = false, want true", err) } if IsInvalidPath(err) { t.Fatalf("IsInvalidPath(%v) = true, want false", err) } } func TestRegistry(t *testing.T) { registry := NewRegistry() if err := registry.Register("test", func(context.Context, OpenConfig) (Backend, error) { return walkBackend{}, nil }); err != nil { t.Fatalf("Register() error = %v", err) } if _, err := registry.Open(context.Background(), "test", nil); err != nil { t.Fatalf("Open() error = %v", err) } if _, err := registry.Open(context.Background(), "missing", nil); !IsUnsupported(err) { t.Fatalf("Open() error = %v, want unsupported", err) } } type walkBackend struct { entries []Entry } func (b walkBackend) ReadFile(context.Context, string) ([]byte, error) { return nil, nil } func (b walkBackend) OpenReader(context.Context, string) (io.ReadCloser, error) { return nil, nil } func (b walkBackend) WriteFile(context.Context, string, []byte, WriteOptions) (Entry, error) { return Entry{}, nil } func (b walkBackend) WriteFrom(context.Context, string, io.Reader, WriteOptions) (Entry, error) { return Entry{}, nil } func (b walkBackend) Stat(context.Context, string) (Entry, error) { return Entry{}, nil } func (b walkBackend) Walk(_ context.Context, _ string, _ WalkOptions, fn WalkFunc) error { for _, entry := range b.entries { if err := fn(entry); err != nil { return err } } return nil } func (b walkBackend) HasAny(context.Context, string) (bool, error) { return false, nil } func (b walkBackend) DeleteManagedBundle(context.Context, string, []string, DeleteOptions) error { return nil } func (b walkBackend) DeleteManagedOutputs(context.Context, string, []string, DeleteOptions) error { return nil } func (b walkBackend) DeletePrefix(context.Context, string, DeleteOptions) error { return nil }