fix: preserve formatted date message entities
This commit is contained in:
parent
53105b6823
commit
03b785ebf4
10 changed files with 214 additions and 31 deletions
|
|
@ -89,6 +89,52 @@ func TestTranscodeMessageGolden(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTranscodeFormattedDateEntityLayerBoundary(t *testing.T) {
|
||||||
|
const formattedDateEntityCRC = 0x904ac7c7
|
||||||
|
entityCRC := func(crc uint32) []byte {
|
||||||
|
return []byte{byte(crc), byte(crc >> 8), byte(crc >> 16), byte(crc >> 24)}
|
||||||
|
}
|
||||||
|
msg := &tg.Message{
|
||||||
|
ID: 7,
|
||||||
|
PeerID: &tg.PeerUser{UserID: 2},
|
||||||
|
Date: 100,
|
||||||
|
Message: "Meet soon",
|
||||||
|
Entities: []tg.MessageEntityClass{
|
||||||
|
&tg.MessageEntityFormattedDate{Offset: 5, Length: 4, Date: 1773436800, ShortDate: true, ShortTime: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
raw := mustEncode(t, msg)
|
||||||
|
|
||||||
|
out222, err := Transcode(raw, 222)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("transcode message->222: %v", err)
|
||||||
|
}
|
||||||
|
if bytes.Contains(out222, entityCRC(formattedDateEntityCRC)) {
|
||||||
|
t.Fatalf("layer 222 output leaked formatted-date entity")
|
||||||
|
}
|
||||||
|
if !bytes.Contains(out222, entityCRC(messageEntityUnknownID)) {
|
||||||
|
t.Fatalf("layer 222 output missing messageEntityUnknown fallback")
|
||||||
|
}
|
||||||
|
m222 := loadLayerModel(t, 222)
|
||||||
|
b222 := &bin.Buffer{Buf: append([]byte(nil), out222...)}
|
||||||
|
if err := m222.skipObject(b222); err != nil || b222.Len() != 0 {
|
||||||
|
t.Fatalf("layer 222 formatted-date fallback does not parse cleanly (err=%v left=%d)", err, b222.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
out223, err := Transcode(raw, 223)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("transcode message->223: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Contains(out223, entityCRC(formattedDateEntityCRC)) {
|
||||||
|
t.Fatalf("layer 223 output did not preserve formatted-date entity")
|
||||||
|
}
|
||||||
|
m223 := loadLayerModel(t, 223)
|
||||||
|
b223 := &bin.Buffer{Buf: append([]byte(nil), out223...)}
|
||||||
|
if err := m223.skipObject(b223); err != nil || b223.Len() != 0 {
|
||||||
|
t.Fatalf("layer 223 formatted-date output does not parse cleanly (err=%v left=%d)", err, b223.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestTranscodePassthroughNonAPI verifies that a top-level constructor absent
|
// TestTranscodePassthroughNonAPI verifies that a top-level constructor absent
|
||||||
// from the tg schema (an MTProto control object such as rpc_error) passes
|
// from the tg schema (an MTProto control object such as rpc_error) passes
|
||||||
// through untouched at any layer.
|
// through untouched at any layer.
|
||||||
|
|
|
||||||
|
|
@ -4,28 +4,29 @@ package domain
|
||||||
type MessageEntityType string
|
type MessageEntityType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MessageEntityBold MessageEntityType = "bold"
|
MessageEntityBold MessageEntityType = "bold"
|
||||||
MessageEntityItalic MessageEntityType = "italic"
|
MessageEntityItalic MessageEntityType = "italic"
|
||||||
MessageEntityUnderline MessageEntityType = "underline"
|
MessageEntityUnderline MessageEntityType = "underline"
|
||||||
MessageEntityStrike MessageEntityType = "strike"
|
MessageEntityStrike MessageEntityType = "strike"
|
||||||
MessageEntityCode MessageEntityType = "code"
|
MessageEntityCode MessageEntityType = "code"
|
||||||
MessageEntityPre MessageEntityType = "pre"
|
MessageEntityPre MessageEntityType = "pre"
|
||||||
MessageEntityTextURL MessageEntityType = "text_url"
|
MessageEntityTextURL MessageEntityType = "text_url"
|
||||||
MessageEntityMentionName MessageEntityType = "mention_name"
|
MessageEntityMentionName MessageEntityType = "mention_name"
|
||||||
MessageEntitySpoiler MessageEntityType = "spoiler"
|
MessageEntitySpoiler MessageEntityType = "spoiler"
|
||||||
MessageEntityBlockquote MessageEntityType = "blockquote"
|
MessageEntityBlockquote MessageEntityType = "blockquote"
|
||||||
MessageEntityCustomEmoji MessageEntityType = "custom_emoji"
|
MessageEntityCustomEmoji MessageEntityType = "custom_emoji"
|
||||||
MessageEntityMention MessageEntityType = "mention"
|
MessageEntityMention MessageEntityType = "mention"
|
||||||
MessageEntityHashtag MessageEntityType = "hashtag"
|
MessageEntityHashtag MessageEntityType = "hashtag"
|
||||||
MessageEntityCashtag MessageEntityType = "cashtag"
|
MessageEntityCashtag MessageEntityType = "cashtag"
|
||||||
MessageEntityBotCommand MessageEntityType = "bot_command"
|
MessageEntityBotCommand MessageEntityType = "bot_command"
|
||||||
MessageEntityURL MessageEntityType = "url"
|
MessageEntityURL MessageEntityType = "url"
|
||||||
MessageEntityEmail MessageEntityType = "email"
|
MessageEntityEmail MessageEntityType = "email"
|
||||||
MessageEntityPhone MessageEntityType = "phone"
|
MessageEntityPhone MessageEntityType = "phone"
|
||||||
MessageEntityBankCard MessageEntityType = "bank_card"
|
MessageEntityBankCard MessageEntityType = "bank_card"
|
||||||
MessageEntityDiffInsert MessageEntityType = "diff_insert"
|
MessageEntityFormattedDate MessageEntityType = "formatted_date"
|
||||||
MessageEntityDiffReplace MessageEntityType = "diff_replace"
|
MessageEntityDiffInsert MessageEntityType = "diff_insert"
|
||||||
MessageEntityDiffDelete MessageEntityType = "diff_delete"
|
MessageEntityDiffReplace MessageEntityType = "diff_replace"
|
||||||
|
MessageEntityDiffDelete MessageEntityType = "diff_delete"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -104,6 +105,15 @@ type MessageEntity struct {
|
||||||
DocumentID int64
|
DocumentID int64
|
||||||
// Collapsed 仅 blockquote 使用。
|
// Collapsed 仅 blockquote 使用。
|
||||||
Collapsed bool
|
Collapsed bool
|
||||||
|
// Date / Relative / ShortTime / LongTime / ShortDate / LongDate / DayOfWeek
|
||||||
|
// 仅 formatted_date 使用。
|
||||||
|
Date int
|
||||||
|
Relative bool
|
||||||
|
ShortTime bool
|
||||||
|
LongTime bool
|
||||||
|
ShortDate bool
|
||||||
|
LongDate bool
|
||||||
|
DayOfWeek bool
|
||||||
// OldText 仅 AI compose diff_replace 使用;普通消息输入不会接受该实体。
|
// OldText 仅 AI compose diff_replace 使用;普通消息输入不会接受该实体。
|
||||||
OldText string
|
OldText string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -473,6 +473,18 @@ func tgMessageEntities(entities []domain.MessageEntity) []tg.MessageEntityClass
|
||||||
out = append(out, &tg.MessageEntityPhone{Offset: entity.Offset, Length: entity.Length})
|
out = append(out, &tg.MessageEntityPhone{Offset: entity.Offset, Length: entity.Length})
|
||||||
case domain.MessageEntityBankCard:
|
case domain.MessageEntityBankCard:
|
||||||
out = append(out, &tg.MessageEntityBankCard{Offset: entity.Offset, Length: entity.Length})
|
out = append(out, &tg.MessageEntityBankCard{Offset: entity.Offset, Length: entity.Length})
|
||||||
|
case domain.MessageEntityFormattedDate:
|
||||||
|
out = append(out, &tg.MessageEntityFormattedDate{
|
||||||
|
Offset: entity.Offset,
|
||||||
|
Length: entity.Length,
|
||||||
|
Date: entity.Date,
|
||||||
|
Relative: entity.Relative,
|
||||||
|
ShortTime: entity.ShortTime,
|
||||||
|
LongTime: entity.LongTime,
|
||||||
|
ShortDate: entity.ShortDate,
|
||||||
|
LongDate: entity.LongDate,
|
||||||
|
DayOfWeek: entity.DayOfWeek,
|
||||||
|
})
|
||||||
case domain.MessageEntityDiffInsert:
|
case domain.MessageEntityDiffInsert:
|
||||||
out = append(out, &tg.MessageEntityDiffInsert{Offset: entity.Offset, Length: entity.Length})
|
out = append(out, &tg.MessageEntityDiffInsert{Offset: entity.Offset, Length: entity.Length})
|
||||||
case domain.MessageEntityDiffReplace:
|
case domain.MessageEntityDiffReplace:
|
||||||
|
|
@ -549,6 +561,19 @@ func domainMessageEntitiesForViewer(viewerUserID int64, entities []tg.MessageEnt
|
||||||
out = append(out, domain.MessageEntity{Type: domain.MessageEntityPhone, Offset: e.Offset, Length: e.Length})
|
out = append(out, domain.MessageEntity{Type: domain.MessageEntityPhone, Offset: e.Offset, Length: e.Length})
|
||||||
case *tg.MessageEntityBankCard:
|
case *tg.MessageEntityBankCard:
|
||||||
out = append(out, domain.MessageEntity{Type: domain.MessageEntityBankCard, Offset: e.Offset, Length: e.Length})
|
out = append(out, domain.MessageEntity{Type: domain.MessageEntityBankCard, Offset: e.Offset, Length: e.Length})
|
||||||
|
case *tg.MessageEntityFormattedDate:
|
||||||
|
out = append(out, domain.MessageEntity{
|
||||||
|
Type: domain.MessageEntityFormattedDate,
|
||||||
|
Offset: e.Offset,
|
||||||
|
Length: e.Length,
|
||||||
|
Date: e.Date,
|
||||||
|
Relative: e.Relative || e.GetRelative(),
|
||||||
|
ShortTime: e.ShortTime || e.GetShortTime(),
|
||||||
|
LongTime: e.LongTime || e.GetLongTime(),
|
||||||
|
ShortDate: e.ShortDate || e.GetShortDate(),
|
||||||
|
LongDate: e.LongDate || e.GetLongDate(),
|
||||||
|
DayOfWeek: e.DayOfWeek || e.GetDayOfWeek(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package rpc
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gotd/td/bin"
|
||||||
"github.com/gotd/td/tg"
|
"github.com/gotd/td/tg"
|
||||||
|
|
||||||
"telesrv/internal/domain"
|
"telesrv/internal/domain"
|
||||||
|
|
@ -116,6 +117,7 @@ func TestMessageEntitiesRoundTripAllStyledTypes(t *testing.T) {
|
||||||
&tg.MessageEntityMention{Offset: 12, Length: 13},
|
&tg.MessageEntityMention{Offset: 12, Length: 13},
|
||||||
&tg.MessageEntityHashtag{Offset: 13, Length: 14},
|
&tg.MessageEntityHashtag{Offset: 13, Length: 14},
|
||||||
&tg.MessageEntityURL{Offset: 14, Length: 15},
|
&tg.MessageEntityURL{Offset: 14, Length: 15},
|
||||||
|
&tg.MessageEntityFormattedDate{Offset: 15, Length: 16, Date: 1773436800, ShortTime: true, LongDate: true, DayOfWeek: true},
|
||||||
}
|
}
|
||||||
converted := domainMessageEntitiesForViewer(viewerID, in)
|
converted := domainMessageEntitiesForViewer(viewerID, in)
|
||||||
if len(converted) != len(in) {
|
if len(converted) != len(in) {
|
||||||
|
|
@ -133,6 +135,9 @@ func TestMessageEntitiesRoundTripAllStyledTypes(t *testing.T) {
|
||||||
if converted[11].DocumentID != 777 {
|
if converted[11].DocumentID != 777 {
|
||||||
t.Fatalf("custom emoji document = %d, want 777", converted[11].DocumentID)
|
t.Fatalf("custom emoji document = %d, want 777", converted[11].DocumentID)
|
||||||
}
|
}
|
||||||
|
if formatted := converted[15]; formatted.Type != domain.MessageEntityFormattedDate || formatted.Date != 1773436800 || !formatted.ShortTime || !formatted.LongDate || !formatted.DayOfWeek {
|
||||||
|
t.Fatalf("formatted date entity = %+v, want date and flags preserved", formatted)
|
||||||
|
}
|
||||||
out := tgMessageEntities(converted)
|
out := tgMessageEntities(converted)
|
||||||
if len(out) != len(in) {
|
if len(out) != len(in) {
|
||||||
t.Fatalf("round-trip produced %d entities, want %d", len(out), len(in))
|
t.Fatalf("round-trip produced %d entities, want %d", len(out), len(in))
|
||||||
|
|
@ -143,4 +148,22 @@ func TestMessageEntitiesRoundTripAllStyledTypes(t *testing.T) {
|
||||||
if mention, ok := out[8].(*tg.MessageEntityMentionName); !ok || mention.UserID != viewerID {
|
if mention, ok := out[8].(*tg.MessageEntityMentionName); !ok || mention.UserID != viewerID {
|
||||||
t.Fatalf("self mention round-trip = %#v, want messageEntityMentionName self", out[8])
|
t.Fatalf("self mention round-trip = %#v, want messageEntityMentionName self", out[8])
|
||||||
}
|
}
|
||||||
|
if formatted, ok := out[15].(*tg.MessageEntityFormattedDate); !ok || formatted.Date != 1773436800 || !formatted.ShortTime || !formatted.LongDate || !formatted.DayOfWeek {
|
||||||
|
t.Fatalf("formatted date round-trip = %#v, want date and flags preserved", out[15])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageEntityFormattedDatePreservesFlagsOnlyInput(t *testing.T) {
|
||||||
|
var flags bin.Fields
|
||||||
|
flags.Set(1) // short_time
|
||||||
|
flags.Set(4) // long_date
|
||||||
|
converted := domainMessageEntities([]tg.MessageEntityClass{
|
||||||
|
&tg.MessageEntityFormattedDate{Flags: flags, Offset: 1, Length: 2, Date: 1773436800},
|
||||||
|
})
|
||||||
|
if len(converted) != 1 {
|
||||||
|
t.Fatalf("converted %d entities, want one", len(converted))
|
||||||
|
}
|
||||||
|
if got := converted[0]; got.Type != domain.MessageEntityFormattedDate || got.Date != 1773436800 || !got.ShortTime || !got.LongDate {
|
||||||
|
t.Fatalf("formatted date flags-only input = %+v, want short_time/long_date preserved", got)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ func TestMessagesSendMessageReturnsUpdateAndRecordsOwnerContext(t *testing.T) {
|
||||||
RandomID: 123456,
|
RandomID: 123456,
|
||||||
Entities: []tg.MessageEntityClass{
|
Entities: []tg.MessageEntityClass{
|
||||||
&tg.MessageEntityBold{Offset: 0, Length: 5},
|
&tg.MessageEntityBold{Offset: 0, Length: 5},
|
||||||
|
&tg.MessageEntityFormattedDate{Offset: 6, Length: 8, Date: 1773436800, ShortDate: true, ShortTime: true},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var in bin.Buffer
|
var in bin.Buffer
|
||||||
|
|
@ -51,8 +52,12 @@ func TestMessagesSendMessageReturnsUpdateAndRecordsOwnerContext(t *testing.T) {
|
||||||
if messages.sendUserID != sender.ID || messages.sendReq.SenderUserID != sender.ID || messages.sendReq.RecipientUserID != recipient.ID || messages.sendReq.OriginSessionID != 77 {
|
if messages.sendUserID != sender.ID || messages.sendReq.SenderUserID != sender.ID || messages.sendReq.RecipientUserID != recipient.ID || messages.sendReq.OriginSessionID != 77 {
|
||||||
t.Fatalf("send context = user %d req %+v, want sender/recipient/session", messages.sendUserID, messages.sendReq)
|
t.Fatalf("send context = user %d req %+v, want sender/recipient/session", messages.sendUserID, messages.sendReq)
|
||||||
}
|
}
|
||||||
if len(messages.sendReq.Entities) != 1 || messages.sendReq.Entities[0].Type != domain.MessageEntityBold {
|
if len(messages.sendReq.Entities) != 2 || messages.sendReq.Entities[0].Type != domain.MessageEntityBold {
|
||||||
t.Fatalf("entities = %+v, want bold entity converted to domain", messages.sendReq.Entities)
|
t.Fatalf("entities = %+v, want bold and formatted date converted to domain", messages.sendReq.Entities)
|
||||||
|
}
|
||||||
|
dateEntity := messages.sendReq.Entities[1]
|
||||||
|
if dateEntity.Type != domain.MessageEntityFormattedDate || dateEntity.Date != 1773436800 || !dateEntity.ShortDate || !dateEntity.ShortTime {
|
||||||
|
t.Fatalf("formatted date entity = %+v, want date and flags preserved", dateEntity)
|
||||||
}
|
}
|
||||||
if len(got.Updates) != 2 {
|
if len(got.Updates) != 2 {
|
||||||
t.Fatalf("updates = %+v, want message id + new message", got.Updates)
|
t.Fatalf("updates = %+v, want message id + new message", got.Updates)
|
||||||
|
|
@ -68,6 +73,13 @@ func TestMessagesSendMessageReturnsUpdateAndRecordsOwnerContext(t *testing.T) {
|
||||||
if !ok || !msg.Out || msg.PeerID.(*tg.PeerUser).UserID != recipient.ID || msg.Message != req.Message {
|
if !ok || !msg.Out || msg.PeerID.(*tg.PeerUser).UserID != recipient.ID || msg.Message != req.Message {
|
||||||
t.Fatalf("message = %#v, want outgoing private text to recipient", newMsg.Message)
|
t.Fatalf("message = %#v, want outgoing private text to recipient", newMsg.Message)
|
||||||
}
|
}
|
||||||
|
if len(msg.Entities) != 2 {
|
||||||
|
t.Fatalf("message entities = %+v, want 2", msg.Entities)
|
||||||
|
}
|
||||||
|
formatted, ok := msg.Entities[1].(*tg.MessageEntityFormattedDate)
|
||||||
|
if !ok || formatted.Date != 1773436800 || !formatted.ShortDate || !formatted.ShortTime {
|
||||||
|
t.Fatalf("formatted response entity = %#v, want date and flags preserved", msg.Entities[1])
|
||||||
|
}
|
||||||
if metrics.messageSend != 1 || metrics.messageSendErr != nil {
|
if metrics.messageSend != 1 || metrics.messageSendErr != nil {
|
||||||
t.Fatalf("metrics send=%d err=%v, want one successful send", metrics.messageSend, metrics.messageSendErr)
|
t.Fatalf("metrics send=%d err=%v, want one successful send", metrics.messageSend, metrics.messageSendErr)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1481,7 +1481,8 @@ func storyCaptionEntitySupported(entity tg.MessageEntityClass) bool {
|
||||||
*tg.MessageEntityBankCard,
|
*tg.MessageEntityBankCard,
|
||||||
*tg.MessageEntitySpoiler,
|
*tg.MessageEntitySpoiler,
|
||||||
*tg.MessageEntityCustomEmoji,
|
*tg.MessageEntityCustomEmoji,
|
||||||
*tg.MessageEntityBlockquote:
|
*tg.MessageEntityBlockquote,
|
||||||
|
*tg.MessageEntityFormattedDate:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -7048,8 +7048,11 @@ func TestStoriesCaptionEntitiesRejectMalformedInputsBeforePeerAndMutation(t *tes
|
||||||
PrivacyRules: []tg.InputPrivacyRuleClass{&tg.InputPrivacyValueAllowAll{}},
|
PrivacyRules: []tg.InputPrivacyRuleClass{&tg.InputPrivacyValueAllowAll{}},
|
||||||
RandomID: 7320,
|
RandomID: 7320,
|
||||||
Period: 86400,
|
Period: 86400,
|
||||||
Caption: "🙂 ok",
|
Caption: "🙂 ok soon",
|
||||||
Entities: []tg.MessageEntityClass{&tg.MessageEntityBold{Offset: 0, Length: 2}},
|
Entities: []tg.MessageEntityClass{
|
||||||
|
&tg.MessageEntityBold{Offset: 0, Length: 2},
|
||||||
|
&tg.MessageEntityFormattedDate{Offset: 6, Length: 4, Date: 1773436800, ShortDate: true, ShortTime: true},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
updates, err := r.onStoriesSendStory(reqCtx, validReq)
|
updates, err := r.onStoriesSendStory(reqCtx, validReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -7057,12 +7060,15 @@ func TestStoriesCaptionEntitiesRejectMalformedInputsBeforePeerAndMutation(t *tes
|
||||||
}
|
}
|
||||||
item := updates.(*tg.Updates).Updates[1].(*tg.UpdateStory).Story.(*tg.StoryItem)
|
item := updates.(*tg.Updates).Updates[1].(*tg.UpdateStory).Story.(*tg.StoryItem)
|
||||||
entities, ok := item.GetEntities()
|
entities, ok := item.GetEntities()
|
||||||
if !ok || len(entities) != 1 {
|
if !ok || len(entities) != 2 {
|
||||||
t.Fatalf("story entities = ok %v %+v, want one entity", ok, entities)
|
t.Fatalf("story entities = ok %v %+v, want two entities", ok, entities)
|
||||||
}
|
}
|
||||||
if bold, ok := entities[0].(*tg.MessageEntityBold); !ok || bold.Offset != 0 || bold.Length != 2 {
|
if bold, ok := entities[0].(*tg.MessageEntityBold); !ok || bold.Offset != 0 || bold.Length != 2 {
|
||||||
t.Fatalf("story entity = %T %+v, want bold offset 0 length 2", entities[0], entities[0])
|
t.Fatalf("story entity = %T %+v, want bold offset 0 length 2", entities[0], entities[0])
|
||||||
}
|
}
|
||||||
|
if formatted, ok := entities[1].(*tg.MessageEntityFormattedDate); !ok || formatted.Offset != 6 || formatted.Length != 4 || formatted.Date != 1773436800 || !formatted.ShortDate || !formatted.ShortTime {
|
||||||
|
t.Fatalf("story entity = %T %+v, want formatted date preserved", entities[1], entities[1])
|
||||||
|
}
|
||||||
storyID := updates.(*tg.Updates).Updates[0].(*tg.UpdateStoryID).ID
|
storyID := updates.(*tg.Updates).Updates[0].(*tg.UpdateStoryID).ID
|
||||||
|
|
||||||
var typedNilBold *tg.MessageEntityBold
|
var typedNilBold *tg.MessageEntityBold
|
||||||
|
|
@ -7139,7 +7145,7 @@ func TestStoriesCaptionEntitiesRejectMalformedInputsBeforePeerAndMutation(t *tes
|
||||||
t.Fatalf("stories after rejected caption entities = %+v, want original story only", stories.Stories.Stories)
|
t.Fatalf("stories after rejected caption entities = %+v, want original story only", stories.Stories.Stories)
|
||||||
}
|
}
|
||||||
stored := stories.Stories.Stories[0].(*tg.StoryItem)
|
stored := stories.Stories.Stories[0].(*tg.StoryItem)
|
||||||
if stored.Caption != "🙂 ok" || stored.Edited {
|
if stored.Caption != validReq.Caption || stored.Edited {
|
||||||
t.Fatalf("story after rejected caption entities = %+v, want original caption and not edited", stored)
|
t.Fatalf("story after rejected caption entities = %+v, want original caption and not edited", stored)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,13 @@ type messageEntityJSON struct {
|
||||||
Language string `json:"language,omitempty"`
|
Language string `json:"language,omitempty"`
|
||||||
DocumentID int64 `json:"document_id,omitempty"`
|
DocumentID int64 `json:"document_id,omitempty"`
|
||||||
Collapsed bool `json:"collapsed,omitempty"`
|
Collapsed bool `json:"collapsed,omitempty"`
|
||||||
|
Date int `json:"date,omitempty"`
|
||||||
|
Relative bool `json:"relative,omitempty"`
|
||||||
|
ShortTime bool `json:"short_time,omitempty"`
|
||||||
|
LongTime bool `json:"long_time,omitempty"`
|
||||||
|
ShortDate bool `json:"short_date,omitempty"`
|
||||||
|
LongDate bool `json:"long_date,omitempty"`
|
||||||
|
DayOfWeek bool `json:"day_of_week,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeMessageEntities(entities []domain.MessageEntity) ([]byte, error) {
|
func encodeMessageEntities(entities []domain.MessageEntity) ([]byte, error) {
|
||||||
|
|
@ -260,6 +267,13 @@ func encodeMessageEntities(entities []domain.MessageEntity) ([]byte, error) {
|
||||||
Language: entity.Language,
|
Language: entity.Language,
|
||||||
DocumentID: entity.DocumentID,
|
DocumentID: entity.DocumentID,
|
||||||
Collapsed: entity.Collapsed,
|
Collapsed: entity.Collapsed,
|
||||||
|
Date: entity.Date,
|
||||||
|
Relative: entity.Relative,
|
||||||
|
ShortTime: entity.ShortTime,
|
||||||
|
LongTime: entity.LongTime,
|
||||||
|
ShortDate: entity.ShortDate,
|
||||||
|
LongDate: entity.LongDate,
|
||||||
|
DayOfWeek: entity.DayOfWeek,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
raw, err := json.Marshal(wire)
|
raw, err := json.Marshal(wire)
|
||||||
|
|
@ -288,6 +302,13 @@ func decodeMessageEntities(raw string) ([]domain.MessageEntity, error) {
|
||||||
Language: entity.Language,
|
Language: entity.Language,
|
||||||
DocumentID: entity.DocumentID,
|
DocumentID: entity.DocumentID,
|
||||||
Collapsed: entity.Collapsed,
|
Collapsed: entity.Collapsed,
|
||||||
|
Date: entity.Date,
|
||||||
|
Relative: entity.Relative,
|
||||||
|
ShortTime: entity.ShortTime,
|
||||||
|
LongTime: entity.LongTime,
|
||||||
|
ShortDate: entity.ShortDate,
|
||||||
|
LongDate: entity.LongDate,
|
||||||
|
DayOfWeek: entity.DayOfWeek,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|
|
||||||
33
internal/store/postgres/message_helpers_test.go
Normal file
33
internal/store/postgres/message_helpers_test.go
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"telesrv/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMessageEntityJSONRoundTripPreservesFormattedDate(t *testing.T) {
|
||||||
|
in := []domain.MessageEntity{
|
||||||
|
{
|
||||||
|
Type: domain.MessageEntityFormattedDate,
|
||||||
|
Offset: 2,
|
||||||
|
Length: 5,
|
||||||
|
Date: 1773436800,
|
||||||
|
Relative: true,
|
||||||
|
ShortTime: true,
|
||||||
|
LongDate: true,
|
||||||
|
DayOfWeek: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
raw, err := encodeMessageEntities(in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("encodeMessageEntities: %v", err)
|
||||||
|
}
|
||||||
|
out, err := decodeMessageEntities(string(raw))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decodeMessageEntities: %v", err)
|
||||||
|
}
|
||||||
|
if !sameMessageEntities(in, out) {
|
||||||
|
t.Fatalf("round-trip entities = %+v raw=%s, want %+v", out, raw, in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,7 +43,10 @@ func TestMessageStoreSendPrivateTextRoundTrip(t *testing.T) {
|
||||||
RecipientUserID: recipient.ID,
|
RecipientUserID: recipient.ID,
|
||||||
RandomID: 123456,
|
RandomID: 123456,
|
||||||
Message: "hello from pg",
|
Message: "hello from pg",
|
||||||
Entities: []domain.MessageEntity{{Type: domain.MessageEntityBold, Offset: 0, Length: 5}},
|
Entities: []domain.MessageEntity{
|
||||||
|
{Type: domain.MessageEntityBold, Offset: 0, Length: 5},
|
||||||
|
{Type: domain.MessageEntityFormattedDate, Offset: 6, Length: 4, Date: 1773436800, ShortTime: true, LongDate: true},
|
||||||
|
},
|
||||||
Date: 1700000200,
|
Date: 1700000200,
|
||||||
OriginAuthKeyID: originAuthKeyID,
|
OriginAuthKeyID: originAuthKeyID,
|
||||||
OriginSessionID: 77,
|
OriginSessionID: 77,
|
||||||
|
|
@ -73,6 +76,9 @@ func TestMessageStoreSendPrivateTextRoundTrip(t *testing.T) {
|
||||||
if len(senderHistory.Messages) != 1 || len(recipientHistory.Messages) != 1 {
|
if len(senderHistory.Messages) != 1 || len(recipientHistory.Messages) != 1 {
|
||||||
t.Fatalf("history sizes = sender %d recipient %d, want both owner partitions populated", len(senderHistory.Messages), len(recipientHistory.Messages))
|
t.Fatalf("history sizes = sender %d recipient %d, want both owner partitions populated", len(senderHistory.Messages), len(recipientHistory.Messages))
|
||||||
}
|
}
|
||||||
|
if !sameMessageEntities(senderHistory.Messages[0].Entities, req.Entities) || !sameMessageEntities(recipientHistory.Messages[0].Entities, req.Entities) {
|
||||||
|
t.Fatalf("history entities sender=%+v recipient=%+v, want %+v", senderHistory.Messages[0].Entities, recipientHistory.Messages[0].Entities, req.Entities)
|
||||||
|
}
|
||||||
|
|
||||||
events, err := NewUpdateEventStore(pool).ListAfter(ctx, recipient.ID, 0, 10)
|
events, err := NewUpdateEventStore(pool).ListAfter(ctx, recipient.ID, 0, 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue