fix: handle opaque attachment references

This commit is contained in:
richjohnrambo
2026-09-30 10:50:42 +08:00
parent 9f30995787
commit 06e08a65e4
4 changed files with 88 additions and 14 deletions
+24 -12
View File
@@ -41,8 +41,9 @@ func runDBVerify(ctx context.Context, coordinatorPath, output, tenantCode, dsnEn
return err return err
} }
var coordinator struct { var coordinator struct {
Start StartInfo `json:"start"` Start StartInfo `json:"start"`
Ledgers map[string][]AcceptedMessage `json:"ledgers"` Ledgers map[string][]AcceptedMessage `json:"ledgers"`
Observed map[string][]AcceptedMessage `json:"observed"`
} }
if err = json.Unmarshal(raw, &coordinator); err != nil { if err = json.Unmarshal(raw, &coordinator); err != nil {
return fmt.Errorf("decode coordinator: %w", err) return fmt.Errorf("decode coordinator: %w", err)
@@ -77,17 +78,11 @@ func runDBVerify(ctx context.Context, coordinatorPath, output, tenantCode, dsnEn
return err return err
} }
expected := make(map[string]AcceptedMessage) expected := expectedDBMessages(coordinator.Start.RunID, coordinator.Ledgers, coordinator.Observed)
months := make(map[string]struct{}) months := make(map[string]struct{})
for _, ledger := range coordinator.Ledgers { for _, message := range expected {
for _, message := range ledger { if message.Kind == "group" {
if message.RunID != coordinator.Start.RunID { months[message.SentAt.UTC().Format("200601")] = struct{}{}
continue
}
expected[message.ClientMid] = message
if message.Kind == "group" {
months[message.SentAt.UTC().Format("200601")] = struct{}{}
}
} }
} }
tables := []string{fmt.Sprintf("im_%03d_messages", tenantID)} tables := []string{fmt.Sprintf("im_%03d_messages", tenantID)}
@@ -167,3 +162,20 @@ func runDBVerify(ctx context.Context, coordinatorPath, output, tenantCode, dsnEn
} }
return nil return nil
} }
func expectedDBMessages(runID string, ledgers, observed map[string][]AcceptedMessage) map[string]AcceptedMessage {
all := make([]AcceptedMessage, 0)
for _, messages := range observed {
all = append(all, messages...)
}
for _, messages := range ledgers {
all = append(all, messages...)
}
expected := make(map[string]AcceptedMessage, len(all))
for _, message := range mergeMessageLedgers(nil, all) {
if message.RunID == runID && message.ClientMid != "" {
expected[message.ClientMid] = message
}
}
return expected
}
+24
View File
@@ -0,0 +1,24 @@
package main
import "testing"
func TestExpectedDBMessagesIncludesObservedWithoutAck(t *testing.T) {
ledgers := map[string][]AcceptedMessage{
"a": {{RunID: "run-1", ClientMid: "acked", Topic: "grp-one", Seq: 1}},
}
observed := map[string][]AcceptedMessage{
"a": {
{RunID: "run-1", ClientMid: "unacked", Topic: "grp-one", Seq: 2},
{RunID: "other", ClientMid: "foreign", Topic: "grp-one", Seq: 3},
},
"b": {{RunID: "run-1", ClientMid: "unacked", Topic: "grp-one", Seq: 2}},
}
expected := expectedDBMessages("run-1", ledgers, observed)
if len(expected) != 2 {
t.Fatalf("expected messages=%d want=2: %+v", len(expected), expected)
}
if expected["acked"].Seq != 1 || expected["unacked"].Seq != 2 {
t.Fatalf("unexpected merged messages: %+v", expected)
}
}
+30
View File
@@ -0,0 +1,30 @@
package store
import (
"testing"
"github.com/tinode/chat/server/store/types"
)
func TestFileLinkUserIDAllowsOpaqueTopicReferences(t *testing.T) {
tests := []struct {
name string
topic string
isUser bool
wantUID types.Uid
}{
{name: "cultivate reference", topic: "cvpbzxw1aio4ni8x0"},
{name: "short opaque reference", topic: "x"},
{name: "empty reference", topic: ""},
{name: "user reference", topic: types.Uid(42).UserId(), isUser: true, wantUID: types.Uid(42)},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
gotUID, gotIsUser := fileLinkUserID(test.topic)
if gotIsUser != test.isUser || gotUID != test.wantUID {
t.Fatalf("fileLinkUserID(%q) = (%v, %v), want (%v, %v)", test.topic, gotUID, gotIsUser, test.wantUID, test.isUser)
}
})
}
}
+10 -2
View File
@@ -4684,8 +4684,8 @@ func (fileMapper) LinkAttachments(tenantID types.TenantID, topic string, msgId t
return err return err
} }
userId := types.ZeroUid userId := types.ZeroUid
if topic != "" && types.GetTopicCat(topic) == types.TopicCatMe { if parsedUserID, ok := fileLinkUserID(topic); ok {
userId = types.ParseUserId(topic) userId = parsedUserID
topic = "" topic = ""
} }
return tenantAdp.TenantFileLinkAttachments(tenantID, topic, userId, msgId, fids) return tenantAdp.TenantFileLinkAttachments(tenantID, topic, userId, msgId, fids)
@@ -4693,6 +4693,14 @@ func (fileMapper) LinkAttachments(tenantID types.TenantID, topic string, msgId t
return nil return nil
} }
// File link topics may also be opaque retention keys, such as cultivate's cvp prefix.
func fileLinkUserID(topic string) (types.Uid, bool) {
if !strings.HasPrefix(topic, "usr") {
return types.ZeroUid, false
}
return types.ParseUserId(topic), true
}
func (fileMapper) UserHasAccess(tenantID types.TenantID, fid string, uid types.Uid) (bool, error) { func (fileMapper) UserHasAccess(tenantID types.TenantID, fid string, uid types.Uid) (bool, error) {
if fid == "" || uid.IsZero() { if fid == "" || uid.IsZero() {
return false, types.ErrMalformed return false, types.ErrMalformed