mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
* fix(client): 3 defect(s) (OC-0037, OC-0063, OC-0116) Route the tray Status submenu through saveUserStatus() (mapping the legacy "offline" to "invisible") so notifications, autoIdle, and reconnect presence restore all agree with the tray's choice; build the connected overlay from the auth_ok payload instead of a pre-dispatch authStore snapshot; keep the TOTP overlay open across a rejected verify (totpPending latch) and retain the partial token for the retry instead of clearing it in finally. Hand-applied combined cluster preserved from the previous fix run's overlap-guard block (both clusters edit main.ts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(voice): 2 defect(s) (OC-0010, OC-0011) * fix(ws): 1 defect(s) (OC-0050) * fix(db): 1 defect(s) (OC-0052) * fix(client): 1 defect(s) (OC-0054) * fix(client): 1 defect(s) (OC-0059) * fix(auth): 1 defect(s) (OC-0061) * fix(ws): 1 defect(s) (OC-0062) * fix(client): 1 defect(s) (OC-0064) * fix(service): 1 defect(s) (OC-0070) * fix(ws): 1 defect(s) (OC-0073) * fix(service): 2 defect(s) (OC-0075, OC-0120) * fix(admin): 1 defect(s) (OC-0076) * fix(voice): 1 defect(s) (OC-0084) * fix(client): 2 defect(s) (OC-0085, OC-0094) Scope collapsed-category persistence to the connected host instead of the server display name, and stop the DM back button from jumping to the first text channel when DM mode was entered without recording channelBeforeDm. * fix(service): 1 defect(s) (OC-0087) * fix(client): 1 defect(s) (OC-0089) * fix(ws): 1 defect(s) (OC-0091) * fix(api): 1 defect(s) (OC-0093) * fix(identity): 1 defect(s) (OC-0118) * fix(dm): 1 defect(s) (OC-0119) * fix(voice): 1 defect(s) (OC-0135) * fix(api): 1 defect(s) (OC-0137) * fix(client): 1 defect(s) (OC-0142) * fix(client): 1 defect(s) (OC-0144) * fix(admin): 1 defect(s) (OC-0145) * fix(updater): 1 defect(s) (OC-0146) * fix(client): 1 defect(s) (OC-0150) * fix(mentions): 1 defect(s) (OC-0131) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
66 lines
2.3 KiB
Go
66 lines
2.3 KiB
Go
package admin_test
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/owncord/server/admin"
|
|
"github.com/owncord/server/auth"
|
|
)
|
|
|
|
// TestSetupLimiter_ReapsStaleEntries pins OC-0076: setupLimiter — the
|
|
// dedicated auth.RateLimiter behind POST /setup — is never reaped, so a
|
|
// distinct one-shot source IP (the common case once the server is already
|
|
// configured: every unauthenticated caller 403s but still records a rate
|
|
// limit entry before the CreateOwnerIfEmpty check rejects them) leaves a
|
|
// windows[] entry that lives forever. Unlike a repeat caller, whose entry
|
|
// self-prunes on its next Allow() call, a one-shot caller never revisits its
|
|
// key, so only a periodic sweep (RateLimiter.Cleanup) can ever evict it.
|
|
func TestSetupLimiter_ReapsStaleEntries(t *testing.T) {
|
|
restoreTiming := admin.SetSetupLimiterReapTiming(5*time.Millisecond, 5*time.Millisecond)
|
|
defer restoreTiming()
|
|
|
|
var limiter *auth.RateLimiter
|
|
restoreHook := admin.CaptureSetupLimiter(func(rl *auth.RateLimiter) { limiter = rl })
|
|
defer restoreHook()
|
|
|
|
database := openAdminTestDB(t)
|
|
handler := admin.NewAdminAPI(database, "1.0.0", &mockHub{}, nil, nil, nil, nil, newTestModService(database), newTestRoleService(database))
|
|
|
|
if limiter == nil {
|
|
t.Fatal("setup limiter was not captured — CaptureSetupLimiter hook not wired into NewAdminAPI")
|
|
}
|
|
|
|
// Simulate 20 distinct source IPs each making one POST /setup request —
|
|
// each leaves its own windows[] entry that nothing but a reap can evict.
|
|
const n = 20
|
|
for i := range n {
|
|
req := httptest.NewRequest(http.MethodPost, "/setup", strings.NewReader(`{}`))
|
|
req.RemoteAddr = fmt.Sprintf("203.0.113.%d:1234", i)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
}
|
|
|
|
if wins, _ := limiter.Len(); wins != n {
|
|
t.Fatalf("Len().windows = %d immediately after %d one-shot requests, want %d", wins, n, n)
|
|
}
|
|
|
|
// Wait well past the (shrunk) reap interval + max window for the sweep
|
|
// to evict every now-stale entry.
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for {
|
|
wins, _ := limiter.Len()
|
|
if wins == 0 {
|
|
return
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("Len().windows = %d after waiting past the reap interval, want 0 — setupLimiter is never reaped (OC-0076)", wins)
|
|
}
|
|
time.Sleep(5 * time.Millisecond)
|
|
}
|
|
}
|