17d8ec3988
- release: пакет обновления (ExtractBundle/ReadStagedManifest/WriteBundle), Artifact.MinCore, план блокирует модуль, которому нужно ядро новее, чем будет после установки - bj-release: модули получают min_core = версия релиза - настройки → Обновления: «Обновить из файла» (zip до 2 ГБ), фон как у сетевого обновления, причина блокировки в таблице компонентов Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
108 lines
4.1 KiB
Go
108 lines
4.1 KiB
Go
package release
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Пакет обновления: сборка → распаковка → подпись → план в порядке «ядро, потом модули»;
|
|
// модуль, требующий более новое ядро, чем будет после установки, помечается заблокированным.
|
|
func TestBundleRoundTripAndPlanOrder(t *testing.T) {
|
|
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
|
rel := t.TempDir()
|
|
write := func(name, body string) Artifact {
|
|
p := filepath.Join(rel, name)
|
|
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sha, size, _ := HashFile(p)
|
|
return Artifact{Name: name, File: name, Version: "2.0.0", SHA256: sha, Size: size, Exec: true}
|
|
}
|
|
core := write("bj-server", "ядро 2.0.0")
|
|
lk := write("bj-lk", "кабинет 2.0.0")
|
|
lk.MinCore = "2.0.0"
|
|
npr := write("bj-npr", "нпр 2.0.0")
|
|
npr.MinCore = "2.5.0" // собран под ядро новее этого релиза — ставить нельзя
|
|
m := &Manifest{Schema: CurrentSchema, Version: "2.0.0", Channel: "stable", Artifacts: []Artifact{lk, npr, core}}
|
|
sm, err := Sign(m, priv, "k1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, _ := json.Marshal(sm)
|
|
if err := os.WriteFile(filepath.Join(rel, BundleManifestName), raw, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := WriteBundle(&buf, rel, func(a Artifact) bool { return a.Name != "bj-npr" }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
zipPath := filepath.Join(t.TempDir(), "bundle.zip")
|
|
if err := os.WriteFile(zipPath, buf.Bytes(), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
staging := filepath.Join(t.TempDir(), "staging")
|
|
names, err := ExtractBundle(zipPath, staging)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Join(names, ",") != "manifest.json,bj-lk,bj-server" {
|
|
t.Fatalf("распаковано: %v", names)
|
|
}
|
|
if _, _, err := ReadStagedManifest(staging, pub); err != nil {
|
|
t.Fatalf("подпись: %v", err)
|
|
}
|
|
other, _, _ := ed25519.GenerateKey(rand.Reader)
|
|
if _, _, err := ReadStagedManifest(staging, other); err == nil {
|
|
t.Fatal("манифест принят с чужим ключом")
|
|
}
|
|
|
|
// План на сервере с ядром 1.9.0 и кабинетом 1.9.0
|
|
root := t.TempDir()
|
|
for _, c := range []string{"bj-server", "bj-lk", "bj-npr"} {
|
|
comp, _ := ComponentByName(c)
|
|
_ = os.MkdirAll(filepath.Dir(comp.PathAt(root)), 0o755)
|
|
_ = os.WriteFile(comp.PathAt(root), []byte("старый "+c), 0o755)
|
|
}
|
|
plan := Plan(root, m, map[string]Installed{}, "1.9.0")
|
|
var order []string
|
|
byName := map[string]ComponentState{}
|
|
for _, c := range plan {
|
|
byName[c.Name] = c
|
|
if c.Update {
|
|
order = append(order, c.Name)
|
|
}
|
|
}
|
|
if strings.Join(order, ",") != "bj-server,bj-lk" {
|
|
t.Fatalf("порядок установки: %v (ядро должно быть первым, npr заблокирован)", order)
|
|
}
|
|
if b := byName["bj-npr"]; b.Update || !strings.Contains(b.Blocked, "2.5.0") {
|
|
t.Fatalf("модуль с требованием к ядру не заблокирован: %+v", b)
|
|
}
|
|
// Без ядра в пакете кабинет 2.0.0 на ядро 1.9.0 не встаёт
|
|
m2 := &Manifest{Schema: CurrentSchema, Version: "2.0.0", Artifacts: []Artifact{lk}}
|
|
if b := Plan(root, m2, map[string]Installed{}, "1.9.0"); !strings.Contains(findState(b, "bj-lk").Blocked, "сначала обновите ядро") {
|
|
t.Fatalf("кабинет без нового ядра должен быть заблокирован: %+v", findState(b, "bj-lk"))
|
|
}
|
|
// Не zip — понятная ошибка
|
|
bad := filepath.Join(t.TempDir(), "bad.zip")
|
|
_ = os.WriteFile(bad, []byte("не архив"), 0o644)
|
|
if _, err := ExtractBundle(bad, filepath.Join(t.TempDir(), "s")); err == nil || !strings.Contains(err.Error(), "zip") {
|
|
t.Fatalf("не-zip принят: %v", err)
|
|
}
|
|
}
|
|
|
|
func findState(list []ComponentState, name string) ComponentState {
|
|
for _, c := range list {
|
|
if c.Name == name {
|
|
return c
|
|
}
|
|
}
|
|
return ComponentState{}
|
|
}
|