hub v0.7.2: ingest agent storage_targets (slice 5 Phase A)
Accept + persist the now-populated host-report storage_targets. Minimal — the authoritative storage manifest is hub-owned (slice 10); this mirrors what the agent observes. - hostReportPayload.StorageTargets: full mirror of the agent's hub.StorageTarget wire contract; persisted verbatim in report_json (no schema change); count + WARN on disconnected targets. - shared host-report golden updated with two populated targets; byte-identical with felhom-agent's copy. - TestHostStorageTarget_GoldenContract: hub half of the bidirectional key-set test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -229,4 +231,78 @@ func TestHostReport_GoldenContract(t *testing.T) {
|
||||
if guestCount != 2 {
|
||||
t.Errorf("guests upserted = %d, want 2", guestCount)
|
||||
}
|
||||
|
||||
// storage_targets (slice 5) must be persisted verbatim in report_json.
|
||||
var reportJSON string
|
||||
if err := db.QueryRow(`SELECT report_json FROM host_reports WHERE host_id='demo-host-01' ORDER BY id DESC LIMIT 1`).
|
||||
Scan(&reportJSON); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var parsed hostReportPayload
|
||||
if err := json.Unmarshal([]byte(reportJSON), &parsed); err != nil {
|
||||
t.Fatalf("persisted report_json does not parse: %v", err)
|
||||
}
|
||||
if len(parsed.StorageTargets) != 2 {
|
||||
t.Errorf("persisted storage_targets = %d, want 2", len(parsed.StorageTargets))
|
||||
}
|
||||
if parsed.StorageTargets[0].Type != "lvmthin" || parsed.StorageTargets[0].ThinPool == nil {
|
||||
t.Errorf("storage_targets[0] = %+v, want lvmthin with thin_pool", parsed.StorageTargets[0])
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostStorageTarget_GoldenContract asserts the hub's hostStorageTarget mirror covers
|
||||
// the golden's storage_targets[0] field-for-field (the bidirectional key-set test, the hub
|
||||
// half of the cross-repo contract). It round-trips the golden element through the mirror
|
||||
// struct and requires the re-marshaled key set to match exactly — neither missing a field
|
||||
// the agent sends nor inventing one it doesn't.
|
||||
func TestHostStorageTarget_GoldenContract(t *testing.T) {
|
||||
raw, err := os.ReadFile("testdata/host-report.golden.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var golden struct {
|
||||
StorageTargets []json.RawMessage `json:"storage_targets"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &golden); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(golden.StorageTargets) == 0 {
|
||||
t.Fatal("golden has no storage_targets to check")
|
||||
}
|
||||
|
||||
var goldenKeys map[string]any
|
||||
json.Unmarshal(golden.StorageTargets[0], &goldenKeys)
|
||||
|
||||
var mirror hostStorageTarget
|
||||
if err := json.Unmarshal(golden.StorageTargets[0], &mirror); err != nil {
|
||||
t.Fatalf("golden storage target does not parse into the mirror: %v", err)
|
||||
}
|
||||
b, _ := json.Marshal(mirror)
|
||||
var mirrorKeys map[string]any
|
||||
json.Unmarshal(b, &mirrorKeys)
|
||||
|
||||
assertSameStorageKeys(t, "storage_targets[0]", goldenKeys, mirrorKeys)
|
||||
assertSameStorageKeys(t, "storage_targets[0].smart", goldenKeys["smart"], mirrorKeys["smart"])
|
||||
assertSameStorageKeys(t, "storage_targets[0].thin_pool", goldenKeys["thin_pool"], mirrorKeys["thin_pool"])
|
||||
}
|
||||
|
||||
func assertSameStorageKeys(t *testing.T, where string, a, b any) {
|
||||
t.Helper()
|
||||
ka, kb := sortedKeys(a), sortedKeys(b)
|
||||
if !reflect.DeepEqual(ka, kb) {
|
||||
t.Errorf("contract drift at %s:\n golden keys = %v\n mirror keys = %v", where, ka, kb)
|
||||
}
|
||||
}
|
||||
|
||||
func sortedKeys(v any) []string {
|
||||
m, ok := v.(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user