217 lines
6.2 KiB
Go
217 lines
6.2 KiB
Go
package e2e
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/alexedwards/argon2id"
|
|
embeddedpostgres "github.com/fergusstrange/embedded-postgres"
|
|
|
|
appusecase "lambdamail/protocols/internal/application/usecase"
|
|
"lambdamail/protocols/internal/infrastructure/postgres"
|
|
managesievepresentation "lambdamail/protocols/internal/presentation/managesieve"
|
|
)
|
|
|
|
func TestManageSieveEndToEnd(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("e2e test skipped in -short mode")
|
|
}
|
|
ctx := context.Background()
|
|
|
|
runtimeDir := t.TempDir()
|
|
pg := embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().
|
|
Port(15433). // distinct port for ManageSieve e2e test execution
|
|
RuntimePath(runtimeDir).
|
|
StartTimeout(120 * time.Second))
|
|
if err := pg.Start(); err != nil {
|
|
t.Fatalf("start embedded postgres: %v", err)
|
|
}
|
|
defer pg.Stop()
|
|
|
|
dbURL := "postgres://postgres:postgres@localhost:15433/postgres?sslmode=disable"
|
|
pool, err := postgres.NewPool(ctx, dbURL)
|
|
if err != nil {
|
|
t.Fatalf("connect: %v", err)
|
|
}
|
|
defer pool.Close()
|
|
|
|
root := repoRoot(t)
|
|
sql, err := os.ReadFile(filepath.Join(root, "migrations", "0001_init_schema.up.sql"))
|
|
if err != nil {
|
|
t.Fatalf("read migration: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, string(sql)); err != nil {
|
|
t.Fatalf("apply migration: %v", err)
|
|
}
|
|
|
|
// --- Seed domain & mailbox -----------------------------------------------
|
|
password := "correct horse battery staple"
|
|
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
|
if err != nil {
|
|
t.Fatalf("hash password: %v", err)
|
|
}
|
|
domainID := "00000000-0000-0000-0000-000000000001"
|
|
mailboxID := "00000000-0000-0000-0000-000000000002"
|
|
if _, err := pool.Exec(ctx, `INSERT INTO domains (id, name, punycode_name) VALUES ($1, 'example.test', 'example.test')`, domainID); err != nil {
|
|
t.Fatalf("seed domain: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `INSERT INTO mailboxes (id, domain_id, local_part, email_address, password_hash) VALUES ($1, $2, 'user', 'user@example.test', $3)`, mailboxID, domainID, hash); err != nil {
|
|
t.Fatalf("seed mailbox: %v", err)
|
|
}
|
|
|
|
// Setup ManageSieve Server
|
|
sieveUC := appusecase.NewManageSieveSessionUseCase(
|
|
postgres.NewAuthRepository(pool),
|
|
postgres.NewSieveRepository(pool),
|
|
)
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("listen: %v", err)
|
|
}
|
|
defer ln.Close()
|
|
|
|
srv := managesievepresentation.NewServer(ln.Addr().String(), sieveUC, nil)
|
|
go srv.Serve(ln)
|
|
defer srv.Close()
|
|
|
|
// Dial ManageSieve server
|
|
conn, err := net.Dial("tcp", ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("dial ManageSieve: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
reader := bufio.NewReader(conn)
|
|
writer := bufio.NewWriter(conn)
|
|
|
|
readLine := func() string {
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
t.Fatalf("read line: %v", err)
|
|
}
|
|
return strings.TrimRight(line, "\r\n")
|
|
}
|
|
|
|
sendCmd := func(cmd string) {
|
|
writer.WriteString(cmd + "\r\n")
|
|
writer.Flush()
|
|
}
|
|
|
|
// 1. Greeting
|
|
for {
|
|
l := readLine()
|
|
if strings.HasPrefix(l, "OK") {
|
|
break
|
|
}
|
|
}
|
|
|
|
// 2. CAPABILITY
|
|
sendCmd("CAPABILITY")
|
|
for {
|
|
l := readLine()
|
|
if strings.HasPrefix(l, "OK") {
|
|
break
|
|
}
|
|
}
|
|
|
|
// 3. AUTHENTICATE PLAIN
|
|
saslPayload := base64.StdEncoding.EncodeToString([]byte("\x00user@example.test\x00correct horse battery staple"))
|
|
sendCmd(fmt.Sprintf("AUTHENTICATE \"PLAIN\" %s", saslPayload))
|
|
if authResp := readLine(); !strings.HasPrefix(authResp, "OK") {
|
|
t.Fatalf("AUTH resp = %q, want OK", authResp)
|
|
}
|
|
|
|
// 4. CHECKSCRIPT
|
|
scriptContent := `require ["fileinto"]; if header :is "Subject" "Spam" { fileinto "Junk"; }`
|
|
sendCmd(fmt.Sprintf("CHECKSCRIPT {%d+}\r\n%s", len(scriptContent), scriptContent))
|
|
if checkResp := readLine(); !strings.HasPrefix(checkResp, "OK") {
|
|
t.Fatalf("CHECKSCRIPT resp = %q", checkResp)
|
|
}
|
|
|
|
// 5. PUTSCRIPT
|
|
sendCmd(fmt.Sprintf("PUTSCRIPT \"my_rules\" {%d+}\r\n%s", len(scriptContent), scriptContent))
|
|
if putResp := readLine(); !strings.HasPrefix(putResp, "OK") {
|
|
t.Fatalf("PUTSCRIPT resp = %q", putResp)
|
|
}
|
|
|
|
// 6. GETSCRIPT
|
|
sendCmd("GETSCRIPT \"my_rules\"")
|
|
getLiteralHead := readLine()
|
|
if !strings.HasPrefix(getLiteralHead, "{") {
|
|
t.Fatalf("GETSCRIPT literal head = %q", getLiteralHead)
|
|
}
|
|
gotScript := readLine()
|
|
if gotScript != scriptContent {
|
|
t.Fatalf("GETSCRIPT content = %q, want %q", gotScript, scriptContent)
|
|
}
|
|
if getOk := readLine(); !strings.HasPrefix(getOk, "OK") {
|
|
t.Fatalf("GETSCRIPT ok = %q", getOk)
|
|
}
|
|
|
|
// 7. LISTSCRIPTS
|
|
sendCmd("LISTSCRIPTS")
|
|
listLine := readLine()
|
|
if !strings.Contains(listLine, `"my_rules"`) {
|
|
t.Fatalf("LISTSCRIPTS line = %q", listLine)
|
|
}
|
|
if listOk := readLine(); !strings.HasPrefix(listOk, "OK") {
|
|
t.Fatalf("LISTSCRIPTS ok = %q", listOk)
|
|
}
|
|
|
|
// 8. SETACTIVE
|
|
sendCmd("SETACTIVE \"my_rules\"")
|
|
if setActiveOk := readLine(); !strings.HasPrefix(setActiveOk, "OK") {
|
|
t.Fatalf("SETACTIVE ok = %q", setActiveOk)
|
|
}
|
|
|
|
// Verify in DB that script is active
|
|
var isActive bool
|
|
if err := pool.QueryRow(ctx, `SELECT is_active FROM sieve_scripts WHERE mailbox_id = $1 AND name = 'my_rules'`, mailboxID).Scan(&isActive); err != nil || !isActive {
|
|
t.Fatalf("expected is_active=true in DB, err=%v, isActive=%v", err, isActive)
|
|
}
|
|
|
|
// 9. RENAMESCRIPT
|
|
sendCmd("RENAMESCRIPT \"my_rules\" \"active_rules\"")
|
|
if renameOk := readLine(); !strings.HasPrefix(renameOk, "OK") {
|
|
t.Fatalf("RENAMESCRIPT ok = %q", renameOk)
|
|
}
|
|
|
|
// 10. HAVESPACE
|
|
sendCmd("HAVESPACE \"active_rules\" 500")
|
|
if spaceOk := readLine(); !strings.HasPrefix(spaceOk, "OK") {
|
|
t.Fatalf("HAVESPACE ok = %q", spaceOk)
|
|
}
|
|
|
|
// 11. Deactivate & DELETE
|
|
sendCmd("SETACTIVE \"\"")
|
|
if deactOk := readLine(); !strings.HasPrefix(deactOk, "OK") {
|
|
t.Fatalf("deactivate ok = %q", deactOk)
|
|
}
|
|
|
|
sendCmd("DELETESCRIPT \"active_rules\"")
|
|
if delOk := readLine(); !strings.HasPrefix(delOk, "OK") {
|
|
t.Fatalf("DELETESCRIPT ok = %q", delOk)
|
|
}
|
|
|
|
// Verify in DB that script was deleted
|
|
var count int
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM sieve_scripts WHERE mailbox_id = $1`, mailboxID).Scan(&count); err != nil || count != 0 {
|
|
t.Fatalf("expected 0 scripts in DB, got count=%d, err=%v", count, err)
|
|
}
|
|
|
|
// 12. LOGOUT
|
|
sendCmd("LOGOUT")
|
|
if logoutOk := readLine(); !strings.HasPrefix(logoutOk, "OK") {
|
|
t.Fatalf("LOGOUT ok = %q", logoutOk)
|
|
}
|
|
}
|