chore: import clean Agent Desk snapshot (2/7)
This commit is contained in:
145
internal/repositories/channel_message_outbox_repository_test.go
Normal file
145
internal/repositories/channel_message_outbox_repository_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user