check / check (push) Waiting to run
The server binary carried exported names that only tests call, including raw SQL access and a way to weaken the password hash. Delete testing.go, GetDB and SetBcryptCost from the shipped code, so passwords always hash at bcrypt.DefaultCost. Move GetDB, GetOrCreateChannel, JoinChannel and HasChannelInvite into export_test.go. Delete GetClientHostInfo, whose remaining check GetLatestClientForSession covers. Tests in other packages now build the database with db.New and run raw SQL through dbtest.Exec. Handler tests and the relay tests' aliceWithHTTPClient helper need a password only to log in, so they store one hashed at bcrypt.MinCost. Tests of PASS itself keep the real cost. Model: opus-5-5
146 lines
3.0 KiB
Go
146 lines
3.0 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"log/slog"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/neoirc/internal/dbtest"
|
|
)
|
|
|
|
// NewTestDatabase creates an in-memory database for testing.
|
|
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(),
|
|
}
|
|
|
|
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
|
|
}
|