package game import ( "context" "testing" "time" "go.opentelemetry.io/otel/attribute" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "scrabble/backend/internal/engine" ) // TestGameMetrics records each game instrument through a manual reader and asserts // the counters carry the right "variant" attribute and the histograms observe. func TestGameMetrics(t *testing.T) { ctx := context.Background() reader := sdkmetric.NewManualReader() meter := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)).Meter("test") m := newGameMetrics(meter) m.recordStarted(ctx, engine.VariantEnglish) m.recordStarted(ctx, engine.VariantEnglish) m.recordStarted(ctx, engine.VariantRussianScrabble) m.recordAbandoned(ctx, engine.VariantErudit) m.recordReplay(ctx, engine.VariantEnglish, time.Now().Add(-time.Millisecond)) m.recordValidate(ctx, engine.VariantRussianScrabble, time.Now().Add(-time.Millisecond)) var rm metricdata.ResourceMetrics if err := reader.Collect(ctx, &rm); err != nil { t.Fatalf("collect: %v", err) } started := counterByAttr(t, rm, "games_started_total", "variant") if started["english"] != 2 || started["russian_scrabble"] != 1 { t.Errorf("games_started_total = %v, want english:2 russian_scrabble:1", started) } if abandoned := counterByAttr(t, rm, "games_abandoned_total", "variant"); abandoned["erudit"] != 1 { t.Errorf("games_abandoned_total = %v, want erudit:1", abandoned) } if c := histogramCount(t, rm, "game_replay_duration"); c != 1 { t.Errorf("game_replay_duration observations = %d, want 1", c) } if c := histogramCount(t, rm, "game_move_validate_duration"); c != 1 { t.Errorf("game_move_validate_duration observations = %d, want 1", c) } } // counterByAttr sums the int64 counter named name, grouped by the value of the // attribute key attr. func counterByAttr(t *testing.T, rm metricdata.ResourceMetrics, name, attr string) map[string]int64 { t.Helper() out := map[string]int64{} for _, sm := range rm.ScopeMetrics { for _, md := range sm.Metrics { if md.Name != name { continue } sum, ok := md.Data.(metricdata.Sum[int64]) if !ok { t.Fatalf("%s is not an int64 sum", name) } for _, dp := range sum.DataPoints { v, _ := dp.Attributes.Value(attribute.Key(attr)) out[v.AsString()] += dp.Value } } } return out } // histogramCount returns the total observation count of the float64 histogram // named name. func histogramCount(t *testing.T, rm metricdata.ResourceMetrics, name string) uint64 { t.Helper() for _, sm := range rm.ScopeMetrics { for _, md := range sm.Metrics { if md.Name != name { continue } h, ok := md.Data.(metricdata.Histogram[float64]) if !ok { t.Fatalf("%s is not a float64 histogram", name) } var n uint64 for _, dp := range h.DataPoints { n += dp.Count } return n } } t.Fatalf("%s not found", name) return 0 }