Files
im_server/server/db/mysql/report_group_regression_test.go
T

184 lines
4.9 KiB
Go

//go:build mysql
package mysql
import (
"os"
"strings"
"testing"
"github.com/tinode/chat/server/store/types"
)
func TestGroupAdminAccessModeDoesNotGrantOwner(t *testing.T) {
mode := groupAdminAccessMode()
if !mode.IsAdmin() {
t.Fatalf("admin mode should grant admin access: %s", mode.String())
}
if mode.IsOwner() {
t.Fatalf("admin mode must not grant owner access: %s", mode.String())
}
}
func TestMessageSearchDoesNotClearScannedHead(t *testing.T) {
source, err := os.ReadFile("tenant_adapter.go")
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(source), "msg.Head = kvMapFromJSON(msg.Head)") {
t.Fatal("message search must not clear Head after sqlx StructScan has already decoded it")
}
}
func TestRuntimeOpenDoesNotAutoCreateOrMigrateTables(t *testing.T) {
source, err := os.ReadFile("adapter.go")
if err != nil {
t.Fatal(err)
}
body := string(source)
for _, tc := range []struct {
name string
function string
end string
}{
{
name: "Open",
function: "func (a *adapter) Open",
end: "\nfunc (a *adapter) Close",
},
{
name: "TenantAdapterFor",
function: "func (a *adapter) TenantAdapterFor",
end: "\nfunc (a *adapter) tenantByID",
},
} {
fn := sourceBetween(t, body, tc.function, tc.end)
for _, forbidden := range []string{
"ensureIMRegistrationInviteControlTables",
"ensureIMTenantEntryControlTables",
"ensureIMGroupBusinessTables",
"ensureIMHTTPInfrastructureTables",
"ensureIMAdminGovernanceTables",
"ensureIMFileReviewTables",
"ensureIMOrganizationVisibilityTables",
"ensureSnowflakePrimaryKeys",
"ensureUserIdentitySchema",
"ensureIMRegistrationInviteUseTable",
"CREATE DATABASE",
"CREATE TABLE",
"ALTER TABLE",
} {
if strings.Contains(fn, forbidden) {
t.Fatalf("%s must not auto-create or auto-migrate schema; found %q", tc.name, forbidden)
}
}
}
}
func sourceBetween(t *testing.T, source, startMarker, endMarker string) string {
t.Helper()
start := strings.Index(source, startMarker)
if start < 0 {
t.Fatalf("missing source marker %q", startMarker)
}
end := strings.Index(source[start:], endMarker)
if end < 0 {
t.Fatalf("missing source marker %q after %q", endMarker, startMarker)
}
return source[start : start+end]
}
func TestSearchPaginationCursorUsesLastReturnedRow(t *testing.T) {
source, err := os.ReadFile("tenant_adapter.go")
if err != nil {
t.Fatal(err)
}
body := string(source)
for _, tc := range []struct {
name string
function string
forbidden string
required string
}{
{
name: "p2p message search",
function: "func (a *adapter) TenantMessageSearch",
forbidden: "nextCursor = strconv.FormatInt(msg.SeqId, 10)",
required: "nextCursor = strconv.FormatInt(lastSeq, 10)",
},
{
name: "group message search",
function: "func (a *adapter) tenantGroupMessageSearch",
forbidden: "nextCursor = strconv.FormatInt(msg.SeqId, 10)",
required: "nextCursor = strconv.FormatInt(lastSeq, 10)",
},
{
name: "favorite list",
function: "func (a *adapter) TenantFavoriteList",
forbidden: "nextCursor = strconv.FormatInt(item.ID, 10)",
required: "nextCursor = strconv.FormatInt(lastID, 10)",
},
} {
fn := functionSource(t, body, tc.function)
if strings.Contains(fn, tc.forbidden) {
t.Fatalf("%s must not use the extra fetched row as next cursor", tc.name)
}
if !strings.Contains(fn, tc.required) {
t.Fatalf("%s must use the last returned row as next cursor", tc.name)
}
}
}
func TestMessageSearchTextMatchesStructuredContent(t *testing.T) {
msg := types.Message{
Content: types.KVMap{
"file": map[string]any{
"file_name": "项目方案.pdf",
},
"link": map[string]any{
"title": "季度复盘",
"summary": "增长分析",
},
},
}
for _, query := range []string{"项目方案", "季度", "增长 分析"} {
if !messageMatchesSearch(msg, query) {
t.Fatalf("structured message should match query %q, text=%q", query, messageSearchText(msg.Content))
}
}
if messageMatchesSearch(msg, "不存在") {
t.Fatal("structured message matched unrelated query")
}
}
func TestTenantFileAccessIncludesAvatarAudienceRules(t *testing.T) {
source, err := os.ReadFile("tenant_adapter.go")
if err != nil {
t.Fatal(err)
}
body := string(source)
for _, want := range []string{
"INNER JOIN %s AS fs",
"fs.user_id=? AND fs.peer_id=fml.userid AND fs.status=?",
"INNER JOIN im_group_members AS gm",
"gm.topic=fml.topic AND gm.user_id=? AND gm.member_status=?",
} {
if !strings.Contains(body, want) {
t.Fatalf("file ACL must include avatar audience rule %q", want)
}
}
}
func functionSource(t *testing.T, source, marker string) string {
t.Helper()
start := strings.Index(source, marker)
if start < 0 {
t.Fatalf("function %q not found", marker)
}
next := strings.Index(source[start+len(marker):], "\nfunc ")
if next < 0 {
return source[start:]
}
return source[start : start+len(marker)+next]
}