Skip to content

Commit 263efd5

Browse files
committed
test(store): pin ResolveDataset premium_usd read-path column order
Guards the unplanned data.go SELECT/Scan edit from om-0pqe: premium_usd, basis_usd, and face_value_usd are all REAL, so a same-type reorder in the SELECT would silently swap lots' money fields with every existing test green. Seeds distinct basis/premium/face values and asserts each threads through ResolveDataset, so a reorder fails loudly. (om-0pqe, verify finding F1)
1 parent 1261d79 commit 263efd5

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

internal/store/data_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package store_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/tompscanlan/coinrollhunter/internal/model"
7+
"github.com/tompscanlan/coinrollhunter/internal/store"
8+
)
9+
10+
// TestResolveDatasetReadPathColumnAlignment pins the SELECT/Scan column order of
11+
// ResolveDataset's live-lots query. premium_usd, basis_usd, and face_value_usd are
12+
// all REAL columns, so a same-type reorder in the SELECT (without a matching Scan
13+
// reorder) would compile, run, and pass every count-based test while silently
14+
// serving each lot's money fields swapped. The seeded values below are deliberately
15+
// distinct so any such swap makes an assertion fail. Guards the unplanned data.go
16+
// read-path edit from om-0pqe (audit #5).
17+
func TestResolveDatasetReadPathColumnAlignment(t *testing.T) {
18+
s, err := store.Open(":memory:")
19+
if err != nil {
20+
t.Fatal(err)
21+
}
22+
t.Cleanup(func() { s.Close() })
23+
24+
itID, err := s.InsertItemType(model.ItemType{
25+
Name: "1 oz American Gold Eagle",
26+
Metal: "gold",
27+
FineOzEach: 1.0,
28+
Fineness: "22k .9167",
29+
})
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
34+
// Distinct values across the adjacent REAL money columns so a swap is caught.
35+
_, err = s.InsertHolding(model.Holding{
36+
ItemTypeID: itID,
37+
Activity: "bullion",
38+
Qty: 3,
39+
BasisUSD: 1512,
40+
PremiumUSD: 62,
41+
FaceValueUSD: 25,
42+
Acquired: "2026-01-01",
43+
Source: "Blue Moon Bullion",
44+
})
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
49+
d, err := s.ResolveDataset()
50+
if err != nil {
51+
t.Fatal(err)
52+
}
53+
if len(d.Lots) != 1 {
54+
t.Fatalf("ResolveDataset returned %d lots, want 1", len(d.Lots))
55+
}
56+
lot := d.Lots[0]
57+
58+
// Money columns — the swap-sensitive trio. PremiumUSD is the om-5 field; Basis
59+
// and Face bracket it in the SELECT and pin the reorder both directions.
60+
if lot.BasisUSD != 1512 {
61+
t.Errorf("BasisUSD = %v, want 1512 (column misaligned?)", lot.BasisUSD)
62+
}
63+
if lot.PremiumUSD != 62 {
64+
t.Errorf("PremiumUSD = %v, want 62 (column misaligned or not selected?)", lot.PremiumUSD)
65+
}
66+
if lot.FaceValueUSD != 25 {
67+
t.Errorf("FaceValueUSD = %v, want 25 (column misaligned?)", lot.FaceValueUSD)
68+
}
69+
70+
// Broader alignment: Qty (pre-money) and the join-resolved fields (post-money,
71+
// keyed on item_type_id) pin the rest of the column list around the edit.
72+
if lot.Qty != 3 {
73+
t.Errorf("Qty = %v, want 3", lot.Qty)
74+
}
75+
if lot.Product != "1 oz American Gold Eagle" {
76+
t.Errorf("Product = %q, want the seeded item type (item_type_id misaligned?)", lot.Product)
77+
}
78+
if lot.Metal != "gold" {
79+
t.Errorf("Metal = %q, want gold", lot.Metal)
80+
}
81+
}

0 commit comments

Comments
 (0)