check / check (push) Failing after 3s
- Tests hash passwords at bcrypt.MinCost, set through the PasswordCost field of db.Params. TestPasswordCost checks the cost with and without it. - 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. config exports DefaultTrustedProxies and ParseTrustedProxies for the handlers test config. - 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
149 lines
3.1 KiB
Go
149 lines
3.1 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"log/slog"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
"sneak.berlin/go/neoirc/internal/dbtest"
|
|
)
|
|
|
|
// NewTestDatabase creates an in-memory database for testing. It
|
|
// hashes passwords at bcrypt.MinCost.
|
|
func NewTestDatabase(tb testing.TB) (*Database, error) {
|
|
tb.Helper()
|
|
|
|
dsn := dbtest.NewURL(tb) + "&_pragma=foreign_keys(1)"
|
|
|
|
conn, err := sql.Open("sqlite", dsn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open test db: %w", err)
|
|
}
|
|
|
|
database := &Database{ //nolint:exhaustruct // test helper, params not needed
|
|
conn: conn,
|
|
log: slog.Default(),
|
|
passwordCost: bcrypt.MinCost,
|
|
}
|
|
|
|
err = database.runMigrations(context.Background())
|
|
if err != nil {
|
|
closeErr := conn.Close()
|
|
if closeErr != nil {
|
|
return nil, fmt.Errorf(
|
|
"close after migration failure: %w",
|
|
closeErr,
|
|
)
|
|
}
|
|
|
|
return nil, fmt.Errorf(
|
|
"run test migrations: %w", err,
|
|
)
|
|
}
|
|
|
|
return database, nil
|
|
}
|
|
|
|
// Close closes the underlying database connection.
|
|
func (database *Database) Close() error {
|
|
err := database.conn.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("close database: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExecForTest runs a raw statement, such as a trigger that
|
|
// makes a write fail.
|
|
func (database *Database) ExecForTest(
|
|
ctx context.Context,
|
|
query string,
|
|
) error {
|
|
_, err := database.conn.ExecContext(ctx, query)
|
|
if err != nil {
|
|
return fmt.Errorf("exec for test: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetDB returns the underlying sql.DB connection.
|
|
func (database *Database) GetDB() *sql.DB {
|
|
return database.conn
|
|
}
|
|
|
|
// GetOrCreateChannel returns channel id, creating if needed.
|
|
// Uses INSERT OR IGNORE to avoid TOCTOU races.
|
|
func (database *Database) GetOrCreateChannel(
|
|
ctx context.Context,
|
|
name string,
|
|
) (int64, error) {
|
|
now := time.Now()
|
|
|
|
_, err := database.conn.ExecContext(ctx,
|
|
`INSERT OR IGNORE INTO channels
|
|
(name, created_at, updated_at)
|
|
VALUES (?, ?, ?)`,
|
|
name, now, now)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("create channel: %w", err)
|
|
}
|
|
|
|
var channelID int64
|
|
|
|
err = database.conn.QueryRowContext(
|
|
ctx,
|
|
"SELECT id FROM channels WHERE name = ?",
|
|
name,
|
|
).Scan(&channelID)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("get channel: %w", err)
|
|
}
|
|
|
|
return channelID, nil
|
|
}
|
|
|
|
// JoinChannel adds a session to a channel.
|
|
func (database *Database) JoinChannel(
|
|
ctx context.Context,
|
|
channelID, sessionID int64,
|
|
) error {
|
|
_, err := database.conn.ExecContext(ctx,
|
|
`INSERT OR IGNORE INTO channel_members
|
|
(channel_id, session_id, joined_at)
|
|
VALUES (?, ?, ?)`,
|
|
channelID, sessionID, time.Now())
|
|
if err != nil {
|
|
return fmt.Errorf("join channel: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasChannelInvite checks if a session has been invited
|
|
// to a channel.
|
|
func (database *Database) HasChannelInvite(
|
|
ctx context.Context,
|
|
channelID, sessionID int64,
|
|
) (bool, error) {
|
|
var count int
|
|
|
|
err := database.conn.QueryRowContext(ctx,
|
|
`SELECT COUNT(*) FROM channel_invites
|
|
WHERE channel_id = ? AND session_id = ?`,
|
|
channelID, sessionID,
|
|
).Scan(&count)
|
|
if err != nil {
|
|
return false, fmt.Errorf(
|
|
"check invite: %w", err,
|
|
)
|
|
}
|
|
|
|
return count > 0, nil
|
|
}
|