fix(t236): retry rejected ERP captchas
This commit is contained in:
@@ -259,6 +259,162 @@ func TestSessionManagerEnsureAuthenticatedUsesRecognizerOnce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionManagerEnsureAuthenticatedRetriesCaptchaRejectionSixTimes(t *testing.T) {
|
||||
var captchaCalls, loginCalls, userCalls int
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case CaptchaPath:
|
||||
captchaCalls++
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
_, _ = w.Write([]byte("captcha-image"))
|
||||
case LoginPath:
|
||||
loginCalls++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if loginCalls <= maxCaptchaRetries {
|
||||
_, _ = w.Write([]byte(`{"status":false,"data":null,"msg":"图片验证码不正确"}`))
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"status":true,"data":{"user":{"id":12,"username":"test-user"}}}`))
|
||||
case UserPath:
|
||||
userCalls++
|
||||
_, _ = w.Write([]byte(`{"status":true,"data":{"id":12,"username":"test-user"}}`))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
var events []string
|
||||
recognizer := &fixedRecognizer{code: "1234"}
|
||||
manager, err := NewSessionManager(SessionConfig{
|
||||
BaseURL: server.URL,
|
||||
Username: "test-user",
|
||||
Password: "test-password",
|
||||
Timeout: time.Second,
|
||||
AllowInsecureHTTP: true,
|
||||
CaptchaRecognizer: recognizer,
|
||||
DiagnosticLogger: func(event string) {
|
||||
events = append(events, event)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewSessionManager() error = %v", err)
|
||||
}
|
||||
if err := manager.EnsureAuthenticated(context.Background()); err != nil {
|
||||
t.Fatalf("EnsureAuthenticated() error = %v", err)
|
||||
}
|
||||
if captchaCalls != maxCaptchaAttempts || recognizer.calls != maxCaptchaAttempts ||
|
||||
loginCalls != maxCaptchaAttempts || userCalls != 1 {
|
||||
t.Fatalf(
|
||||
"captcha/OCR/login/user calls = %d/%d/%d/%d",
|
||||
captchaCalls,
|
||||
recognizer.calls,
|
||||
loginCalls,
|
||||
userCalls,
|
||||
)
|
||||
}
|
||||
actual := strings.Join(events, "\n")
|
||||
for _, expected := range []string{
|
||||
"erp_login_attempt attempt=1 max_attempts=7 result=captcha_rejected",
|
||||
"erp_login_attempt attempt=6 max_attempts=7 result=captcha_rejected",
|
||||
"erp_login_attempt attempt=7 max_attempts=7 result=success",
|
||||
} {
|
||||
if !strings.Contains(actual, expected) {
|
||||
t.Fatalf("diagnostic log missing %q: %s", expected, actual)
|
||||
}
|
||||
}
|
||||
for _, secret := range []string{"test-user", "test-password"} {
|
||||
if strings.Contains(actual, secret) {
|
||||
t.Fatalf("diagnostic log leaked %q: %s", secret, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionManagerEnsureAuthenticatedStopsAfterCaptchaRetries(t *testing.T) {
|
||||
var captchaCalls, loginCalls int
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case CaptchaPath:
|
||||
captchaCalls++
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
_, _ = w.Write([]byte("captcha-image"))
|
||||
case LoginPath:
|
||||
loginCalls++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"status":false,"data":null,"msg":"图片验证码不正确"}`))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
recognizer := &fixedRecognizer{code: "1234"}
|
||||
manager := testSessionManager(t, server.URL, "test-user", "test-password")
|
||||
manager.recognizer = recognizer
|
||||
err := manager.EnsureAuthenticated(context.Background())
|
||||
if !errors.Is(err, domain.ErrFreightSourceLoginRejected) {
|
||||
t.Fatalf("EnsureAuthenticated() error = %v", err)
|
||||
}
|
||||
if captchaCalls != maxCaptchaAttempts || recognizer.calls != maxCaptchaAttempts ||
|
||||
loginCalls != maxCaptchaAttempts {
|
||||
t.Fatalf(
|
||||
"captcha/OCR/login calls = %d/%d/%d",
|
||||
captchaCalls,
|
||||
recognizer.calls,
|
||||
loginCalls,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionManagerEnsureAuthenticatedDoesNotRetryOtherLoginRejections(t *testing.T) {
|
||||
var captchaCalls, loginCalls int
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case CaptchaPath:
|
||||
captchaCalls++
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
_, _ = w.Write([]byte("captcha-image"))
|
||||
case LoginPath:
|
||||
loginCalls++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"status":false,"data":null,"msg":"账号或密码错误"}`))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
recognizer := &fixedRecognizer{code: "1234"}
|
||||
manager := testSessionManager(t, server.URL, "test-user", "test-password")
|
||||
manager.recognizer = recognizer
|
||||
err := manager.EnsureAuthenticated(context.Background())
|
||||
if !errors.Is(err, domain.ErrFreightSourceLoginRejected) {
|
||||
t.Fatalf("EnsureAuthenticated() error = %v", err)
|
||||
}
|
||||
if captchaCalls != 1 || recognizer.calls != 1 || loginCalls != 1 {
|
||||
t.Fatalf(
|
||||
"captcha/OCR/login calls = %d/%d/%d",
|
||||
captchaCalls,
|
||||
recognizer.calls,
|
||||
loginCalls,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaptchaRejectedMessageIsExact(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
message string
|
||||
want bool
|
||||
}{
|
||||
{message: "图片验证码不正确", want: true},
|
||||
{message: " 图片验证码不正确 ", want: true},
|
||||
{message: "验证码不正确", want: false},
|
||||
{message: "账号或密码错误", want: false},
|
||||
{message: "", want: false},
|
||||
} {
|
||||
if actual := captchaRejectedMessage(testCase.message); actual != testCase.want {
|
||||
t.Fatalf("captchaRejectedMessage(%q) = %t, want %t", testCase.message, actual, testCase.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionManagerEnsureAuthenticatedRequiresRecognizer(t *testing.T) {
|
||||
manager := testSessionManager(t, "https://erp.example.test", "test-user", "test-password")
|
||||
err := manager.EnsureAuthenticated(context.Background())
|
||||
|
||||
Reference in New Issue
Block a user