Accounts finishing done_with_errors showed only a count and a single
last_error. This adds a modal listing every concrete error of the
account's most recent run.
- migration 0005: account_errors table (kind folder|message|account,
folder, message_ref, error, created_at; ON DELETE CASCADE; indexed)
- store: AddAccountError / ClearAccountErrors / ListAccountErrors
- copy: OnError callback captures per-message error text (previously
only counted), with a "UID N: subject" reference
- orchestrator: clear errors at run start; persist folder/message/
account errors; cap 500 rows/account/run with a suppressed-note row
- api: GET /api/tasks/{id}/accounts/{accountId}/errors
- web: AccountErrorsModal, clickable ERRORS count, api + styles
Verified: migration applies on Postgres 18; store add/list/clear and
cascade tests pass against real pg; backend build/vet/test green; web
tsc+vite build and oxlint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9Eq4JtWjyNTv5qat3B3mM
61 lines
1.5 KiB
Go
61 lines
1.5 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
func testStore(t *testing.T) *Store {
|
|
dsn := os.Getenv("TEST_DATABASE_URL")
|
|
if dsn == "" {
|
|
t.Skip("TEST_DATABASE_URL not set")
|
|
}
|
|
s, err := New(context.Background(), dsn)
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
s.Pool.Exec(context.Background(),
|
|
`TRUNCATE endpoints, tasks, accounts, runs, migrated_messages, account_errors RESTART IDENTITY CASCADE`)
|
|
s.Pool.Close()
|
|
})
|
|
return s
|
|
}
|
|
|
|
func TestCreateAndGetEndpoint(t *testing.T) {
|
|
s := testStore(t)
|
|
ctx := context.Background()
|
|
id, err := s.CreateEndpoint(ctx, Endpoint{RoleLabel: "src", Host: "imap.a.com", Port: 993, TLSMode: "ssl"})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
got, err := s.GetEndpoint(ctx, id)
|
|
if err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
if got.Host != "imap.a.com" || got.Port != 993 {
|
|
t.Fatalf("got %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestListEndpointsOrdered(t *testing.T) {
|
|
s := testStore(t)
|
|
ctx := context.Background()
|
|
id1, _ := s.CreateEndpoint(ctx, Endpoint{RoleLabel: "src", Host: "a.com", Port: 993, TLSMode: "ssl"})
|
|
id2, _ := s.CreateEndpoint(ctx, Endpoint{RoleLabel: "dst", Host: "b.com", Port: 143, TLSMode: "starttls"})
|
|
eps, err := s.ListEndpoints(ctx)
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(eps) != 2 {
|
|
t.Fatalf("len=%d want 2", len(eps))
|
|
}
|
|
if eps[0].ID != id1 || eps[1].ID != id2 {
|
|
t.Fatalf("order wrong: %d,%d want %d,%d", eps[0].ID, eps[1].ID, id1, id2)
|
|
}
|
|
if eps[1].TLSMode != "starttls" {
|
|
t.Fatalf("eps[1].TLSMode=%q want starttls", eps[1].TLSMode)
|
|
}
|
|
}
|