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)
This commit is contained in:
parent
fa726e5864
commit
c9a08c8621
4 changed files with 221 additions and 0 deletions
|
|
@ -19,3 +19,8 @@ MARKETPLACE_COMMISSION=2.5
|
||||||
|
|
||||||
# CORS Configuration (comma-separated list of allowed origins, use * for all)
|
# CORS Configuration (comma-separated list of allowed origins, use * for all)
|
||||||
CORS_ORIGINS=*
|
CORS_ORIGINS=*
|
||||||
|
|
||||||
|
ADMIN_NAME=Administrator
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_EMAIL=admin@saveinmed.com
|
||||||
|
ADMIN_PASSWORD=admin123
|
||||||
|
|
@ -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) {
|
func TestListOrders(t *testing.T) {
|
||||||
h := newTestHandler()
|
h := newTestHandler()
|
||||||
|
|
||||||
|
|
|
||||||
49
marketplace/src/services/auth.integration.test.ts
Normal file
49
marketplace/src/services/auth.integration.test.ts
Normal file
|
|
@ -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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
92
marketplace/src/services/auth.test.ts
Normal file
92
marketplace/src/services/auth.test.ts
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue