carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "math"
6 "path/filepath"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/carlosframework/platform/internal/economics"
12 "github.com/carlosframework/platform/internal/metrics"
13 "github.com/carlosframework/platform/internal/registry"
14 "github.com/carlosframework/platform/internal/usage"
15)
16
17// TestLoadEconomicsGolden reuses TestStatementsPassGolden's golden objects
18// (economicspass_test.go) — one box, one route, cpu 0.5/mem 1e9 avg over 60
19// samples in one hour, 2GB/4000req edge traffic for the same route's
20// account — plus a named scenario ("test") set active, and asserts
21// LoadEconomics prices it: the active scenario loads by name, the account
22// shows up with real cost, box1's allocation reconstructs its own cost via
23// the very same economics.AllocateBox call used for the golden fixture, and
24// the embedded payload names its "meters" field for Task 13's inline JS.
25//
26// Dates are relative to the real wall clock (today), not a fixed 2026-08
27// date, because LoadEconomics has no `now` parameter — it reads real time —
28// mirroring how metricsview_test.go seeds LoadMetrics fixtures.
29func TestLoadEconomicsGolden(t *testing.T) {
30 fx := newConsoleFixture(t)
31 ctx := context.Background()
32
33 reg, err := registry.Open(fx.regPath)
34 if err != nil {
35 t.Fatal(err)
36 }
37 if err := reg.Upsert(registry.Route{Host: "a.example.com", Kind: "instance",
38 Status: "ready", App: fixtureApp, Version: "v1"}); err != nil {
39 t.Fatal(err)
40 }
41
42 date := usage.DayOf(time.Now())
43 cpu, mem := 0.5, 1e9
44 roll := metrics.DayRollup{V: 1, Box: "box1", Date: date, Hours: map[string]metrics.HourAgg{
45 "09": {Units: map[string]map[string]metrics.Agg{
46 "carlos-app@a.example.com.service": {
47 "cpu": {Avg: cpu, N: 60},
48 "mem_bytes": {Avg: mem, N: 60},
49 },
50 }},
51 }}
52 putStoreJSON(t, fx.rel, metrics.DayKey("box1", date), roll)
53
54 putStoreJSON(t, fx.rel, usage.EdgeDayKey("box1", date), usage.EdgeDay{
55 V: 1, Box: "box1", Date: date,
56 Hours: map[string]map[string]usage.EdgeRow{"09": {
57 "a.example.com": {Account: fixtureAcct, App: fixtureApp, Requests: 4000, BytesOut: 2e9},
58 }},
59 })
60
61 // One day of storage (31 GB) and census (2 apps) for today, to prove
62 // buildEconomicsView folds MeterStorageGBMonth/MeterAppMonths in — the
63 // MAJOR fix-round finding: the view path used to call sweepUsage alone
64 // and never see these meters at all. Expected contribution uses the
65 // controller's denominator ruling: divide by days in THIS day's own
66 // calendar month, computed the same way production code does, so the
67 // assertion holds regardless of which real month the test runs in.
68 now := time.Now().UTC()
69 daysInMonth := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.UTC).Day()
70 putStoreJSON(t, fx.rel, usage.StorageDayKey(date), usage.StorageDay{
71 V: 1, Date: date, Apps: map[string]usage.AppStorage{
72 fixtureAcct + "/" + fixtureApp: {BucketBytes: 31 * 1e9},
73 },
74 })
75 putStoreJSON(t, fx.rel, usage.CensusDayKey(date), usage.CensusDay{
76 V: 1, Date: date, Accounts: map[string]usage.CensusAccount{
77 fixtureAcct: {Apps: 2, Routes: 3},
78 },
79 })
80
81 sc := economics.DefaultScenario()
82 sc.Name = "test"
83 putStoreJSON(t, fx.rel, economics.ScenarioKey("test"), sc)
84 putStoreJSON(t, fx.rel, economics.ActiveKey, economics.Active{Schema: 1, Name: "test"})
85
86 // Task 5 (tier-aware revenue): an untiered account is pool-tier by
87 // default and prices at RevenueUSD=0 — this test's revenue assertions
88 // need the fixture account tiered "hosted" to keep meaning under that
89 // rule. Sanctioned test-only change (economics-v2 plan, Task 5); no
90 // production behavior here.
91 if err := fx.st.SetAccountTier(OpsAccountID, "hosted"); err != nil {
92 t.Fatal(err)
93 }
94
95 view, err := fx.srv.LoadEconomics(ctx, "mtd")
96 if err != nil {
97 t.Fatal(err)
98 }
99
100 if view.Scenario.Name != "test" {
101 t.Fatalf("Scenario.Name = %q, want %q", view.Scenario.Name, "test")
102 }
103 if view.ScenarioWarning != "" {
104 t.Fatalf("ScenarioWarning = %q, want empty (a named, valid active scenario)", view.ScenarioWarning)
105 }
106
107 var acctRow *economics.AccountEconomics
108 for i := range view.Accounts {
109 if view.Accounts[i].Account == fixtureAcct {
110 acctRow = &view.Accounts[i]
111 }
112 }
113 if acctRow == nil {
114 t.Fatalf("no AccountEconomics row for %q in %+v", fixtureAcct, view.Accounts)
115 }
116 if acctRow.CostUSD <= 0 {
117 t.Fatalf("acctRow.CostUSD = %v, want > 0", acctRow.CostUSD)
118 }
119
120 var boxRow *BoxEconomics
121 for i := range view.Boxes {
122 if view.Boxes[i].Box == "box1" {
123 boxRow = &view.Boxes[i]
124 }
125 }
126 if boxRow == nil {
127 t.Fatalf("no BoxEconomics row for box1 in %+v", view.Boxes)
128 }
129 if boxRow.Hours != 1 {
130 t.Fatalf("boxRow.Hours = %v, want 1 (one hour bucket, \"09\", in the golden day)", boxRow.Hours)
131 }
132
133 // Independently recompute the same box's allocation via
134 // economics.AllocateBox with hand-built load matching the golden
135 // fixture's cpu/mem averages, and confirm LoadEconomics's box row agrees
136 // — idle + allocated reconstructs the box's own total cost.
137 load := economics.BoxLoad{
138 Box: "box1",
139 Hours: 1,
140 ByAccount: map[string]economics.BoxAccountLoad{
141 fixtureAcct: {CPUCoreHours: 0.5, MemGBHours: 1.0},
142 },
143 }
144 perAcct, idleUSD, totalUSD := economics.AllocateBox(sc.RateCard, sc.Allocation, load)
145 allocated := perAcct[fixtureAcct]
146 if math.Abs((idleUSD+allocated)-totalUSD) > 1e-9 {
147 t.Fatalf("idle+allocated = %v, want totalUSD %v", idleUSD+allocated, totalUSD)
148 }
149 if math.Abs(boxRow.CostUSD-totalUSD) > 1e-9 {
150 t.Fatalf("boxRow.CostUSD = %v, want %v", boxRow.CostUSD, totalUSD)
151 }
152 if math.Abs(boxRow.IdleUSD-idleUSD) > 1e-9 {
153 t.Fatalf("boxRow.IdleUSD = %v, want %v", boxRow.IdleUSD, idleUSD)
154 }
155
156 if !strings.Contains(string(view.DataJSON), "meters") {
157 t.Fatalf("DataJSON missing %q: %s", "meters", view.DataJSON)
158 }
159
160 wantStorage := 31.0 / float64(daysInMonth)
161 if got := view.Meters[fixtureAcct][usage.MeterStorageGBMonth]; math.Abs(got-wantStorage) > 1e-9 {
162 t.Fatalf("Meters[%q][storage_gb_month] = %v, want %v (31 GB / %d days in month)", fixtureAcct, got, wantStorage, daysInMonth)
163 }
164 wantAppMonths := 2.0 / float64(daysInMonth)
165 if got := view.Meters[fixtureAcct][usage.MeterAppMonths]; math.Abs(got-wantAppMonths) > 1e-9 {
166 t.Fatalf("Meters[%q][app_months] = %v, want %v (2 apps / %d days in month)", fixtureAcct, got, wantAppMonths, daysInMonth)
167 }
168 // AccountEconomics.CostUSD is derived from Meters, so the storage line
169 // must be priced in too — the MAJOR finding's other symptom (cost
170 // dropping the S3-storage line entirely).
171 if got, want := acctRow.CostUSD, wantStorage*sc.RateCard.StorageGBMonthUSD; got < want {
172 t.Fatalf("acctRow.CostUSD = %v, want >= %v (must include the storage line)", got, want)
173 }
174}
175
176// TestLoadEconomicsNamesFromAccounts (account-names batch): EconomicsView.Names
177// is populated sqid->display-name from the SAME s.store.Accounts() read
178// buildEconomicsView already performs for the universe/Tiers — the fixture's
179// built-in account is named "ops" (newConsoleFixture / store.go's backfill),
180// keyed by fixtureAcct (its sqid, "bab"), exactly like Tiers is keyed. The
181// embedded payload carries the same map under "names" (economicsPayload),
182// the JS twin of view.Names, for parity with how "tiers" already travels.
183func TestLoadEconomicsNamesFromAccounts(t *testing.T) {
184 fx := newConsoleFixture(t)
185 ctx := context.Background()
186
187 view, err := fx.srv.LoadEconomics(ctx, "mtd")
188 if err != nil {
189 t.Fatal(err)
190 }
191 if got, want := view.Names[fixtureAcct], "ops"; got != want {
192 t.Fatalf("Names[%q] = %q, want %q (the fixture account's own name)", fixtureAcct, got, want)
193 }
194 if !strings.Contains(string(view.DataJSON), `"names"`) {
195 t.Fatalf("DataJSON missing %q: %s", `"names"`, view.DataJSON)
196 }
197}
198
199// TestLoadEconomicsTierRevenueAndPool: tier-aware pricing (Task 5). A
200// hosted-tier account prices normally — economics.PriceAccount's own
201// RevenueUSD stands. A pool-tier account, even one with real cost AND fleet
202// meters merged into its meters map (interface (a)'s merge, proven via
203// view.Meters below), gets RevenueUSD=0/MarginFrac=0 and FreeTierPos
204// overridden to "pooled" in the VIEW layer — never economics.PriceAccount's
205// own "inside"/"partial"/"paying", which is exactly the trap the carried
206// ruling calls out: a fleet-only pool account has real (fleet) revenue by
207// PriceAccount's tier-unaware maths, so naively zeroing revenue and then
208// recomputing FreeTierPos off that zero would misreport it "inside" the
209// free tier rather than "pooled". PoolLedger sums the paying side's revenue
210// and the pool side's cost per economicsview.go's documented formula,
211// top-up prorated to the period via view's own PeriodDays/DaysInMonth.
212func TestLoadEconomicsTierRevenueAndPool(t *testing.T) {
213 fx := newConsoleFixture(t)
214 ctx := context.Background()
215
216 reg, err := registry.Open(fx.regPath)
217 if err != nil {
218 t.Fatal(err)
219 }
220 if err := reg.Upsert(registry.Route{Host: "a.example.com", Kind: "instance",
221 Status: "ready", App: fixtureApp, Version: "v1"}); err != nil {
222 t.Fatal(err)
223 }
224
225 date := usage.DayOf(time.Now())
226 cpu, mem := 0.5, 1e9
227 roll := metrics.DayRollup{V: 1, Box: "box1", Date: date, Hours: map[string]metrics.HourAgg{
228 "09": {Units: map[string]map[string]metrics.Agg{
229 "carlos-app@a.example.com.service": {
230 "cpu": {Avg: cpu, N: 60},
231 "mem_bytes": {Avg: mem, N: 60},
232 },
233 }},
234 }}
235 putStoreJSON(t, fx.rel, metrics.DayKey("box1", date), roll)
236
237 // A second, pool-tier account with no console app and no registered
238 // route of its own — CreateAccount alone is enough for it to land in
239 // s.store.Accounts() and therefore the pricing universe.
240 poolID := testAccountID()
241 poolAcct := "poolsqid"
242 if err := fx.st.CreateAccount(poolID, poolAcct, "Poolco", "paul@keymail.dev", time.Now()); err != nil {
243 t.Fatal(err)
244 }
245
246 // Edge usage for both accounts on the same box's edge day: fixtureAcct
247 // gets the golden test's revenue-bearing traffic, poolAcct gets its own
248 // smaller row — EdgeRow.Account is read directly by sweepUsage with no
249 // registry route required (economicspass.go's edge loop).
250 putStoreJSON(t, fx.rel, usage.EdgeDayKey("box1", date), usage.EdgeDay{
251 V: 1, Box: "box1", Date: date,
252 Hours: map[string]map[string]usage.EdgeRow{"09": {
253 "a.example.com": {Account: fixtureAcct, App: fixtureApp, Requests: 4000, BytesOut: 2e9},
254 "b.example.com": {Account: poolAcct, App: "poolapp", Requests: 1000, BytesOut: 1e9},
255 }},
256 })
257
258 // Fleet meters (Task 3) attributed to poolAcct — proves interface (a)'s
259 // merge reaches an account that never touched a platform route at all.
260 if _, err := reg.CreateFleet(registry.Fleet{Name: "fx", Account: poolAcct, Bucket: "fx-data"}); err != nil {
261 t.Fatal(err)
262 }
263 if err := reg.AddFleetBox(registry.FleetBox{Fleet: "fx", Label: "bx1", TokenHash: strings.Repeat("a", 64)}); err != nil {
264 t.Fatal(err)
265 }
266 putStoreJSON(t, fx.rel, "fleets/fx/"+metrics.DayKey("bx1", date), metrics.DayRollup{
267 V: 1, Box: "bx1", Date: date, Hours: map[string]metrics.HourAgg{
268 "09": {Host: map[string]metrics.Agg{"cpu": {N: 60}}},
269 },
270 })
271
272 sc := economics.DefaultScenario()
273 sc.Name = "test"
274 putStoreJSON(t, fx.rel, economics.ScenarioKey("test"), sc)
275 putStoreJSON(t, fx.rel, economics.ActiveKey, economics.Active{Schema: 1, Name: "test"})
276
277 if err := fx.st.SetAccountTier(OpsAccountID, "hosted"); err != nil {
278 t.Fatal(err)
279 }
280 if err := fx.st.SetAccountTier(poolID, "pool"); err != nil {
281 t.Fatal(err)
282 }
283
284 view, err := fx.srv.LoadEconomics(ctx, "mtd")
285 if err != nil {
286 t.Fatal(err)
287 }
288
289 var hostedRow, poolRow *economics.AccountEconomics
290 for i := range view.Accounts {
291 switch view.Accounts[i].Account {
292 case fixtureAcct:
293 hostedRow = &view.Accounts[i]
294 case poolAcct:
295 poolRow = &view.Accounts[i]
296 }
297 }
298 if hostedRow == nil {
299 t.Fatalf("no AccountEconomics row for hosted %q in %+v", fixtureAcct, view.Accounts)
300 }
301 if poolRow == nil {
302 t.Fatalf("no AccountEconomics row for pool %q in %+v", poolAcct, view.Accounts)
303 }
304
305 if hostedRow.RevenueUSD <= 0 {
306 t.Fatalf("hostedRow.RevenueUSD = %v, want > 0", hostedRow.RevenueUSD)
307 }
308 if poolRow.RevenueUSD != 0 {
309 t.Fatalf("poolRow.RevenueUSD = %v, want 0 (pool tier)", poolRow.RevenueUSD)
310 }
311 if poolRow.MarginFrac != 0 {
312 t.Fatalf("poolRow.MarginFrac = %v, want 0 (pool tier)", poolRow.MarginFrac)
313 }
314 if poolRow.CostUSD <= 0 {
315 t.Fatalf("poolRow.CostUSD = %v, want > 0 (its cost stands)", poolRow.CostUSD)
316 }
317 if poolRow.FreeTierPos != "pooled" {
318 t.Fatalf("poolRow.FreeTierPos = %q, want %q", poolRow.FreeTierPos, "pooled")
319 }
320 if got := view.Meters[poolAcct][usage.MeterFleetBoxHoursServer]; got <= 0 {
321 t.Fatalf("Meters[%q][fleet_box_hours_server] = %v, want > 0 (fleet meters merged)", poolAcct, got)
322 }
323
324 if got, want := view.Pool.PayingRevenueUSD, hostedRow.RevenueUSD; math.Abs(got-want) > 1e-9 {
325 t.Fatalf("Pool.PayingRevenueUSD = %v, want %v (only hosted/fleet/dedicated revenue)", got, want)
326 }
327 // Independent expected value for the topup term: today's day-of-month
328 // and today's calendar month length, derived from the wall clock
329 // directly rather than read back off view.PeriodDays/view.DaysInMonth
330 // (the SUT's own reported fields) — an "mtd" period is wholly inside
331 // the current month, so per-day accrual (poolTopupAccrual) reduces to
332 // exactly this fraction, same as it always did for "mtd".
333 nowUTC := time.Now().UTC()
334 wantDaysElapsed := float64(nowUTC.Day())
335 wantDaysInMonth := float64(time.Date(nowUTC.Year(), nowUTC.Month()+1, 0, 0, 0, 0, 0, time.UTC).Day())
336 wantPoolUSD := sc.Pool.RevenueFrac*view.Pool.PayingRevenueUSD +
337 sc.Pool.TopupUSD*(wantDaysElapsed/wantDaysInMonth)
338 if math.Abs(view.Pool.PoolUSD-wantPoolUSD) > 1e-9 {
339 t.Fatalf("Pool.PoolUSD = %v, want %v", view.Pool.PoolUSD, wantPoolUSD)
340 }
341 if math.Abs(view.Pool.DrawUSD-poolRow.CostUSD) > 1e-9 {
342 t.Fatalf("Pool.DrawUSD = %v, want %v (sum of pool-tier CostUSD)", view.Pool.DrawUSD, poolRow.CostUSD)
343 }
344 wantHeadroom := view.Pool.PoolUSD - view.Pool.DrawUSD
345 if math.Abs(view.Pool.HeadroomUSD-wantHeadroom) > 1e-9 {
346 t.Fatalf("Pool.HeadroomUSD = %v, want %v (Pool - Draw)", view.Pool.HeadroomUSD, wantHeadroom)
347 }
348
349 if !strings.Contains(string(view.DataJSON), `"tiers"`) {
350 t.Fatalf("DataJSON missing %q: %s", "tiers", view.DataJSON)
351 }
352}
353
354// TestLoadEconomicsPoolTopupAccrualLast is the fix-round regression for
355// IMPORTANT 1: the pool's top-up term must accrue day-by-day
356// (poolTopupAccrual), not the brief's literal PeriodDays/DaysInMonth single
357// fraction — dishonest on period=last, since PeriodDays there counts LAST
358// month's days while DaysInMonth is pinned to THIS month's (a Jan-from-Feb
359// "last" view would credit 31/28 of a month, over 100%). No usage is seeded
360// at all — every live account (just the built-in ops account) sits at its
361// default "" pool tier with zero cost, so PayingRevenueUSD is exactly 0 and
362// PoolUSD reduces to the topup term alone. That lets the assertion use a
363// truly INDEPENDENT expected value — sc.Pool.TopupUSD itself, no formula,
364// no day arithmetic re-derived from the view at all — which per-day accrual
365// over the WHOLE previous calendar month must equal exactly: one full
366// month's top-up, never 110% of one.
367func TestLoadEconomicsPoolTopupAccrualLast(t *testing.T) {
368 fx := newConsoleFixture(t)
369 ctx := context.Background()
370
371 sc := economics.DefaultScenario()
372 sc.Name = "test"
373 putStoreJSON(t, fx.rel, economics.ScenarioKey("test"), sc)
374 putStoreJSON(t, fx.rel, economics.ActiveKey, economics.Active{Schema: 1, Name: "test"})
375
376 view, err := fx.srv.LoadEconomics(ctx, "last")
377 if err != nil {
378 t.Fatal(err)
379 }
380
381 if view.Pool.PayingRevenueUSD != 0 {
382 t.Fatalf("Pool.PayingRevenueUSD = %v, want 0 (no usage seeded, only the pool-tier ops account exists)", view.Pool.PayingRevenueUSD)
383 }
384 if math.Abs(view.Pool.PoolUSD-sc.Pool.TopupUSD) > 1e-9 {
385 t.Fatalf("Pool.PoolUSD = %v, want exactly %v (one full previous month's top-up; PayingRevenueUSD is 0)", view.Pool.PoolUSD, sc.Pool.TopupUSD)
386 }
387}
388
389// TestLoadEconomicsWarnsOnMissingNamedScenario: active.json naming a
390// scenario that doesn't exist under economics/scenarios/ is a WARNING, not
391// silence — the spec distinguishes "no active.json at all" (fresh
392// deployment, silent DefaultScenario) from "active.json points at
393// something broken" (visible warning, still DefaultScenario so the page
394// still renders).
395func TestLoadEconomicsWarnsOnMissingNamedScenario(t *testing.T) {
396 fx := newConsoleFixture(t)
397 ctx := context.Background()
398 putStoreJSON(t, fx.rel, economics.ActiveKey, economics.Active{Schema: 1, Name: "missing"})
399
400 view, err := fx.srv.LoadEconomics(ctx, "mtd")
401 if err != nil {
402 t.Fatal(err)
403 }
404 if view.Scenario.Name != economics.DefaultScenario().Name {
405 t.Fatalf("Scenario.Name = %q, want default %q", view.Scenario.Name, economics.DefaultScenario().Name)
406 }
407 if view.ScenarioWarning == "" {
408 t.Fatal("ScenarioWarning is empty, want a warning naming the unloadable scenario")
409 }
410}
411
412// TestLoadEconomicsDefaultsSilentlyWithNoActiveFile: a fresh deployment with
413// no economics/active.json at all must fall back to DefaultScenario with NO
414// warning — that absence is the expected initial state, not a fault.
415func TestLoadEconomicsDefaultsSilentlyWithNoActiveFile(t *testing.T) {
416 fx := newConsoleFixture(t)
417 ctx := context.Background()
418
419 view, err := fx.srv.LoadEconomics(ctx, "mtd")
420 if err != nil {
421 t.Fatal(err)
422 }
423 if view.Scenario.Name != economics.DefaultScenario().Name {
424 t.Fatalf("Scenario.Name = %q, want default %q", view.Scenario.Name, economics.DefaultScenario().Name)
425 }
426 if view.ScenarioWarning != "" {
427 t.Fatalf("ScenarioWarning = %q, want empty (missing active.json is a fresh deployment, not a fault)", view.ScenarioWarning)
428 }
429}
430
431// TestLoadEconomicsDegradesWithoutRegistry: a console with no registry
432// configured/mounted (MINOR 2, fix round) must still render the dashboard —
433// sweepUsage simply attributes every sampled unit to release.OpsAccount,
434// same as any other unmatched unit — with a visible Warning, never a hard
435// error. This is the opposite of censusPass/statementsPass's own registry
436// guard (TestCensusPassRefusesMissingRegistry): those are WRITE passes
437// protecting a create-only day object from a bogus all-zero write; this is
438// a READ path with nothing to protect by refusing.
439func TestLoadEconomicsDegradesWithoutRegistry(t *testing.T) {
440 fx := newConsoleFixture(t)
441 ctx := context.Background()
442 fx.srv.cfg.Registry = filepath.Join(t.TempDir(), "nonexistent-registry.db")
443
444 view, err := fx.srv.LoadEconomics(ctx, "mtd")
445 if err != nil {
446 t.Fatalf("LoadEconomics with no registry returned an error, want a degraded view: %v", err)
447 }
448 if view.Warning == "" {
449 t.Fatal("Warning is empty, want a notice that the sweep degraded (no registry)")
450 }
451}
452
453// TestLoadEconomicsAccountsSortStableOnTies: CostUSD-desc sorting must
454// tie-break on Account ascending (MINOR 4, fix round) — otherwise the
455// zero-cost tail (every account with no usage this period, all tied at 0)
456// has no defined order and can visibly reshuffle on every background sweep.
457func TestLoadEconomicsAccountsSortStableOnTies(t *testing.T) {
458 fx := newConsoleFixture(t)
459 ctx := context.Background()
460 now := time.Now()
461
462 if err := fx.st.CreateAccount(testAccountID(), "zzzsqid", "Zedco", "paul@keymail.dev", now); err != nil {
463 t.Fatal(err)
464 }
465 if err := fx.st.CreateAccount(testAccountID(), "aaasqid", "Aardco", "paul@keymail.dev", now); err != nil {
466 t.Fatal(err)
467 }
468
469 view, err := fx.srv.LoadEconomics(ctx, "mtd")
470 if err != nil {
471 t.Fatal(err)
472 }
473
474 var order []string
475 for _, a := range view.Accounts {
476 if a.Account == "zzzsqid" || a.Account == "aaasqid" || a.Account == fixtureAcct {
477 order = append(order, a.Account)
478 }
479 }
480 want := []string{"aaasqid", fixtureAcct, "zzzsqid"}
481 if len(order) != len(want) {
482 t.Fatalf("order = %v, want %v", order, want)
483 }
484 for i := range want {
485 if order[i] != want[i] {
486 t.Fatalf("order = %v, want %v (Account ascending among CostUSD==0 ties)", order, want)
487 }
488 }
489}
490