- Tests hash passwords at bcrypt.MinCost, set through the PasswordCost field of db.Params. - Test IRC servers check the queue only on broker notification; one test keeps the 100 ms fallback poll. - The stop-timeout test shortens the timeout through a test-only setter. - service and handlers tests build config.Config directly instead of through config.New, so they run in parallel: service had run serially, and handlers started its servers one at a time. - Repeated race trials run as parallel subtests. - Fixed sleeps become waits on the broker. - A new test covers the relay's failed queue read, which only the 100 ms poll had reached. Model: opus-5-5
This commit is contained in:
1 parent
bc8aa6bb96
commit
d39e0f2bb5
12 files changed
+362
-209
No files matched your search
@@ -145,13 +145,6 @@ type testServer struct {
|
||||
handlers *handlers.Handlers
|
||||
}
|
||||
|
||||
// viperMu makes tests start their servers one at a time,
|
||||
// because config.New writes the global viper instance and
|
||||
// the server reads it as it starts.
|
||||
//
|
||||
//nolint:gochecknoglobals // guards a global, so must be one
|
||||
var viperMu sync.Mutex
|
||||
|
||||
func newTestServer(
|
||||
t *testing.T,
|
||||
) *testServer {
|
||||
@@ -167,9 +160,6 @@ func newTestServerWith(
|
||||
) *testServer {
|
||||
t.Helper()
|
||||
|
||||
viperMu.Lock()
|
||||
defer viperMu.Unlock()
|
||||
|
||||
dbURL := dbtest.NewURL(t)
|
||||
copyTemplateDB(t, dbURL)
|
||||
|
||||
@@ -184,31 +174,20 @@ func newTestServerWith(
|
||||
fx.Provide(
|
||||
newTestGlobals,
|
||||
logger.New,
|
||||
func(
|
||||
lifecycle fx.Lifecycle,
|
||||
globs *globals.Globals,
|
||||
log *logger.Logger,
|
||||
) (*config.Config, error) {
|
||||
cfg, err := config.New(
|
||||
lifecycle, config.Params{ //nolint:exhaustruct
|
||||
Globals: globs, Logger: log,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"test config: %w", err,
|
||||
)
|
||||
// Built here rather than by config.New, which writes
|
||||
// the global viper instance, so servers start in
|
||||
// parallel. Settings left out take their defaults.
|
||||
func() *config.Config {
|
||||
return &config.Config{
|
||||
DBURL: dbURL,
|
||||
// One line: each line is a database write per new session.
|
||||
MOTD: "neoirc test server",
|
||||
HashcashBits: hashcashBits,
|
||||
OperName: operName,
|
||||
OperPassword: operPassword,
|
||||
MessageMaxAge: "720h",
|
||||
QueueMaxAge: "720h",
|
||||
}
|
||||
|
||||
cfg.DBURL = dbURL
|
||||
cfg.Port = 0
|
||||
// One line: each line is a database write per new session.
|
||||
cfg.MOTD = "neoirc test server"
|
||||
cfg.HashcashBits = hashcashBits
|
||||
cfg.OperName = operName
|
||||
cfg.OperPassword = operPassword
|
||||
|
||||
return cfg, nil
|
||||
},
|
||||
newTestDB,
|
||||
broker.New,
|
||||
@@ -255,7 +234,7 @@ func newTestDB(
|
||||
cfg *config.Config,
|
||||
) (*db.Database, error) {
|
||||
database, err := db.New(lifecycle, db.Params{ //nolint:exhaustruct
|
||||
Logger: log, Config: cfg,
|
||||
Logger: log, Config: cfg, PasswordCost: bcrypt.MinCost,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("test db: %w", err)
|
||||
@@ -1750,52 +1729,67 @@ func TestConcurrentQuitsSendOneQuit(t *testing.T) {
|
||||
// that the second session then holds one QUIT from the
|
||||
// first, and that the first session is deleted: its tokens
|
||||
// are refused and its nick is free. It does all this
|
||||
// quitRaceTrials times, each on a new server, because each
|
||||
// trial logs in and logins are rate limited.
|
||||
// quitRaceTrials times, at once, each on a new server, because
|
||||
// each trial logs in and logins are rate limited.
|
||||
func checkQuitRace(
|
||||
t *testing.T,
|
||||
leave func(tserver *testServer, tokens []string),
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
for trial := range quitRaceTrials {
|
||||
t.Run(fmt.Sprintf("trial %d", trial), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkQuitRaceOnce(t, newTestServer(t), leave)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// checkQuitRaceOnce runs one trial of checkQuitRace on
|
||||
// tserver.
|
||||
func checkQuitRaceOnce(
|
||||
t *testing.T,
|
||||
tserver *testServer,
|
||||
leave func(tserver *testServer, tokens []string),
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
const nick = "racingquitter"
|
||||
|
||||
for trial := range quitRaceTrials {
|
||||
tserver := newTestServer(t)
|
||||
firstToken := tserver.createSession(nick)
|
||||
observerToken := tserver.createSession("raceobserver")
|
||||
firstToken := tserver.createSession(nick)
|
||||
observerToken := tserver.createSession("raceobserver")
|
||||
|
||||
tserver.setPassword(nick)
|
||||
tserver.setPassword(nick)
|
||||
|
||||
tokens := []string{firstToken, tserver.login(nick)}
|
||||
tokens := []string{firstToken, tserver.login(nick)}
|
||||
|
||||
for _, token := range []string{firstToken, observerToken} {
|
||||
tserver.sendCommand(token, map[string]any{
|
||||
commandKey: joinCmd, toKey: "#quitrace",
|
||||
})
|
||||
}
|
||||
|
||||
_, lastID := tserver.pollMessages(observerToken, 0)
|
||||
|
||||
leave(tserver, tokens)
|
||||
|
||||
msgs, _ := tserver.pollMessages(observerToken, lastID)
|
||||
|
||||
quits := countQuits(msgs, nick)
|
||||
if quits != 1 {
|
||||
t.Errorf("trial %d: %d QUITs, want 1: %v", trial, quits, msgs)
|
||||
}
|
||||
|
||||
for _, token := range tokens {
|
||||
status, _ := tserver.getState(token)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("trial %d: state got %d, want 401", trial, status)
|
||||
}
|
||||
}
|
||||
|
||||
// createSession fails the test if the nick is taken.
|
||||
tserver.createSession(nick)
|
||||
for _, token := range []string{firstToken, observerToken} {
|
||||
tserver.sendCommand(token, map[string]any{
|
||||
commandKey: joinCmd, toKey: "#quitrace",
|
||||
})
|
||||
}
|
||||
|
||||
_, lastID := tserver.pollMessages(observerToken, 0)
|
||||
|
||||
leave(tserver, tokens)
|
||||
|
||||
msgs, _ := tserver.pollMessages(observerToken, lastID)
|
||||
|
||||
quits := countQuits(msgs, nick)
|
||||
if quits != 1 {
|
||||
t.Errorf("%d QUITs, want 1: %v", quits, msgs)
|
||||
}
|
||||
|
||||
for _, token := range tokens {
|
||||
status, _ := tserver.getState(token)
|
||||
if status != http.StatusUnauthorized {
|
||||
t.Errorf("state got %d, want 401", status)
|
||||
}
|
||||
}
|
||||
|
||||
// createSession fails the test if the nick is taken.
|
||||
tserver.createSession(nick)
|
||||
}
|
||||
|
||||
// TestLogoutOfOneClientKeepsSession checks that when one of
|
||||
@@ -1933,11 +1927,28 @@ const (
|
||||
// return once the session has left.
|
||||
quitWakeDeadline = 5 * time.Second
|
||||
|
||||
// pollWaitStep is how often checkQuitWakesPoll checks
|
||||
// pollWaitStep is how often waitForLongPoll checks
|
||||
// whether the long poll is waiting yet.
|
||||
pollWaitStep = 10 * time.Millisecond
|
||||
)
|
||||
|
||||
// waitForLongPoll waits until a long poll of the session
|
||||
// sessionID is waiting, and fails the test if none is within
|
||||
// quitWakeDeadline.
|
||||
func (tserver *testServer) waitForLongPoll(sessionID int64) {
|
||||
tserver.t.Helper()
|
||||
|
||||
deadline := time.Now().Add(quitWakeDeadline)
|
||||
|
||||
for tserver.broker.ListenerCount(sessionID) == 0 {
|
||||
if time.Now().After(deadline) {
|
||||
tserver.t.Fatal("the long poll did not start waiting")
|
||||
}
|
||||
|
||||
time.Sleep(pollWaitStep)
|
||||
}
|
||||
}
|
||||
|
||||
// TestQuitWakesPeerLongPoll checks that a session's QUIT
|
||||
// wakes the long poll of a user who shares a channel with
|
||||
// it.
|
||||
@@ -2012,15 +2023,7 @@ func checkQuitWakesPoll(
|
||||
msgs, pollErr = tserver.longPoll(peerToken, lastID, quitWakePollTimeout)
|
||||
}()
|
||||
|
||||
deadline := time.Now().Add(quitWakeDeadline)
|
||||
|
||||
for tserver.broker.ListenerCount(peerID) == 0 {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("the long poll did not start waiting")
|
||||
}
|
||||
|
||||
time.Sleep(pollWaitStep)
|
||||
}
|
||||
tserver.waitForLongPoll(peerID)
|
||||
|
||||
leave(tserver, token)
|
||||
|
||||
@@ -2850,7 +2853,12 @@ func TestLongPoll(t *testing.T) {
|
||||
pollMsgs = result.Messages
|
||||
}()
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
bobID, err := tserver.database.GetSessionByNick(t.Context(), "lp_bob")
|
||||
if err != nil {
|
||||
t.Fatalf("get session of lp_bob: %v", err)
|
||||
}
|
||||
|
||||
tserver.waitForLongPoll(bobID)
|
||||
|
||||
tserver.sendCommand(aliceToken, map[string]any{
|
||||
commandKey: privmsgCmd,
|
||||
|
||||
Reference in new issue
Block a user