Netpulse_SasS/server/internal/gitstore/branches_test.go
byrsapty 08801f631c
All checks were successful
CI / hygiene (push) Successful in 9s
CI / web (push) Successful in 59s
CI / server (push) Successful in 1m13s
CI / agent (push) Successful in 2m54s
Стеля журналів, терплячіша самоперевірка, Етап 13 у плані
ЖУРНАЛИ БЕЗ СТЕЛІ. На запитання «чому не пишемо в /var/log» правильна
відповідь — «бо в контейнері це не той шар»: файл усередині зникає при
перестворенні, невидимий для docker logs і вимагає власної ротації.
Але за питанням стояла справжня вада: драйвер json-file був
налаштований порожньо, тобто НЕ КРУТИВ НІЧОГО. Проксі за добу набрав
27 МБ; до повного диска були місяці, і першим ліг би Postgres.

Тепер 10 МБ × 3 файли на службу — близько 250 МБ на інсталяцію, і
драйвер міняється однією змінною (journald, syslog) для тих, кому
потрібні справжні файли або чужий збирач.

САМОПЕРЕВІРКА ЗОНДА чекала хвилину й одного разу вже дала хибне
червоне: зонд зареєструвався, просто пізніше — перший старт припадає
на найзавантаженішу мить установки. Тепер три хвилини з повідомленням
кожні півхвилини: мовчазна пауза невідрізненна від зависання, а хибне
червоне після успішної установки коштує години пошуку неіснуючої
поломки.

GITSTORE: TestDeleteBranchLocal кличе зовнішній git і не мав перевірки
на його відсутність — падав у golang:1.25-alpine, тобто саме там, де
його проганяють. Сусідній тест таку перевірку має.

ЕТАП 13 у ROADMAP: реєстр образів, релізи, пакети. Записано, чому
порядок саме такий (пакет без реєстру ставив би «зберіть самі») і чого
треба досягти до першого тегу — оновлення з версії на версію не
перевіряв ніхто, а ламається найчастіше саме воно.
2026-08-27 21:52:27 +03:00

151 lines
6.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gitstore
import (
"context"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// Видалення гілки: локально й на «дзеркалі».
//
// Дзеркалом тут працює звичайний голий репозиторій на диску, до якого
// ходимо по file://. Це не спрощення заради тесту: у справжньому push
// відрізняється лише транспорт, а перевіряється тут інше — що видалення
// адресне. Тобто що після нього на тому кінці зникає РІВНО названа
// гілка, а сусідня, про яку ніхто не говорив, лишається на місці. Саме
// це відрізняє нашу поведінку від prune, якого ми свідомо не робимо.
func gitOut(t *testing.T, dir string, args ...string) string {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
}
return string(out)
}
func TestDeleteBranchLocal(t *testing.T) {
// Сам gitstore працює на go-git і зовнішнього git не потребує — а
// от перевірка результату (`git branch --list`) потребує. Без цієї
// умови тест падає там, де git не встановлено: у голому
// golang:1.25-alpine, тобто саме в контейнері, де його й проганяють.
// Сусідній тест таку умову має, цей її не мав.
if _, err := exec.LookPath("git"); err != nil {
t.Skip("для звірки гілок потрібен git у PATH")
}
s := New(t.TempDir())
write(t, s, "device/sw-01-10.0.0.1", "sw-01/running.cfg", "hostname sw-01\n")
write(t, s, "device/sw-02-10.0.0.2", "sw-02/running.cfg", "hostname sw-02\n")
removed, err := s.DeleteBranch("t.git", "device/sw-01-10.0.0.1")
if err != nil {
t.Fatalf("видалення: %v", err)
}
if !removed {
t.Fatal("гілка була — мало повернутись true")
}
refs := gitOut(t, filepath.Join(s.Root(), "t.git"), "branch", "--list")
if strings.Contains(refs, "sw-01") {
t.Errorf("гілка лишилась: %s", refs)
}
if !strings.Contains(refs, "sw-02") {
t.Errorf("сусідню гілку не мало зачепити: %s", refs)
}
// Повтор — не помилка: черга видалень повторює спробу, доки не
// дійде до дзеркала, і другий прохід не має падати.
removed, err = s.DeleteBranch("t.git", "device/sw-01-10.0.0.1")
if err != nil {
t.Fatalf("повторне видалення: %v", err)
}
if removed {
t.Error("гілки вже не було — мало повернутись false")
}
// Репозиторію немає взагалі — теж не помилка: хост могли видалити
// до першого зібраного конфігу.
if _, err := s.DeleteBranch("немає.git", "device/x"); err != nil {
t.Errorf("відсутній репозиторій: %v", err)
}
}
// localURLPath робить із шляху файлової системи шлях для file://.
func localURLPath(dir string) string {
p := filepath.ToSlash(dir)
if !strings.HasPrefix(p, "/") {
p = "/" + p
}
return p
}
func TestDeleteRemoteRefIsTargeted(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("для file:// потрібен git у PATH")
}
root := t.TempDir()
s := New(root)
write(t, s, "device/sw-01-10.0.0.1", "sw-01/running.cfg", "hostname sw-01\n")
write(t, s, "device/sw-02-10.0.0.2", "sw-02/running.cfg", "hostname sw-02\n")
// «Дзеркало».
remoteDir := filepath.Join(root, "mirror.git")
gitOut(t, root, "init", "--bare", remoteDir)
// Приймати push у поточну гілку голого репозиторію нікому не
// заважає, але Forgejo цього не вимагає — вирівнюємо поведінку.
gitOut(t, remoteDir, "config", "receive.denyCurrentBranch", "ignore")
// Три скісні, не дві: у file:// одразу за ними йде ХОСТ, і на
// Windows "file://C:/..." означає репозиторій на машині "C:".
// На Linux провідна скісна в шляху вже є, тому помилки там не
// видно — тест зеленів, доки його не прогнали на Windows.
rem := Remote{URL: "file://" + localURLPath(remoteDir), Auth: AuthNone}
if _, err := s.Mirror(context.Background(), "t.git", rem); err != nil {
t.Fatalf("перший push: %v", err)
}
if out := gitOut(t, remoteDir, "branch", "--list"); !strings.Contains(out, "sw-01") ||
!strings.Contains(out, "sw-02") {
t.Fatalf("дзеркало не отримало обидві гілки: %s", out)
}
// Хост видалено: локальна гілка знята, і на дзеркало йде видалення
// рівно її.
if _, err := s.DeleteBranch("t.git", "device/sw-01-10.0.0.1"); err != nil {
t.Fatalf("локальне видалення: %v", err)
}
if err := s.DeleteRemoteRefs(context.Background(), "t.git", rem,
[]string{"device/sw-01-10.0.0.1"}); err != nil {
t.Fatalf("видалення на дзеркалі: %v", err)
}
out := gitOut(t, remoteDir, "branch", "--list")
if strings.Contains(out, "sw-01") {
t.Errorf("гілка лишилась на дзеркалі: %s", out)
}
if !strings.Contains(out, "sw-02") {
t.Errorf("сусідня гілка мала лишитись — ми її не називали: %s", out)
}
// Повтор після успіху. Черга повторює спробу після будь-якої
// невизначеності (обрив на відповіді, перезапуск процесу), і
// «такої гілки там немає» для видалення означає успіх, а не збій.
if err := s.DeleteRemoteRefs(context.Background(), "t.git", rem,
[]string{"device/sw-01-10.0.0.1"}); err != nil {
t.Errorf("повторне видалення на дзеркалі: %v", err)
}
// І головне: звичайне дзеркалення після цього не намагається
// повернути видалену гілку назад.
if _, err := s.Mirror(context.Background(), "t.git", rem); err != nil {
t.Fatalf("push після видалення: %v", err)
}
if out := gitOut(t, remoteDir, "branch", "--list"); strings.Contains(out, "sw-01") {
t.Errorf("push повернув видалену гілку: %s", out)
}
}