aboutsummaryrefslogtreecommitdiff
path: root/internal/cli/remote_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/cli/remote_test.go')
-rw-r--r--internal/cli/remote_test.go141
1 files changed, 141 insertions, 0 deletions
diff --git a/internal/cli/remote_test.go b/internal/cli/remote_test.go
new file mode 100644
index 0000000..b4e20d9
--- /dev/null
+++ b/internal/cli/remote_test.go
@@ -0,0 +1,141 @@
+package cli
+
+import (
+ "sort"
+ "strings"
+ "testing"
+)
+
+func dir(path string) remoteEntry {
+ return remoteEntry{path: path, isDir: true, orderKey: pathOrderKey(path)}
+}
+func file(path string) remoteEntry {
+ return remoteEntry{path: path, orderKey: pathOrderKey(path)}
+}
+
+func deleted(e remoteEntry) remoteEntry {
+ e.deleted = true
+ return e
+}
+
+func emptyDirPaths(entries []remoteEntry) []string {
+ // Reproduce the production ordering (see listRemoteEntries) so the sort rule
+ // and the emptyDirs scan rule are always exercised together, rather than
+ // trusting hand-ordered literals.
+ sort.Slice(entries, func(i, j int) bool {
+ return entries[i].orderKey < entries[j].orderKey
+ })
+ got := emptyDirs(entries)
+ paths := make([]string, len(got))
+ for i, e := range got {
+ paths[i] = e.path
+ }
+ sort.Strings(paths)
+ return paths
+}
+
+func TestEmptyDirs(t *testing.T) {
+ tests := []struct {
+ name string
+ entries []remoteEntry
+ want []string
+ }{
+ {
+ name: "dir with only a deleted file is empty",
+ entries: []remoteEntry{
+ dir("a"),
+ deleted(file("a/gone.js")),
+ },
+ want: []string{"a"},
+ },
+ {
+ name: "dir with a surviving file is kept",
+ entries: []remoteEntry{
+ dir("a"),
+ file("a/live.js"),
+ },
+ want: []string{},
+ },
+ {
+ name: "bottom-up cascade removes parent and child",
+ entries: []remoteEntry{
+ dir("a"),
+ dir("a/b"),
+ deleted(file("a/b/gone.js")),
+ },
+ want: []string{"a"},
+ },
+ {
+ name: "deep file deleted but sibling file alive keeps parent",
+ entries: []remoteEntry{
+ dir("a"),
+ dir("a/b"),
+ deleted(file("a/b/gone.js")),
+ file("a/c.js"),
+ },
+ want: []string{"a/b"},
+ },
+ {
+ name: "sibling with shared name prefix is not swallowed",
+ entries: []remoteEntry{
+ dir("a"),
+ deleted(file("a/gone.js")),
+ file("ab/live.js"),
+ },
+ want: []string{"a"},
+ },
+ {
+ name: "sibling colliding on base name does not hide live children",
+ entries: []remoteEntry{
+ dir("a"),
+ file("a.js"),
+ file("a/live.js"),
+ },
+ want: []string{},
+ },
+ {
+ name: "empty dir with no children is removed",
+ entries: []remoteEntry{
+ dir("a"),
+ },
+ want: []string{"a"},
+ },
+ {
+ name: "nested dirs with no files at all are all removed",
+ entries: []remoteEntry{
+ dir("a"),
+ dir("a/b"),
+ dir("a/c"),
+ },
+ want: []string{"a"},
+ },
+ {
+ name: "empty dir nested under a dir holding a live file keeps outer, removes inner",
+ entries: []remoteEntry{
+ dir("a"),
+ dir("a/b"),
+ dir("a/b/c"),
+ file("a/live.js"),
+ },
+ want: []string{"a/b"},
+ },
+ {
+ name: "live file under a nested dir keeps the whole chain",
+ entries: []remoteEntry{
+ dir("a"),
+ dir("a/b"),
+ file("a/b/live.js"),
+ },
+ want: []string{},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := emptyDirPaths(tt.entries)
+ if strings.Join(got, ",") != strings.Join(tt.want, ",") {
+ t.Fatalf("emptyDirs = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}