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

99 lines
3.1 KiB
Go

package httpapi
import (
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"evobgp/internal/bundle"
)
func TestLegacyCompatibilityNodePullAndAuth(t *testing.T) {
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,operator|"+tenant+"|operator,other-node|other-tenant|node")
if err := srv.Store().PublishRevisionForSpeaker(speaker, revision); err != nil {
t.Fatal(err)
}
handler := srv.Handler()
call := func(path, token string) (int, []byte, string) {
t.Helper()
req, err := http.NewRequest(http.MethodGet, path, nil)
if err != nil {
t.Fatal(err)
}
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
resp := w.Result()
defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return resp.StatusCode, body, resp.Header.Get("Content-Type")
}
latestPath := "/v1/speakers/" + speaker + "/revisions/latest"
bundlePath := "/v1/speakers/" + speaker + "/bundle/" + revision
for _, path := range []string{latestPath, bundlePath} {
for _, tc := range []struct {
token string
want int
}{{"", http.StatusUnauthorized}, {"operator", http.StatusForbidden}, {"other-node", http.StatusForbidden}} {
status, _, _ := call(path, tc.token)
if status != tc.want {
t.Fatalf("%s token %q: status %d, want %d", path, tc.token, status, tc.want)
}
}
}
status, raw, _ := call(latestPath, "legacy-node")
if status != http.StatusOK {
t.Fatalf("legacy latest: status %d: %s", status, raw)
}
var latest struct {
RevisionID string `json:"revision_id"`
PublishedAt string `json:"published_at"`
}
if err := json.Unmarshal(raw, &latest); err != nil {
t.Fatal(err)
}
if latest.RevisionID != revision || latest.PublishedAt == "" {
t.Fatalf("legacy latest response: %+v", latest)
}
status, raw, contentType := call("/v1/bundle/signing-public-key", "operator")
if status != http.StatusOK || contentType != "application/json" {
t.Fatalf("public key status %d, type %q: %s", status, contentType, raw)
}
var key struct {
PublicKeyBase64 string `json:"public_key_base64"`
}
if err := json.Unmarshal(raw, &key); err != nil {
t.Fatal(err)
}
pub, err := base64.StdEncoding.DecodeString(key.PublicKeyBase64)
if err != nil || len(pub) != ed25519.PublicKeySize {
t.Fatalf("legacy public key: len=%d err=%v", len(pub), err)
}
status, raw, contentType = call(bundlePath, "legacy-node")
if status != http.StatusOK || contentType != "application/gzip" {
t.Fatalf("legacy bundle: status %d, type %q: %s", status, contentType, raw)
}
verified, err := bundle.VerifyGzippedTar(raw, ed25519.PublicKey(pub))
if err != nil {
t.Fatalf("legacy signed bundle verification: %v", err)
}
if verified.Manifest.RevisionID != revision || verified.Manifest.SpeakerID != speaker || len(verified.Files) == 0 {
t.Fatalf("legacy bundle manifest: %+v", verified.Manifest)
}
}