package sync import ( "log" "os" "os/exec" "path/filepath" "strings" "testing" "time" "gitea.dooplex.hu/admin/felhom-controller/internal/config" ) // Local git repositories only (file://) — no network. func gitT(t *testing.T, dir string, args ...string) string { t.Helper() cmd := exec.Command("git", append([]string{"-c", "user.email=t@example.invalid", "-c", "user.name=t", "-c", "init.defaultBranch=main", "-c", "commit.gpgsign=false"}, args...)...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %v: %v\n%s", args, err, out) } return strings.TrimSpace(string(out)) } // newCatalogRepo makes a work repo with one template and a marker file, on branch main. func newCatalogRepo(t *testing.T, marker string) string { t.Helper() dir := t.TempDir() gitT(t, dir, "init", "-q", "-b", "main") app := filepath.Join(dir, "templates", "demo") if err := os.MkdirAll(app, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(app, "docker-compose.yml"), []byte("services:\n web:\n image: busybox:1\n"), 0o644); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "MARKER"), []byte(marker), 0o644); err != nil { t.Fatal(err) } gitT(t, dir, "add", "-A") gitT(t, dir, "commit", "-q", "-m", "init") return dir } func newLocalSyncer(t *testing.T, repo string, rescans *int) *Syncer { t.Helper() root := t.TempDir() cfg := &config.Config{} cfg.Git.RepoURL = "file://" + repo cfg.Git.Branch = "main" cfg.Git.SyncInterval = "15m" cfg.Paths.DataDir = filepath.Join(root, "data") cfg.Paths.StacksDir = filepath.Join(root, "stacks") if err := os.MkdirAll(cfg.Paths.StacksDir, 0o755); err != nil { t.Fatal(err) } return New(cfg, log.New(os.Stderr, "", 0), func() error { *rescans++; return nil }, nil) } func syncNow(t *testing.T, s *Syncer) SyncResult { t.Helper() s.mu.Lock() s.lastSync = time.Now().Add(-time.Hour) // past the debounce s.mu.Unlock() res := s.TriggerSync() if !res.OK { t.Fatalf("sync failed: %s", res.Message) } return res } // R-615: changing git.repo_url under an existing cache makes the next sync read the NEW repository. func TestR615_ChangedRepoURLIsFollowed(t *testing.T) { a := newCatalogRepo(t, "catalog-A") b := newCatalogRepo(t, "catalog-B") n := 0 s := newLocalSyncer(t, a, &n) syncNow(t, s) readMarker := func() string { bs, err := os.ReadFile(filepath.Join(s.cacheDir, "MARKER")) if err != nil { t.Fatal(err) } return string(bs) } if readMarker() != "catalog-A" { t.Fatalf("setup: cache should hold catalog A") } s.cfg.Git.RepoURL = "file://" + b // the operator repoints the box syncNow(t, s) if got := readMarker(); got != "catalog-B" { t.Fatalf("R-615: after changing repo_url the cache still reads %q — the old repository is still being followed", got) } if origin := gitT(t, s.cacheDir, "config", "--get", "remote.origin.url"); origin != "file://"+b { t.Errorf("the cache's origin must be the configured repository, got %q", origin) } } // R-607: a catalog move that changes no stack directory still rescans (CatalogImages is read from the // cache) and is not reported as „nincs változás". func TestR607_CatalogMoveWithoutStackChangeRescansAndSaysSo(t *testing.T) { a := newCatalogRepo(t, "v1") n := 0 s := newLocalSyncer(t, a, &n) syncNow(t, s) before := n // No change at all: honest „nincs változás", no rescan. res := syncNow(t, s) if res.CatalogMoved || !strings.Contains(res.Message, "nincs v") || n != before { t.Fatalf("an unchanged catalog: moved=%v msg=%q rescans %d->%d", res.CatalogMoved, res.Message, before, n) } // The catalog moves in a file no stack dir copies. if err := os.WriteFile(filepath.Join(a, "MARKER"), []byte("v2"), 0o644); err != nil { t.Fatal(err) } gitT(t, a, "commit", "-q", "-am", "move") res = syncNow(t, s) if !res.CatalogMoved { t.Fatal("R-607: the sync did not notice the catalog moved") } if n != before+1 { t.Fatalf("R-607: a catalog move must rescan (CatalogImages is read from the cache); rescans %d->%d", before, n) } if strings.Contains(res.Message, "nincs v") { t.Fatalf("R-607: a moved catalog was reported as unchanged: %q", res.Message) } }