package backup import ( "io" "log" "os" "path/filepath" "strings" "testing" ) // R-658 (v0.268.0) — the unit restore recreated each named volume with a bare `docker volume create`, // so the volume carried no compose label; the undo (until v0.267.0) selected volumes by that label and // copied nothing after any restore. The restore now creates the volume WITH compose's labels. // // The docker here is a STUB script in t.TempDir() on PATH (R-650's seam) that records every call. // // COMPANION RED-PROOF (REPORT.md): drop composeVolumeLabelArgs from the create call. This test then // fails at "the restore created vikunja_files WITHOUT the project label". func TestR658_RestoreCreatesLabelledVolumes(t *testing.T) { bin := t.TempDir() calls := filepath.Join(bin, "calls.log") stub := "#!/bin/sh\necho \"$@\" >> " + calls + "\n" + "case \"$*\" in 'compose version --short') echo v2.29.7;; esac\nexit 0\n" if err := os.WriteFile(filepath.Join(bin, "docker"), []byte(stub), 0o755); err != nil { t.Fatal(err) } t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) dump := t.TempDir() for _, n := range []string{"vikunja_files.tar", "vikunja_db.tar"} { if err := os.WriteFile(filepath.Join(dump, n), []byte("tar"), 0o644); err != nil { t.Fatal(err) } } m := &Manager{logger: log.New(io.Discard, "", 0)} n, err := m.restoreDockerVolumesFrom("vikunja", dump) if err != nil || n != 2 { t.Fatalf("restored %d, err %v — want 2, nil", n, err) } b, _ := os.ReadFile(calls) var creates []string for _, l := range strings.Split(string(b), "\n") { if strings.HasPrefix(l, "volume create") { creates = append(creates, l) } } if len(creates) != 2 { t.Fatalf("want 2 volume creates, got %q", creates) } for _, want := range []struct{ vol, key string }{{"vikunja_files", "files"}, {"vikunja_db", "db"}} { found := false for _, c := range creates { if !strings.HasSuffix(c, " "+want.vol) { continue } found = true if !strings.Contains(c, "--label com.docker.compose.project=vikunja") { t.Fatalf("the restore created %s WITHOUT the project label: %q", want.vol, c) } if !strings.Contains(c, "--label com.docker.compose.volume="+want.key) { t.Fatalf("the restore created %s without its volume key %q: %q", want.vol, want.key, c) } if !strings.Contains(c, "--label com.docker.compose.version=2.29.7") { t.Fatalf("the restore created %s without the compose version: %q", want.vol, c) } if strings.Contains(c, "config-hash") { t.Fatalf("a guessed config-hash label would make compose offer to recreate the volume: %q", c) } } if !found { t.Fatalf("no create for %s in %q", want.vol, creates) } } } // A volume without the `_` prefix is created without labels — never with a guessed key. func TestR658_LabelArgsNeverGuessAKey(t *testing.T) { if got := composeVolumeLabelArgs("vikunja", "custom_name", "2.29.7"); got != nil { t.Fatalf("got %v, want no labels", got) } if got := composeVolumeLabelArgs("vikunja", "vikunja_db", ""); len(got) != 4 { t.Fatalf("an unreadable version drops only the version label; got %v", got) } }