package handler import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gofiber/fiber/v2" ) func newVerifyLoginCodeTestApp(h *AuthHandler) *fiber.App { app := fiber.New() app.Post("/api/v1/auth/login/code/verify", h.VerifyLoginCode) app.Post("/api/v1/auth/login/code/verify-short", h.VerifyLoginShortCode) return app } func decodeJSONBody(t *testing.T, resp *http.Response) map[string]any { t.Helper() var got map[string]any if err := json.NewDecoder(resp.Body).Decode(&got); err != nil { t.Fatalf("failed to decode response body: %v", err) } return got } func TestVerifyLoginCode_InvalidBody_ReturnsExplicitCode(t *testing.T) { h := &AuthHandler{} app := newVerifyLoginCodeTestApp(h) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify", bytes.NewBufferString("{")) req.Header.Set("Content-Type", "application/json") resp, err := app.Test(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Fatalf("expected 400, got %d", resp.StatusCode) } got := decodeJSONBody(t, resp) if got["code"] != "bad_request" { t.Fatalf("expected code=bad_request, got %v", got["code"]) } } func TestVerifyLoginCode_IdpUnavailable_ReturnsExplicitCode(t *testing.T) { h := &AuthHandler{} app := newVerifyLoginCodeTestApp(h) body, _ := json.Marshal(map[string]any{ "loginId": "user@example.com", "code": "AA-111111", }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := app.Test(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusServiceUnavailable { t.Fatalf("expected 503, got %d", resp.StatusCode) } got := decodeJSONBody(t, resp) if got["code"] != "service_unavailable" { t.Fatalf("expected code=service_unavailable, got %v", got["code"]) } } func TestVerifyLoginCode_VerifyOnlyInvalidCode_ReturnsExplicitCode(t *testing.T) { redis := &mockRedisRepo{data: make(map[string]string)} redis.data[prefixLoginCode+"user@example.com"] = "flow-1" redis.data[prefixLoginCodePending+"user@example.com"] = "pending-1" redis.data[prefixLoginCodeValue+"pending-1"] = "AB-123" h := &AuthHandler{ RedisService: redis, IdpProvider: &mockIdpProvider{}, } app := newVerifyLoginCodeTestApp(h) body, _ := json.Marshal(map[string]any{ "loginId": "user@example.com", "code": "ZZ-999", "verifyOnly": true, }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := app.Test(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", resp.StatusCode) } got := decodeJSONBody(t, resp) if got["code"] != "invalid_code" { t.Fatalf("expected code=invalid_code, got %v", got["code"]) } } func TestVerifyLoginShortCode_MissingShortCode_ReturnsExplicitCode(t *testing.T) { h := &AuthHandler{ RedisService: &mockRedisRepo{data: make(map[string]string)}, } app := newVerifyLoginCodeTestApp(h) body, _ := json.Marshal(map[string]any{ "shortCode": "", }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify-short", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := app.Test(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Fatalf("expected 400, got %d", resp.StatusCode) } got := decodeJSONBody(t, resp) if got["code"] != "bad_request" { t.Fatalf("expected code=bad_request, got %v", got["code"]) } } func TestVerifyLoginShortCode_InvalidOrExpired_ReturnsExplicitCode(t *testing.T) { h := &AuthHandler{ RedisService: &mockRedisRepo{data: make(map[string]string)}, } app := newVerifyLoginCodeTestApp(h) body, _ := json.Marshal(map[string]any{ "shortCode": "AB-123456", }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify-short", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := app.Test(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", resp.StatusCode) } got := decodeJSONBody(t, resp) if got["code"] != "invalid_or_expired_code" { t.Fatalf("expected code=invalid_or_expired_code, got %v", got["code"]) } } func TestVerifyLoginShortCode_VerifyOnlyMissingPendingRef_ReturnsExplicitCode(t *testing.T) { redis := &mockRedisRepo{data: make(map[string]string)} payload, _ := json.Marshal(shortLoginCodePayload{ LoginID: "user@example.com", Code: "AB-123", }) redis.data[prefixLoginCodeShort+"AB-123456"] = string(payload) h := &AuthHandler{ RedisService: redis, } app := newVerifyLoginCodeTestApp(h) body, _ := json.Marshal(map[string]any{ "shortCode": "AB-123456", "verifyOnly": true, }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify-short", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := app.Test(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Fatalf("expected 400, got %d", resp.StatusCode) } got := decodeJSONBody(t, resp) if got["code"] != "invalid_session_reference" { t.Fatalf("expected code=invalid_session_reference, got %v", got["code"]) } }