package metrics import ( "context" "encoding/json" "testing" "time" "github.com/carlosframework/platform/internal/release" ) func TestHourOf(t *testing.T) { got := HourOf(time.Date(2026, 8, 2, 21, 45, 0, 0, time.UTC)) if got != "2026-08-02-21" { t.Fatalf("got %q, want 2026-08-02-21", got) } } func TestRawKeyDayKey(t *testing.T) { if got, want := RawKey("b", "2026-08-02-21"), "metrics/boxes/b/raw/2026-08-02-21.json"; got != want { t.Fatalf("RawKey got %q, want %q", got, want) } if got, want := DayKey("b", "2026-08-02"), "metrics/boxes/b/days/2026-08-02.json"; got != want { t.Fatalf("DayKey got %q, want %q", got, want) } } func sampleFloat(v float64) *float64 { return &v } func TestWriteRawRoundTrip(t *testing.T) { store := release.NewDirStore(t.TempDir()) ctx := context.Background() c := RawChunk{ V: 1, Box: "b", Hour: "2026-08-02-21", Samples: []Sample{ { At: time.Date(2026, 8, 2, 21, 0, 0, 0, time.UTC), Host: HostSample{CPUFrac: sampleFloat(0.5)}, Units: map[string]UnitSample{ "carlos-app@x.example.com.service": {CPUFrac: sampleFloat(0.1)}, }, }, }, } if err := WriteRaw(ctx, store, c); err != nil { t.Fatalf("WriteRaw: %v", err) } body, _, err := store.Get(ctx, RawKey(c.Box, c.Hour)) if err != nil { t.Fatalf("Get: %v", err) } var got RawChunk if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("Unmarshal: %v", err) } if got.V != c.V || got.Box != c.Box || got.Hour != c.Hour || len(got.Samples) != 1 { t.Fatalf("got %+v, want %+v", got, c) } if got.Samples[0].Host.CPUFrac == nil || *got.Samples[0].Host.CPUFrac != 0.5 { t.Fatalf("host cpu round-trip mismatch: %+v", got.Samples[0].Host) } } func TestWriteRawOverwriteSameHour(t *testing.T) { store := release.NewDirStore(t.TempDir()) ctx := context.Background() first := RawChunk{V: 1, Box: "b", Hour: "2026-08-02-21", Samples: []Sample{ {At: time.Date(2026, 8, 2, 21, 0, 0, 0, time.UTC), Host: HostSample{CPUFrac: sampleFloat(0.1)}}, }} second := RawChunk{V: 1, Box: "b", Hour: "2026-08-02-21", Samples: []Sample{ {At: time.Date(2026, 8, 2, 21, 5, 0, 0, time.UTC), Host: HostSample{CPUFrac: sampleFloat(0.2)}}, {At: time.Date(2026, 8, 2, 21, 10, 0, 0, time.UTC), Host: HostSample{CPUFrac: sampleFloat(0.3)}}, }} if err := WriteRaw(ctx, store, first); err != nil { t.Fatalf("WriteRaw first: %v", err) } if err := WriteRaw(ctx, store, second); err != nil { t.Fatalf("WriteRaw second: %v", err) } keys, err := store.List(ctx, "metrics/boxes/b/raw/") if err != nil { t.Fatalf("List: %v", err) } if len(keys) != 1 { t.Fatalf("got %d keys, want exactly one (overwrite, not a new object): %v", len(keys), keys) } body, _, err := store.Get(ctx, RawKey("b", "2026-08-02-21")) if err != nil { t.Fatalf("Get: %v", err) } var got RawChunk if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("Unmarshal: %v", err) } if len(got.Samples) != 2 { t.Fatalf("got %d samples, want the latest write's 2 (latest content wins)", len(got.Samples)) } } func TestStaleRawKeys(t *testing.T) { now := time.Date(2026, 8, 2, 22, 30, 0, 0, time.UTC) keys := []string{ "metrics/boxes/b/raw/2026-07-31-10.json", // stale "metrics/boxes/b/raw/2026-08-02-21.json", // fresh "metrics/boxes/b/raw/garbage.json", // unparsable — never returned } got := StaleRawKeys(keys, now, 48*time.Hour) if len(got) != 1 || got[0] != keys[0] { t.Fatalf("got %v, want only the stale key", got) } }