Files
EvoBGP/internal/httpapi/legacy_node_binary_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 <[email protected]>
2026-11-02 17:23:59 +07:00

90 lines
3.0 KiB
Go

package httpapi
import (
"context"
"crypto/ed25519"
"encoding/base64"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"net/http/httptest"
"evobgp/internal/bundle"
)
// TestLegacyNodeBinaryCurrentControlPlane exercises an independently built old
// CLI against today's HTTP handlers. It never calls apply or touches host BIRD.
func TestLegacyNodeBinaryCurrentControlPlane(t *testing.T) {
binary := os.Getenv("EVOBGP_TEST_LEGACY_NODE_BIN")
if binary == "" {
t.Skip("EVOBGP_TEST_LEGACY_NODE_BIN not set")
}
binary, err := filepath.Abs(binary)
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(binary); err != nil {
t.Fatal(err)
}
srv, err := New(Options{SeedDemo: true, BundleSeedHex: testBundleSeed})
if err != nil {
t.Fatal(err)
}
defer srv.Close()
tenant, _, _, revision, speaker := srv.Store().DemoIDs()
mustSetTestAPIKeys(t, srv, "legacy-node|"+tenant+"|node,other-node|other-tenant|node")
if err := srv.Store().PublishRevisionForSpeaker(speaker, revision); err != nil {
t.Fatal(err)
}
server := httptest.NewServer(srv.Handler())
defer server.Close()
run := func(wantSuccess bool, args ...string) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
output, err := exec.CommandContext(ctx, binary, args...).CombinedOutput()
if ctx.Err() != nil {
t.Fatalf("legacy CLI timed out: %v", ctx.Err())
}
if wantSuccess && err != nil {
t.Fatalf("legacy CLI failed: %v: %s", err, output)
}
if !wantSuccess && err == nil {
t.Fatalf("legacy CLI unexpectedly accepted invalid request: %s", output)
}
}
path := filepath.Join(t.TempDir(), "current-bundle.tar.gz")
run(true, "pull-bundle", "-base-url", server.URL, "-token", "legacy-node", "-speaker-id", speaker, "-o", path)
run(true, "verify-bundle", "-f", path, "-pubkey-base64", srv.BundlePublicKeyBase64())
raw, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
pub, err := base64.StdEncoding.DecodeString(srv.BundlePublicKeyBase64())
if err != nil {
t.Fatal(err)
}
verified, err := bundle.VerifyGzippedTar(raw, ed25519.PublicKey(pub))
if err != nil || verified.Manifest.RevisionID != revision || verified.Manifest.SpeakerID != speaker {
t.Fatalf("legacy CLI fetched the wrong revision/speaker or an invalid bundle: %v", err)
}
// The archive is valid, but verification with an unrelated key must fail.
run(false, "verify-bundle", "-f", path, "-pubkey-hex", strings.Repeat("00", ed25519.PublicKeySize))
for _, token := range []string{"invalid-node", "other-node"} {
denied := filepath.Join(t.TempDir(), "denied.tar.gz")
run(false, "pull-bundle", "-base-url", server.URL, "-token", token, "-speaker-id", speaker, "-o", denied)
if _, err := os.Stat(denied); !os.IsNotExist(err) {
t.Fatalf("unauthorized CLI wrote a bundle: %v", err)
}
}
// A damaged payload must not pass the old verifier with the correct key.
if err := os.WriteFile(path, []byte("not a signed bundle"), 0o600); err != nil {
t.Fatal(err)
}
run(false, "verify-bundle", "-f", path, "-pubkey-base64", srv.BundlePublicKeyBase64())
}