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
152 lines
4.4 KiB
Go
152 lines
4.4 KiB
Go
package ws_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/owncord/server/ws"
|
|
)
|
|
|
|
// BroadcastUserUpdate, BroadcastDropCount and SetEventPersister had no
|
|
// coverage. The first is what propagates a profile or identity-key change to
|
|
// every connected client — an identity key that fails to propagate silently
|
|
// breaks E2EE key agreement for everyone already online.
|
|
|
|
// awaitMessage reads one message from ch, failing if none arrives.
|
|
func awaitMessage(t *testing.T, ch chan []byte) map[string]any {
|
|
t.Helper()
|
|
select {
|
|
case raw := <-ch:
|
|
var msg map[string]any
|
|
if err := json.Unmarshal(raw, &msg); err != nil {
|
|
t.Fatalf("unmarshal %q: %v", raw, err)
|
|
}
|
|
return msg
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("no message received")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func TestHub_BroadcastUserUpdate(t *testing.T) {
|
|
hub, _ := newTestHub(t)
|
|
go hub.Run()
|
|
t.Cleanup(hub.Stop)
|
|
|
|
send := make(chan []byte, 8)
|
|
client := ws.NewTestClient(hub, 1, send)
|
|
hub.RegisterNowForTest(client)
|
|
|
|
avatar := "avatar.png"
|
|
identityKey := "pubkey-abc"
|
|
hub.BroadcastUserUpdate(42, "renamed", &avatar, &identityKey)
|
|
|
|
msg := awaitMessage(t, send)
|
|
if msg["type"] != "user_update" {
|
|
t.Fatalf("type = %v, want user_update", msg["type"])
|
|
}
|
|
payload, ok := msg["payload"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("payload is not an object: %v", msg["payload"])
|
|
}
|
|
if payload["user_id"] != float64(42) {
|
|
t.Errorf("user_id = %v, want 42", payload["user_id"])
|
|
}
|
|
if payload["username"] != "renamed" {
|
|
t.Errorf("username = %v, want renamed", payload["username"])
|
|
}
|
|
if payload["avatar"] != "avatar.png" {
|
|
t.Errorf("avatar = %v, want avatar.png", payload["avatar"])
|
|
}
|
|
// The identity key is the E2EE handshake input; dropping it here would
|
|
// leave peers unable to derive a session with this user.
|
|
if payload["identity_public_key"] != "pubkey-abc" {
|
|
t.Errorf("identity_public_key = %v, want pubkey-abc", payload["identity_public_key"])
|
|
}
|
|
}
|
|
|
|
func TestHub_BroadcastUserUpdate_NilOptionalFields(t *testing.T) {
|
|
hub, _ := newTestHub(t)
|
|
go hub.Run()
|
|
t.Cleanup(hub.Stop)
|
|
|
|
send := make(chan []byte, 8)
|
|
hub.RegisterNowForTest(ws.NewTestClient(hub, 1, send))
|
|
|
|
hub.BroadcastUserUpdate(42, "noextras", nil, nil)
|
|
|
|
msg := awaitMessage(t, send)
|
|
payload, ok := msg["payload"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("payload is not an object: %v", msg["payload"])
|
|
}
|
|
if payload["username"] != "noextras" {
|
|
t.Errorf("username = %v, want noextras", payload["username"])
|
|
}
|
|
// A user with no avatar / no published key must serialize as null rather
|
|
// than an empty string, so clients can tell "unset" from "cleared".
|
|
if v, present := payload["avatar"]; present && v != nil {
|
|
t.Errorf("avatar = %v, want null", v)
|
|
}
|
|
if v, present := payload["identity_public_key"]; present && v != nil {
|
|
t.Errorf("identity_public_key = %v, want null", v)
|
|
}
|
|
}
|
|
|
|
func TestHub_BroadcastUserUpdate_ReachesEveryClient(t *testing.T) {
|
|
hub, _ := newTestHub(t)
|
|
go hub.Run()
|
|
t.Cleanup(hub.Stop)
|
|
|
|
a := make(chan []byte, 8)
|
|
b := make(chan []byte, 8)
|
|
hub.RegisterNowForTest(ws.NewTestClient(hub, 1, a))
|
|
hub.RegisterNowForTest(ws.NewTestClient(hub, 2, b))
|
|
|
|
hub.BroadcastUserUpdate(7, "everyone", nil, nil)
|
|
|
|
for i, ch := range []chan []byte{a, b} {
|
|
msg := awaitMessage(t, ch)
|
|
if msg["type"] != "user_update" {
|
|
t.Errorf("client %d got type %v, want user_update", i, msg["type"])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHub_BroadcastDropCount(t *testing.T) {
|
|
hub, _ := newTestHub(t)
|
|
|
|
// A hub that has broadcast nothing has dropped nothing. The admin
|
|
// diagnostics endpoint reads this counter, so a nonzero baseline would
|
|
// read as backpressure that never happened.
|
|
if got := hub.BroadcastDropCount(); got != 0 {
|
|
t.Errorf("BroadcastDropCount = %d on a fresh hub, want 0", got)
|
|
}
|
|
|
|
go hub.Run()
|
|
t.Cleanup(hub.Stop)
|
|
|
|
send := make(chan []byte, 8)
|
|
hub.RegisterNowForTest(ws.NewTestClient(hub, 1, send))
|
|
hub.BroadcastUserUpdate(1, "u", nil, nil)
|
|
awaitMessage(t, send)
|
|
|
|
// A single delivered broadcast must not increment the drop counter.
|
|
if got := hub.BroadcastDropCount(); got != 0 {
|
|
t.Errorf("BroadcastDropCount = %d after one delivered broadcast, want 0", got)
|
|
}
|
|
}
|
|
|
|
func TestHub_SetEventPersister(t *testing.T) {
|
|
hub, database := newTestHub(t)
|
|
|
|
persister := ws.NewEventPersister(database, 16, 4, 10*time.Millisecond)
|
|
|
|
// Setting and clearing must both be safe — SetEventPersister is called at
|
|
// startup and again on shutdown/reconfiguration.
|
|
hub.SetEventPersister(persister)
|
|
hub.SetEventPersister(nil)
|
|
hub.SetEventPersister(persister)
|
|
}
|