206b0357d1
gates / gates (push) Successful in 27s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
86 lines
3.1 KiB
Go
86 lines
3.1 KiB
Go
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 `<project>_` 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)
|
|
}
|
|
}
|