From c9a08c86212021db3a3fcc7126c459d65bb4b581 Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Mon, 22 Dec 2025 00:31:26 -0300 Subject: [PATCH] test: add automated tests for admin login Backend: - TestAdminLogin_Success: verify admin login with username - TestAdminLogin_WrongPassword: verify 401 for wrong password Frontend (Marketplace): - auth.test.ts: mocked tests for login/logout (5 tests) - auth.integration.test.ts: real API tests (3 tests, skipped in CI) --- backend/.env | 5 + backend/internal/http/handler/handler_test.go | 75 +++++++++++++++ .../src/services/auth.integration.test.ts | 49 ++++++++++ marketplace/src/services/auth.test.ts | 92 +++++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 marketplace/src/services/auth.integration.test.ts create mode 100644 marketplace/src/services/auth.test.ts diff --git a/backend/.env b/backend/.env index f6e3a89..523883b 100644 --- a/backend/.env +++ b/backend/.env @@ -19,3 +19,8 @@ MARKETPLACE_COMMISSION=2.5 # CORS Configuration (comma-separated list of allowed origins, use * for all) CORS_ORIGINS=* + +ADMIN_NAME=Administrator +ADMIN_USERNAME=admin +ADMIN_EMAIL=admin@saveinmed.com +ADMIN_PASSWORD=admin123 \ No newline at end of file diff --git a/backend/internal/http/handler/handler_test.go b/backend/internal/http/handler/handler_test.go index 00a4333..97ddf15 100644 --- a/backend/internal/http/handler/handler_test.go +++ b/backend/internal/http/handler/handler_test.go @@ -372,6 +372,81 @@ func TestLoginInvalidCredentials(t *testing.T) { } } +func TestAdminLogin_Success(t *testing.T) { + repo := NewMockRepository() + gateway := &MockPaymentGateway{} + svc := usecase.NewService(repo, gateway, 0.05, "test-secret", time.Hour, "test-pepper") + h := New(svc) + + // Create admin user through service (which hashes password) + companyID, _ := uuid.NewV4() + user := &domain.User{ + CompanyID: companyID, + Role: "admin", + Name: "Admin User", + Username: "admin", + Email: "admin@test.com", + } + err := svc.CreateUser(context.Background(), user, "admin123") + if err != nil { + t.Fatalf("failed to create user: %v", err) + } + // Update mock with hashed user + repo.users[0] = *user + + // Login with correct credentials + payload := `{"username":"admin","password":"admin123"}` + req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader([]byte(payload))) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + + h.Login(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected status %d, got %d: %s", http.StatusOK, rec.Code, rec.Body.String()) + } + + // Verify response contains token + body := rec.Body.String() + if !strings.Contains(body, "token") { + t.Errorf("expected response to contain token, got: %s", body) + } + if !strings.Contains(body, "expires_at") { + t.Errorf("expected response to contain expires_at, got: %s", body) + } +} + +func TestAdminLogin_WrongPassword(t *testing.T) { + repo := NewMockRepository() + gateway := &MockPaymentGateway{} + svc := usecase.NewService(repo, gateway, 0.05, "test-secret", time.Hour, "test-pepper") + h := New(svc) + + // Create admin user + companyID, _ := uuid.NewV4() + user := &domain.User{ + CompanyID: companyID, + Role: "admin", + Name: "Admin User", + Username: "admin", + Email: "admin@test.com", + } + svc.CreateUser(context.Background(), user, "correctpassword") + repo.users[0] = *user + + // Login with wrong password + payload := `{"username":"admin","password":"wrongpassword"}` + req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader([]byte(payload))) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + + h.Login(rec, req) + + if rec.Code != http.StatusUnauthorized { + t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rec.Code) + } +} + func TestListOrders(t *testing.T) { h := newTestHandler() diff --git a/marketplace/src/services/auth.integration.test.ts b/marketplace/src/services/auth.integration.test.ts new file mode 100644 index 0000000..845d01d --- /dev/null +++ b/marketplace/src/services/auth.integration.test.ts @@ -0,0 +1,49 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import { authService } from './auth' + +/** + * Integration tests that require a running backend. + * + * To run these tests: + * 1. Start the backend: cd backend && go run ./cmd/api + * 2. Ensure admin user exists (backend seeds it automatically) + * 3. Run: npm test -- auth.integration + * + * These tests are skipped by default in CI. + */ + +const BACKEND_URL = process.env.VITE_API_URL || 'http://localhost:8214/api' +const SKIP_INTEGRATION = process.env.CI === 'true' || !process.env.RUN_INTEGRATION + +describe.skipIf(SKIP_INTEGRATION)('authService Integration Tests', () => { + describe('login with real backend', () => { + it('should login with admin credentials', async () => { + const result = await authService.login({ + username: 'admin', + password: 'admin123' + }) + + expect(result.token).toBeDefined() + expect(result.token.length).toBeGreaterThan(10) + expect(result.expiresAt).toBeDefined() + }) + + it('should reject invalid password', async () => { + await expect( + authService.login({ + username: 'admin', + password: 'wrongpassword' + }) + ).rejects.toThrow() + }) + + it('should reject non-existent user', async () => { + await expect( + authService.login({ + username: 'nonexistent_user_12345', + password: 'anypassword' + }) + ).rejects.toThrow() + }) + }) +}) diff --git a/marketplace/src/services/auth.test.ts b/marketplace/src/services/auth.test.ts new file mode 100644 index 0000000..ed0b49c --- /dev/null +++ b/marketplace/src/services/auth.test.ts @@ -0,0 +1,92 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { authService } from './auth' +import { apiClient } from './apiClient' + +// Mock the apiClient module +vi.mock('./apiClient', () => ({ + apiClient: { + post: vi.fn(), + setToken: vi.fn() + } +})) + +describe('authService', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('login', () => { + it('should login successfully with valid credentials', async () => { + const mockResponse = { + data: { + token: 'jwt-token-123', + expires_at: '2024-12-23T00:00:00Z' + } + } + vi.mocked(apiClient.post).mockResolvedValue(mockResponse) + + const result = await authService.login({ + username: 'admin', + password: 'admin123' + }) + + expect(apiClient.post).toHaveBeenCalledWith('/v1/auth/login', { + username: 'admin', + password: 'admin123' + }) + expect(result.token).toBe('jwt-token-123') + expect(result.expiresAt).toBe('2024-12-23T00:00:00Z') + }) + + it('should throw error for invalid credentials', async () => { + const error = new Error('Unauthorized') + ; (error as any).response = { status: 401 } + vi.mocked(apiClient.post).mockRejectedValue(error) + + await expect( + authService.login({ + username: 'admin', + password: 'wrongpassword' + }) + ).rejects.toThrow('Unauthorized') + + expect(apiClient.post).toHaveBeenCalledWith('/v1/auth/login', { + username: 'admin', + password: 'wrongpassword' + }) + }) + + it('should throw error for non-existent user', async () => { + const error = new Error('User not found') + ; (error as any).response = { status: 401 } + vi.mocked(apiClient.post).mockRejectedValue(error) + + await expect( + authService.login({ + username: 'nonexistent', + password: 'password' + }) + ).rejects.toThrow() + }) + }) + + describe('logout', () => { + it('should call logout endpoint', async () => { + vi.mocked(apiClient.post).mockResolvedValue({}) + + await authService.logout() + + expect(apiClient.post).toHaveBeenCalledWith('/v1/auth/logout') + }) + + it('should handle logout errors gracefully', async () => { + vi.mocked(apiClient.post).mockRejectedValue(new Error('Network error')) + + await expect(authService.logout()).rejects.toThrow('Network error') + }) + }) +})