Files
OwnCord/Server/db/plugin_queries_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

323 lines
9.4 KiB
Go

package db_test
import (
"bytes"
"context"
"database/sql"
"errors"
"testing"
)
// Plugin persistence (migration 015) and the per-plugin KV namespace had no
// coverage. The KV namespace is the isolation boundary between plugins — the
// primary key is (plugin_id, key), so a plugin cannot name another plugin's
// namespace — and that property is worth pinning explicitly.
func installTestPlugin(t *testing.T, database interface {
InstallPlugin(ctx context.Context, name, version, manifestJSON string) (int64, error)
}, name string) int64 {
t.Helper()
id, err := database.InstallPlugin(context.Background(), name, "1.0.0", `{"name":"`+name+`"}`)
if err != nil {
t.Fatalf("InstallPlugin(%s): %v", name, err)
}
return id
}
func TestInstallPlugin_AndGetPlugin(t *testing.T) {
database := newMigratedTestDB(t)
ctx := context.Background()
id := installTestPlugin(t, database, "hello")
if id == 0 {
t.Fatal("InstallPlugin returned id 0")
}
got, err := database.GetPlugin(ctx, id)
if err != nil {
t.Fatalf("GetPlugin: %v", err)
}
if got.Name != "hello" {
t.Errorf("Name = %q, want %q", got.Name, "hello")
}
if got.Version != "1.0.0" {
t.Errorf("Version = %q, want %q", got.Version, "1.0.0")
}
if got.Enabled {
t.Error("Enabled = true; a freshly installed plugin must default to disabled")
}
if got.InstalledAt.IsZero() {
t.Error("InstalledAt is zero; scanPluginRow should have parsed the timestamp")
}
}
func TestInstallPlugin_ReinstallUpdatesInPlace(t *testing.T) {
database := newMigratedTestDB(t)
ctx := context.Background()
first := installTestPlugin(t, database, "hello")
// ON CONFLICT(name) DO UPDATE — a reinstall must upgrade the existing row
// rather than create a second one, and must return the same id (the path
// where LastInsertId is 0 and the code falls back to a lookup by name).
second, err := database.InstallPlugin(ctx, "hello", "2.0.0", `{"name":"hello","v":2}`)
if err != nil {
t.Fatalf("InstallPlugin reinstall: %v", err)
}
if second != first {
t.Errorf("reinstall returned id %d, want the original %d", second, first)
}
got, err := database.GetPlugin(ctx, first)
if err != nil {
t.Fatalf("GetPlugin: %v", err)
}
if got.Version != "2.0.0" {
t.Errorf("Version = %q after reinstall, want %q", got.Version, "2.0.0")
}
all, err := database.ListPlugins(ctx)
if err != nil {
t.Fatalf("ListPlugins: %v", err)
}
if len(all) != 1 {
t.Errorf("ListPlugins len = %d after reinstall, want 1", len(all))
}
}
func TestEnableDisablePlugin(t *testing.T) {
database := newMigratedTestDB(t)
ctx := context.Background()
id := installTestPlugin(t, database, "hello")
if err := database.EnablePlugin(ctx, id); err != nil {
t.Fatalf("EnablePlugin: %v", err)
}
got, err := database.GetPlugin(ctx, id)
if err != nil {
t.Fatalf("GetPlugin: %v", err)
}
if !got.Enabled {
t.Error("Enabled = false after EnablePlugin")
}
if err := database.DisablePlugin(ctx, id); err != nil {
t.Fatalf("DisablePlugin: %v", err)
}
got, err = database.GetPlugin(ctx, id)
if err != nil {
t.Fatalf("GetPlugin: %v", err)
}
if got.Enabled {
t.Error("Enabled = true after DisablePlugin")
}
}
func TestGetPluginByName(t *testing.T) {
database := newMigratedTestDB(t)
ctx := context.Background()
id := installTestPlugin(t, database, "hello")
got, err := database.GetPluginByName(ctx, "hello")
if err != nil {
t.Fatalf("GetPluginByName: %v", err)
}
if got.ID != id {
t.Errorf("ID = %d, want %d", got.ID, id)
}
_, err = database.GetPluginByName(ctx, "does-not-exist")
if !errors.Is(err, sql.ErrNoRows) {
t.Errorf("GetPluginByName on a missing name = %v, want sql.ErrNoRows", err)
}
}
func TestGetPlugin_Missing(t *testing.T) {
database := newMigratedTestDB(t)
_, err := database.GetPlugin(context.Background(), 4242)
if !errors.Is(err, sql.ErrNoRows) {
t.Errorf("GetPlugin on a missing id = %v, want sql.ErrNoRows", err)
}
}
func TestUninstallPlugin_CascadesKV(t *testing.T) {
database := newMigratedTestDB(t)
ctx := context.Background()
id := installTestPlugin(t, database, "hello")
if err := database.PluginKVSet(ctx, id, "k", []byte("v")); err != nil {
t.Fatalf("PluginKVSet: %v", err)
}
if err := database.UninstallPlugin(ctx, id); err != nil {
t.Fatalf("UninstallPlugin: %v", err)
}
if _, err := database.GetPlugin(ctx, id); !errors.Is(err, sql.ErrNoRows) {
t.Errorf("GetPlugin after uninstall = %v, want sql.ErrNoRows", err)
}
// plugin_kv has ON DELETE CASCADE; an uninstall must not leave the removed
// plugin's stored data behind for whatever reinstalls under that id.
var leftover int
row := database.QueryRowContext(ctx, `SELECT COUNT(*) FROM plugin_kv WHERE plugin_id = ?`, id)
if err := row.Scan(&leftover); err != nil {
t.Fatalf("count plugin_kv: %v", err)
}
if leftover != 0 {
t.Errorf("%d plugin_kv rows survived the uninstall, want 0", leftover)
}
}
func TestListPlugins_OrderedByName(t *testing.T) {
database := newMigratedTestDB(t)
ctx := context.Background()
empty, err := database.ListPlugins(ctx)
if err != nil {
t.Fatalf("ListPlugins on empty: %v", err)
}
if len(empty) != 0 {
t.Errorf("ListPlugins on empty = %v, want none", empty)
}
for _, name := range []string{"zeta", "alpha", "mid"} {
installTestPlugin(t, database, name)
}
got, err := database.ListPlugins(ctx)
if err != nil {
t.Fatalf("ListPlugins: %v", err)
}
want := []string{"alpha", "mid", "zeta"}
if len(got) != len(want) {
t.Fatalf("ListPlugins len = %d, want %d", len(got), len(want))
}
for i, w := range want {
if got[i].Name != w {
t.Errorf("ListPlugins[%d].Name = %q, want %q", i, got[i].Name, w)
}
}
}
func TestPluginKV_SetGetDelete(t *testing.T) {
database := newMigratedTestDB(t)
ctx := context.Background()
id := installTestPlugin(t, database, "hello")
if _, err := database.PluginKVGet(ctx, id, "absent"); !errors.Is(err, sql.ErrNoRows) {
t.Errorf("PluginKVGet on a missing key = %v, want sql.ErrNoRows", err)
}
if err := database.PluginKVSet(ctx, id, "greeting", []byte("hi")); err != nil {
t.Fatalf("PluginKVSet: %v", err)
}
v, err := database.PluginKVGet(ctx, id, "greeting")
if err != nil {
t.Fatalf("PluginKVGet: %v", err)
}
if !bytes.Equal(v, []byte("hi")) {
t.Errorf("value = %q, want %q", v, "hi")
}
// ON CONFLICT(plugin_id, key) DO UPDATE — a second Set overwrites.
if err := database.PluginKVSet(ctx, id, "greeting", []byte("hello again")); err != nil {
t.Fatalf("PluginKVSet overwrite: %v", err)
}
v, err = database.PluginKVGet(ctx, id, "greeting")
if err != nil {
t.Fatalf("PluginKVGet after overwrite: %v", err)
}
if !bytes.Equal(v, []byte("hello again")) {
t.Errorf("value = %q after overwrite, want %q", v, "hello again")
}
if err := database.PluginKVDelete(ctx, id, "greeting"); err != nil {
t.Fatalf("PluginKVDelete: %v", err)
}
if _, err := database.PluginKVGet(ctx, id, "greeting"); !errors.Is(err, sql.ErrNoRows) {
t.Errorf("PluginKVGet after delete = %v, want sql.ErrNoRows", err)
}
}
func TestPluginKV_NamespacesAreIsolated(t *testing.T) {
database := newMigratedTestDB(t)
ctx := context.Background()
a := installTestPlugin(t, database, "plugin-a")
b := installTestPlugin(t, database, "plugin-b")
if err := database.PluginKVSet(ctx, a, "secret", []byte("a-value")); err != nil {
t.Fatalf("PluginKVSet a: %v", err)
}
if err := database.PluginKVSet(ctx, b, "secret", []byte("b-value")); err != nil {
t.Fatalf("PluginKVSet b: %v", err)
}
// Same key, different namespaces — neither plugin can read or clobber the
// other's value. This is the whole isolation guarantee of the KV store.
got, err := database.PluginKVGet(ctx, a, "secret")
if err != nil {
t.Fatalf("PluginKVGet a: %v", err)
}
if !bytes.Equal(got, []byte("a-value")) {
t.Errorf("plugin-a read %q, want %q", got, "a-value")
}
if err := database.PluginKVDelete(ctx, a, "secret"); err != nil {
t.Fatalf("PluginKVDelete a: %v", err)
}
got, err = database.PluginKVGet(ctx, b, "secret")
if err != nil {
t.Fatalf("PluginKVGet b after deleting a's key: %v", err)
}
if !bytes.Equal(got, []byte("b-value")) {
t.Errorf("plugin-b value = %q after plugin-a deleted its own key, want %q", got, "b-value")
}
}
func TestPluginKVScan(t *testing.T) {
database := newMigratedTestDB(t)
ctx := context.Background()
id := installTestPlugin(t, database, "hello")
other := installTestPlugin(t, database, "other")
for k, v := range map[string]string{
"cfg:a": "1",
"cfg:b": "2",
"state:x": "3",
} {
if err := database.PluginKVSet(ctx, id, k, []byte(v)); err != nil {
t.Fatalf("PluginKVSet(%s): %v", k, err)
}
}
if err := database.PluginKVSet(ctx, other, "cfg:a", []byte("nope")); err != nil {
t.Fatalf("PluginKVSet other: %v", err)
}
got, err := database.PluginKVScan(ctx, id, "cfg:", 100)
if err != nil {
t.Fatalf("PluginKVScan: %v", err)
}
if len(got) != 2 {
t.Fatalf("PluginKVScan returned %d entries, want 2: %v", len(got), got)
}
if !bytes.Equal(got["cfg:a"], []byte("1")) || !bytes.Equal(got["cfg:b"], []byte("2")) {
t.Errorf("PluginKVScan = %v, want cfg:a=1 and cfg:b=2 from this plugin only", got)
}
limited, err := database.PluginKVScan(ctx, id, "cfg:", 1)
if err != nil {
t.Fatalf("PluginKVScan with limit: %v", err)
}
if len(limited) != 1 {
t.Errorf("PluginKVScan with limit 1 returned %d entries, want 1", len(limited))
}
none, err := database.PluginKVScan(ctx, id, "nomatch:", 100)
if err != nil {
t.Fatalf("PluginKVScan no match: %v", err)
}
if len(none) != 0 {
t.Errorf("PluginKVScan with a non-matching prefix = %v, want empty", none)
}
}