fix(hub): slice-3 follow-ups — /host-report 413 oversize + contract golden (v0.7.1)
- handleHostReport: read maxHostReportBytes+1 (4 MiB const) and reject oversize with 413 instead of silent LimitReader truncation. Controller handleReport (1 MiB) is unchanged. Test asserts 413. - contract: hub/internal/api/testdata/host-report.golden.json (byte-identical with felhom-agent's copy) + TestHostReport_GoldenContract drives the real handler and asserts 200 + denorm + both guests upserted. - CHANGELOG v0.7.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -152,8 +153,8 @@ func TestHandleHostReport_OversizeRejected(t *testing.T) {
|
||||
st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "HKEY"})
|
||||
big := `{"host_id":"h1","guests":[{"vmid":1,"name":"` + strings.Repeat("a", 5<<20) + `"}]}`
|
||||
rr := do(h, http.MethodPost, "/host-report", "HKEY", big)
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("oversize body status = %d, want 400", rr.Code)
|
||||
if rr.Code != http.StatusRequestEntityTooLarge {
|
||||
t.Errorf("oversize body status = %d, want 413", rr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,3 +189,44 @@ func TestAdminCreateHost(t *testing.T) {
|
||||
t.Errorf("round-trip host-report with minted key = %d, body=%s", rr2.Code, rr2.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostReport_GoldenContract drives the real handler with the shared golden
|
||||
// host-report and proves hostReportPayload still extracts what it needs from the
|
||||
// real wire shape (denorm + guest upsert).
|
||||
//
|
||||
// testdata/host-report.golden.json MUST be kept byte-identical with felhom-agent's
|
||||
// internal/hub/testdata/host-report.golden.json — it is a duplicated contract until
|
||||
// a shared types module exists (revisit when slices 5/6 add real fields).
|
||||
func TestHostReport_GoldenContract(t *testing.T) {
|
||||
h, st, dbPath := newTestHandler(t)
|
||||
st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "c1", APIKey: "ckey", RetrievalPassword: "p"})
|
||||
st.UpsertHost(&store.Host{HostID: "demo-host-01", CustomerID: "c1", APIKey: "HKEY"})
|
||||
|
||||
golden, err := os.ReadFile("testdata/host-report.golden.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rr := do(h, http.MethodPost, "/host-report", "HKEY", string(golden))
|
||||
if rr.Code != 200 {
|
||||
t.Fatalf("golden report status = %d, body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
|
||||
db, _ := sql.Open("sqlite", dbPath)
|
||||
defer db.Close()
|
||||
|
||||
var total, running int
|
||||
var cf string
|
||||
if err := db.QueryRow(`SELECT guest_total, guest_running, cloudflared_status FROM host_reports WHERE host_id='demo-host-01' ORDER BY id DESC LIMIT 1`).
|
||||
Scan(&total, &running, &cf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if total != 2 || running != 1 || cf != "active" {
|
||||
t.Errorf("denorm total=%d running=%d cloudflared=%q (want 2,1,active)", total, running, cf)
|
||||
}
|
||||
|
||||
var guestCount int
|
||||
db.QueryRow(`SELECT COUNT(*) FROM guests WHERE host_id='demo-host-01'`).Scan(&guestCount)
|
||||
if guestCount != 2 {
|
||||
t.Errorf("guests upserted = %d, want 2", guestCount)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user