mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
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
134 lines
3.6 KiB
Go
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())
|
|
}
|