Files
EvoBGP/internal/bundle/legacy_compatibility_test.go
DenozordecandCursor f8e0520817 feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 17:23:59 +07:00

121 lines
3.6 KiB
Go

package bundle
import (
"archive/tar"
"bytes"
"compress/gzip"
"crypto/ed25519"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"io"
"os"
"reflect"
"sort"
"testing"
)
// legacyManifestV1 mirrors the decoder fields present in the locally available
// v1.0.0 source. It deliberately has no future capability or deployment fields.
type legacyManifestV1 struct {
RevisionID string `json:"revision_id"`
SpeakerID string `json:"speaker_id"`
CreatedAt string `json:"created_at"`
Files []struct {
Path string `json:"path"`
SHA256 string `json:"sha256"`
} `json:"files"`
Algorithm string `json:"signature_algorithm"`
PublicKeyBase64 string `json:"public_key_base64"`
}
func TestLegacyCompatibilityFrozenSignedCurrentBaseline(t *testing.T) {
raw, err := os.ReadFile("testdata/current_baseline_signed_bundle.tar.gz")
if err != nil {
t.Fatal(err)
}
pub := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{0x42}, ed25519.SeedSize)).Public().(ed25519.PublicKey)
verified, err := VerifyGzippedTar(raw, pub)
if err != nil {
t.Fatalf("frozen signed baseline rejected: %v", err)
}
if verified.Manifest.RevisionID != "revision-legacy" || verified.Manifest.SpeakerID != "speaker-legacy" || len(verified.Files) != 2 {
t.Fatalf("frozen baseline contents: %+v", verified.Manifest)
}
}
func TestLegacyCompatibilityBundleWire(t *testing.T) {
seed := bytes.Repeat([]byte{0x42}, ed25519.SeedSize)
priv := ed25519.NewKeyFromSeed(seed)
pub := priv.Public().(ed25519.PublicKey)
fragments := map[string]string{
"bird.conf": "router id 192.0.2.1;\n",
"bird.d/evobgp_prefixes_v4.conf": "# revision\n",
"_preview": "must not ship",
}
raw, err := BuildGzippedTar("revision-legacy", "speaker-legacy", fragments, priv)
if err != nil {
t.Fatal(err)
}
gr, err := gzip.NewReader(bytes.NewReader(raw))
if err != nil {
t.Fatal(err)
}
defer func() { _ = gr.Close() }()
tr := tar.NewReader(gr)
entries := map[string][]byte{}
var order []string
for {
h, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
body, err := io.ReadAll(tr)
if err != nil {
t.Fatal(err)
}
order = append(order, h.Name)
entries[h.Name] = body
}
wantOrder := []string{"manifest.json", "bundle.sig", "bird.conf", "bird.d/evobgp_prefixes_v4.conf"}
if !reflect.DeepEqual(order, wantOrder) {
t.Fatalf("legacy tar layout: got %v, want %v", order, wantOrder)
}
var top map[string]json.RawMessage
if err := json.Unmarshal(entries["manifest.json"], &top); err != nil {
t.Fatal(err)
}
wantFields := []string{"created_at", "files", "public_key_base64", "revision_id", "signature_algorithm", "speaker_id"}
fields := make([]string, 0, len(top))
for k := range top {
fields = append(fields, k)
}
sort.Strings(fields)
if !reflect.DeepEqual(fields, wantFields) {
t.Fatalf("manifest fields: got %v, want %v", fields, wantFields)
}
var m legacyManifestV1
if err := json.Unmarshal(entries["manifest.json"], &m); err != nil {
t.Fatal(err)
}
if m.RevisionID != "revision-legacy" || m.SpeakerID != "speaker-legacy" || m.CreatedAt == "" || m.Algorithm != "ed25519" || m.PublicKeyBase64 != base64.StdEncoding.EncodeToString(pub) {
t.Fatalf("legacy manifest fields: %+v", m)
}
if !ed25519.Verify(pub, entries["manifest.json"], entries["bundle.sig"]) {
t.Fatal("legacy Ed25519 verifier rejected current signed manifest")
}
if len(m.Files) != 2 {
t.Fatalf("legacy manifest file count: %d", len(m.Files))
}
for _, f := range m.Files {
sum := sha256.Sum256(entries[f.Path])
if hex.EncodeToString(sum[:]) != f.SHA256 {
t.Fatalf("legacy SHA-256 mismatch for %q", f.Path)
}
}
}