Files
OwnCord/Server/telemetry/noop_test.go
T
Claude 0918f859a0 test: close measured test-coverage gaps across server, client and Rust
Audits what actually has tests, then closes the gaps it found. Full write-up
with before/after numbers in docs/audit-test-coverage-2026-07-25.md.

Measurement first: `go test ./... -coverprofile` (what CI runs) instruments
each package only for itself, so code exercised through another package's
tests reads as uncovered — `service` reported 36.7% against a real 85%. All
analysis here uses -coverpkg=./..., and both views now have Makefile targets.

Features that had zero coverage at every layer:
- user blocking (db + service + the /api/v1/blocks routes)
- auth lockout persistence — the DB round-trip that survives a restart
- plugin install/enable/disable/uninstall and the plugin KV namespace
- event replay bounds (GetMaxEventSeq, PruneEventsOlderThan)
- LiveKit participant_joined webhook (replayed-token guard), the room-service
  client, and proxyWebSocket/copyWS
- ws_proxy.rs and livekit_proxy.rs — pure helpers extracted, matching the
  existing tofu.rs pattern, so cert-pin and header-injection checks are testable

Gaps that were hidden rather than absent:
- Server/admin reported 0.3% coverage with 307 tests passing. TestSpawnDetached_*
  re-execs the test binary; the child inherited GOCOVERDIR and the parent's
  stdout, clobbering the profile and printing "[no tests to run]". Now 71.4%,
  and CI's uploaded artifact is correct.
- vitest.config.ts excluded 2.2k LOC unexplained, including two files that
  already had tests. Trimmed to three entries, each justified inline.
- api.HandleLiveKitHealthForTest re-implemented the handler it claimed to
  expose, so eight call sites tested a copy. Added a hook to the real one.

Two bugs found and pinned rather than silently patched: logctx.WithGroup nests
req_id under the group, and drag-reorder.ts takes one listener ref per channel
but releases one per sidebar, so the count never reaches zero.

Coverage: client 92.93% -> 94.87% statements (3371 -> 3572 tests) even after
un-excluding hidden files; Rust 47 -> 74 tests; Go zero-coverage functions
~70 -> 21, with plugin 61->77%, admin 67->86%, db 76->84%, service 85->91%.

Verified: go vet, all four build-tag variants, go test -race, -tags deadlock,
vitest --coverage, cargo test --lib, cargo clippy --all-targets, playwright.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AEETs3Vh6sAHHb1jMBL75g
2026-07-25 14:47:21 +00:00

134 lines
3.6 KiB
Go

package telemetry_test
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/owncord/server/telemetry"
)
// The no-op provider is what runs in every build without -tags otel — i.e. the
// default binary and all of CI. Its methods had no coverage, so nothing caught
// a nil-pointer or panic on the path every instrumented call takes in the
// shipped build.
func TestNoopProvider_TracerAndSpanAreInert(t *testing.T) {
tracer := telemetry.GlobalTracer("test")
if tracer == nil {
t.Fatal("GlobalTracer returned nil")
}
ctx, span := tracer.Start(context.Background(), "op",
telemetry.String("s", "v"),
telemetry.Int64("i", 7),
telemetry.Float64("f", 1.5),
)
if ctx == nil {
t.Fatal("Start returned a nil context")
}
if span == nil {
t.Fatal("Start returned a nil span")
}
// Every span method must be safe to call on the no-op implementation;
// service code calls these unconditionally.
span.SetAttributes(telemetry.String("k", "v"))
span.RecordError(errors.New("boom"))
span.End()
}
func TestNoopProvider_MeterInstrumentsAreInert(t *testing.T) {
meter := telemetry.GlobalMeter("test")
if meter == nil {
t.Fatal("GlobalMeter returned nil")
}
ctx := context.Background()
counter := meter.Counter("c", "count of things")
if counter == nil {
t.Fatal("Counter returned nil")
}
counter.Add(ctx, 1, telemetry.String("k", "v"))
hist := meter.Histogram("h", "s", "durations")
if hist == nil {
t.Fatal("Histogram returned nil")
}
hist.Record(ctx, 0.25, telemetry.String("k", "v"))
gauge := meter.Gauge("g", "a level")
if gauge == nil {
t.Fatal("Gauge returned nil")
}
gauge.Set(ctx, 3, telemetry.String("k", "v"))
}
func TestAttrConstructors(t *testing.T) {
tests := []struct {
name string
got telemetry.Attr
key string
val any
}{
{"String", telemetry.String("s", "v"), "s", "v"},
{"Int64", telemetry.Int64("i", 7), "i", int64(7)},
{"Float64", telemetry.Float64("f", 1.5), "f", 1.5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.got.Key != tt.key {
t.Errorf("Key = %q, want %q", tt.got.Key, tt.key)
}
if tt.got.Value != tt.val {
t.Errorf("Value = %#v, want %#v", tt.got.Value, tt.val)
}
})
}
}
func TestTimeSince(t *testing.T) {
// TimeSince is called from deferred blocks all over the service layer with
// whatever the metrics struct holds — including nil in the no-op build.
telemetry.TimeSince(context.Background(), nil, time.Now())
hist := telemetry.GlobalMeter("test").Histogram("h", "s", "d")
telemetry.TimeSince(context.Background(), hist, time.Now().Add(-time.Second),
telemetry.String("method", "Test"))
}
func TestNoopProvider_HTTPMiddlewareIsPassThrough(t *testing.T) {
called := false
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
called = true
w.WriteHeader(http.StatusTeapot)
})
wrapped := telemetry.Global().HTTPMiddleware(next)
rr := httptest.NewRecorder()
wrapped.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", nil))
if !called {
t.Error("the wrapped handler was not invoked")
}
if rr.Code != http.StatusTeapot {
t.Errorf("status = %d, want the inner handler's 418", rr.Code)
}
}
func TestNewAppMetrics_InstrumentsAreUsable(t *testing.T) {
m := telemetry.NewAppMetrics()
if m == nil {
t.Fatal("NewAppMetrics returned nil")
}
// ServiceCallDurationSec is passed straight into TimeSince by the service
// layer, so it must never be nil.
if m.ServiceCallDurationSec == nil {
t.Error("ServiceCallDurationSec is nil")
}
telemetry.TimeSince(context.Background(), m.ServiceCallDurationSec, time.Now())
}