Files
agent-desk-ng/internal/repositories/channel_message_outbox_repository_test.go

146 lines
4.6 KiB
Go

package repositories
import (
"testing"
"agent-desk/internal/models"
"agent-desk/internal/pkg/enums"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
// Updates passes the map straight to GORM, so every key becomes a column name
// in the generated SQL. A key with no matching column makes the whole UPDATE
// fail, which is how the email channel silently lost every status transition:
// it wrote "send_detail" while the model only had LastError.
func TestChannelMessageOutboxUpdatesAcceptsServiceColumnNames(t *testing.T) {
db, err := gorm.Open(sqlite.Open("file:channel_message_outbox_repository_test?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("open sqlite error = %v", err)
}
if err := db.AutoMigrate(&models.ChannelMessageOutbox{}); err != nil {
t.Fatalf("auto migrate error = %v", err)
}
if err := db.Exec("DELETE FROM channel_message_outboxes").Error; err != nil {
t.Fatalf("clean channel message outboxes error = %v", err)
}
item := &models.ChannelMessageOutbox{
ChannelType: enums.ChannelTypeEmail,
ConversationID: 1,
MessageID: 1,
SendStatus: string(enums.ChannelMessageOutboxStatusPending),
}
if err := db.Create(item).Error; err != nil {
t.Fatalf("create outbox error = %v", err)
}
// The exact column sets the outbound services write.
tests := []struct {
name string
columns map[string]interface{}
}{
{
name: "email marks sent",
columns: map[string]interface{}{
"send_status": string(enums.ChannelMessageOutboxStatusSent),
"send_detail": "sent to user@example.com via smtp",
"sent_at": nil,
"updated_at": nil,
"next_retry_at": nil,
},
},
{
name: "email marks failed",
columns: map[string]interface{}{
"send_status": string(enums.ChannelMessageOutboxStatusFailed),
"send_detail": "smtp: connection refused",
"updated_at": nil,
"next_retry_at": nil,
},
},
{
name: "telegram records last error",
columns: map[string]interface{}{
"send_status": string(enums.ChannelMessageOutboxStatusFailed),
"last_error": "telegram: 429 too many requests",
"updated_at": nil,
},
},
{
name: "retry bookkeeping",
columns: map[string]interface{}{
"retry_count": 2,
"next_retry_at": nil,
"updated_at": nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := ChannelMessageOutboxRepository.Updates(db, item.ID, tt.columns); err != nil {
t.Fatalf("Updates() error = %v", err)
}
})
}
var stored models.ChannelMessageOutbox
if err := db.Where("id = ?", item.ID).First(&stored).Error; err != nil {
t.Fatalf("reload outbox error = %v", err)
}
if stored.SendDetail != "smtp: connection refused" {
t.Fatalf("SendDetail = %q, want %q", stored.SendDetail, "smtp: connection refused")
}
if stored.LastError != "telegram: 429 too many requests" {
t.Fatalf("LastError = %q, want %q", stored.LastError, "telegram: 429 too many requests")
}
if stored.RetryCount != 2 {
t.Fatalf("RetryCount = %d, want 2", stored.RetryCount)
}
}
// A retried row must come back as eligible for the dispatcher again.
func TestChannelMessageOutboxUpdatesClearsNextRetryAt(t *testing.T) {
db, err := gorm.Open(sqlite.Open("file:channel_message_outbox_retry_test?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("open sqlite error = %v", err)
}
if err := db.AutoMigrate(&models.ChannelMessageOutbox{}); err != nil {
t.Fatalf("auto migrate error = %v", err)
}
if err := db.Exec("DELETE FROM channel_message_outboxes").Error; err != nil {
t.Fatalf("clean channel message outboxes error = %v", err)
}
item := &models.ChannelMessageOutbox{
ChannelType: enums.ChannelTypeEmail,
MessageID: 2,
SendStatus: string(enums.ChannelMessageOutboxStatusPending),
}
if err := db.Create(item).Error; err != nil {
t.Fatalf("create outbox error = %v", err)
}
if err := ChannelMessageOutboxRepository.Updates(db, item.ID, map[string]interface{}{
"send_status": string(enums.ChannelMessageOutboxStatusFailed),
"send_detail": "temporary failure",
"retry_count": 1,
"updated_at": nil,
"next_retry_at": nil,
}); err != nil {
t.Fatalf("Updates() error = %v", err)
}
var stored models.ChannelMessageOutbox
if err := db.Where("id = ?", item.ID).First(&stored).Error; err != nil {
t.Fatalf("reload outbox error = %v", err)
}
if stored.SendStatus != string(enums.ChannelMessageOutboxStatusFailed) {
t.Fatalf("SendStatus = %q, want %q", stored.SendStatus, enums.ChannelMessageOutboxStatusFailed)
}
if stored.NextRetryAt != nil {
t.Fatalf("NextRetryAt = %v, want nil so the row is eligible again", stored.NextRetryAt)
}
}