- Added configuration options for Stalwart hosted mail in .env.example. - Updated Docker Compose to include Stalwart service with health checks. - Introduced new API endpoints for managing mail domains and migration projects. - Enhanced Authentik blueprints for user enrollment and post-migration security. - Updated OAuth handling for Google and Microsoft migration processes. - Improved error handling and response structures in the mail API. - Added integration tests for email claiming and migration workflows.
104 lines
2.6 KiB
Go
104 lines
2.6 KiB
Go
package migration
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseRetryAfter(t *testing.T) {
|
|
if got := parseRetryAfter("30"); got != 30*time.Second {
|
|
t.Fatalf("seconds = %v", got)
|
|
}
|
|
if got := parseRetryAfter(""); got != 0 {
|
|
t.Fatalf("empty = %v", got)
|
|
}
|
|
future := time.Now().Add(45 * time.Second).UTC().Format(http.TimeFormat)
|
|
if got := parseRetryAfter(future); got < 40*time.Second || got > 50*time.Second {
|
|
t.Fatalf("http date = %v", got)
|
|
}
|
|
}
|
|
|
|
func TestRateLimitDelayUsesRetryAfter(t *testing.T) {
|
|
ConfigureRateLimit(RateLimitConfig{
|
|
MaxRetries: 3,
|
|
BaseDelay: 100 * time.Millisecond,
|
|
MaxDelay: time.Second,
|
|
})
|
|
delay := rateLimitDelay(1, 500*time.Millisecond)
|
|
if delay != 500*time.Millisecond {
|
|
t.Fatalf("delay = %v", delay)
|
|
}
|
|
delay = rateLimitDelay(3, 0)
|
|
if delay != 400*time.Millisecond {
|
|
t.Fatalf("exponential delay = %v", delay)
|
|
}
|
|
}
|
|
|
|
func TestAPIGETRetries429ThenSucceeds(t *testing.T) {
|
|
ConfigureRateLimit(RateLimitConfig{
|
|
MaxRetries: 5,
|
|
BaseDelay: 5 * time.Millisecond,
|
|
MaxDelay: 50 * time.Millisecond,
|
|
})
|
|
|
|
calls := 0
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls++
|
|
if calls < 3 {
|
|
w.Header().Set("Retry-After", "0")
|
|
w.WriteHeader(http.StatusTooManyRequests)
|
|
_, _ = w.Write([]byte(`{"error":"rateLimitExceeded"}`))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`{"ok":true}`))
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
|
|
body, err := apiGet(context.Background(), srv.Client(), srv.URL, "token")
|
|
if err != nil {
|
|
t.Fatalf("apiGet: %v", err)
|
|
}
|
|
if string(body) != `{"ok":true}` {
|
|
t.Fatalf("body = %q", body)
|
|
}
|
|
if calls != 3 {
|
|
t.Fatalf("calls = %d", calls)
|
|
}
|
|
}
|
|
|
|
func TestAPIGETReturnsRateLimitErrorAfterMaxRetries(t *testing.T) {
|
|
ConfigureRateLimit(RateLimitConfig{
|
|
MaxRetries: 2,
|
|
BaseDelay: time.Millisecond,
|
|
MaxDelay: 10 * time.Millisecond,
|
|
})
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Retry-After", "0")
|
|
w.WriteHeader(http.StatusTooManyRequests)
|
|
_, _ = w.Write([]byte(`{"error":"quota"}`))
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
|
|
_, err := apiGet(context.Background(), srv.Client(), srv.URL, "token")
|
|
if !IsRateLimitError(err) {
|
|
t.Fatalf("expected RateLimitError, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWorkerRateLimitErrorIsPending(t *testing.T) {
|
|
if !IsRateLimitError(&RateLimitError{Cause: errTestRateLimit}) {
|
|
t.Fatal("expected typed rate limit error")
|
|
}
|
|
}
|
|
|
|
var errTestRateLimit = &RateLimitError{Cause: errTestRateLimitCause{}}
|
|
|
|
type errTestRateLimitCause struct{}
|
|
|
|
func (errTestRateLimitCause) Error() string { return "api rate limited (429): quota" }
|