mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
* fix(ws): 2 defect(s) (OC-0013, OC-0140) * fix(voice): 1 defect(s) (OC-0044) * fix(ws): 1 defect(s) (OC-0024) * fix(server): 1 defect(s) (OC-0027) * fix(ws): 1 defect(s) (OC-0028) * fix(server): 7 defect(s) (OC-0033, OC-0066, OC-0067, OC-0068, OC-0074, OC-0077, OC-0106) * fix(ws): 1 defect(s) (OC-0051) * fix(client): 1 defect(s) (OC-0053) * fix(client): 1 defect(s) (OC-0055) * fix(service): 1 defect(s) (OC-0069) * fix(voice): 1 defect(s) (OC-0072) * fix(service): 1 defect(s) (OC-0082) * fix(client): 1 defect(s) (OC-0083) * fix(plugin): 1 defect(s) (OC-0088) * fix(plugin): 4 defect(s) (OC-0104, OC-0126, OC-0127, OC-0133) * fix(admin): 1 defect(s) (OC-0110) * fix(client): 1 defect(s) (OC-0114) * fix(api): 1 defect(s) (OC-0139) * fix(client): 1 defect(s) (OC-0149) * test(server): adapt existing tests to updated OpenDM and IncrementMentionCounts signatures * style(plugin): modernize loops and goroutine spawns in race test * fix(ws): mirror the focus admission gate in the post-subscribe revalidation * fix(service): detach DM post-commit side effects from the request ctx, fail delete closed, add empty-fan-out fallback * fix(plugin): preserve enabled intent when upgrade reactivation hits a runtime-less build * chore(skills): harden bughunt-fix workflow and fold review lessons into bughunt-run/db-change * Add comprehensive documentation for task-observer skill - Introduced environments.md to outline activation setup, compaction behavior, and handoff-doc mode. - Created skill-authoring.md detailing taxonomy, licensing, confidentiality, and editing rules for skill creation. - Added weekly-review.md for a structured review process of OPEN observations, including scheduled and in-session fallback modes. * chore(go): pin toolchain go1.26.6 (stdlib CVE fixes flagged by govulncheck)
523 lines
17 KiB
Go
523 lines
17 KiB
Go
package db_test
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/owncord/server/db"
|
|
)
|
|
|
|
// storageMentionCap mirrors the package-internal maxMentionsPerMessage backstop.
|
|
const storageMentionCap = 20
|
|
|
|
// seedMentionFixture creates two users and a text channel for mention tests.
|
|
func seedMentionFixture(t *testing.T, database *db.DB) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
for _, name := range []string{"alice", "bob", "Carol"} {
|
|
if _, err := database.CreateUser(ctx, name, "hash", 4); err != nil {
|
|
t.Fatalf("CreateUser(%s): %v", name, err)
|
|
}
|
|
}
|
|
if _, err := database.CreateChannel(ctx, "general", "text", "", "", 0); err != nil {
|
|
t.Fatalf("CreateChannel: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateMessageWithMentions_StoresRowsAndFlag(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
msg, err := database.CreateMessageWithMentions(ctx, 1, 1, "hi @bob", nil, []int64{2}, true)
|
|
if err != nil {
|
|
t.Fatalf("CreateMessageWithMentions: %v", err)
|
|
}
|
|
if !msg.MentionsEveryone {
|
|
t.Error("MentionsEveryone = false, want true")
|
|
}
|
|
|
|
got, err := database.GetMentionsByMessageIDs(ctx, []int64{msg.ID})
|
|
if err != nil {
|
|
t.Fatalf("GetMentionsByMessageIDs: %v", err)
|
|
}
|
|
if len(got[msg.ID]) != 1 || got[msg.ID][0] != 2 {
|
|
t.Errorf("mentions = %v, want [2]", got[msg.ID])
|
|
}
|
|
|
|
// The flag must survive a re-read through the ordinary message getter.
|
|
reread, err := database.GetMessage(ctx, msg.ID)
|
|
if err != nil || reread == nil {
|
|
t.Fatalf("GetMessage: %v", err)
|
|
}
|
|
if !reread.MentionsEveryone {
|
|
t.Error("re-read MentionsEveryone = false, want true")
|
|
}
|
|
}
|
|
|
|
// TestCreateMessageWithMentions_CapsStoredRows locks the storage-side backstop:
|
|
// a caller cannot widen the fan-out past storageMentionCap.
|
|
func TestCreateMessageWithMentions_CapsStoredRows(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
ids := make([]int64, 0, storageMentionCap+5)
|
|
for i := range storageMentionCap + 5 {
|
|
uid, err := database.CreateUser(ctx, "capped"+string(rune('a'+i)), "hash", 4)
|
|
if err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
ids = append(ids, uid)
|
|
}
|
|
|
|
msg, err := database.CreateMessageWithMentions(ctx, 1, 1, "spam", nil, ids, false)
|
|
if err != nil {
|
|
t.Fatalf("CreateMessageWithMentions: %v", err)
|
|
}
|
|
got, err := database.GetMentionsByMessageIDs(ctx, []int64{msg.ID})
|
|
if err != nil {
|
|
t.Fatalf("GetMentionsByMessageIDs: %v", err)
|
|
}
|
|
if len(got[msg.ID]) != storageMentionCap {
|
|
t.Errorf("stored = %d, want %d", len(got[msg.ID]), storageMentionCap)
|
|
}
|
|
}
|
|
|
|
func TestReplaceMessageMentions(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
msg, err := database.CreateMessageWithMentions(ctx, 1, 1, "hi @bob", nil, []int64{2}, true)
|
|
if err != nil {
|
|
t.Fatalf("CreateMessageWithMentions: %v", err)
|
|
}
|
|
if err := database.ReplaceMessageMentions(ctx, msg.ID, []int64{3}, false); err != nil {
|
|
t.Fatalf("ReplaceMessageMentions: %v", err)
|
|
}
|
|
|
|
got, err := database.GetMentionsByMessageIDs(ctx, []int64{msg.ID})
|
|
if err != nil {
|
|
t.Fatalf("GetMentionsByMessageIDs: %v", err)
|
|
}
|
|
if len(got[msg.ID]) != 1 || got[msg.ID][0] != 3 {
|
|
t.Errorf("mentions = %v, want [3]", got[msg.ID])
|
|
}
|
|
reread, err := database.GetMessage(ctx, msg.ID)
|
|
if err != nil || reread == nil {
|
|
t.Fatalf("GetMessage: %v", err)
|
|
}
|
|
if reread.MentionsEveryone {
|
|
t.Error("MentionsEveryone = true, want false after replace")
|
|
}
|
|
}
|
|
|
|
func TestIncrementMentionCounts_AndReadStateClear(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
if err := database.IncrementMentionCounts(ctx, 1, 100, []int64{2, 3}); err != nil {
|
|
t.Fatalf("IncrementMentionCounts: %v", err)
|
|
}
|
|
if err := database.IncrementMentionCounts(ctx, 1, 100, []int64{2}); err != nil {
|
|
t.Fatalf("IncrementMentionCounts: %v", err)
|
|
}
|
|
|
|
if n, _ := database.GetMentionCount(ctx, 2, 1); n != 2 {
|
|
t.Errorf("user 2 mention_count = %d, want 2", n)
|
|
}
|
|
if n, _ := database.GetMentionCount(ctx, 3, 1); n != 1 {
|
|
t.Errorf("user 3 mention_count = %d, want 1", n)
|
|
}
|
|
|
|
// Marking the channel read clears the badge, and only for that user.
|
|
if err := database.UpdateReadState(ctx, 2, 1, 99); err != nil {
|
|
t.Fatalf("UpdateReadState: %v", err)
|
|
}
|
|
if n, _ := database.GetMentionCount(ctx, 2, 1); n != 0 {
|
|
t.Errorf("after read state, user 2 mention_count = %d, want 0", n)
|
|
}
|
|
if n, _ := database.GetMentionCount(ctx, 3, 1); n != 1 {
|
|
t.Errorf("user 3 mention_count = %d, want 1", n)
|
|
}
|
|
}
|
|
|
|
// TestIncrementMentionCounts_BatchesAcrossChunkBoundary exercises the
|
|
// multi-row upsert with a recipient count that spans more than one exec
|
|
// chunk (mentionCountChunkSize=500), pinning that every recipient still gets
|
|
// exactly one increment (or a freshly seeded row) regardless of which chunk
|
|
// it landed in — the batching must not drop or double-count a row at the
|
|
// boundary.
|
|
func TestIncrementMentionCounts_BatchesAcrossChunkBoundary(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
ctx := context.Background()
|
|
if _, err := database.CreateChannel(ctx, "everyone-chan", "text", "", "", 0); err != nil {
|
|
t.Fatalf("CreateChannel: %v", err)
|
|
}
|
|
|
|
const n = 520 // > one 500-row chunk, so the run crosses a chunk boundary.
|
|
ids := make([]int64, 0, n)
|
|
for i := range n {
|
|
uid, err := database.CreateUser(ctx, "batchuser"+strconv.Itoa(i), "hash", 4)
|
|
if err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
ids = append(ids, uid)
|
|
}
|
|
|
|
if err := database.IncrementMentionCounts(ctx, 1, 1, ids); err != nil {
|
|
t.Fatalf("IncrementMentionCounts: %v", err)
|
|
}
|
|
|
|
for _, uid := range []int64{ids[0], ids[499], ids[500], ids[n-1]} {
|
|
if got, err := database.GetMentionCount(ctx, uid, 1); err != nil || got != 1 {
|
|
t.Errorf("user %d mention_count = %d (err=%v), want 1", uid, got, err)
|
|
}
|
|
}
|
|
|
|
// A second pass bumps every one of them again, chunk boundary included.
|
|
// msgID is higher than the first pass's so the read-state guard does not
|
|
// treat this as the same already-seen message.
|
|
if err := database.IncrementMentionCounts(ctx, 1, 2, ids); err != nil {
|
|
t.Fatalf("IncrementMentionCounts (second pass): %v", err)
|
|
}
|
|
for _, uid := range []int64{ids[0], ids[499], ids[500], ids[n-1]} {
|
|
if got, err := database.GetMentionCount(ctx, uid, 1); err != nil || got != 2 {
|
|
t.Errorf("user %d mention_count after second pass = %d (err=%v), want 2", uid, got, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIncrementMentionCounts_EmptyIsNoop(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
if err := database.IncrementMentionCounts(context.Background(), 1, 1, nil); err != nil {
|
|
t.Fatalf("IncrementMentionCounts(nil): %v", err)
|
|
}
|
|
}
|
|
|
|
// TestIncrementMentionCounts_NoOpWhenReaderAlreadyPastMessage locks OC-0066:
|
|
// a mark_read that lands between the message commit and the deferred badge
|
|
// increment must not leave a phantom mention_count behind. If the recipient's
|
|
// read state already covers the mentioning message (last_message_id >=
|
|
// msgID), the increment for that message must be a no-op — otherwise the
|
|
// badge is stuck at 1 forever on a channel with zero unread, since nothing
|
|
// else ever zeroes it again.
|
|
func TestIncrementMentionCounts_NoOpWhenReaderAlreadyPastMessage(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
msgID, err := database.CreateMessage(ctx, 1, 1, "hey @bob", nil)
|
|
if err != nil {
|
|
t.Fatalf("CreateMessage: %v", err)
|
|
}
|
|
|
|
// The reader (user 2 = bob) marks the channel read before the deferred
|
|
// badge increment for this same message lands.
|
|
if err := database.UpdateReadState(ctx, 2, 1, msgID); err != nil {
|
|
t.Fatalf("UpdateReadState: %v", err)
|
|
}
|
|
|
|
if err := database.IncrementMentionCounts(ctx, 1, msgID, []int64{2}); err != nil {
|
|
t.Fatalf("IncrementMentionCounts: %v", err)
|
|
}
|
|
|
|
if n, _ := database.GetMentionCount(ctx, 2, 1); n != 0 {
|
|
t.Errorf("mention_count = %d after a read state that already covers the mentioning message, want 0 (phantom badge)", n)
|
|
}
|
|
}
|
|
|
|
// TestIncrementMentionCounts_StillAppliesWhenReaderIsBehind is the control:
|
|
// the guard must not turn every increment into a no-op — a reader who has NOT
|
|
// read up to msgID still gets the badge.
|
|
func TestIncrementMentionCounts_StillAppliesWhenReaderIsBehind(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
// Bob's read state is behind: he has read message 1's predecessor, not the
|
|
// mention itself.
|
|
older, err := database.CreateMessage(ctx, 1, 1, "hi", nil)
|
|
if err != nil {
|
|
t.Fatalf("CreateMessage(older): %v", err)
|
|
}
|
|
if err := database.UpdateReadState(ctx, 2, 1, older); err != nil {
|
|
t.Fatalf("UpdateReadState: %v", err)
|
|
}
|
|
|
|
msgID, err := database.CreateMessage(ctx, 1, 1, "hey @bob", nil)
|
|
if err != nil {
|
|
t.Fatalf("CreateMessage: %v", err)
|
|
}
|
|
if err := database.IncrementMentionCounts(ctx, 1, msgID, []int64{2}); err != nil {
|
|
t.Fatalf("IncrementMentionCounts: %v", err)
|
|
}
|
|
|
|
if n, _ := database.GetMentionCount(ctx, 2, 1); n != 1 {
|
|
t.Errorf("mention_count = %d for a reader behind the mentioning message, want 1", n)
|
|
}
|
|
}
|
|
|
|
func TestGetUserIDsByUsernames_CaseInsensitive(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
|
|
got, err := database.GetUserIDsByUsernames(context.Background(), []string{"BOB", "carol", "ghost"})
|
|
if err != nil {
|
|
t.Fatalf("GetUserIDsByUsernames: %v", err)
|
|
}
|
|
if got["bob"] != 2 {
|
|
t.Errorf("bob = %d, want 2", got["bob"])
|
|
}
|
|
if got["carol"] != 3 {
|
|
t.Errorf("carol = %d, want 3 (stored as \"Carol\")", got["carol"])
|
|
}
|
|
if _, ok := got["ghost"]; ok {
|
|
t.Error("unknown username must not resolve")
|
|
}
|
|
}
|
|
|
|
// TestGetUserIDsByUsernames_LapsedTempBan_StillResolves locks the "reconverged
|
|
// raw column" fix: nothing clears users.banned when a temp ban's ban_expires
|
|
// lapses (that's decided lazily, at login, by auth.IsEffectivelyBanned), so a
|
|
// raw `banned = 0` filter would leave a reinstated user permanently
|
|
// unresolvable as an @mention target even though they can log in and post
|
|
// again.
|
|
func TestGetUserIDsByUsernames_LapsedTempBan_StillResolves(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
past := time.Now().Add(-1 * time.Hour)
|
|
if err := database.BanUser(ctx, 2, "temp ban", &past); err != nil {
|
|
t.Fatalf("BanUser: %v", err)
|
|
}
|
|
|
|
got, err := database.GetUserIDsByUsernames(ctx, []string{"bob"})
|
|
if err != nil {
|
|
t.Fatalf("GetUserIDsByUsernames: %v", err)
|
|
}
|
|
if got["bob"] != 2 {
|
|
t.Errorf("bob = %d, want 2 (a lapsed temp ban must not hide the user from mention resolution)", got["bob"])
|
|
}
|
|
}
|
|
|
|
// TestGetUserIDsByUsernames_ActiveTempBan_Excluded is the complement: a temp
|
|
// ban that has NOT yet lapsed must still exclude the user, same as today.
|
|
func TestGetUserIDsByUsernames_ActiveTempBan_Excluded(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
future := time.Now().Add(1 * time.Hour)
|
|
if err := database.BanUser(ctx, 2, "temp ban", &future); err != nil {
|
|
t.Fatalf("BanUser: %v", err)
|
|
}
|
|
|
|
got, err := database.GetUserIDsByUsernames(ctx, []string{"bob"})
|
|
if err != nil {
|
|
t.Fatalf("GetUserIDsByUsernames: %v", err)
|
|
}
|
|
if _, ok := got["bob"]; ok {
|
|
t.Error("actively temp-banned user must not resolve as a mention target")
|
|
}
|
|
}
|
|
|
|
// TestGetUserIDsByUsernames_ActiveTempBan_SQLiteTimeFormat_Excluded pins the
|
|
// expiry filter against the second ban_expires spelling auth.IsEffectivelyBanned
|
|
// accepts (and that auth/helpers_test.go locks): SQLite's space-separated
|
|
// "2006-01-02 15:04:05". The comparison in the SQL filter is lexical and ' '
|
|
// sorts BELOW 'T', so an unnormalised clause reads any same-day space-form
|
|
// expiry as already lapsed and fails OPEN — a genuinely banned user back in
|
|
// the fan-out. The expiry here is deliberately later on the *same UTC day* so
|
|
// only the separator, not the date, can decide the comparison.
|
|
func TestGetUserIDsByUsernames_ActiveTempBan_SQLiteTimeFormat_Excluded(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
// End of the current UTC day: still in the future, still the same date, so
|
|
// the ' ' vs 'T' separator is the only thing that can flip the comparison.
|
|
now := time.Now().UTC()
|
|
future := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.UTC)
|
|
if !future.After(now) {
|
|
t.Skip("run within the last second of the UTC day; no same-day future instant exists")
|
|
}
|
|
if _, err := database.ExecContext(ctx,
|
|
`UPDATE users SET banned = 1, ban_reason = 'temp ban', ban_expires = ? WHERE id = ?`,
|
|
future.Format("2006-01-02 15:04:05"), 2,
|
|
); err != nil {
|
|
t.Fatalf("seed space-format ban_expires: %v", err)
|
|
}
|
|
|
|
got, err := database.GetUserIDsByUsernames(ctx, []string{"bob"})
|
|
if err != nil {
|
|
t.Fatalf("GetUserIDsByUsernames: %v", err)
|
|
}
|
|
if _, ok := got["bob"]; ok {
|
|
t.Error("temp ban stored in SQLite's space-separated time format must still exclude the user")
|
|
}
|
|
}
|
|
|
|
// TestGetUserIDsByUsernames_PermanentBan_Excluded locks the nil-expiry case:
|
|
// a permanent ban (ban_expires NULL) must keep excluding the user forever.
|
|
func TestGetUserIDsByUsernames_PermanentBan_Excluded(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
if err := database.BanUser(ctx, 2, "permanent ban", nil); err != nil {
|
|
t.Fatalf("BanUser: %v", err)
|
|
}
|
|
|
|
got, err := database.GetUserIDsByUsernames(ctx, []string{"bob"})
|
|
if err != nil {
|
|
t.Fatalf("GetUserIDsByUsernames: %v", err)
|
|
}
|
|
if _, ok := got["bob"]; ok {
|
|
t.Error("permanently banned user must not resolve as a mention target")
|
|
}
|
|
}
|
|
|
|
// TestListMentionTargetsByRoles_LapsedTempBan_Included covers the same
|
|
// expiry-aware fix on the @everyone/@here fan-out path.
|
|
func TestListMentionTargetsByRoles_LapsedTempBan_Included(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
past := time.Now().Add(-1 * time.Hour)
|
|
if err := database.BanUser(ctx, 2, "temp ban", &past); err != nil {
|
|
t.Fatalf("BanUser: %v", err)
|
|
}
|
|
|
|
targets, err := database.ListMentionTargetsByRoles(ctx, []int64{4})
|
|
if err != nil {
|
|
t.Fatalf("ListMentionTargetsByRoles: %v", err)
|
|
}
|
|
found := false
|
|
for _, tgt := range targets {
|
|
if tgt.UserID == 2 {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("user with a lapsed temp ban must still appear in the @everyone/@here fan-out")
|
|
}
|
|
}
|
|
|
|
func TestListMentionTargetsByRoles(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
if err := database.UpdateUserStatus(ctx, 2, "online"); err != nil {
|
|
t.Fatalf("UpdateUserStatus: %v", err)
|
|
}
|
|
|
|
targets, err := database.ListMentionTargetsByRoles(ctx, []int64{4})
|
|
if err != nil {
|
|
t.Fatalf("ListMentionTargetsByRoles: %v", err)
|
|
}
|
|
if len(targets) != 3 {
|
|
t.Fatalf("targets = %d, want 3", len(targets))
|
|
}
|
|
for _, tgt := range targets {
|
|
if tgt.UserID == 2 && tgt.Status != "online" {
|
|
t.Errorf("user 2 status = %q, want online", tgt.Status)
|
|
}
|
|
}
|
|
|
|
empty, err := database.ListMentionTargetsByRoles(ctx, nil)
|
|
if err != nil || len(empty) != 0 {
|
|
t.Errorf("no roles must yield no targets: %v %v", empty, err)
|
|
}
|
|
}
|
|
|
|
func TestListBlockersOf(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
if err := database.BlockUser(ctx, 2, 1); err != nil {
|
|
t.Fatalf("BlockUser: %v", err)
|
|
}
|
|
blockers, err := database.ListBlockersOf(ctx, 1)
|
|
if err != nil {
|
|
t.Fatalf("ListBlockersOf: %v", err)
|
|
}
|
|
if len(blockers) != 1 || blockers[0] != 2 {
|
|
t.Errorf("blockers = %v, want [2]", blockers)
|
|
}
|
|
}
|
|
|
|
func TestGetChannelOverrides(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
if err := database.UpsertChannelOverride(ctx, 1, 4, 0, 2); err != nil {
|
|
t.Fatalf("UpsertChannelOverride: %v", err)
|
|
}
|
|
got, err := database.GetChannelOverrides(ctx, 1)
|
|
if err != nil {
|
|
t.Fatalf("GetChannelOverrides: %v", err)
|
|
}
|
|
if o, ok := got[4]; !ok || o.Deny != 2 {
|
|
t.Errorf("override for role 4 = %+v, want deny=2", o)
|
|
}
|
|
}
|
|
|
|
// TestMessagesForAPI_CarryMentions locks that REST history hands back the
|
|
// resolved mention list and the everyone flag.
|
|
func TestMessagesForAPI_CarryMentions(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
if _, err := database.CreateMessageWithMentions(ctx, 1, 1, "hi @bob", nil, []int64{2}, true); err != nil {
|
|
t.Fatalf("CreateMessageWithMentions: %v", err)
|
|
}
|
|
msgs, err := database.GetMessagesForAPI(ctx, 1, 0, 10, 1)
|
|
if err != nil {
|
|
t.Fatalf("GetMessagesForAPI: %v", err)
|
|
}
|
|
if len(msgs) != 1 {
|
|
t.Fatalf("messages = %d, want 1", len(msgs))
|
|
}
|
|
if len(msgs[0].Mentions) != 1 || msgs[0].Mentions[0] != 2 {
|
|
t.Errorf("mentions = %v, want [2]", msgs[0].Mentions)
|
|
}
|
|
if !msgs[0].MentionsEveryone {
|
|
t.Error("mentions_everyone = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestSearchMessages_CarryMentions(t *testing.T) {
|
|
database := newMigratedTestDB(t)
|
|
seedMentionFixture(t, database)
|
|
ctx := context.Background()
|
|
|
|
if _, err := database.CreateMessageWithMentions(ctx, 1, 1, "deployment notes", nil, []int64{2}, false); err != nil {
|
|
t.Fatalf("CreateMessageWithMentions: %v", err)
|
|
}
|
|
results, err := database.SearchMessages(ctx, "deployment", nil, 10)
|
|
if err != nil {
|
|
t.Fatalf("SearchMessages: %v", err)
|
|
}
|
|
if len(results) != 1 {
|
|
t.Fatalf("results = %d, want 1", len(results))
|
|
}
|
|
if len(results[0].Mentions) != 1 || results[0].Mentions[0] != 2 {
|
|
t.Errorf("mentions = %v, want [2]", results[0].Mentions)
|
|
}
|
|
if results[0].MentionsEveryone {
|
|
t.Error("mentions_everyone = true, want false")
|
|
}
|
|
}
|