| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "io" |
| 6 | "math" |
| 7 | "net/http" |
| 8 | "testing" |
| 9 | |
| 10 | "github.com/carlosframework/platform/internal/economics" |
| 11 | "github.com/carlosframework/platform/internal/usage" |
| 12 | ) |
| 13 | |
| 14 | // The report is the hop subset priced at the rate card's egress rate and |
| 15 | // stated as absorbed; share is against egress; accounts sort by backhaul |
| 16 | // desc, sqid asc on ties; no egress means no share, never a division. |
| 17 | func TestBackhaulReportMaths(t *testing.T) { |
| 18 | v := &EconomicsView{ |
| 19 | Period: "mtd", CoveredDays: 3, PeriodDays: 10, |
| 20 | Scenario: economics.Scenario{RateCard: economics.RateCard{EgressGBUSD: 0.09, InterRegionGBUSD: 0.02}}, |
| 21 | Names: map[string]string{"bab": "ops"}, |
| 22 | Meters: map[string]map[string]float64{ |
| 23 | "bab": {usage.MeterEgressGB: 10, usage.MeterBackhaulGB: 2.5, usage.MeterBackhaulRequests1k: 7}, |
| 24 | "bae": {usage.MeterEgressGB: 4, usage.MeterBackhaulGB: 2.5}, |
| 25 | "bad": {usage.MeterEgressGB: 0}, |
| 26 | }, |
| 27 | } |
| 28 | rep := buildBackhaulReport(v) |
| 29 | if rep.EgressGB != 14 || rep.BackhaulGB != 5 || rep.BackhaulReq1k != 7 { |
| 30 | t.Fatalf("totals = %+v", rep) |
| 31 | } |
| 32 | if math.Abs(rep.ShareOfEgress-5.0/14) > 1e-9 || math.Abs(rep.AbsorbedUSD-0.10) > 1e-9 || rep.InterRegionGBUSD != 0.02 { |
| 33 | t.Fatalf("share/absorbed = %v/%v", rep.ShareOfEgress, rep.AbsorbedUSD) |
| 34 | } |
| 35 | // bad has egress but no backhaul: counted in the totals, no row. |
| 36 | if len(rep.Accounts) != 2 || rep.Accounts[0].Account != "bab" || rep.Accounts[1].Account != "bae" { |
| 37 | t.Fatalf("order = %+v", rep.Accounts) |
| 38 | } |
| 39 | if rep.Accounts[0].Name != "ops" || math.Abs(rep.Accounts[0].ShareOfEgress-0.25) > 1e-9 || math.Abs(rep.Accounts[1].ShareOfEgress-0.625) > 1e-9 || math.Abs(rep.Accounts[0].AbsorbedUSD-0.05) > 1e-9 { |
| 40 | t.Fatalf("accounts = %+v", rep.Accounts) |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | func getBearer(t *testing.T, u, bearer string) (int, []byte) { |
| 45 | t.Helper() |
| 46 | req, err := http.NewRequest(http.MethodGet, u, nil) |
| 47 | if err != nil { |
| 48 | t.Fatal(err) |
| 49 | } |
| 50 | if bearer != "" { |
| 51 | req.Header.Set("Authorization", "Bearer "+bearer) |
| 52 | } |
| 53 | resp, err := http.DefaultClient.Do(req) |
| 54 | if err != nil { |
| 55 | t.Fatal(err) |
| 56 | } |
| 57 | defer resp.Body.Close() |
| 58 | body, _ := io.ReadAll(resp.Body) |
| 59 | return resp.StatusCode, body |
| 60 | } |
| 61 | |
| 62 | // Operator only, with the bill door's posture: anyone else sees the same |
| 63 | // bare 404 an unknown route gets. |
| 64 | func TestAPIEconomicsBackhaulGate(t *testing.T) { |
| 65 | fx := newConsoleFixture(t) |
| 66 | token := cliToken(t, fx, "paul@keymail.dev", "code-apibackhaul") |
| 67 | ghostStatus, ghostBody := getBearer(t, fx.ts.URL+"/this-api-route-does-not-exist", token) |
| 68 | // No token: the CLI door's own 401 with its login hint (cliUser), as |
| 69 | // every /api door answers; a token without the operator bit gets the |
| 70 | // ghost 404 — the bit itself must not be probeable. |
| 71 | if status, _ := getBearer(t, fx.ts.URL+"/api/economics/backhaul", ""); status != http.StatusUnauthorized { |
| 72 | t.Fatalf("unauthenticated = %d; want 401", status) |
| 73 | } |
| 74 | if status, body := getBearer(t, fx.ts.URL+"/api/economics/backhaul", token); status != ghostStatus || string(body) != string(ghostBody) { |
| 75 | t.Fatalf("non-operator = %d %q; want the ghost 404", status, body) |
| 76 | } |
| 77 | if err := fx.st.SetOperator("paul@keymail.dev", true); err != nil { |
| 78 | t.Fatal(err) |
| 79 | } |
| 80 | status, body := getBearer(t, fx.ts.URL+"/api/economics/backhaul?period=mtd", token) |
| 81 | if status != http.StatusOK { |
| 82 | t.Fatalf("operator = %d %q", status, body) |
| 83 | } |
| 84 | var rep backhaulReport |
| 85 | if err := json.Unmarshal(body, &rep); err != nil { |
| 86 | t.Fatalf("unreadable: %v: %s", err, body) |
| 87 | } |
| 88 | if rep.Period != "mtd" || rep.Accounts == nil { |
| 89 | t.Fatalf("report = %+v", rep) |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | // The whole "priced nowhere" claim in one assertion: an account prices |
| 94 | // identically with and without the backhaul meters present. |
| 95 | func TestBackhaulMetersNeverPriceAnAccount(t *testing.T) { |
| 96 | sc := economics.DefaultScenario() |
| 97 | base := map[string]float64{usage.MeterEgressGB: 40, usage.MeterRequests1k: 900, usage.MeterMemGBHours: 800, usage.MeterAppMonths: 3} |
| 98 | with := map[string]float64{} |
| 99 | for k, v := range base { |
| 100 | with[k] = v |
| 101 | } |
| 102 | with[usage.MeterBackhaulGB] = 25 |
| 103 | with[usage.MeterBackhaulRequests1k] = 500 |
| 104 | a := economics.PriceAccount(sc, "bab", base, 12.5) |
| 105 | b := economics.PriceAccount(sc, "bab", with, 12.5) |
| 106 | if a != b { |
| 107 | t.Fatalf("backhaul meters changed an account's price:\n%+v\n%+v", a, b) |
| 108 | } |
| 109 | // And with nothing else metered: the meters must not even count as |
| 110 | // usage (FreeTierPos), the one generic meter walk PriceAccount has. |
| 111 | none := economics.PriceAccount(sc, "bab", map[string]float64{}, 0) |
| 112 | only := economics.PriceAccount(sc, "bab", map[string]float64{usage.MeterBackhaulGB: 25, usage.MeterBackhaulRequests1k: 500}, 0) |
| 113 | if none != only { |
| 114 | t.Fatalf("backhaul-only meters changed an otherwise idle account:\n%+v\n%+v", none, only) |
| 115 | } |
| 116 | } |
| 117 | |