carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package analytics
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/release"
11)
12
13func pv(country, src, state string) Obs {
14 return Obs{Acct: "a", App: "x", Host: "x.test", Method: "GET", Status: 200, ContentType: "text/html",
15 Path: "/", UA: "Mozilla/5.0", ClientIP: "203.0.113.1", When: time.Date(2026, 10, 6, 9, 0, 0, 0, time.UTC),
16 Country: country, GeoSource: src, GeoState: state}
17}
18
19func TestMeterCountsCountriesByState(t *testing.T) {
20 now := time.Date(2026, 10, 6, 9, 0, 0, 0, time.UTC)
21 m := NewMeter(t.TempDir(), "box1", func() time.Time { return now })
22 m.Observe(pv("GB", "DBIP-City-Lite", "hit"))
23 m.Observe(pv("", "DBIP-City-Lite", "hit")) // coordinate-only (codex R3-3)
24 m.Observe(pv("", "DBIP-City-Lite", "miss"))
25 m.Observe(pv("", "", "off"))
26 m.Observe(pv("", "", "")) // an edge from before GeoIP
27 nonPV := pv("FR", "DBIP-City-Lite", "hit")
28 nonPV.Status = 404
29 m.Observe(nonPV)
30 d := m.Drain()[AppKey{Acct: "a", App: "x"}][0]
31 if d.Countries["GB"] != 1 || d.Countries["_unknown"] != 2 || len(d.Countries) != 2 {
32 t.Fatalf("countries = %v", d.Countries)
33 }
34 if d.GeoSources["DBIP-City-Lite"] != 3 || len(d.GeoSources) != 1 {
35 t.Fatalf("sources = %v", d.GeoSources)
36 }
37}
38
39func TestReadRangeMergesCountriesAcrossBoxesDaysAndOldDays(t *testing.T) {
40 ctx := context.Background()
41 s := release.NewDirStore(t.TempDir())
42 put := func(box, date string, d Day) {
43 raw, _ := json.Marshal(d)
44 if _, err := s.Put(ctx, must(t)(DayKey("bab", "x", box, date)), raw, ""); err != nil {
45 t.Fatal(err)
46 }
47 }
48 // An old day, from before GeoIP: no geo_sources, empty countries.
49 put("b1", "2026-10-04", Day{V: 1, Date: "2026-10-04", Countries: map[string]int64{}})
50 put("b1", "2026-10-05", Day{V: 1, Date: "2026-10-05", Countries: map[string]int64{"GB": 3}, GeoSources: map[string]int64{"DBIP-City-Lite": 3}})
51 put("b2", "2026-10-05", Day{V: 1, Date: "2026-10-05", Countries: map[string]int64{"GB": 1, "IE": 2}, GeoSources: map[string]int64{"GeoLite2-City": 3}})
52 sum, err := ReadRange(ctx, s, "bab", "x", time.Date(2026, 10, 4, 0, 0, 0, 0, time.UTC), time.Date(2026, 10, 5, 0, 0, 0, 0, time.UTC))
53 if err != nil {
54 t.Fatal(err)
55 }
56 if sum.Countries["GB"] != 4 || sum.Countries["IE"] != 2 {
57 t.Fatalf("countries = %v", sum.Countries)
58 }
59 if sum.GeoSources["DBIP-City-Lite"] != 3 || sum.GeoSources["GeoLite2-City"] != 3 {
60 t.Fatalf("both sources must be credited (spec §9): %v", sum.GeoSources)
61 }
62}
63
64func TestDayMergeAddsCountries(t *testing.T) {
65 var d Day
66 d.Merge(Delta{Box: "b", Seq: 1, Countries: map[string]int64{"GB": 1}, GeoSources: map[string]int64{"s": 1}})
67 d.Merge(Delta{Box: "b", Seq: 2, Countries: map[string]int64{"GB": 2, "IE": 1}, GeoSources: map[string]int64{"s": 3}})
68 if d.Countries["GB"] != 3 || d.Countries["IE"] != 1 || d.GeoSources["s"] != 4 {
69 t.Fatalf("day = %v %v", d.Countries, d.GeoSources)
70 }
71}
72
73func TestBoundedAddFoldsIntoOther(t *testing.T) {
74 m := map[string]int64{}
75 for i := 0; i < 12; i++ {
76 boundedAdd(m, fmt.Sprintf("K%02d", i), 1, 8, nil)
77 }
78 if len(m) != 9 || m["_other"] != 4 {
79 t.Fatalf("bounded = %v", m)
80 }
81}
82
83// Codex R7-3: an attributed source is never folded, however many other
84// types crowd the map, across flushes and days.
85func TestAttributedSourcesSurviveOverflow(t *testing.T) {
86 var d Day
87 junk := map[string]int64{}
88 for i := 0; i < 20; i++ {
89 junk[fmt.Sprintf("Custom-%02d", i)] = 1
90 }
91 d.Merge(Delta{Box: "b", Seq: 1, GeoSources: junk})
92 d.Merge(Delta{Box: "b", Seq: 2, GeoSources: map[string]int64{"DBIP-City-Lite": 5, "GeoLite2-Country": 2}})
93 if d.GeoSources["DBIP-City-Lite"] != 5 || d.GeoSources["GeoLite2-Country"] != 2 {
94 t.Fatalf("attributed sources folded: %v", d.GeoSources)
95 }
96 if d.GeoSources["_other"] == 0 {
97 t.Fatalf("unattributed overflow must still fold: %v", d.GeoSources)
98 }
99}
100