package db_test import ( "encoding/json" "testing" "git.eeqj.de/sneak/chat/internal/db" _ "modernc.org/sqlite" ) func setupTestDB(t *testing.T) *db.Database { t.Helper() d, err := db.NewTestDatabase() if err != nil { t.Fatal(err) } t.Cleanup(func() { closeErr := d.Close() if closeErr != nil { t.Logf("close db: %v", closeErr) } }) return d } func TestCreateUser(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() id, token, err := database.CreateUser(ctx, "alice") if err != nil { t.Fatal(err) } if id == 0 || token == "" { t.Fatal("expected valid id and token") } _, _, err = database.CreateUser(ctx, "alice") if err == nil { t.Fatal("expected error for duplicate nick") } } func TestGetUserByToken(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() _, token, err := database.CreateUser(ctx, "bob") if err != nil { t.Fatal(err) } id, nick, err := database.GetUserByToken(ctx, token) if err != nil { t.Fatal(err) } if nick != "bob" || id == 0 { t.Fatalf("expected bob, got %s", nick) } _, _, err = database.GetUserByToken(ctx, "badtoken") if err == nil { t.Fatal("expected error for bad token") } } func TestGetUserByNick(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() _, _, err := database.CreateUser(ctx, "charlie") if err != nil { t.Fatal(err) } id, err := database.GetUserByNick(ctx, "charlie") if err != nil || id == 0 { t.Fatal("expected to find charlie") } _, err = database.GetUserByNick(ctx, "nobody") if err == nil { t.Fatal("expected error for unknown nick") } } func TestChannelOperations(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() chID, err := database.GetOrCreateChannel(ctx, "#test") if err != nil || chID == 0 { t.Fatal("expected channel id") } chID2, err := database.GetOrCreateChannel(ctx, "#test") if err != nil || chID2 != chID { t.Fatal("expected same channel id") } chID3, err := database.GetChannelByName(ctx, "#test") if err != nil || chID3 != chID { t.Fatal("expected same channel id") } _, err = database.GetChannelByName(ctx, "#nope") if err == nil { t.Fatal("expected error for nonexistent channel") } } func TestJoinAndPart(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() uid, _, err := database.CreateUser(ctx, "user1") if err != nil { t.Fatal(err) } chID, err := database.GetOrCreateChannel(ctx, "#chan") if err != nil { t.Fatal(err) } err = database.JoinChannel(ctx, chID, uid) if err != nil { t.Fatal(err) } ids, err := database.GetChannelMemberIDs(ctx, chID) if err != nil || len(ids) != 1 || ids[0] != uid { t.Fatal("expected user in channel") } err = database.JoinChannel(ctx, chID, uid) if err != nil { t.Fatal(err) } err = database.PartChannel(ctx, chID, uid) if err != nil { t.Fatal(err) } ids, _ = database.GetChannelMemberIDs(ctx, chID) if len(ids) != 0 { t.Fatal("expected empty channel") } } func TestDeleteChannelIfEmpty(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() chID, err := database.GetOrCreateChannel( ctx, "#empty", ) if err != nil { t.Fatal(err) } uid, _, err := database.CreateUser(ctx, "temp") if err != nil { t.Fatal(err) } err = database.JoinChannel(ctx, chID, uid) if err != nil { t.Fatal(err) } err = database.PartChannel(ctx, chID, uid) if err != nil { t.Fatal(err) } err = database.DeleteChannelIfEmpty(ctx, chID) if err != nil { t.Fatal(err) } _, err = database.GetChannelByName(ctx, "#empty") if err == nil { t.Fatal("expected channel to be deleted") } } func createUserWithChannels( t *testing.T, database *db.Database, nick, ch1Name, ch2Name string, ) (int64, int64, int64) { t.Helper() ctx := t.Context() uid, _, err := database.CreateUser(ctx, nick) if err != nil { t.Fatal(err) } ch1, err := database.GetOrCreateChannel( ctx, ch1Name, ) if err != nil { t.Fatal(err) } ch2, err := database.GetOrCreateChannel( ctx, ch2Name, ) if err != nil { t.Fatal(err) } err = database.JoinChannel(ctx, ch1, uid) if err != nil { t.Fatal(err) } err = database.JoinChannel(ctx, ch2, uid) if err != nil { t.Fatal(err) } return uid, ch1, ch2 } func TestListChannels(t *testing.T) { t.Parallel() database := setupTestDB(t) uid, _, _ := createUserWithChannels( t, database, "lister", "#a", "#b", ) channels, err := database.ListChannels( t.Context(), uid, ) if err != nil || len(channels) != 2 { t.Fatalf( "expected 2 channels, got %d", len(channels), ) } } func TestListAllChannels(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() _, err := database.GetOrCreateChannel(ctx, "#x") if err != nil { t.Fatal(err) } _, err = database.GetOrCreateChannel(ctx, "#y") if err != nil { t.Fatal(err) } channels, err := database.ListAllChannels(ctx) if err != nil || len(channels) < 2 { t.Fatalf( "expected >= 2 channels, got %d", len(channels), ) } } func TestChangeNick(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() uid, token, err := database.CreateUser(ctx, "old") if err != nil { t.Fatal(err) } err = database.ChangeNick(ctx, uid, "new") if err != nil { t.Fatal(err) } _, nick, err := database.GetUserByToken(ctx, token) if err != nil { t.Fatal(err) } if nick != "new" { t.Fatalf("expected new, got %s", nick) } } func TestSetTopic(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() _, err := database.GetOrCreateChannel( ctx, "#topictest", ) if err != nil { t.Fatal(err) } err = database.SetTopic(ctx, "#topictest", "Hello") if err != nil { t.Fatal(err) } channels, err := database.ListAllChannels(ctx) if err != nil { t.Fatal(err) } for _, ch := range channels { if ch.Name == "#topictest" && ch.Topic != "Hello" { t.Fatalf( "expected topic Hello, got %s", ch.Topic, ) } } } func insertTestMessage( t *testing.T, database *db.Database, ) (int64, int64) { t.Helper() ctx := t.Context() uid, _, err := database.CreateUser(ctx, "poller") if err != nil { t.Fatal(err) } body := json.RawMessage(`["hello"]`) dbID, msgUUID, err := database.InsertMessage( ctx, "PRIVMSG", "poller", "#test", body, nil, ) if err != nil || dbID == 0 || msgUUID == "" { t.Fatal("insert failed") } err = database.EnqueueMessage(ctx, uid, dbID) if err != nil { t.Fatal(err) } return uid, dbID } func TestInsertAndPollMessages(t *testing.T) { t.Parallel() database := setupTestDB(t) uid, _ := insertTestMessage(t, database) const batchSize = 10 msgs, lastQID, err := database.PollMessages( t.Context(), uid, 0, batchSize, ) if err != nil { t.Fatal(err) } if len(msgs) != 1 { t.Fatalf( "expected 1 message, got %d", len(msgs), ) } if msgs[0].Command != "PRIVMSG" { t.Fatalf( "expected PRIVMSG, got %s", msgs[0].Command, ) } if lastQID == 0 { t.Fatal("expected nonzero lastQID") } msgs, _, _ = database.PollMessages( t.Context(), uid, lastQID, batchSize, ) if len(msgs) != 0 { t.Fatalf( "expected 0 messages, got %d", len(msgs), ) } } func TestGetHistory(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() const msgCount = 10 for range msgCount { _, _, err := database.InsertMessage( ctx, "PRIVMSG", "user", "#hist", json.RawMessage(`["msg"]`), nil, ) if err != nil { t.Fatal(err) } } const histLimit = 5 msgs, err := database.GetHistory( ctx, "#hist", 0, histLimit, ) if err != nil { t.Fatal(err) } if len(msgs) != histLimit { t.Fatalf("expected %d, got %d", histLimit, len(msgs)) } if msgs[0].DBID > msgs[histLimit-1].DBID { t.Fatal("expected ascending order") } } func TestDeleteUser(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() uid, _, err := database.CreateUser(ctx, "deleteme") if err != nil { t.Fatal(err) } chID, err := database.GetOrCreateChannel( ctx, "#delchan", ) if err != nil { t.Fatal(err) } err = database.JoinChannel(ctx, chID, uid) if err != nil { t.Fatal(err) } err = database.DeleteUser(ctx, uid) if err != nil { t.Fatal(err) } _, err = database.GetUserByNick(ctx, "deleteme") if err == nil { t.Fatal("user should be deleted") } ids, _ := database.GetChannelMemberIDs(ctx, chID) if len(ids) != 0 { t.Fatal("expected no members after deletion") } } func TestChannelMembers(t *testing.T) { t.Parallel() database := setupTestDB(t) ctx := t.Context() uid1, _, err := database.CreateUser(ctx, "m1") if err != nil { t.Fatal(err) } uid2, _, err := database.CreateUser(ctx, "m2") if err != nil { t.Fatal(err) } chID, err := database.GetOrCreateChannel( ctx, "#members", ) if err != nil { t.Fatal(err) } err = database.JoinChannel(ctx, chID, uid1) if err != nil { t.Fatal(err) } err = database.JoinChannel(ctx, chID, uid2) if err != nil { t.Fatal(err) } members, err := database.ChannelMembers(ctx, chID) if err != nil || len(members) != 2 { t.Fatalf( "expected 2 members, got %d", len(members), ) } } func TestGetAllChannelMembershipsForUser(t *testing.T) { t.Parallel() database := setupTestDB(t) uid, _, _ := createUserWithChannels( t, database, "multi", "#m1", "#m2", ) channels, err := database.GetAllChannelMembershipsForUser( t.Context(), uid, ) if err != nil || len(channels) != 2 { t.Fatalf( "expected 2 channels, got %d", len(channels), ) } }