Full pipeline: IMAP ingress -> OpenClaw dispatch -> callback -> SMTP reply. SQLite stateful storage with idempotency, threading, and retry logic. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package database_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"thuanle.me/claw-email-bridge/internal/database"
|
|
)
|
|
|
|
// Test matrix #5: Duplicate message_id → không tạo task mới.
|
|
func TestDuplicateMessageID(t *testing.T) {
|
|
tdb := database.NewTestDB(t)
|
|
|
|
task1 := &database.Task{
|
|
TaskUUID: "uuid-1",
|
|
ThreadID: "thread-1",
|
|
MessageID: "msg-duplicate@test.com",
|
|
Sender: "user@test.com",
|
|
Subject: "First email",
|
|
Status: database.StatusReceived,
|
|
}
|
|
if err := database.CreateTask(tdb.DB, task1); err != nil {
|
|
t.Fatalf("create first task: %v", err)
|
|
}
|
|
|
|
// Lookup should find the existing task.
|
|
found, err := database.FindByMessageID(tdb.DB, "msg-duplicate@test.com")
|
|
if err != nil {
|
|
t.Fatalf("find by message_id: %v", err)
|
|
}
|
|
if found == nil {
|
|
t.Fatal("expected to find existing task")
|
|
}
|
|
if found.TaskUUID != "uuid-1" {
|
|
t.Errorf("expected uuid-1, got %s", found.TaskUUID)
|
|
}
|
|
|
|
// Attempting to create another task with same task_uuid should fail (unique constraint).
|
|
task2 := &database.Task{
|
|
TaskUUID: "uuid-1", // Same UUID — unique constraint violation.
|
|
ThreadID: "thread-1",
|
|
MessageID: "msg-duplicate@test.com",
|
|
Sender: "user@test.com",
|
|
Subject: "Duplicate",
|
|
Status: database.StatusReceived,
|
|
}
|
|
err = database.CreateTask(tdb.DB, task2)
|
|
if err == nil {
|
|
t.Error("expected error creating duplicate task_uuid, got nil")
|
|
}
|
|
}
|
|
|
|
func TestFindByMessageID_NotFound(t *testing.T) {
|
|
tdb := database.NewTestDB(t)
|
|
|
|
found, err := database.FindByMessageID(tdb.DB, "nonexistent@test.com")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if found != nil {
|
|
t.Error("expected nil for nonexistent message_id")
|
|
}
|
|
}
|
|
|
|
func TestThreadHistory(t *testing.T) {
|
|
tdb := database.NewTestDB(t)
|
|
|
|
// Create two completed tasks in same thread.
|
|
for i, uuid := range []string{"uuid-a", "uuid-b"} {
|
|
task := &database.Task{
|
|
TaskUUID: uuid,
|
|
ThreadID: "thread-history",
|
|
MessageID: uuid + "@test.com",
|
|
Sender: "user@test.com",
|
|
Subject: "Thread test",
|
|
BodyPlain: "Message " + uuid,
|
|
AIResponse: "Reply " + uuid,
|
|
Status: database.StatusCompleted,
|
|
}
|
|
_ = i
|
|
if err := database.CreateTask(tdb.DB, task); err != nil {
|
|
t.Fatalf("create task %s: %v", uuid, err)
|
|
}
|
|
}
|
|
|
|
// Create one non-completed task — should not appear in history.
|
|
task3 := &database.Task{
|
|
TaskUUID: "uuid-c",
|
|
ThreadID: "thread-history",
|
|
MessageID: "uuid-c@test.com",
|
|
Sender: "user@test.com",
|
|
Subject: "Pending",
|
|
Status: database.StatusAIProcessing,
|
|
}
|
|
if err := database.CreateTask(tdb.DB, task3); err != nil {
|
|
t.Fatalf("create task: %v", err)
|
|
}
|
|
|
|
history, err := database.ThreadHistory(tdb.DB, "thread-history")
|
|
if err != nil {
|
|
t.Fatalf("thread history: %v", err)
|
|
}
|
|
if len(history) != 2 {
|
|
t.Errorf("expected 2 completed tasks in history, got %d", len(history))
|
|
}
|
|
}
|