diplomail (Stage F): docs + edge-case tests + LibreTranslate recipe
Tests · Integration / integration (pull_request) Successful in 1m37s
Tests · Go / test (push) Successful in 2m2s
Tests · Go / test (pull_request) Successful in 2m4s

Closes the documentation gaps from the freshly-audited diplomail
implementation. FUNCTIONAL.md gains a §11 "Diplomatic mail" with
the full user-facing story across all five stages, mirrored into
FUNCTIONAL_ru.md as the project conventions require. A new
backend/docs/diplomail-translator-setup.md captures the
LibreTranslate operational recipe (Docker image, env wiring,
manual smoke test, troubleshooting). The package README gains a
"Multi-instance posture" note documenting the deliberate absence
of FOR UPDATE in the worker pickup query — single-instance is
safe today; multi-instance scaling will revisit the claim
mechanism.

Two small edge-case tests round things out: malformed
LibreTranslate response bodies (single string, short array,
empty array, missing field) must surface as errors so the worker
falls back instead of crashing; and an empty translation queue
must produce zero events on three consecutive Worker.Tick calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-15 20:35:36 +02:00
parent 9f7c9099bc
commit 2d36b54b8d
6 changed files with 688 additions and 0 deletions
@@ -808,6 +808,36 @@ func TestDiplomailLifecycleMembershipKick(t *testing.T) {
}
}
// TestDiplomailWorkerTickOnEmptyQueueIsNoop confirms the async
// worker tolerates an empty pending queue: no error, no panic, no
// publisher events. Belt-and-suspenders for the case where backend
// starts, mounts the worker as an `app.Component`, and ticks before
// any user has sent mail.
func TestDiplomailWorkerTickOnEmptyQueueIsNoop(t *testing.T) {
db := startPostgres(t)
ctx := context.Background()
publisher := &recordingPublisher{}
svc := diplomail.NewService(diplomail.Deps{
Store: diplomail.NewStore(db),
Memberships: &staticMembershipLookup{},
Notification: publisher,
Config: config.DiplomailConfig{
MaxBodyBytes: 4096,
MaxSubjectBytes: 256,
},
})
worker := diplomail.NewWorker(svc)
for i := 0; i < 3; i++ {
if err := worker.Tick(ctx); err != nil {
t.Fatalf("tick %d on empty queue: %v", i, err)
}
}
if got := publisher.snapshot(); len(got) != 0 {
t.Fatalf("publisher fired %d events on empty queue", len(got))
}
}
// TestDiplomailAsyncTranslationDelivery covers the Stage E flow:
// 1. SendPersonal where recipient.preferred_language != body_lang
// materialises a recipient with `AvailableAt == nil`; the inbox