package gitstore import ( "os/exec" "path/filepath" "strings" "testing" "time" ) func write(t *testing.T, s *Store, branch, path, body string) Result { t.Helper() r, err := s.Write(Commit{ Repo: "t.git", Branch: branch, Path: path, Body: []byte(body), Message: "тест", Author: "QA", Email: "qa@netpulse.local", When: time.Date(2026, 8, 25, 12, 0, 0, 0, time.UTC), }) if err != nil { t.Fatalf("запис: %v", err) } return r } func TestWriteReadHistory(t *testing.T) { s := New(t.TempDir()) first := write(t, s, "device/d1", "sw-01/running.cfg", "hostname sw-01\n") if first.Unchanged { t.Fatal("перший запис не може бути незміненим") } if len(first.CommitSHA) != 40 { t.Fatalf("commit sha не схожий на git: %q", first.CommitSHA) } // Той самий вміст не створює коміту — інакше історія заростає // порожніми записами від нічних бекапів. same := write(t, s, "device/d1", "sw-01/running.cfg", "hostname sw-01\n") if !same.Unchanged { t.Fatal("повторний запис того самого вмісту дав новий коміт") } if same.CommitSHA != first.CommitSHA { t.Fatalf("незмінений запис змінив коміт: %s → %s", first.CommitSHA, same.CommitSHA) } second := write(t, s, "device/d1", "sw-01/running.cfg", "hostname sw-01\nntp server 1.1.1.1\n") if second.CommitSHA == first.CommitSHA { t.Fatal("зміна вмісту не дала нового коміту") } body, err := s.Read("t.git", first.CommitSHA, "sw-01/running.cfg") if err != nil { t.Fatalf("читання першої версії: %v", err) } if string(body) != "hostname sw-01\n" { t.Fatalf("перша версія зіпсована: %q", body) } hist, err := s.History("t.git", "device/d1", 0) if err != nil { t.Fatalf("історія: %v", err) } if len(hist) != 2 { t.Fatalf("очікував 2 коміти, маю %d", len(hist)) } if hist[0].CommitSHA != second.CommitSHA { t.Fatal("історія не починається з найновішого") } if hist[0].Author != "QA" { t.Fatalf("автор загубився: %q", hist[0].Author) } } // Гілки різних пристроїв не мають бачити одна одну: інакше історія // одного комутатора виявиться перемішаною з чужою. func TestBranchesIndependent(t *testing.T) { s := New(t.TempDir()) write(t, s, "device/d1", "sw-01/running.cfg", "a\n") write(t, s, "device/d2", "sw-02/running.cfg", "b\n") write(t, s, "device/d1", "sw-01/running.cfg", "a2\n") h1, _ := s.History("t.git", "device/d1", 0) h2, _ := s.History("t.git", "device/d2", 0) if len(h1) != 2 || len(h2) != 1 { t.Fatalf("гілки перемішались: d1=%d d2=%d", len(h1), len(h2)) } } // Кілька зрізів одного пристрою живуть в одній гілці різними файлами. func TestSeveralConfigTypes(t *testing.T) { s := New(t.TempDir()) write(t, s, "device/d1", "sw-01/running.cfg", "running\n") last := write(t, s, "device/d1", "sw-01/startup.cfg", "startup\n") running, err := s.Read("t.git", last.CommitSHA, "sw-01/running.cfg") if err != nil { t.Fatalf("running після додавання startup: %v", err) } if string(running) != "running\n" { t.Fatalf("сусідній файл затерто: %q", running) } } // Дерева, зібрані вручну, мусять бути коректними з погляду самого git. // Це єдина перевірка, яка ловить неправильний порядок записів у дереві: // go-git такий об'єкт приймає й читає, а git — ні. func TestFsckClean(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git не знайдено") } dir := t.TempDir() s := New(dir) write(t, s, "device/d1", "sw-01/running.cfg", "a\n") write(t, s, "device/d1", "sw-01/startup.cfg", "b\n") write(t, s, "device/d1", "kyiv/dc1/sw-01/vlan.cfg", "c\n") write(t, s, "device/d1", "sw-01/running.cfg", "a2\n") out, err := exec.Command("git", "--git-dir", filepath.Join(dir, "t.git"), "fsck", "--strict").CombinedOutput() if err != nil { t.Fatalf("git fsck: %v\n%s", err, out) } if s := strings.TrimSpace(string(out)); s != "" { t.Fatalf("git fsck поскаржився:\n%s", s) } } func TestDisabled(t *testing.T) { s := New("") if _, err := s.Write(Commit{Repo: "t.git", Branch: "b", Path: "p", Body: []byte("x")}); err == nil { t.Fatal("порожній корінь мав дати помилку") } }