package readonlyfs import ( "errors" "io" "io/fs" "testing" "testing/fstest" ) func TestNewPassesFSTest(t *testing.T) { for _, test := range []struct { name string files map[string][]byte paths []string }{ {name: "empty", files: map[string][]byte{}, paths: nil}, {name: "single file", files: map[string][]byte{"message.txt": []byte("hello")}, paths: []string{"message.txt"}}, {name: "nested tree", files: map[string][]byte{"a/one.txt": []byte("one"), "a/b/two.txt": []byte("two")}, paths: []string{"a/one.txt", "a/b/two.txt"}}, } { t.Run(test.name, func(t *testing.T) { fsys, err := New(test.files) if err != nil { t.Fatal(err) } if err := fstest.TestFS(fsys, test.paths...); err != nil { t.Fatal(err) } }) } } func TestNewOwnsConstructionAndReadBytes(t *testing.T) { input := []byte("value") fsys, err := New(map[string][]byte{"value.txt": input}) if err != nil { t.Fatal(err) } input[0] = 'x' first, err := fsys.ReadFile("value.txt") if err != nil { t.Fatal(err) } first[0] = 'y' second, err := fsys.ReadFile("value.txt") if err != nil { t.Fatal(err) } if string(second) != "value" { t.Fatalf("ReadFile() = %q, want owned bytes", second) } } func TestFilesystemStandardBehavior(t *testing.T) { fsys, err := New(map[string][]byte{ "z.txt": []byte("z"), "nested/a.md": []byte("abc"), "nested/b.md": []byte("b"), }) if err != nil { t.Fatal(err) } entries, err := fsys.ReadDir("nested") if err != nil { t.Fatal(err) } if len(entries) != 2 || entries[0].Name() != "a.md" || entries[1].Name() != "b.md" { t.Fatalf("ReadDir(nested) = %#v, want sorted entries", entries) } info, err := entries[0].Info() if err != nil || info.Mode().Perm() != 0o444 || info.Size() != 3 { t.Fatalf("file info = %#v, %v", info, err) } root, err := fsys.Open(".") if err != nil { t.Fatal(err) } rootInfo, err := root.Stat() if err != nil || !rootInfo.IsDir() || rootInfo.Mode() != fs.ModeDir|0o555 { t.Fatalf("root info = %#v, %v", rootInfo, err) } directory := root.(fs.ReadDirFile) first, err := directory.ReadDir(1) if err != nil || len(first) != 1 || first[0].Name() != "nested" { t.Fatalf("first directory page = %#v, %v", first, err) } second, err := directory.ReadDir(1) if err != nil || len(second) != 1 || second[0].Name() != "z.txt" { t.Fatalf("second directory page = %#v, %v", second, err) } if _, err := directory.ReadDir(1); !errors.Is(err, io.EOF) { t.Fatalf("final directory page error = %v, want EOF", err) } secondRoot, err := fsys.Open(".") if err != nil { t.Fatal(err) } secondDirectory := secondRoot.(fs.ReadDirFile) firstAgain, err := secondDirectory.ReadDir(1) if err != nil || len(firstAgain) != 1 || firstAgain[0].Name() != "nested" { t.Fatalf("independent directory handle page = %#v, %v", firstAgain, err) } left, err := fsys.Open("nested/a.md") if err != nil { t.Fatal(err) } right, err := fsys.Open("nested/a.md") if err != nil { t.Fatal(err) } leftByte := make([]byte, 1) rightByte := make([]byte, 1) if _, err := left.Read(leftByte); err != nil { t.Fatal(err) } if _, err := right.Read(rightByte); err != nil { t.Fatal(err) } if string(leftByte) != "a" || string(rightByte) != "a" { t.Fatalf("independent file handles read %q and %q", leftByte, rightByte) } for _, test := range []struct { name string op func() error want error }{ {name: "invalid open path", op: func() error { _, err := fsys.Open("../value"); return err }, want: fs.ErrInvalid}, {name: "missing file", op: func() error { _, err := fsys.Open("missing"); return err }, want: fs.ErrNotExist}, {name: "read directory", op: func() error { _, err := fsys.ReadFile("nested"); return err }, want: errIsDirectory}, {name: "read directory from file", op: func() error { _, err := fsys.ReadDir("z.txt"); return err }, want: errNotDirectory}, } { t.Run(test.name, func(t *testing.T) { if err := test.op(); !errors.Is(err, test.want) { t.Fatalf("operation error = %v, want %v", err, test.want) } }) } } func TestNewRejectsInvalidAndConflictingPaths(t *testing.T) { for _, files := range []map[string][]byte{ {".": []byte("root")}, {"": []byte("empty")}, {"../outside": []byte("outside")}, {"nested//file": []byte("invalid")}, {"file": []byte("file"), "file/nested": []byte("nested")}, } { if _, err := New(files); err == nil { t.Fatalf("New(%#v) error = nil, want rejection", files) } } }