check / check (push) Waiting to run
JoinChannel made the channel, counted its members, ran the join checks and inserted the member as separate statements, so two requests could each decide on a count the other was about to change: +l was exceeded, two first joiners both became operator, and a JOIN racing the last PART failed its foreign key and answered 500. db.CreateOrJoinChannel now does all of that, and uses up the invite, in one transaction; the join checks moved into it, and the service turns its refusals into the same IRC errors as before. PART and KICK remove the member and delete an empty channel in one transaction (db.PartChannelAndDeleteIfEmpty). The db functions this leaves with no caller are removed. Model: opus-5-5
1688 lines
30 KiB
Go
1688 lines
30 KiB
Go
package db_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/neoirc/internal/db"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func setupTestDB(t *testing.T) *db.Database {
|
|
t.Helper()
|
|
|
|
database, err := db.NewTestDatabase()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
closeErr := database.Close()
|
|
if closeErr != nil {
|
|
t.Logf("close db: %v", closeErr)
|
|
}
|
|
})
|
|
|
|
return database
|
|
}
|
|
|
|
func TestCreateSession(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sessionID, _, token, err := database.CreateSession(
|
|
ctx, "alice", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if sessionID == 0 || token == "" {
|
|
t.Fatal("expected valid id and token")
|
|
}
|
|
|
|
_, _, dupToken, dupErr := database.CreateSession(
|
|
ctx, "alice", "", "", "",
|
|
)
|
|
if dupErr == nil {
|
|
t.Fatal("expected error for duplicate nick")
|
|
}
|
|
|
|
_ = dupToken
|
|
}
|
|
|
|
// assertSessionHostInfo creates a session and verifies
|
|
// the stored username and hostname match expectations.
|
|
func assertSessionHostInfo(
|
|
t *testing.T,
|
|
database *db.Database,
|
|
nick, inputUser, inputHost,
|
|
expectUser, expectHost string,
|
|
) {
|
|
t.Helper()
|
|
|
|
sessionID, _, _, err := database.CreateSession(
|
|
t.Context(), nick, inputUser, inputHost, "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
info, err := database.GetSessionHostInfo(
|
|
t.Context(), sessionID,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if info.Username != expectUser {
|
|
t.Fatalf(
|
|
"expected username %s, got %s",
|
|
expectUser, info.Username,
|
|
)
|
|
}
|
|
|
|
if info.Hostname != expectHost {
|
|
t.Fatalf(
|
|
"expected hostname %s, got %s",
|
|
expectHost, info.Hostname,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestCreateSessionWithUserHost(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
|
|
assertSessionHostInfo(
|
|
t, database,
|
|
"hostuser", "myident", "example.com",
|
|
"myident", "example.com",
|
|
)
|
|
}
|
|
|
|
func TestCreateSessionDefaultUsername(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
|
|
// Empty username defaults to nick.
|
|
assertSessionHostInfo(
|
|
t, database,
|
|
"defaultu", "", "host.local",
|
|
"defaultu", "host.local",
|
|
)
|
|
}
|
|
|
|
func TestCreateSessionStoresIP(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sessionID, clientID, _, err := database.CreateSession(
|
|
ctx, "ipuser", "ident", "host.example.com",
|
|
"192.168.1.42",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
info, err := database.GetSessionHostInfo(
|
|
ctx, sessionID,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if info.IP != "192.168.1.42" {
|
|
t.Fatalf(
|
|
"expected session IP 192.168.1.42, got %s",
|
|
info.IP,
|
|
)
|
|
}
|
|
|
|
clientInfo, err := database.GetClientHostInfo(
|
|
ctx, clientID,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if clientInfo.IP != "192.168.1.42" {
|
|
t.Fatalf(
|
|
"expected client IP 192.168.1.42, got %s",
|
|
clientInfo.IP,
|
|
)
|
|
}
|
|
|
|
if clientInfo.Hostname != "host.example.com" {
|
|
t.Fatalf(
|
|
"expected client hostname host.example.com, got %s",
|
|
clientInfo.Hostname,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestGetClientHostInfoNotFound(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
|
|
_, err := database.GetClientHostInfo(
|
|
t.Context(), 99999,
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected error for nonexistent client")
|
|
}
|
|
}
|
|
|
|
func TestGetSessionHostInfoNotFound(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
|
|
_, err := database.GetSessionHostInfo(
|
|
t.Context(), 99999,
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected error for nonexistent session")
|
|
}
|
|
}
|
|
|
|
func TestFormatHostmask(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result := db.FormatHostmask(
|
|
"nick", "user", "host.com",
|
|
)
|
|
if result != "nick!user@host.com" {
|
|
t.Fatalf(
|
|
"expected nick!user@host.com, got %s",
|
|
result,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestFormatHostmaskDefaults(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result := db.FormatHostmask("nick", "", "")
|
|
if result != "nick!nick@*" {
|
|
t.Fatalf(
|
|
"expected nick!nick@*, got %s",
|
|
result,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestMemberInfoHostmask(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
member := &db.MemberInfo{ //nolint:exhaustruct // test only uses hostmask fields
|
|
Nick: "alice",
|
|
Username: "aliceident",
|
|
Hostname: "alice.example.com",
|
|
}
|
|
|
|
hostmask := member.Hostmask()
|
|
expected := "alice!aliceident@alice.example.com"
|
|
|
|
if hostmask != expected {
|
|
t.Fatalf(
|
|
"expected %s, got %s", expected, hostmask,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestChannelMembersIncludeUserHost(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sid, _, _, err := database.CreateSession(
|
|
ctx, "memuser", "myuser", "myhost.net", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
chID, err := database.GetOrCreateChannel(
|
|
ctx, "#hostchan",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.JoinChannel(ctx, chID, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
members, err := database.ChannelMembers(ctx, chID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(members) != 1 {
|
|
t.Fatalf(
|
|
"expected 1 member, got %d", len(members),
|
|
)
|
|
}
|
|
|
|
if members[0].Username != "myuser" {
|
|
t.Fatalf(
|
|
"expected username myuser, got %s",
|
|
members[0].Username,
|
|
)
|
|
}
|
|
|
|
if members[0].Hostname != "myhost.net" {
|
|
t.Fatalf(
|
|
"expected hostname myhost.net, got %s",
|
|
members[0].Hostname,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestGetSessionByToken(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
_, _, token, err := database.CreateSession(ctx, "bob", "", "", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sessionID, clientID, nick, err :=
|
|
database.GetSessionByToken(ctx, token)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if nick != "bob" || sessionID == 0 || clientID == 0 {
|
|
t.Fatalf("expected bob, got %s", nick)
|
|
}
|
|
|
|
badSID, badCID, badNick, badErr :=
|
|
database.GetSessionByToken(ctx, "badtoken")
|
|
if badErr == nil {
|
|
t.Fatal("expected error for bad token")
|
|
}
|
|
|
|
if badSID != 0 || badCID != 0 || badNick != "" {
|
|
t.Fatal("expected zero values on error")
|
|
}
|
|
}
|
|
|
|
func TestGetSessionByNick(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
charlieID, charlieClientID, charlieToken, err :=
|
|
database.CreateSession(ctx, "charlie", "", "", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if charlieID == 0 || charlieClientID == 0 {
|
|
t.Fatal("expected valid session/client IDs")
|
|
}
|
|
|
|
if charlieToken == "" {
|
|
t.Fatal("expected non-empty token")
|
|
}
|
|
|
|
id, err := database.GetSessionByNick(ctx, "charlie")
|
|
if err != nil || id == 0 {
|
|
t.Fatal("expected to find charlie")
|
|
}
|
|
|
|
_, err = database.GetSessionByNick(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()
|
|
|
|
sid, _, _, err := database.CreateSession(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, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ids, err := database.GetChannelMemberIDs(ctx, chID)
|
|
if err != nil || len(ids) != 1 || ids[0] != sid {
|
|
t.Fatal("expected session in channel")
|
|
}
|
|
|
|
err = database.JoinChannel(ctx, chID, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.PartChannel(ctx, chID, sid)
|
|
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)
|
|
}
|
|
|
|
sid, _, _, err := database.CreateSession(ctx, "temp", "", "", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.JoinChannel(ctx, chID, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.PartChannel(ctx, chID, sid)
|
|
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 createSessionWithChannels(
|
|
t *testing.T,
|
|
database *db.Database,
|
|
nick, ch1Name, ch2Name string,
|
|
) (int64, int64, int64) {
|
|
t.Helper()
|
|
|
|
ctx := t.Context()
|
|
|
|
sid, _, _, err := database.CreateSession(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, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.JoinChannel(ctx, ch2, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return sid, ch1, ch2
|
|
}
|
|
|
|
func TestListChannels(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
sid, _, _ := createSessionWithChannels(
|
|
t, database, "lister", "#a", "#b",
|
|
)
|
|
|
|
channels, err := database.ListChannels(
|
|
t.Context(), sid,
|
|
)
|
|
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()
|
|
|
|
sid, _, token, err := database.CreateSession(
|
|
ctx, "old", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.ChangeNick(ctx, sid, "new")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, _, nick, err := database.GetSessionByToken(
|
|
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 TestInsertMessage(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
body := json.RawMessage(`["hello"]`)
|
|
|
|
dbID, msgUUID, err := database.InsertMessage(
|
|
ctx, "PRIVMSG", "poller", "#test", 0, 0, nil, body, nil,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if dbID == 0 || msgUUID == "" {
|
|
t.Fatal("expected valid id and uuid")
|
|
}
|
|
}
|
|
|
|
func TestPollMessages(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sid, _, token, err := database.CreateSession(
|
|
ctx, "poller", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, clientID, _, err := database.GetSessionByToken(
|
|
ctx, token,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
body := json.RawMessage(`["hello"]`)
|
|
|
|
dbID, _, err := database.InsertMessage(
|
|
ctx, "PRIVMSG", "poller", "#test", 0, 0, nil, body, nil,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.EnqueueToSession(ctx, sid, dbID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
const batchSize = 10
|
|
|
|
msgs, lastQID, err := database.PollMessages(
|
|
ctx, clientID, 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(
|
|
ctx, clientID, 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()
|
|
|
|
chID, err := database.GetOrCreateChannel(ctx, "#hist")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
const msgCount = 10
|
|
|
|
for range msgCount {
|
|
_, _, err = database.InsertMessage(
|
|
ctx, "PRIVMSG", "user", "#hist", 0, chID,
|
|
nil, json.RawMessage(`["msg"]`), nil,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
const histLimit = 5
|
|
|
|
msgs, err := database.GetHistory(
|
|
ctx, 0, chID, "", 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")
|
|
}
|
|
}
|
|
|
|
// TestGetHistoryReusedNames checks that history follows the
|
|
// session and the channel row, not the nick or the channel
|
|
// name: a session that takes a released nick, and a channel
|
|
// created again under a deleted one's name, start with none.
|
|
func TestGetHistoryReusedNames(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
historyLen := func(toSessionID, toChannelID int64) int {
|
|
t.Helper()
|
|
|
|
msgs, err := database.GetHistory(
|
|
ctx, toSessionID, toChannelID, "", 0,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return len(msgs)
|
|
}
|
|
|
|
oldSID, _, _, err := database.CreateSession(
|
|
ctx, "reused", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
oldChID, err := database.GetOrCreateChannel(ctx, "#reused")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
body := json.RawMessage(`["hi"]`)
|
|
|
|
_, _, err = database.InsertMessage(
|
|
ctx, "PRIVMSG", "sender", "reused", oldSID, 0,
|
|
nil, body, nil,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, _, err = database.InsertMessage(
|
|
ctx, "PRIVMSG", "sender", "#reused", 0, oldChID,
|
|
nil, body, nil,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if historyLen(oldSID, 0) != 1 || historyLen(0, oldChID) != 1 {
|
|
t.Fatal("expected one DM and one channel message")
|
|
}
|
|
|
|
err = database.DeleteSession(ctx, oldSID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.DeleteChannelIfEmpty(ctx, oldChID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
newSID, _, _, err := database.CreateSession(
|
|
ctx, "reused", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
newChID, err := database.GetOrCreateChannel(ctx, "#reused")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if historyLen(newSID, 0) != 0 || historyLen(0, newChID) != 0 {
|
|
t.Fatal("expected no history for the names' new holders")
|
|
}
|
|
}
|
|
|
|
func TestDeleteSession(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sid, _, _, err := database.CreateSession(
|
|
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, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.DeleteSession(ctx, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = database.GetSessionByNick(ctx, "deleteme")
|
|
if err == nil {
|
|
t.Fatal("session 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()
|
|
|
|
sid1, _, _, err := database.CreateSession(ctx, "m1", "", "", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sid2, _, _, err := database.CreateSession(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, sid1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.JoinChannel(ctx, chID, sid2)
|
|
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 TestGetSessionChannels(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
sid, _, _ := createSessionWithChannels(
|
|
t, database, "multi", "#m1", "#m2",
|
|
)
|
|
|
|
channels, err :=
|
|
database.GetSessionChannels(
|
|
t.Context(), sid,
|
|
)
|
|
if err != nil || len(channels) != 2 {
|
|
t.Fatalf(
|
|
"expected 2 channels, got %d",
|
|
len(channels),
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestEnqueueToClient(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
_, _, token, err := database.CreateSession(
|
|
ctx, "enqclient", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, clientID, _, err := database.GetSessionByToken(
|
|
ctx, token,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
body := json.RawMessage(`["test"]`)
|
|
|
|
dbID, _, err := database.InsertMessage(
|
|
ctx, "PRIVMSG", "sender", "#ch", 0, 0, nil, body, nil,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.EnqueueToClient(ctx, clientID, dbID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
const batchSize = 10
|
|
|
|
msgs, _, err := database.PollMessages(
|
|
ctx, clientID, 0, batchSize,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(msgs) != 1 {
|
|
t.Fatalf("expected 1, got %d", len(msgs))
|
|
}
|
|
}
|
|
|
|
func TestSetAndCheckSessionOper(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sessionID, _, _, err := database.CreateSession(
|
|
ctx, "opernick", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Initially not oper.
|
|
isOper, err := database.IsSessionOper(ctx, sessionID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if isOper {
|
|
t.Fatal("expected session not to be oper")
|
|
}
|
|
|
|
// Set oper.
|
|
err = database.SetSessionOper(ctx, sessionID, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
isOper, err = database.IsSessionOper(ctx, sessionID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !isOper {
|
|
t.Fatal("expected session to be oper")
|
|
}
|
|
|
|
// Unset oper.
|
|
err = database.SetSessionOper(ctx, sessionID, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
isOper, err = database.IsSessionOper(ctx, sessionID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if isOper {
|
|
t.Fatal("expected session not to be oper")
|
|
}
|
|
}
|
|
|
|
func TestGetLatestClientForSession(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sessionID, _, _, err := database.CreateSession(
|
|
ctx, "clientnick", "", "", "10.0.0.1",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
clientInfo, err := database.GetLatestClientForSession(
|
|
ctx, sessionID,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if clientInfo.IP != "10.0.0.1" {
|
|
t.Fatalf(
|
|
"expected IP 10.0.0.1, got %s",
|
|
clientInfo.IP,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestGetOperCount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
// Create two sessions.
|
|
sid1, _, _, err := database.CreateSession(
|
|
ctx, "user1", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sid2, _, _, err := database.CreateSession(
|
|
ctx, "user2", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_ = sid2
|
|
|
|
// Initially zero opers.
|
|
count, err := database.GetOperCount(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if count != 0 {
|
|
t.Fatalf("expected 0 opers, got %d", count)
|
|
}
|
|
|
|
// Set one as oper.
|
|
err = database.SetSessionOper(ctx, sid1, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
count, err = database.GetOperCount(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if count != 1 {
|
|
t.Fatalf("expected 1 oper, got %d", count)
|
|
}
|
|
}
|
|
|
|
// --- Tier 2 Tests ---
|
|
|
|
func TestWildcardMatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const hostmask = "nick!user@host"
|
|
|
|
tests := []struct {
|
|
pattern string
|
|
input string
|
|
match bool
|
|
}{
|
|
{"*!*@*", hostmask, true},
|
|
{"*!*@*.example.com", "nick!user@foo.example.com", true},
|
|
{"*!*@*.example.com", "nick!user@other.net", false},
|
|
{"badnick!*@*", "badnick!user@host", true},
|
|
{"badnick!*@*", "goodnick!user@host", false},
|
|
{hostmask, hostmask, true},
|
|
{hostmask, "nick!user@other", false},
|
|
{"*", "anything", true},
|
|
{"?ick!*@*", hostmask, true},
|
|
{"?ick!*@*", "nn!user@host", false},
|
|
// Case-insensitive.
|
|
{"Nick!*@*", hostmask, true},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
result := db.MatchBanMask(tc.pattern, tc.input)
|
|
if result != tc.match {
|
|
t.Errorf(
|
|
"MatchBanMask(%q, %q) = %v, want %v",
|
|
tc.pattern, tc.input, result, tc.match,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChannelBanCRUD(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
chID, err := database.GetOrCreateChannel(ctx, "#test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// No bans initially.
|
|
bans, err := database.ListChannelBans(ctx, chID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(bans) != 0 {
|
|
t.Fatalf("expected 0 bans, got %d", len(bans))
|
|
}
|
|
|
|
// Add a ban.
|
|
err = database.AddChannelBan(
|
|
ctx, chID, "*!*@evil.com", "op",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bans, err = database.ListChannelBans(ctx, chID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(bans) != 1 {
|
|
t.Fatalf("expected 1 ban, got %d", len(bans))
|
|
}
|
|
|
|
if bans[0].Mask != "*!*@evil.com" {
|
|
t.Fatalf("wrong mask: %s", bans[0].Mask)
|
|
}
|
|
|
|
// Duplicate add is ignored (OR IGNORE).
|
|
err = database.AddChannelBan(
|
|
ctx, chID, "*!*@evil.com", "op2",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bans, _ = database.ListChannelBans(ctx, chID)
|
|
if len(bans) != 1 {
|
|
t.Fatalf("expected 1 ban after dup, got %d", len(bans))
|
|
}
|
|
|
|
// Remove ban.
|
|
err = database.RemoveChannelBan(
|
|
ctx, chID, "*!*@evil.com",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bans, _ = database.ListChannelBans(ctx, chID)
|
|
if len(bans) != 0 {
|
|
t.Fatalf("expected 0 bans after remove, got %d", len(bans))
|
|
}
|
|
}
|
|
|
|
func TestIsSessionBanned(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sid, _, _, err := database.CreateSession(
|
|
ctx, "victim", "victim", "evil.com", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
chID, err := database.GetOrCreateChannel(ctx, "#bantest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Not banned initially.
|
|
banned, err := database.IsSessionBanned(ctx, chID, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if banned {
|
|
t.Fatal("should not be banned initially")
|
|
}
|
|
|
|
// Add ban matching the hostmask.
|
|
err = database.AddChannelBan(
|
|
ctx, chID, "*!*@evil.com", "op",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
banned, err = database.IsSessionBanned(ctx, chID, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !banned {
|
|
t.Fatal("should be banned")
|
|
}
|
|
}
|
|
|
|
func TestChannelInviteOnly(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
chID, err := database.GetOrCreateChannel(ctx, "#invite")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Default: not invite-only.
|
|
isIO, err := database.IsChannelInviteOnly(ctx, chID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if isIO {
|
|
t.Fatal("should not be invite-only by default")
|
|
}
|
|
|
|
// Set invite-only.
|
|
err = database.SetChannelInviteOnly(ctx, chID, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
isIO, _ = database.IsChannelInviteOnly(ctx, chID)
|
|
if !isIO {
|
|
t.Fatal("should be invite-only")
|
|
}
|
|
|
|
// Unset.
|
|
err = database.SetChannelInviteOnly(ctx, chID, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
isIO, _ = database.IsChannelInviteOnly(ctx, chID)
|
|
if isIO {
|
|
t.Fatal("should not be invite-only")
|
|
}
|
|
}
|
|
|
|
func TestChannelInviteCRUD(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sid, _, _, err := database.CreateSession(
|
|
ctx, "invited", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
chID, err := database.GetOrCreateChannel(ctx, "#inv")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// No invite initially.
|
|
has, err := database.HasChannelInvite(ctx, chID, sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if has {
|
|
t.Fatal("should not have invite")
|
|
}
|
|
|
|
// Add invite.
|
|
err = database.AddChannelInvite(ctx, chID, sid, "op")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
has, _ = database.HasChannelInvite(ctx, chID, sid)
|
|
if !has {
|
|
t.Fatal("should have invite")
|
|
}
|
|
}
|
|
|
|
func TestChannelSecret(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
chID, err := database.GetOrCreateChannel(ctx, "#secret")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Default: not secret.
|
|
isSec, err := database.IsChannelSecret(ctx, chID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if isSec {
|
|
t.Fatal("should not be secret by default")
|
|
}
|
|
|
|
err = database.SetChannelSecret(ctx, chID, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
isSec, _ = database.IsChannelSecret(ctx, chID)
|
|
if !isSec {
|
|
t.Fatal("should be secret")
|
|
}
|
|
}
|
|
|
|
// createTestSession is a helper to create a session and
|
|
// return only the session ID.
|
|
func createTestSession(
|
|
t *testing.T,
|
|
database *db.Database,
|
|
nick string,
|
|
) int64 {
|
|
t.Helper()
|
|
|
|
sid, _, _, err := database.CreateSession(
|
|
t.Context(), nick, "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("create session %s: %v", nick, err)
|
|
}
|
|
|
|
return sid
|
|
}
|
|
|
|
func TestSecretChannelFiltering(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
// Create two sessions.
|
|
sid1 := createTestSession(t, database, "member")
|
|
sid2 := createTestSession(t, database, "outsider")
|
|
|
|
// Create a secret channel.
|
|
chID, _ := database.GetOrCreateChannel(ctx, "#secret")
|
|
_ = database.SetChannelSecret(ctx, chID, true)
|
|
_ = database.JoinChannel(ctx, chID, sid1)
|
|
|
|
// Create a non-secret channel.
|
|
chID2, _ := database.GetOrCreateChannel(ctx, "#public")
|
|
_ = database.JoinChannel(ctx, chID2, sid1)
|
|
|
|
// Member should see both.
|
|
list, err := database.ListAllChannelsWithCountsFiltered(
|
|
ctx, sid1,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(list) != 2 {
|
|
t.Fatalf("member should see 2 channels, got %d", len(list))
|
|
}
|
|
|
|
// Outsider should only see public.
|
|
list, _ = database.ListAllChannelsWithCountsFiltered(
|
|
ctx, sid2,
|
|
)
|
|
if len(list) != 1 {
|
|
t.Fatalf("outsider should see 1 channel, got %d", len(list))
|
|
}
|
|
|
|
if list[0].Name != "#public" {
|
|
t.Fatalf("outsider should see #public, got %s", list[0].Name)
|
|
}
|
|
}
|
|
|
|
func TestWhoisChannelFiltering(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sid1 := createTestSession(t, database, "target")
|
|
sid2 := createTestSession(t, database, "querier")
|
|
|
|
// Create secret channel, target joins it.
|
|
chID, _ := database.GetOrCreateChannel(ctx, "#hidden")
|
|
_ = database.SetChannelSecret(ctx, chID, true)
|
|
_ = database.JoinChannel(ctx, chID, sid1)
|
|
|
|
// Querier (non-member) should not see the channel.
|
|
channels, err := database.GetSessionChannelsFiltered(
|
|
ctx, sid1, sid2,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(channels) != 0 {
|
|
t.Fatalf(
|
|
"querier should see 0 channels, got %d",
|
|
len(channels),
|
|
)
|
|
}
|
|
|
|
// Target querying self should see it.
|
|
channels, _ = database.GetSessionChannelsFiltered(
|
|
ctx, sid1, sid1,
|
|
)
|
|
if len(channels) != 1 {
|
|
t.Fatalf(
|
|
"self-query should see 1 channel, got %d",
|
|
len(channels),
|
|
)
|
|
}
|
|
}
|
|
|
|
//nolint:dupl // structurally similar to TestChannelUserLimit
|
|
func TestChannelKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
chID, err := database.GetOrCreateChannel(ctx, "#keyed")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Default: no key.
|
|
key, err := database.GetChannelKey(ctx, chID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if key != "" {
|
|
t.Fatalf("expected empty key, got %q", key)
|
|
}
|
|
|
|
// Set key.
|
|
err = database.SetChannelKey(ctx, chID, "secret123")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
key, _ = database.GetChannelKey(ctx, chID)
|
|
if key != "secret123" {
|
|
t.Fatalf("expected secret123, got %q", key)
|
|
}
|
|
|
|
// Clear key.
|
|
err = database.SetChannelKey(ctx, chID, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
key, _ = database.GetChannelKey(ctx, chID)
|
|
if key != "" {
|
|
t.Fatalf("expected empty key, got %q", key)
|
|
}
|
|
}
|
|
|
|
//nolint:dupl // structurally similar to TestChannelKey
|
|
func TestChannelUserLimit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
chID, err := database.GetOrCreateChannel(ctx, "#limited")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Default: no limit.
|
|
limit, err := database.GetChannelUserLimit(ctx, chID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if limit != 0 {
|
|
t.Fatalf("expected 0 limit, got %d", limit)
|
|
}
|
|
|
|
// Set limit.
|
|
err = database.SetChannelUserLimit(ctx, chID, 50)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
limit, _ = database.GetChannelUserLimit(ctx, chID)
|
|
if limit != 50 {
|
|
t.Fatalf("expected 50, got %d", limit)
|
|
}
|
|
|
|
// Clear limit.
|
|
err = database.SetChannelUserLimit(ctx, chID, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
limit, _ = database.GetChannelUserLimit(ctx, chID)
|
|
if limit != 0 {
|
|
t.Fatalf("expected 0, got %d", limit)
|
|
}
|
|
}
|
|
|
|
// TestSetSessionUserModesIsAtomic makes the is_oper write
|
|
// fail after the is_wallops write has run, and checks that
|
|
// the wallops change was rolled back.
|
|
func TestSetSessionUserModesIsAtomic(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sessionID, _, _, err := database.CreateSession(
|
|
ctx, "alice", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.ExecForTest(ctx,
|
|
`CREATE TRIGGER reject_oper
|
|
BEFORE UPDATE OF is_oper ON sessions
|
|
BEGIN SELECT RAISE(ABORT, 'oper write rejected');
|
|
END`,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
wallops := true
|
|
oper := false
|
|
|
|
err = database.SetSessionUserModes(
|
|
ctx, sessionID, &wallops, &oper,
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected the rejected oper write to fail")
|
|
}
|
|
|
|
gotWallops, err := database.IsSessionWallops(
|
|
ctx, sessionID,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if gotWallops {
|
|
t.Error("wallops was stored although the change failed")
|
|
}
|
|
}
|
|
|
|
// TestSetSessionUserModesAppliesBoth checks that both flags
|
|
// are written, and that a nil flag is left as it is.
|
|
func TestSetSessionUserModesAppliesBoth(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
database := setupTestDB(t)
|
|
ctx := t.Context()
|
|
|
|
sessionID, _, _, err := database.CreateSession(
|
|
ctx, "alice", "", "", "",
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = database.SetSessionOper(ctx, sessionID, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
wallops := true
|
|
oper := false
|
|
|
|
err = database.SetSessionUserModes(
|
|
ctx, sessionID, &wallops, &oper,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gotWallops, err := database.IsSessionWallops(
|
|
ctx, sessionID,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gotOper, err := database.IsSessionOper(ctx, sessionID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !gotWallops || gotOper {
|
|
t.Errorf(
|
|
"want wallops=true oper=false, got %v/%v",
|
|
gotWallops, gotOper,
|
|
)
|
|
}
|
|
|
|
err = database.SetSessionUserModes(
|
|
ctx, sessionID, nil, nil,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
gotWallops, err = database.IsSessionWallops(
|
|
ctx, sessionID,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !gotWallops {
|
|
t.Error("a nil wallops flag cleared wallops")
|
|
}
|
|
}
|