mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
chore(server): delete production-dead code; move test helpers to export_test
Applied from a deadcode (RTA from mains, all build tags) sweep with
per-symbol adversarial verification:
Deleted (nothing but their own self-tests used them):
- admin.Handler (deprecated since Phase 6; production mounts NewHandler)
plus its two self-tests
- ws.Hub.broadcastVoiceStateUpdate + wrapper + two self-tests (pre-V2
leftover; the live voice_state path is the hub voice routines)
- ws.VoiceLeaveEvent + methods ('retained as scaffolding', never
constructed in production; MsgTypeVoiceLeaveBC stays — live via the
leave routine)
- ws.parseIdentity (production calls parseParticipantIdentity directly;
ParseIdentityForTest now exercises the real parser)
- telemetry.Float64 (String/Int64 are used; the float case is covered by
the otel-tagged internal test, re-addable when a caller appears)
Moved into export_test.go so they leave the production binary (all
callers are same-package tests): the eight ws test-client constructors
and voice/E2EE setters from ws/client.go, admin.SetBackupBaseDir
(new admin/export_test.go), api.SecurityHeaders (test-only wrapper;
production uses SecurityHeadersWithTLS — docs/api.md updated to the
real name). Client.getVoiceJoinToken/setVoiceChID inlined into their
existing ForTest wrappers; TestSetVoiceChID_* self-tests deleted.
Kept after verification: updater.SetBaseURL (11 cross-package test call
sites) and telemetry.resetAppMetricsForInit (live under -tags otel —
untagged deadcode false positive).
Full gate green: gofmt/vet, 4 build-tag variants, full suite, deadlock,
race.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,11 +56,3 @@ func NewHandler(database *db.DB, version string, hub HubBroadcaster, u *updater.
|
|||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler returns the admin panel http.Handler using a nil database.
|
|
||||||
//
|
|
||||||
// Deprecated: use NewHandler instead. Kept for backwards-compat with any
|
|
||||||
// caller that already imported this symbol before Phase 6.
|
|
||||||
func Handler() http.Handler {
|
|
||||||
return http.FileServer(http.FS(staticFiles))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -115,33 +115,6 @@ func TestNewHandler_WithUpdater(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Handler (deprecated) ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// TestHandler_ReturnsNonNil verifies the deprecated Handler() function returns
|
|
||||||
// a non-nil http.Handler (it serves the embedded static files).
|
|
||||||
func TestHandler_ReturnsNonNil(t *testing.T) {
|
|
||||||
h := admin.Handler()
|
|
||||||
if h == nil {
|
|
||||||
t.Fatal("Handler() returned nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestHandler_ServesEmbeddedFiles verifies that the deprecated Handler() serves
|
|
||||||
// a response (the embedded static FS) without panicking.
|
|
||||||
func TestHandler_ServesEmbeddedFiles(t *testing.T) {
|
|
||||||
h := admin.Handler()
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/index.html", nil)
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
// http.FileServer returns 200 for a found file or 301/404 for others;
|
|
||||||
// the important thing is it doesn't panic and returns a valid HTTP status.
|
|
||||||
if w.Code == 0 {
|
|
||||||
t.Error("Handler() response has zero status code")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── ownerOnlyMiddleware (tested via API endpoints that use it) ───────────────
|
// ─── ownerOnlyMiddleware (tested via API endpoints that use it) ───────────────
|
||||||
|
|
||||||
// TestOwnerOnlyMiddleware_OwnerAllowed verifies that a user with Owner role
|
// TestOwnerOnlyMiddleware_OwnerAllowed verifies that a user with Owner role
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
// SetBackupBaseDir overrides backupBaseDir so tests can point backup handlers
|
||||||
|
// at a temp dir. Lives here so it stays out of the production binary.
|
||||||
|
func SetBackupBaseDir(dir string) { backupBaseDir = dir }
|
||||||
@@ -28,9 +28,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBackupBaseDir overrides backupBaseDir. Intended for tests only.
|
|
||||||
func SetBackupBaseDir(dir string) { backupBaseDir = dir }
|
|
||||||
|
|
||||||
// ─── Backup Handlers ─────────────────────────────────────────────────────────
|
// ─── Backup Handlers ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func handleBackup(database *db.DB) http.Handler {
|
func handleBackup(database *db.DB) http.Handler {
|
||||||
|
|||||||
@@ -44,3 +44,9 @@ func SetGIFUpstreamForTest(baseURL string, client *http.Client) func() {
|
|||||||
gifAPIBase, gifClient = baseURL, client
|
gifAPIBase, gifClient = baseURL, client
|
||||||
return func() { gifAPIBase, gifClient = prevBase, prevClient }
|
return func() { gifAPIBase, gifClient = prevBase, prevClient }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecurityHeaders is SecurityHeadersWithTLS with TLS disabled (no HSTS).
|
||||||
|
// Test-only convenience — production always goes through SecurityHeadersWithTLS.
|
||||||
|
func SecurityHeaders(next http.Handler) http.Handler {
|
||||||
|
return SecurityHeadersWithTLS("")(next)
|
||||||
|
}
|
||||||
|
|||||||
@@ -375,12 +375,6 @@ func SecurityHeadersWithTLS(tlsMode string) func(http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecurityHeaders is a convenience wrapper for SecurityHeadersWithTLS with TLS
|
|
||||||
// disabled (no HSTS header). Kept for backwards compatibility with tests.
|
|
||||||
func SecurityHeaders(next http.Handler) http.Handler {
|
|
||||||
return SecurityHeadersWithTLS("")(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxBodySize wraps r.Body with http.MaxBytesReader so that reads beyond
|
// MaxBodySize wraps r.Body with http.MaxBytesReader so that reads beyond
|
||||||
// maxBytes return an error. This prevents clients from exhausting server memory
|
// maxBytes return an error. This prevents clients from exhausting server memory
|
||||||
// by sending arbitrarily large request bodies.
|
// by sending arbitrarily large request bodies.
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ func String(k, v string) Attr { return Attr{Key: k, Value: v} }
|
|||||||
// Int64 constructs an int64 attribute.
|
// Int64 constructs an int64 attribute.
|
||||||
func Int64(k string, v int64) Attr { return Attr{Key: k, Value: v} }
|
func Int64(k string, v int64) Attr { return Attr{Key: k, Value: v} }
|
||||||
|
|
||||||
// Float64 constructs a float64 attribute.
|
|
||||||
func Float64(k string, v float64) Attr { return Attr{Key: k, Value: v} }
|
|
||||||
|
|
||||||
// Span is a single tracing span.
|
// Span is a single tracing span.
|
||||||
type Span interface {
|
type Span interface {
|
||||||
End()
|
End()
|
||||||
|
|||||||
@@ -83,92 +83,6 @@ func (c *Client) GetTokenHash() string {
|
|||||||
return c.tokenHash
|
return c.tokenHash
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTestClient creates a client with a caller-supplied send channel.
|
|
||||||
// Intended for unit tests only — conn is nil.
|
|
||||||
func NewTestClient(hub *Hub, userID int64, send chan []byte) *Client {
|
|
||||||
return &Client{
|
|
||||||
hub: hub,
|
|
||||||
ctx: context.Background(),
|
|
||||||
userID: userID,
|
|
||||||
send: send,
|
|
||||||
sendHigh: send, // unified for test observability
|
|
||||||
sendLow: send,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTestClientWithChannel creates a test client subscribed to a specific channel.
|
|
||||||
func NewTestClientWithChannel(hub *Hub, userID, channelID int64, send chan []byte) *Client {
|
|
||||||
return &Client{
|
|
||||||
hub: hub,
|
|
||||||
ctx: context.Background(),
|
|
||||||
userID: userID,
|
|
||||||
channelID: channelID,
|
|
||||||
send: send,
|
|
||||||
sendHigh: send, // unified for test observability
|
|
||||||
sendLow: send,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTestClientWithUser creates a test client with an authenticated user record set.
|
|
||||||
// Use this when tests need the client to pass permission checks.
|
|
||||||
func NewTestClientWithUser(hub *Hub, user *db.User, channelID int64, send chan []byte) *Client {
|
|
||||||
return &Client{
|
|
||||||
hub: hub,
|
|
||||||
ctx: context.Background(),
|
|
||||||
userID: user.ID,
|
|
||||||
user: user,
|
|
||||||
channelID: channelID,
|
|
||||||
send: send,
|
|
||||||
sendHigh: send, // unified for test observability
|
|
||||||
sendLow: send,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClientVoiceChID sets the voiceChID field on a client. For test use only.
|
|
||||||
func SetClientVoiceChID(c *Client, channelID int64) {
|
|
||||||
c.voiceMu.Lock()
|
|
||||||
defer c.voiceMu.Unlock()
|
|
||||||
c.voiceChID = channelID
|
|
||||||
if channelID == 0 {
|
|
||||||
c.voiceJoinToken = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClientVoiceStateForTest sets both the voice channel and join token.
|
|
||||||
// For test use only.
|
|
||||||
func SetClientVoiceStateForTest(c *Client, channelID int64, joinToken string) {
|
|
||||||
c.voiceMu.Lock()
|
|
||||||
defer c.voiceMu.Unlock()
|
|
||||||
c.voiceChID = channelID
|
|
||||||
c.voiceJoinToken = joinToken
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClientE2EEPubKeyForTest sets the E2EE public key on a client. For test use only.
|
|
||||||
func SetClientE2EEPubKeyForTest(c *Client, key string) {
|
|
||||||
c.setE2EEPubKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClientE2EEPubKeyForTest returns the E2EE public key from a client. For test use only.
|
|
||||||
func GetClientE2EEPubKeyForTest(c *Client) string {
|
|
||||||
return c.getE2EEPubKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTestClientWithTokenHash creates a test client that carries a session token
|
|
||||||
// hash. Use this when tests need to exercise the periodic session-expiry check.
|
|
||||||
func NewTestClientWithTokenHash(hub *Hub, user *db.User, tokenHash string, channelID int64, send chan []byte) *Client {
|
|
||||||
return &Client{
|
|
||||||
hub: hub,
|
|
||||||
ctx: context.Background(),
|
|
||||||
userID: user.ID,
|
|
||||||
user: user,
|
|
||||||
tokenHash: tokenHash,
|
|
||||||
channelID: channelID,
|
|
||||||
send: send,
|
|
||||||
sendHigh: send, // unified for test observability
|
|
||||||
sendLow: send,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// touch updates the last activity timestamp and increments the received counter.
|
// touch updates the last activity timestamp and increments the received counter.
|
||||||
func (c *Client) touch() {
|
func (c *Client) touch() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -198,28 +112,12 @@ func (c *Client) getVoiceChID() int64 {
|
|||||||
return c.voiceChID
|
return c.voiceChID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getVoiceJoinToken() string {
|
|
||||||
c.voiceMu.Lock()
|
|
||||||
defer c.voiceMu.Unlock()
|
|
||||||
return c.voiceJoinToken
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) getVoiceState() (int64, string) {
|
func (c *Client) getVoiceState() (int64, string) {
|
||||||
c.voiceMu.Lock()
|
c.voiceMu.Lock()
|
||||||
defer c.voiceMu.Unlock()
|
defer c.voiceMu.Unlock()
|
||||||
return c.voiceChID, c.voiceJoinToken
|
return c.voiceChID, c.voiceJoinToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// setVoiceChID sets the voice channel ID atomically.
|
|
||||||
func (c *Client) setVoiceChID(chID int64) {
|
|
||||||
c.voiceMu.Lock()
|
|
||||||
defer c.voiceMu.Unlock()
|
|
||||||
c.voiceChID = chID
|
|
||||||
if chID == 0 {
|
|
||||||
c.voiceJoinToken = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) setVoiceState(chID int64, joinToken string) {
|
func (c *Client) setVoiceState(chID int64, joinToken string) {
|
||||||
c.voiceMu.Lock()
|
c.voiceMu.Lock()
|
||||||
defer c.voiceMu.Unlock()
|
defer c.voiceMu.Unlock()
|
||||||
|
|||||||
@@ -162,75 +162,6 @@ func TestBuildDMChannelOpen_NilAvatar(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── broadcastVoiceStateUpdate ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
func TestBroadcastVoiceStateUpdate_NotInVoice(t *testing.T) {
|
|
||||||
hub, database := newCoverageHub(t)
|
|
||||||
user := seedCoverageOwner(t, database, "bvsu-noop")
|
|
||||||
send := make(chan []byte, 16)
|
|
||||||
c := ws.NewTestClientWithUser(hub, user, 0, send)
|
|
||||||
hub.Register(c)
|
|
||||||
time.Sleep(20 * time.Millisecond)
|
|
||||||
|
|
||||||
// User not in voice — should be a no-op.
|
|
||||||
hub.BroadcastVoiceStateUpdateForTest(c)
|
|
||||||
time.Sleep(20 * time.Millisecond)
|
|
||||||
|
|
||||||
// No voice_state message should have been sent since user is not in voice.
|
|
||||||
for len(send) > 0 {
|
|
||||||
msg := <-send
|
|
||||||
var m struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
_ = json.Unmarshal(msg, &m)
|
|
||||||
if m.Type == "voice_state" {
|
|
||||||
t.Error("expected no voice_state broadcast when user is not in voice")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBroadcastVoiceStateUpdate_InVoice(t *testing.T) {
|
|
||||||
hub, database := newCoverageHub(t)
|
|
||||||
user := seedCoverageOwner(t, database, "bvsu-voice")
|
|
||||||
|
|
||||||
// Create a voice channel.
|
|
||||||
chanID, err := database.CreateChannel("bvsu-ch", "voice", "", "", 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateChannel: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join the voice channel in DB.
|
|
||||||
if err := database.JoinVoiceChannel(user.ID, chanID); err != nil {
|
|
||||||
t.Fatalf("JoinVoiceChannel: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
send := make(chan []byte, 16)
|
|
||||||
c := ws.NewTestClientWithUser(hub, user, 0, send)
|
|
||||||
ws.SetClientVoiceChID(c, chanID)
|
|
||||||
hub.Register(c)
|
|
||||||
time.Sleep(20 * time.Millisecond)
|
|
||||||
|
|
||||||
// Should broadcast a voice_state message.
|
|
||||||
hub.BroadcastVoiceStateUpdateForTest(c)
|
|
||||||
|
|
||||||
// Drain the channel and check for voice_state message.
|
|
||||||
time.Sleep(20 * time.Millisecond)
|
|
||||||
found := false
|
|
||||||
for len(send) > 0 {
|
|
||||||
msg := <-send
|
|
||||||
var m struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
_ = json.Unmarshal(msg, &m)
|
|
||||||
if m.Type == "voice_state" {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
t.Error("expected voice_state broadcast")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── handleVoiceMute via HandleMessageForTest ───────────────────────────────
|
// ─── handleVoiceMute via HandleMessageForTest ───────────────────────────────
|
||||||
|
|
||||||
func TestHandleVoiceMute_NotInVoice2(t *testing.T) {
|
func TestHandleVoiceMute_NotInVoice2(t *testing.T) {
|
||||||
|
|||||||
@@ -2300,43 +2300,6 @@ func TestGetLastActivity_MultipleTouch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── setVoiceChID (client.go:186) ───────────────────────────────────────────
|
|
||||||
|
|
||||||
func TestSetVoiceChID_SetsAndGetsValue(t *testing.T) {
|
|
||||||
hub, _ := newCoverageHub(t)
|
|
||||||
send := make(chan []byte, 4)
|
|
||||||
c := ws.NewTestClient(hub, 1, send)
|
|
||||||
|
|
||||||
ws.SetVoiceChIDForTest(c, 77)
|
|
||||||
if got := ws.GetClientVoiceChIDForTest(c); got != 77 {
|
|
||||||
t.Fatalf("voiceChID = %d, want 77", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetVoiceChID_OverwritesPreviousValue(t *testing.T) {
|
|
||||||
hub, _ := newCoverageHub(t)
|
|
||||||
send := make(chan []byte, 4)
|
|
||||||
c := ws.NewTestClient(hub, 1, send)
|
|
||||||
|
|
||||||
ws.SetVoiceChIDForTest(c, 10)
|
|
||||||
ws.SetVoiceChIDForTest(c, 20)
|
|
||||||
if got := ws.GetClientVoiceChIDForTest(c); got != 20 {
|
|
||||||
t.Fatalf("voiceChID = %d, want 20", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetVoiceChID_ZeroMeansNotInVoice(t *testing.T) {
|
|
||||||
hub, _ := newCoverageHub(t)
|
|
||||||
send := make(chan []byte, 4)
|
|
||||||
c := ws.NewTestClient(hub, 1, send)
|
|
||||||
|
|
||||||
ws.SetVoiceChIDForTest(c, 50)
|
|
||||||
ws.SetVoiceChIDForTest(c, 0)
|
|
||||||
if got := ws.GetClientVoiceChIDForTest(c); got != 0 {
|
|
||||||
t.Fatalf("voiceChID = %d, want 0", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── clearVoiceChID (client.go:203) ─────────────────────────────────────────
|
// ─── clearVoiceChID (client.go:203) ─────────────────────────────────────────
|
||||||
|
|
||||||
func TestClearVoiceChID_ReturnsOldValueAndClearsToZero(t *testing.T) {
|
func TestClearVoiceChID_ReturnsOldValueAndClearsToZero(t *testing.T) {
|
||||||
|
|||||||
@@ -259,17 +259,6 @@ type VoiceStateEvent struct {
|
|||||||
func (e VoiceStateEvent) EventType() string { return MsgTypeVoiceState }
|
func (e VoiceStateEvent) EventType() string { return MsgTypeVoiceState }
|
||||||
func (e VoiceStateEvent) Payload() []byte { return e.payload }
|
func (e VoiceStateEvent) Payload() []byte { return e.payload }
|
||||||
|
|
||||||
// VoiceLeaveEvent is a voice_leave broadcast to all connected clients.
|
|
||||||
// NOTE: Currently unused — the voice_leave V2 handler triggers the hub's
|
|
||||||
// handleVoiceLeave routine (via Result.LeaveVoice), which broadcasts the leave
|
|
||||||
// directly. Retained as forward-compatible scaffolding.
|
|
||||||
type VoiceLeaveEvent struct {
|
|
||||||
payload []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e VoiceLeaveEvent) EventType() string { return MsgTypeVoiceLeaveBC }
|
|
||||||
func (e VoiceLeaveEvent) Payload() []byte { return e.payload }
|
|
||||||
|
|
||||||
// PluginBroadcastEvent is a plugin slash-command result broadcast to a channel
|
// PluginBroadcastEvent is a plugin slash-command result broadcast to a channel
|
||||||
// (sequenced, replayable). Emitted by the chat_command handler after the
|
// (sequenced, replayable). Emitted by the chat_command handler after the
|
||||||
// invoking user's post permission is verified.
|
// invoking user's post permission is verified.
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ func TestEventTypes(t *testing.T) {
|
|||||||
{"ReactionChannelEvent", ReactionChannelEvent{}, MsgTypeReactionUpdate},
|
{"ReactionChannelEvent", ReactionChannelEvent{}, MsgTypeReactionUpdate},
|
||||||
{"ReactionDMEvent", ReactionDMEvent{}, MsgTypeReactionUpdate},
|
{"ReactionDMEvent", ReactionDMEvent{}, MsgTypeReactionUpdate},
|
||||||
{"VoiceStateEvent", VoiceStateEvent{}, MsgTypeVoiceState},
|
{"VoiceStateEvent", VoiceStateEvent{}, MsgTypeVoiceState},
|
||||||
{"VoiceLeaveEvent", VoiceLeaveEvent{}, MsgTypeVoiceLeaveBC},
|
|
||||||
{"VoiceE2EEAnnounceEvent", VoiceE2EEAnnounceEvent{}, MsgTypeVoiceE2EEAnnounceBC},
|
{"VoiceE2EEAnnounceEvent", VoiceE2EEAnnounceEvent{}, MsgTypeVoiceE2EEAnnounceBC},
|
||||||
{"VoiceE2EEOfferGuardedEvent", VoiceE2EEOfferGuardedEvent{}, MsgTypeVoiceE2EEOfferRelay},
|
{"VoiceE2EEOfferGuardedEvent", VoiceE2EEOfferGuardedEvent{}, MsgTypeVoiceE2EEOfferRelay},
|
||||||
{"DMChannelOpenEvent", DMChannelOpenEvent{}, MsgTypeDMChannelOpen},
|
{"DMChannelOpenEvent", DMChannelOpenEvent{}, MsgTypeDMChannelOpen},
|
||||||
@@ -207,7 +206,6 @@ func TestBroadcastAllEventInterface(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"PresenceEvent", PresenceEvent{payload: []byte("p")}},
|
{"PresenceEvent", PresenceEvent{payload: []byte("p")}},
|
||||||
{"VoiceStateEvent", VoiceStateEvent{payload: []byte("vs")}},
|
{"VoiceStateEvent", VoiceStateEvent{payload: []byte("vs")}},
|
||||||
{"VoiceLeaveEvent", VoiceLeaveEvent{payload: []byte("vl")}},
|
|
||||||
}
|
}
|
||||||
for _, tt := range events {
|
for _, tt := range events {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|||||||
+96
-11
@@ -49,9 +49,95 @@ func ClearVoiceChIDForTest(c *Client) int64 {
|
|||||||
return c.clearVoiceChID()
|
return c.clearVoiceChID()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetVoiceChIDForTest exposes Client.setVoiceChID for external tests.
|
// SetVoiceChIDForTest sets the voice channel ID atomically, clearing the join
|
||||||
|
// token when leaving (chID 0) — the same contract production keeps via
|
||||||
|
// setVoiceState. Test-only: production has no set-channel-without-token path.
|
||||||
func SetVoiceChIDForTest(c *Client, chID int64) {
|
func SetVoiceChIDForTest(c *Client, chID int64) {
|
||||||
c.setVoiceChID(chID)
|
c.voiceMu.Lock()
|
||||||
|
defer c.voiceMu.Unlock()
|
||||||
|
c.voiceChID = chID
|
||||||
|
if chID == 0 {
|
||||||
|
c.voiceJoinToken = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClientVoiceChID is an alias kept for existing tests.
|
||||||
|
func SetClientVoiceChID(c *Client, channelID int64) {
|
||||||
|
SetVoiceChIDForTest(c, channelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClientVoiceStateForTest sets both the voice channel and join token.
|
||||||
|
func SetClientVoiceStateForTest(c *Client, channelID int64, joinToken string) {
|
||||||
|
c.voiceMu.Lock()
|
||||||
|
defer c.voiceMu.Unlock()
|
||||||
|
c.voiceChID = channelID
|
||||||
|
c.voiceJoinToken = joinToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClientE2EEPubKeyForTest sets the E2EE public key on a client.
|
||||||
|
func SetClientE2EEPubKeyForTest(c *Client, key string) {
|
||||||
|
c.setE2EEPubKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClientE2EEPubKeyForTest returns the E2EE public key from a client.
|
||||||
|
func GetClientE2EEPubKeyForTest(c *Client) string {
|
||||||
|
return c.getE2EEPubKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTestClient creates a client with a caller-supplied send channel; conn is nil.
|
||||||
|
func NewTestClient(hub *Hub, userID int64, send chan []byte) *Client {
|
||||||
|
return &Client{
|
||||||
|
hub: hub,
|
||||||
|
ctx: context.Background(),
|
||||||
|
userID: userID,
|
||||||
|
send: send,
|
||||||
|
sendHigh: send, // unified for test observability
|
||||||
|
sendLow: send,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTestClientWithChannel creates a test client subscribed to a specific channel.
|
||||||
|
func NewTestClientWithChannel(hub *Hub, userID, channelID int64, send chan []byte) *Client {
|
||||||
|
return &Client{
|
||||||
|
hub: hub,
|
||||||
|
ctx: context.Background(),
|
||||||
|
userID: userID,
|
||||||
|
channelID: channelID,
|
||||||
|
send: send,
|
||||||
|
sendHigh: send, // unified for test observability
|
||||||
|
sendLow: send,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTestClientWithUser creates a test client with an authenticated user record
|
||||||
|
// set. Use this when tests need the client to pass permission checks.
|
||||||
|
func NewTestClientWithUser(hub *Hub, user *db.User, channelID int64, send chan []byte) *Client {
|
||||||
|
return &Client{
|
||||||
|
hub: hub,
|
||||||
|
ctx: context.Background(),
|
||||||
|
userID: user.ID,
|
||||||
|
user: user,
|
||||||
|
channelID: channelID,
|
||||||
|
send: send,
|
||||||
|
sendHigh: send, // unified for test observability
|
||||||
|
sendLow: send,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTestClientWithTokenHash creates a test client that carries a session token
|
||||||
|
// hash. Use this when tests need to exercise the periodic session-expiry check.
|
||||||
|
func NewTestClientWithTokenHash(hub *Hub, user *db.User, tokenHash string, channelID int64, send chan []byte) *Client {
|
||||||
|
return &Client{
|
||||||
|
hub: hub,
|
||||||
|
ctx: context.Background(),
|
||||||
|
userID: user.ID,
|
||||||
|
user: user,
|
||||||
|
tokenHash: tokenHash,
|
||||||
|
channelID: channelID,
|
||||||
|
send: send,
|
||||||
|
sendHigh: send, // unified for test observability
|
||||||
|
sendLow: send,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TouchForTest exposes Client.touch for external tests.
|
// TouchForTest exposes Client.touch for external tests.
|
||||||
@@ -138,9 +224,11 @@ func GetClientVoiceChIDForTest(c *Client) int64 {
|
|||||||
return c.getVoiceChID()
|
return c.getVoiceChID()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClientVoiceJoinTokenForTest exposes Client.getVoiceJoinToken.
|
// GetClientVoiceJoinTokenForTest reads the join token under voiceMu.
|
||||||
func GetClientVoiceJoinTokenForTest(c *Client) string {
|
func GetClientVoiceJoinTokenForTest(c *Client) string {
|
||||||
return c.getVoiceJoinToken()
|
c.voiceMu.Lock()
|
||||||
|
defer c.voiceMu.Unlock()
|
||||||
|
return c.voiceJoinToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpireSettingsCacheForTest forces the settings cache to appear stale so that
|
// ExpireSettingsCacheForTest forces the settings cache to appear stale so that
|
||||||
@@ -161,9 +249,11 @@ func BuildJSONForTest(v any) []byte {
|
|||||||
return buildJSON(v)
|
return buildJSON(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseIdentityForTest exposes parseIdentity for external tests.
|
// ParseIdentityForTest parses a LiveKit participant identity and discards the
|
||||||
|
// join token, exercising the production parseParticipantIdentity.
|
||||||
func ParseIdentityForTest(identity string) (int64, error) {
|
func ParseIdentityForTest(identity string) (int64, error) {
|
||||||
return parseIdentity(identity)
|
userID, _, err := parseParticipantIdentity(identity)
|
||||||
|
return userID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseParticipantIdentityForTest exposes parseParticipantIdentity for tests.
|
// ParseParticipantIdentityForTest exposes parseParticipantIdentity for tests.
|
||||||
@@ -202,11 +292,6 @@ func BuildDMChannelOpenForTest(channelID int64, recipient *db.User) []byte {
|
|||||||
return buildDMChannelOpen(channelID, recipient)
|
return buildDMChannelOpen(channelID, recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BroadcastVoiceStateUpdateForTest exposes broadcastVoiceStateUpdate for external tests.
|
|
||||||
func (h *Hub) BroadcastVoiceStateUpdateForTest(c *Client) {
|
|
||||||
h.broadcastVoiceStateUpdate(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleWebhookParticipantLeftForTest exposes handleWebhookParticipantLeft for
|
// HandleWebhookParticipantLeftForTest exposes handleWebhookParticipantLeft for
|
||||||
// external tests so they can simulate LiveKit webhook events without HTTP.
|
// external tests so they can simulate LiveKit webhook events without HTTP.
|
||||||
func (h *Hub) HandleWebhookParticipantLeftForTest(userID int64, channelID int64, joinToken string) {
|
func (h *Hub) HandleWebhookParticipantLeftForTest(userID int64, channelID int64, joinToken string) {
|
||||||
|
|||||||
@@ -89,12 +89,6 @@ func parseParticipantIdentity(identity string) (int64, string, error) {
|
|||||||
return userID, joinToken, nil
|
return userID, joinToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseIdentity extracts a user ID from a LiveKit participant identity.
|
|
||||||
func parseIdentity(identity string) (int64, error) {
|
|
||||||
userID, _, err := parseParticipantIdentity(identity)
|
|
||||||
return userID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseRoomChannelID extracts a channel ID from a LiveKit room name
|
// parseRoomChannelID extracts a channel ID from a LiveKit room name
|
||||||
// formatted as "channel-{id}".
|
// formatted as "channel-{id}".
|
||||||
func parseRoomChannelID(roomName string) (int64, error) {
|
func parseRoomChannelID(roomName string) (int64, error) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package ws
|
package ws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,18 +32,3 @@ func qualityBitrate(quality string) int {
|
|||||||
}
|
}
|
||||||
return voiceQualities["medium"]
|
return voiceQualities["medium"]
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcastVoiceStateUpdate fetches the current voice state for the client
|
|
||||||
// and broadcasts it to all members of the voice channel they are in.
|
|
||||||
func (h *Hub) broadcastVoiceStateUpdate(c *Client) {
|
|
||||||
state, err := h.db.GetVoiceState(c.userID)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("ws broadcastVoiceStateUpdate GetVoiceState", "err", err, "user_id", c.userID)
|
|
||||||
c.sendMsg(buildErrorMsg(ErrCodeInternal, "failed to broadcast voice state update"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if state == nil {
|
|
||||||
return // user not in a voice channel — nothing to broadcast
|
|
||||||
}
|
|
||||||
h.BroadcastToAll(buildVoiceState(*state))
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ All authenticated endpoints require a session token delivered via the `Authoriza
|
|||||||
1. **RequestID** -- assigns a unique `X-Request-Id` response header.
|
1. **RequestID** -- assigns a unique `X-Request-Id` response header.
|
||||||
2. **Recoverer** -- catches panics and returns 500.
|
2. **Recoverer** -- catches panics and returns 500.
|
||||||
3. **Request Logger** -- structured logging of method, path, status, duration.
|
3. **Request Logger** -- structured logging of method, path, status, duration.
|
||||||
4. **SecurityHeaders** -- sets `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `X-XSS-Protection: 0`, `Referrer-Policy: strict-origin-when-cross-origin`, `Content-Security-Policy: default-src 'self'`, `Permissions-Policy: camera=(), microphone=(), geolocation=()`, `Cache-Control: no-store`.
|
4. **SecurityHeadersWithTLS** -- (adds `Strict-Transport-Security` when TLS is on) sets `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `X-XSS-Protection: 0`, `Referrer-Policy: strict-origin-when-cross-origin`, `Content-Security-Policy: default-src 'self'`, `Permissions-Policy: camera=(), microphone=(), geolocation=()`, `Cache-Control: no-store`.
|
||||||
5. **MaxBodySize** -- 1 MiB default for all routes except `/api/v1/uploads` (which has its own 100 MiB limit).
|
5. **MaxBodySize** -- 1 MiB default for all routes except `/api/v1/uploads` (which has its own 100 MiB limit).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user