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
83 lines
3.2 KiB
Go
83 lines
3.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/owncord/server/db"
|
|
"github.com/owncord/server/permissions"
|
|
)
|
|
|
|
// PermissionService.RequireChannelAccess had no coverage. It is the single
|
|
// authorization gate that splits DM channels (membership check) from regular
|
|
// channels (role + override check); a wrong branch here either leaks a DM to a
|
|
// non-participant or locks legitimate users out of a channel.
|
|
|
|
func TestRequireChannelAccess_RegularChannel(t *testing.T) {
|
|
svc, database := newTestPermService(t)
|
|
seedChannel(t, database, &db.Channel{ID: 10, Name: "general", Type: "text"})
|
|
ctx := context.Background()
|
|
|
|
// The seeded Member role has SendMessages but not ManageChannels.
|
|
if err := svc.RequireChannelAccess(ctx, 1, "text", 10, permissions.SendMessages); err != nil {
|
|
t.Errorf("RequireChannelAccess with a granted permission = %v, want nil", err)
|
|
}
|
|
|
|
err := svc.RequireChannelAccess(ctx, 1, "text", 10, permissions.ManageChannels)
|
|
if !errors.Is(err, permissions.ErrPermissionDenied) {
|
|
t.Errorf("RequireChannelAccess with a missing permission = %v, want ErrPermissionDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestRequireChannelAccess_DMParticipant(t *testing.T) {
|
|
svc, database := newTestPermService(t)
|
|
seedChannel(t, database, &db.Channel{ID: 20, Name: "dm", Type: "dm"})
|
|
seedDMParticipant(t, database, 20, 1)
|
|
ctx := context.Background()
|
|
|
|
// For a DM the permission argument is irrelevant — membership decides.
|
|
// Pass a permission the Member role does not hold to prove that.
|
|
if err := svc.RequireChannelAccess(ctx, 1, "dm", 20, permissions.ManageChannels); err != nil {
|
|
t.Errorf("RequireChannelAccess for a DM participant = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func TestRequireChannelAccess_DMNonParticipant(t *testing.T) {
|
|
svc, database := newTestPermService(t)
|
|
seedChannel(t, database, &db.Channel{ID: 20, Name: "dm", Type: "dm"})
|
|
seedUserRole(t, database, 2, permissions.MemberRoleID)
|
|
seedDMParticipant(t, database, 20, 1)
|
|
ctx := context.Background()
|
|
|
|
// User 2 is not in the DM. Role permissions must not open it.
|
|
err := svc.RequireChannelAccess(ctx, 2, "dm", 20, permissions.ReadMessages)
|
|
if !errors.Is(err, permissions.ErrNotDMParticipant) {
|
|
t.Errorf("RequireChannelAccess for a non-participant = %v, want ErrNotDMParticipant", err)
|
|
}
|
|
}
|
|
|
|
func TestRequireChannelAccess_DMWithNoParticipantsDenies(t *testing.T) {
|
|
svc, database := newTestPermService(t)
|
|
seedChannel(t, database, &db.Channel{ID: 21, Name: "empty-dm", Type: "dm"})
|
|
ctx := context.Background()
|
|
|
|
err := svc.RequireChannelAccess(ctx, 1, "dm", 21, permissions.ReadMessages)
|
|
if !errors.Is(err, permissions.ErrNotDMParticipant) {
|
|
t.Errorf("RequireChannelAccess on a DM with no participants = %v, want ErrNotDMParticipant", err)
|
|
}
|
|
}
|
|
|
|
func TestRequireChannelAccess_UnknownUserDenied(t *testing.T) {
|
|
svc, database := newTestPermService(t)
|
|
seedChannel(t, database, &db.Channel{ID: 10, Name: "general", Type: "text"})
|
|
ctx := context.Background()
|
|
|
|
// A user with no row has no role, so getOrPopulate yields nothing and the
|
|
// check must fail closed.
|
|
err := svc.RequireChannelAccess(ctx, 9999, "text", 10, permissions.ReadMessages)
|
|
if !errors.Is(err, permissions.ErrPermissionDenied) {
|
|
t.Errorf("RequireChannelAccess for an unknown user = %v, want ErrPermissionDenied", err)
|
|
}
|
|
}
|