package release import ( "os" "path/filepath" "testing" ) func writeComp(t *testing.T, root, name, body string) string { t.Helper() c, _ := ComponentByName(name) p := c.PathAt(root) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(p, []byte(body), 0o755); err != nil { t.Fatal(err) } sha, _, err := HashFile(p) if err != nil { t.Fatal(err) } return sha } func state(states []ComponentState, name string) ComponentState { for _, s := range states { if s.Name == name { return s } } return ComponentState{} } func TestPlanIndependentVersions(t *testing.T) { root := t.TempDir() npr := writeComp(t, root, "bj-npr", "npr 1.0.0") writeComp(t, root, "bj-server", "core old") installed := map[string]Installed{ "bj-npr": {Version: "1.0.0", SHA256: npr}, "bj-server": {Version: "2.0.0", SHA256: "другой файл"}, } m := &Manifest{Version: "2026.09.1", Artifacts: []Artifact{ {Name: "bj-server", Version: "2.1.0", SHA256: "new-core"}, {Name: "bj-npr", Version: "1.0.0", SHA256: npr}, {Name: "bj-quikexport", Version: "1.2.0", SHA256: "qe"}, }} st := Plan(root, m, installed, "2.0.0") if s := state(st, "bj-npr"); !s.Present || s.Installed != "1.0.0" || !s.SameFile || s.Update { t.Fatalf("bj-npr актуален и не должен обновляться: %+v", s) } // Запись об ядре не совпала по sha — берётся версия работающего bj-server, релиз новее. if s := state(st, "bj-server"); s.Installed != "2.0.0" || s.Available != "2.1.0" || !s.Update { t.Fatalf("ядро должно обновиться до 2.1.0: %+v", s) } // Модуль не установлен на этом сервере — не ставим. if s := state(st, "bj-quikexport"); s.Present || s.Update { t.Fatalf("неустановленный модуль не должен предлагаться: %+v", s) } // Старая версия в релизе не предлагается (без отката). m.Artifacts[1] = Artifact{Name: "bj-npr", Version: "0.9.0", SHA256: "old"} if s := state(Plan(root, m, installed, "2.0.0"), "bj-npr"); s.Update { t.Fatalf("откат на 0.9.0 не должен предлагаться: %+v", s) } }