feat(imapx): streaming per-folder copy with dedup, idempotent
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
package imapx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// seedInbox logs in as login/pass and APPENDs n minimal messages with unique
|
||||
// Message-IDs into INBOX via a dedicated connection.
|
||||
func seedInbox(t *testing.T, ep Endpoint, login, pass string, n int) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
|
||||
c, err := Connect(ctx, ep)
|
||||
if err != nil {
|
||||
t.Fatalf("seedInbox connect: %v", err)
|
||||
}
|
||||
defer func() { _ = c.Logout().Wait() }()
|
||||
|
||||
if err := c.Login(login, pass).Wait(); err != nil {
|
||||
t.Fatalf("seedInbox login: %v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
msg := fmt.Sprintf(
|
||||
"From: sender@localhost\r\nTo: %s\r\nSubject: seed %d\r\nMessage-Id: <seed-%d-%p@localhost>\r\n\r\nBody %d\r\n",
|
||||
login, i, i, &i, i,
|
||||
)
|
||||
buf := []byte(msg)
|
||||
appendCmd := c.Append("INBOX", int64(len(buf)), nil)
|
||||
if _, err := appendCmd.Write(buf); err != nil {
|
||||
t.Fatalf("seedInbox write %d: %v", i, err)
|
||||
}
|
||||
if err := appendCmd.Close(); err != nil {
|
||||
t.Fatalf("seedInbox close %d: %v", i, err)
|
||||
}
|
||||
if _, err := appendCmd.Wait(); err != nil {
|
||||
t.Fatalf("seedInbox append %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Требует два ящика на greenmail. Первый запуск копирует N, второй — 0 (все skipped).
|
||||
func TestCopyFolderIdempotent(t *testing.T) {
|
||||
ep := testEP(t) // plain greenmail
|
||||
ctx := context.Background()
|
||||
|
||||
// подготовка: APPEND 2 письма в INBOX источника через отдельное соединение
|
||||
seedInbox(t, ep, "src@localhost", "p", 2)
|
||||
|
||||
src, err := Connect(ctx, ep)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = src.Logout().Wait() }()
|
||||
if err := src.Login("src@localhost", "p").Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dst, err := Connect(ctx, ep)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = dst.Logout().Wait() }()
|
||||
if err := dst.Login("dst@localhost", "p").Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
seen := map[string]bool{}
|
||||
deps := CopyDeps{
|
||||
IsMigrated: func(k string) (bool, error) { return seen[k], nil },
|
||||
MarkMigrated: func(_, k string) error { seen[k] = true; return nil },
|
||||
OnProgress: func(_, _ int) {},
|
||||
}
|
||||
|
||||
r1, err := CopyFolder(ctx, src, dst, "INBOX", "INBOX", deps)
|
||||
if err != nil {
|
||||
t.Fatalf("run1: %v", err)
|
||||
}
|
||||
if r1.Copied != 2 {
|
||||
t.Fatalf("run1 copied=%d want 2", r1.Copied)
|
||||
}
|
||||
|
||||
r2, err := CopyFolder(ctx, src, dst, "INBOX", "INBOX", deps)
|
||||
if err != nil {
|
||||
t.Fatalf("run2: %v", err)
|
||||
}
|
||||
if r2.Copied != 0 || r2.Skipped != 2 {
|
||||
t.Fatalf("run2 copied=%d skipped=%d want 0/2", r2.Copied, r2.Skipped)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user