Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <[email protected]>
99 lines
3.1 KiB
Go
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)
|
|
}
|
|
}
|