package main // The free-pool gauge (pool.go): pure budget math, the cost-model config // parser, the sweep's ledger round-trip, and the admin endpoint. No test here // exercises admission control — slice 1 is a gauge and these tests pin that // (nothing refuses, nothing actuates). import ( "encoding/json" "math" "net/http" "net/http/httptest" "testing" "time" ) func near(t *testing.T, name string, got, want float64) { t.Helper() if math.Abs(got-want) > 1e-9 { t.Fatalf("%s = %v, want %v", name, got, want) } } // Easy-arithmetic cost model for the math tests. func testPoolCfg() poolConfig { return poolConfig{ maxShare: 25, revenueShare: 30, baseEURDay: 0.20, demoShare: 20, costs: poolCosts{ fixedDay: 1.0, slotDay: 0.03, reqDay: 0.01, storageGBDay: 0.001, awakeFree: 0.5, awakeDemo: 1.0, }, } } func TestParsePoolCostsDefaults(t *testing.T) { c, err := parsePoolCosts("") if err != nil { t.Fatal(err) } if c != defaultPoolCosts() { t.Fatalf("empty config should yield defaults, got %+v", c) } } func TestParsePoolCostsOverrides(t *testing.T) { c, err := parsePoolCosts("fixed_day=2.5, awake_demo=0.6") if err != nil { t.Fatal(err) } near(t, "fixedDay", c.fixedDay, 2.5) near(t, "awakeDemo", c.awakeDemo, 0.6) // Untouched keys keep their defaults. near(t, "slotDay", c.slotDay, defaultPoolCosts().slotDay) } func TestParsePoolCostsRejectsMistakes(t *testing.T) { for _, bad := range []string{"fixed=1.0", "fixed_day=abc", "slot_day=-1", "fixed_day"} { if _, err := parsePoolCosts(bad); err == nil { t.Fatalf("parsePoolCosts(%q) should error", bad) } } } // Zero revenue: the operator floor carries the pool, clamped by the X ceiling. func TestComputePoolZeroRevenue(t *testing.T) { cfg := testPoolCfg() rows := []poolRow{ {Entitlement: "free", StorageBytes: 1e9}, {Entitlement: "free", StorageBytes: 1e9}, } r := computePool(cfg, rows) // Each free space: 0.5*(0.03+0.01) + 1GB*0.001 = 0.021. near(t, "FreeCostDay", r.FreeCostDay, 0.042) near(t, "TotalCostDay", r.TotalCostDay, 1.042) // budget = min(25% x 1.042, max(0.20, 0)) = 0.20 — the floor wins at zero revenue. near(t, "BudgetDay", r.BudgetDay, 0.20) near(t, "Pressure", r.Pressure, 0.042/0.20) near(t, "DemoBudgetDay", r.DemoBudgetDay, 0.04) if r.FreeCount != 2 || r.PaidCount != 0 || r.DemoCount != 0 { t.Fatalf("counts = %d/%d/%d, want 2 free / 0 demo / 0 paid", r.FreeCount, r.DemoCount, r.PaidCount) } } // With revenue, the Y-term takes over — and the X ceiling clamps it. func TestComputePoolRevenueClampedByCeiling(t *testing.T) { cfg := testPoolCfg() rows := []poolRow{ {Entitlement: "free", StorageBytes: 1e9}, {Entitlement: "free", StorageBytes: 1e9}, {Entitlement: entPaid, Tier: "business", NetEURYear: 3650}, // €10/day net } r := computePool(cfg, rows) near(t, "NetRevenueDay", r.NetRevenueDay, 10) // Paid space still costs (0.5*0.04 = 0.02): total = 0.042+0.02+1.0 = 1.062. near(t, "TotalCostDay", r.TotalCostDay, 1.062) // revenue pool = 3.0 but the invariant cap = 25% x 1.062 = 0.2655 clamps. near(t, "BudgetDay", r.BudgetDay, 0.2655) if r.PaidCount != 1 { t.Fatalf("PaidCount = %d, want 1", r.PaidCount) } } // Demos cost at the demo awake fraction and count into free spend. func TestComputePoolDemo(t *testing.T) { cfg := testPoolCfg() r := computePool(cfg, []poolRow{{Entitlement: "demo"}}) // 1.0*(0.03+0.01) = 0.04, no storage. near(t, "FreeCostDay", r.FreeCostDay, 0.04) if r.DemoCount != 1 || r.FreeCount != 0 { t.Fatalf("counts = %d free / %d demo, want 0/1", r.FreeCount, r.DemoCount) } } func TestPoolStateThresholds(t *testing.T) { for _, tc := range []struct { pressure float64 want string }{{0, "green"}, {0.79, "green"}, {0.8, "amber"}, {0.99, "amber"}, {1.0, "red"}, {1.49, "red"}, {1.5, "deep_red"}} { if got := poolState(tc.pressure); got != tc.want { t.Fatalf("poolState(%v) = %q, want %q", tc.pressure, got, tc.want) } } } // The sweep reads the ledger, resolves net revenue from the price book, and // records a reading that round-trips through pool_meter. func TestPoolSweepRecordsReading(t *testing.T) { db := openTestCloudDB(t) free := insertSpaceRow(t, db, "free.example.com", 1000) paid := insertSpaceRow(t, db, "paid.example.com", 1000) if _, err := db.sql.Exec(`UPDATE spaces SET storage_bytes = 2000000000 WHERE id = ?`, free); err != nil { t.Fatal(err) } if _, err := db.sql.Exec(`UPDATE spaces SET entitlement = 'paid', tier = 'friends' WHERE id = ?`, paid); err != nil { t.Fatal(err) } s := &cloudServer{ db: db, pool: testPoolCfg(), billing: &stripeBilling{tiers: []billingTier{ {Key: "friends", Name: "Friends", Label: "€40/year", Price: "price_f", NetEURYear: 36.5}, }}, } s.poolSweep(time.Unix(2_000_000_000, 0)) ts, r, ok, err := db.latestPoolMeter() if err != nil || !ok { t.Fatalf("latestPoolMeter: ok=%v err=%v", ok, err) } if ts != 2_000_000_000 { t.Fatalf("ts = %d", ts) } if r.FreeCount != 1 || r.PaidCount != 1 || r.DemoCount != 0 { t.Fatalf("counts = %d/%d/%d", r.FreeCount, r.DemoCount, r.PaidCount) } near(t, "NetRevenueDay", r.NetRevenueDay, 36.5/365) // 0.1 — from the price book // free: 0.5*0.04 + 2GB*0.001 = 0.022; paid adds 0.02; fixed 1.0. near(t, "FreeCostDay", r.FreeCostDay, 0.022) near(t, "TotalCostDay", r.TotalCostDay, 1.042) if r.Pressure <= 0 { t.Fatalf("pressure = %v, want > 0", r.Pressure) } } // The gauge rides the meter sweep when enabled — and stays silent when off. func TestMeterOnceRunsPoolSweep(t *testing.T) { db := openTestCloudDB(t) insertSpaceRow(t, db, "a.example.com", 1000) sock := fakeControl(t, func(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{ "members": 1, "setup_complete": true, "has_content": true, "storage_bytes": 1000, }) }) s := &cloudServer{db: db, control: controlClient(sock), controlToken: "t", pool: testPoolCfg()} s.meterOnce() if _, _, ok, _ := db.latestPoolMeter(); !ok { t.Fatal("meterOnce with the pool enabled should record a gauge reading") } // Pool off (the shipped default): same sweep, no reading. db2 := openTestCloudDB(t) insertSpaceRow(t, db2, "b.example.com", 1000) s2 := &cloudServer{db: db2, control: controlClient(sock), controlToken: "t"} s2.meterOnce() if _, _, ok, _ := db2.latestPoolMeter(); ok { t.Fatal("meterOnce with the pool off must not record a reading") } } func poolGET(t *testing.T, url, token string) (*http.Response, map[string]any) { t.Helper() req, err := http.NewRequest("GET", url+"/api/pool", nil) if err != nil { t.Fatal(err) } if token != "" { req.Header.Set("Authorization", "Bearer "+token) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() var body map[string]any _ = json.NewDecoder(resp.Body).Decode(&body) return resp, body } func TestPoolHandler(t *testing.T) { db := openTestCloudDB(t) s := &cloudServer{db: db, adminHash: hashToken("admin-secret"), pool: testPoolCfg()} srv := httptest.NewServer(s.handler()) defer srv.Close() // Admin-gated like the token surface. if resp, _ := poolGET(t, srv.URL, ""); resp.StatusCode != http.StatusUnauthorized { t.Fatalf("no token: HTTP %d, want 401", resp.StatusCode) } if resp, _ := poolGET(t, srv.URL, "wrong"); resp.StatusCode != http.StatusUnauthorized { t.Fatalf("bad token: HTTP %d, want 401", resp.StatusCode) } // Enabled but never swept. resp, body := poolGET(t, srv.URL, "admin-secret") if resp.StatusCode != http.StatusOK || body["enabled"] != true || body["measured"] != false { t.Fatalf("pre-sweep: HTTP %d body %v", resp.StatusCode, body) } // After a sweep: the reading, labelled an estimate. insertSpaceRow(t, db, "c.example.com", 1000) s.poolSweep(time.Unix(2_000_000_000, 0)) resp, body = poolGET(t, srv.URL, "admin-secret") if resp.StatusCode != http.StatusOK || body["measured"] != true || body["estimate"] != true { t.Fatalf("post-sweep: HTTP %d body %v", resp.StatusCode, body) } if body["state"] != "green" { t.Fatalf("state = %v, want green (one tiny free space)", body["state"]) } if _, ok := body["pressure"].(float64); !ok { t.Fatalf("pressure missing: %v", body) } // Pool off: the endpoint says so instead of 404ing (status pages consume this). sOff := &cloudServer{db: db, adminHash: hashToken("admin-secret")} srvOff := httptest.NewServer(sOff.handler()) defer srvOff.Close() resp, body = poolGET(t, srvOff.URL, "admin-secret") if resp.StatusCode != http.StatusOK || body["enabled"] != false { t.Fatalf("pool off: HTTP %d body %v", resp.StatusCode, body) } } // ---- slice 2: admission control --------------------------------------------- // seedPressure plants a gauge reading with a chosen pressure. func seedPressure(t *testing.T, db *cloudDB, pressure float64) { t.Helper() if err := db.insertPoolMeter(1_999_000_000, poolReading{BudgetDay: 1, FreeCostDay: pressure, Pressure: pressure}); err != nil { t.Fatal(err) } } func TestSmoothRevenue(t *testing.T) { near(t, "no history", smoothRevenue(10, nil), 10) near(t, "averaged", smoothRevenue(0, []float64{10, 10, 10}), 7.5) } func TestPoolAdmissionRefusesAtRed(t *testing.T) { srv, s := newTestCloud(t, okControl(t)) s.pool = testPoolCfg() s.demoTTL, s.demoMaxLive = 24*time.Hour, 10 token := mintCaller(t, srv, "home") seedPressure(t, s.db, 1.3) resp := createKind(t, srv, token, "alice", "") if resp.StatusCode != http.StatusServiceUnavailable { t.Fatalf("free create at red: HTTP %d, want 503", resp.StatusCode) } body := decodeBody(t, resp) if body["error"] != "free_pool_full" { t.Fatalf("error = %v", body["error"]) } if msg, _ := body["message"].(string); msg != freePoolFullMsg { t.Fatalf("message = %q", msg) } // Demos stay available at red (the carve-out keeps the try-it path alive). if resp := createKind(t, srv, token, "alice", "demo"); resp.StatusCode != http.StatusCreated { t.Fatalf("demo at red: HTTP %d, want 201", resp.StatusCode) } // The refusal held no quota slot: at green the same requester creates fine. seedPressure(t, s.db, 0.2) if resp := createKind(t, srv, token, "alice", ""); resp.StatusCode != http.StatusCreated { t.Fatalf("free create at green: HTTP %d, want 201", resp.StatusCode) } } func TestPoolAdmissionFailsOpen(t *testing.T) { // Dry-run: red pressure refuses nothing. srv, s := newTestCloud(t, okControl(t)) s.pool = testPoolCfg() s.pool.dryRun = true token := mintCaller(t, srv, "home") seedPressure(t, s.db, 2.0) if resp := createKind(t, srv, token, "bob", ""); resp.StatusCode != http.StatusCreated { t.Fatalf("dry-run at red: HTTP %d, want 201", resp.StatusCode) } // No reading yet: never refuse. srv2, s2 := newTestCloud(t, okControl(t)) s2.pool = testPoolCfg() token2 := mintCaller(t, srv2, "home") if resp := createKind(t, srv2, token2, "carol", ""); resp.StatusCode != http.StatusCreated { t.Fatalf("no reading: HTTP %d, want 201", resp.StatusCode) } // Pool off: a stale red reading in the table gates nothing. srv3, s3 := newTestCloud(t, okControl(t)) token3 := mintCaller(t, srv3, "home") seedPressure(t, s3.db, 5.0) if resp := createKind(t, srv3, token3, "dave", ""); resp.StatusCode != http.StatusCreated { t.Fatalf("pool off: HTTP %d, want 201", resp.StatusCode) } } // The sweep's budget uses smoothed revenue: with fat historical readings and // zero current revenue, the recorded budget sits between floor and history. func TestPoolSweepSmoothsRevenue(t *testing.T) { db := openTestCloudDB(t) insertSpaceRow(t, db, "a.example.com", 1000) // Three past sweeps at €10/day revenue each. for i, ts := range []int64{1_990_000_000, 1_990_100_000, 1_990_200_000} { _ = i if err := db.insertPoolMeter(ts, poolReading{NetRevenueDay: 10}); err != nil { t.Fatal(err) } } s := &cloudServer{db: db, pool: testPoolCfg()} s.poolSweep(time.Unix(1_990_300_000, 0)) _, r, ok, err := db.latestPoolMeter() if err != nil || !ok { t.Fatalf("latestPoolMeter: ok=%v err=%v", ok, err) } // Instantaneous revenue is 0 (no paid rows) and is what the row stores... near(t, "NetRevenueDay", r.NetRevenueDay, 0) // ...but the budget was derived from smoothed (0+10+10+10)/4 = 7.5€/day: // revenue pool = 30% x 7.5 = 2.25, clamped by the X ceiling (25% of total). wantBudget := math.Min(0.25*r.TotalCostDay, 2.25) near(t, "BudgetDay", r.BudgetDay, wantBudget) }