Opensource Hosting Platform Wiki
Open-source hosting platform for servers, billing and support.
Brought to you by:
autobotsolution
This comprehensive guide covers all testing procedures for the HostingCo system, including unit testing, integration testing, end-to-end testing, and quality assurance processes.
The HostingCo system employs a multi-layered testing strategy to ensure code quality, functionality, and reliability across all components.
The project includes comprehensive automation scripts for all testing operations:
# Run all tests with coverage
./scripts/test.sh all --coverage --report
# Run specific test suites
./scripts/test.sh backend
./scripts/test.sh frontend
./scripts/test.sh shared
# Watch mode for development
./scripts/test-watch.sh all
# Coverage analysis with threshold checking
./scripts/test-coverage.sh all --threshold 90 --compare
# Run tests with auto-fix for linting
./scripts/test.sh all --fix
For complete testing script documentation, see Automation Scripts.
// backend/test/setup.js
const { knex } = require('../src/database/database');
// Test database configuration
const testConfig = {
client: 'postgresql',
connection: process.env.TEST_DATABASE_URL || 'postgresql://hostingco:password@localhost:5432/hostingco_test',
migrations: {
directory: './migrations'
},
seeds: {
directory: './seeds'
}
};
// Global test setup
beforeAll(async () => {
// Set up test database
await knex.migrate.latest();
await knex.seed.run();
});
afterAll(async () => {
// Clean up test database
await knex.migrate.rollback();
await knex.destroy();
});
// Reset database before each test
beforeEach(async () => {
await knex('activity_logs').del();
await knex('support_tickets').del();
await knex('invoices').del();
await knex('servers').del();
await knex('users').del();
});
// backend/test/models/user.test.js
const { User } = require('../../src/models/User');
const { createUser, getUserById, updateUser } = require('../../src/services/userService');
describe('User Model', () => {
describe('createUser', () => {
it('should create a new user with valid data', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User',
role: 'user'
};
const user = await createUser(userData);
expect(user).toBeDefined();
expect(user.email).toBe(userData.email);
expect(user.name).toBe(userData.name);
expect(user.role).toBe(userData.role);
expect(user.id).toMatch(/^user_\w+$/);
expect(user.isActive).toBe(true);
expect(user.createdAt).toBeInstanceOf(Date);
});
it('should throw error for invalid email', async () => {
const userData = {
email: 'invalid-email',
password: 'SecurePass123!',
name: 'Test User'
};
await expect(createUser(userData)).rejects.toThrow('Invalid email format');
});
it('should throw error for weak password', async () => {
const userData = {
email: 'test@example.com',
password: 'weak',
name: 'Test User'
};
await expect(createUser(userData)).rejects.toThrow('Password does not meet security requirements');
});
it('should hash password before saving', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
const user = await createUser(userData);
expect(user.passwordHash).not.toBe(userData.password);
expect(user.passwordHash).toMatch(/^\$2[aby]\$\d+\$/); // bcrypt hash
});
});
describe('getUserById', () => {
it('should return user when found', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
const createdUser = await createUser(userData);
const foundUser = await getUserById(createdUser.id);
expect(foundUser).toBeDefined();
expect(foundUser.id).toBe(createdUser.id);
expect(foundUser.email).toBe(userData.email);
});
it('should return null when user not found', async () => {
const user = await getUserById('nonexistent_id');
expect(user).toBeNull();
});
});
describe('updateUser', () => {
it('should update user with valid data', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
const user = await createUser(userData);
const updateData = { name: 'Updated Name', company: 'New Company' };
const updatedUser = await updateUser(user.id, updateData);
expect(updatedUser.name).toBe(updateData.name);
expect(updatedUser.company).toBe(updateData.company);
expect(updatedUser.email).toBe(userData.email); // Unchanged
});
});
});
// backend/test/services/authService.test.js
const { loginUser, registerUser, verifyToken } = require('../../src/services/authService');
const { createUser } = require('../../src/services/userService');
describe('Authentication Service', () => {
describe('loginUser', () => {
it('should login user with valid credentials', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
await createUser(userData);
const loginData = {
email: userData.email,
password: userData.password
};
const result = await loginUser(loginData);
expect(result).toBeDefined();
expect(result.token).toBeDefined();
expect(result.user).toBeDefined();
expect(result.user.email).toBe(userData.email);
expect(result.user.passwordHash).toBeUndefined(); // Password hash should not be returned
});
it('should throw error for invalid credentials', async () => {
const loginData = {
email: 'nonexistent@example.com',
password: 'wrongpassword'
};
await expect(loginUser(loginData)).rejects.toThrow('Invalid credentials');
});
it('should throw error for inactive user', async () => {
const userData = {
email: 'inactive@example.com',
password: 'SecurePass123!',
name: 'Inactive User'
};
const user = await createUser(userData);
await knex('users').where({ id: user.id }).update({ is_active: false });
const loginData = {
email: userData.email,
password: userData.password
};
await expect(loginUser(loginData)).rejects.toThrow('Account is inactive');
});
});
describe('registerUser', () => {
it('should register new user with valid data', async () => {
const userData = {
email: 'newuser@example.com',
password: 'SecurePass123!',
name: 'New User'
};
const result = await registerUser(userData);
expect(result).toBeDefined();
expect(result.user).toBeDefined();
expect(result.user.email).toBe(userData.email);
expect(result.user.isActive).toBe(false); // Email verification required
});
it('should throw error for duplicate email', async () => {
const userData = {
email: 'duplicate@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
await createUser(userData);
await expect(registerUser(userData)).rejects.toThrow('Email already exists');
});
});
});
// backend/test/controllers/userController.test.js
const request = require('supertest');
const app = require('../../src/app');
const { createUser } = require('../../src/services/userService');
describe('User Controller', () => {
describe('GET /api/users', () => {
it('should return users list for admin', async () => {
// Create admin user
const adminUser = await createUser({
email: 'admin@example.com',
password: 'SecurePass123!',
name: 'Admin User',
role: 'admin'
});
// Create regular users
await createUser({
email: 'user1@example.com',
password: 'SecurePass123!',
name: 'User One'
});
await createUser({
email: 'user2@example.com',
password: 'SecurePass123!',
name: 'User Two'
});
// Login admin and get token
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: adminUser.email,
password: 'SecurePass123!'
});
const token = loginResponse.body.data.token;
// Get users list
const response = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data.users).toHaveLength(3);
expect(response.body.data.pagination.total).toBe(3);
});
it('should return 403 for non-admin user', async () => {
const regularUser = await createUser({
email: 'user@example.com',
password: 'SecurePass123!',
name: 'Regular User',
role: 'user'
});
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: regularUser.email,
password: 'SecurePass123!'
});
const token = loginResponse.body.data.token;
await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${token}`)
.expect(403);
});
it('should return 401 without token', async () => {
await request(app)
.get('/api/users')
.expect(401);
});
});
});
// frontend/src/components/__tests__/UserCard.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { UserCard } from '../UserCard';
import { User } from '../../types/user';
const mockUser: User = {
id: 'user_123',
email: 'test@example.com',
name: 'Test User',
role: 'user',
isActive: true,
createdAt: '2026-05-08T03:14:24.942Z'
};
describe('UserCard', () => {
it('renders user information correctly', () => {
render(<UserCard user={mockUser} />);
expect(screen.getByText('Test User')).toBeInTheDocument();
expect(screen.getByText('test@example.com')).toBeInTheDocument();
expect(screen.getByText('user')).toBeInTheDocument();
});
it('shows active status for active users', () => {
render(<UserCard user={mockUser} />);
expect(screen.getByText('Active')).toBeInTheDocument();
expect(screen.getByText('Active')).toHaveClass('text-green-600');
});
it('shows inactive status for inactive users', () => {
const inactiveUser = { ...mockUser, isActive: false };
render(<UserCard user={inactiveUser} />);
expect(screen.getByText('Inactive')).toBeInTheDocument();
expect(screen.getByText('Inactive')).toHaveClass('text-red-600');
});
it('calls onEdit when edit button is clicked', () => {
const mockOnEdit = jest.fn();
render(<UserCard user={mockUser} onEdit={mockOnEdit} />);
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
expect(mockOnEdit).toHaveBeenCalledWith(mockUser);
});
it('calls onDelete when delete button is clicked', () => {
const mockOnDelete = jest.fn();
render(<UserCard user={mockUser} onDelete={mockOnDelete} />);
fireEvent.click(screen.getByRole('button', { name: /delete/i }));
expect(mockOnDelete).toHaveBeenCalledWith(mockUser);
});
});
// frontend/src/hooks/__tests__/useAuth.test.ts
import { renderHook, act } from '@testing-library/react';
import { AuthProvider, useAuth } from '../useAuth';
import { server } from '../../mocks/server';
// Mock server for API calls
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('useAuth', () => {
it('should initialize with null user', () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
<AuthProvider>{children}</AuthProvider>
);
const { result } = renderHook(() => useAuth(), { wrapper });
expect(result.current.user).toBeNull();
expect(result.current.isLoading).toBe(true);
});
it('should login user successfully', async () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
<AuthProvider>{children}</AuthProvider>
);
const { result } = renderHook(() => useAuth(), { wrapper });
await act(async () => {
const loginResult = await result.current.login('test@example.com', 'password');
expect(loginResult.success).toBe(true);
});
expect(result.current.user).toBeDefined();
expect(result.current.user?.email).toBe('test@example.com');
expect(result.current.isLoading).toBe(false);
});
it('should logout user successfully', async () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
<AuthProvider>{children}</AuthProvider>
);
const { result } = renderHook(() => useAuth(), { wrapper });
// Login first
await act(async () => {
await result.current.login('test@example.com', 'password');
});
expect(result.current.user).toBeDefined();
// Then logout
await act(async () => {
result.current.logout();
});
expect(result.current.user).toBeNull();
});
it('should handle login errors', async () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
<AuthProvider>{children}</AuthProvider>
);
const { result } = renderHook(() => useAuth(), { wrapper });
await act(async () => {
const loginResult = await result.current.login('invalid@example.com', 'wrongpassword');
expect(loginResult.success).toBe(false);
});
expect(result.current.user).toBeNull();
expect(result.current.error).toBe('Invalid credentials');
});
});
// backend/test/integration/auth.test.js
const request = require('supertest');
const app = require('../../src/app');
const { createUser } = require('../../src/services/userService');
describe('Authentication Integration', () => {
describe('POST /api/auth/login', () => {
it('should authenticate and return token', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
await createUser(userData);
const response = await request(app)
.post('/api/auth/login')
.send({
email: userData.email,
password: userData.password
})
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data.token).toBeDefined();
expect(response.body.data.user.email).toBe(userData.email);
expect(response.body.data.user.passwordHash).toBeUndefined();
});
it('should reject invalid credentials', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'nonexistent@example.com',
password: 'wrongpassword'
})
.expect(401);
expect(response.body.success).toBe(false);
expect(response.body.error.code).toBe('AUTHENTICATION_ERROR');
});
});
describe('Protected Routes', () => {
let token;
beforeEach(async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
await createUser(userData);
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: userData.email,
password: userData.password
});
token = loginResponse.body.data.token;
});
it('should access protected route with valid token', async () => {
const response = await request(app)
.get('/api/users/profile')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data.email).toBe('test@example.com');
});
it('should reject protected route without token', async () => {
await request(app)
.get('/api/users/profile')
.expect(401);
});
it('should reject protected route with invalid token', async () => {
await request(app)
.get('/api/users/profile')
.set('Authorization', 'Bearer invalid-token')
.expect(401);
});
});
});
// backend/test/integration/database.test.js
const { knex } = require('../../src/database/database');
const { createUser, getUserById, updateUser } = require('../../src/services/userService');
describe('Database Integration', () => {
describe('User Operations', () => {
it('should create and retrieve user from database', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
const createdUser = await createUser(userData);
const retrievedUser = await getUserById(createdUser.id);
expect(retrievedUser).toBeDefined();
expect(retrievedUser.id).toBe(createdUser.id);
expect(retrievedUser.email).toBe(userData.email);
// Verify data is actually in database
const dbUser = await knex('users').where({ id: createdUser.id }).first();
expect(dbUser).toBeDefined();
expect(dbUser.email).toBe(userData.email);
});
it('should update user in database', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
const user = await createUser(userData);
const updateData = { name: 'Updated Name' };
await updateUser(user.id, updateData);
const updatedUser = await getUserById(user.id);
expect(updatedUser.name).toBe(updateData.name);
// Verify update in database
const dbUser = await knex('users').where({ id: user.id }).first();
expect(dbUser.name).toBe(updateData.name);
});
it('should handle database constraints', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
await createUser(userData);
// Try to create user with same email
await expect(createUser(userData)).rejects.toThrow();
// Verify constraint in database
const users = await knex('users').where({ email: userData.email });
expect(users).toHaveLength(1);
});
});
});
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
supportFile: 'cypress/support/e2e.js',
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
viewportWidth: 1280,
viewportHeight: 720,
video: true,
screenshotOnRunFailure: true,
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 10000,
env: {
apiUrl: 'http://localhost:3003/api'
}
},
component: {
devServer: {
framework: 'react',
bundler: 'vite'
}
}
});
// cypress/support/e2e.js
import { faker } from '@faker-js/faker';
// Login command
Cypress.Commands.add('login', (email, password) => {
cy.request({
method: 'POST',
url: `${Cypress.env().apiUrl}/auth/login`,
body: { email, password }
}).then((response) => {
window.localStorage.setItem('token', response.body.data.token);
window.localStorage.setItem('user', JSON.stringify(response.body.data.user));
});
});
// Create test user command
Cypress.Commands.add('createTestUser', (overrides = {}) => {
const userData = {
email: faker.internet.email(),
password: 'SecurePass123!',
name: faker.name.fullName(),
role: 'user',
...overrides
};
return cy.request({
method: 'POST',
url: `${Cypress.env().apiUrl}/auth/register`,
body: userData
});
});
// Logout command
Cypress.Commands.add('logout', () => {
window.localStorage.removeItem('token');
window.localStorage.removeItem('user');
});
// API request command with authentication
Cypress.Commands.add('apiRequest', (method, url, body = {}) => {
const token = window.localStorage.getItem('token');
return cy.request({
method,
url: `${Cypress.env().apiUrl}${url}`,
headers: token ? { Authorization: `Bearer ${token}` } : {},
body
});
});
// cypress/e2e/auth.cy.js
describe('Authentication Flow', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should login with valid credentials', () => {
// Create test user
cy.createTestUser({
email: 'test@example.com',
password: 'SecurePass123!'
});
// Fill login form
cy.get('[data-cy=email-input]').type('test@example.com');
cy.get('[data-cy=password-input]').type('SecurePass123!');
cy.get('[data-cy=login-button]').click();
// Verify successful login
cy.url().should('not.include', '/login');
cy.get('[data-cy=user-menu]').should('contain', 'Test User');
cy.get('[data-cy=dashboard-title]').should('be.visible');
});
it('should show error for invalid credentials', () => {
cy.get('[data-cy=email-input]').type('invalid@example.com');
cy.get('[data-cy=password-input]').type('wrongpassword');
cy.get('[data-cy=login-button]').click();
// Verify error message
cy.get('[data-cy=error-message]').should('be.visible');
cy.get('[data-cy=error-message]').should('contain', 'Invalid credentials');
cy.url().should('include', '/login');
});
it('should logout successfully', () => {
// Login first
cy.login('test@example.com', 'SecurePass123!');
cy.visit('/dashboard');
// Logout
cy.get('[data-cy=user-menu]').click();
cy.get('[data-cy=logout-button]').click();
// Verify logout
cy.url().should('include', '/login');
cy.get('[data-cy=user-menu]').should('not.exist');
});
it('should redirect to login when accessing protected route', () => {
cy.visit('/dashboard');
// Should redirect to login
cy.url().should('include', '/login');
cy.get('[data-cy=login-form]').should('be.visible');
});
});
// cypress/e2e/user-management.cy.js
describe('User Management', () => {
beforeEach(() => {
// Login as admin
cy.login('admin@example.com', 'SecurePass123!');
cy.visit('/users');
});
it('should display users list', () => {
cy.get('[data-cy=users-table]').should('be.visible');
cy.get('[data-cy=user-row]').should('have.length.greaterThan', 0);
// Check table headers
cy.get('[data-cy=table-header-email]').should('contain', 'Email');
cy.get('[data-cy=table-header-name]').should('contain', 'Name');
cy.get('[data-cy=table-header-role]').should('contain', 'Role');
cy.get('[data-cy=table-header-status]').should('contain', 'Status');
});
it('should create new user', () => {
cy.get('[data-cy=add-user-button]').click();
// Fill user form
cy.get('[data-cy=user-email]').type(faker.internet.email());
cy.get('[data-cy=user-name]').type(faker.name.fullName());
cy.get('[data-cy=user-password]').type('SecurePass123!');
cy.get('[data-cy=user-role]').select('user');
// Submit form
cy.get('[data-cy=save-button]').click();
// Verify user created
cy.get('[data-cy=success-message]').should('contain', 'User created successfully');
cy.get('[data-cy=users-table]').should('contain', cy.get('[data-cy=user-name]').invoke('val'));
});
it('should edit existing user', () => {
// Find first user and click edit
cy.get('[data-cy=user-row]').first().within(() => {
cy.get('[data-cy=edit-button]').click();
});
// Update user name
const newName = faker.name.fullName();
cy.get('[data-cy=user-name]').clear().type(newName);
cy.get('[data-cy=save-button]').click();
// Verify update
cy.get('[data-cy=success-message]').should('contain', 'User updated successfully');
cy.get('[data-cy=users-table]').should('contain', newName);
});
it('should delete user', () => {
// Find first user and click delete
cy.get('[data-cy=user-row]').first().within(() => {
cy.get('[data-cy=delete-button]').click();
});
// Confirm deletion
cy.get('[data-cy=confirm-delete]').click();
// Verify deletion
cy.get('[data-cy=success-message]').should('contain', 'User deleted successfully');
});
});
// cypress/e2e/server-management.cy.js
describe('Server Management', () => {
beforeEach(() => {
cy.login('user@example.com', 'SecurePass123!');
cy.visit('/servers');
});
it('should display servers list', () => {
cy.get('[data-cy=servers-grid]').should('be.visible');
cy.get('[data-cy=server-card]').should('have.length.greaterThan', 0);
});
it('should create new server', () => {
cy.get('[data-cy=add-server-button]').click();
// Fill server form
cy.get('[data-cy=server-name]').type('test-server');
cy.get('[data-cy=server-plan]').select('pro');
cy.get('[data-cy=server-location]').select('us-east-1');
// Submit form
cy.get('[data-cy=create-button]').click();
// Verify server created
cy.get('[data-cy=success-message]').should('contain', 'Server created successfully');
cy.get('[data-cy=servers-grid]').should('contain', 'test-server');
});
it('should start server', () => {
// Find first server and click start
cy.get('[data-cy=server-card]').first().within(() => {
cy.get('[data-cy=start-button]').click();
});
// Verify server status
cy.get('[data-cy=server-status]').should('contain', 'Starting');
// Wait for server to be active
cy.get('[data-cy=server-status]', { timeout: 30000 }).should('contain', 'Active');
});
it('should view server details', () => {
// Click on first server
cy.get('[data-cy=server-card]').first().click();
// Verify server details page
cy.url().should('include', '/servers/');
cy.get('[data-cy=server-details]').should('be.visible');
cy.get('[data-cy=server-specs]').should('be.visible');
cy.get('[data-cy=server-statistics]').should('be.visible');
});
});
# artillery-config.yml
config:
target: 'http://localhost:3003'
phases:
- duration: 60
arrivalRate: 10
name: "Warm up"
- duration: 120
arrivalRate: 50
name: "Load test"
- duration: 60
arrivalRate: 100
name: "Stress test"
processor: "./artillery-processor.js"
scenarios:
- name: "API Health Check"
weight: 10
flow:
- get:
url: "/api/health"
- name: "User Authentication"
weight: 20
flow:
- post:
url: "/api/auth/login"
json:
email: "test@example.com"
password: "SecurePass123!"
capture:
- json: "$.data.token"
as: "token"
- get:
url: "/api/users/profile"
headers:
Authorization: "Bearer {{ token }}"
- name: "Get Servers List"
weight: 30
flow:
- get:
url: "/api/servers"
headers:
Authorization: "Bearer {{ token }}"
- name: "Create Support Ticket"
weight: 15
flow:
- post:
url: "/api/support/tickets"
headers:
Authorization: "Bearer {{ token }}"
json:
subject: "Test Ticket"
description: "This is a test ticket"
priority: "medium"
category: "technical"
- name: "Get Dashboard Stats"
weight: 25
flow:
- get:
url: "/api/dashboard/stats"
headers:
Authorization: "Bearer {{ token }}"
// artillery-processor.js
module.exports = {
// Custom processor functions
beforeRequest: (requestParams, context, ee, callback) => {
// Add custom headers or modify request
callback(null, requestParams);
},
afterResponse: (requestParams, response, context, ee, callback) => {
// Process response data
if (response.statusCode >= 400) {
console.error(`Error response: ${response.statusCode}`);
}
callback(null, response);
}
};
// backend/test/performance/api-benchmark.test.js
const { performance } = require('perf_hooks');
const request = require('supertest');
const app = require('../../src/app');
describe('API Performance Benchmarks', () => {
const benchmarks = [];
const benchmark = async (name, testFn) => {
const start = performance.now();
await testFn();
const end = performance.now();
const duration = end - start;
benchmarks.push({ name, duration });
// Assert performance requirements
expect(duration).toBeLessThan(1000); // 1 second max
console.log(`${name}: ${duration.toFixed(2)}ms`);
};
it('should meet performance requirements for health check', async () => {
await benchmark('Health Check', async () => {
await request(app)
.get('/api/health')
.expect(200);
});
});
it('should meet performance requirements for user login', async () => {
await benchmark('User Login', async () => {
await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'SecurePass123!'
})
.expect(200);
});
});
it('should meet performance requirements for servers list', async () => {
await benchmark('Servers List', async () => {
await request(app)
.get('/api/servers')
.set('Authorization', 'Bearer valid-token')
.expect(200);
});
});
afterAll(() => {
console.log('\nPerformance Summary:');
benchmarks.forEach(({ name, duration }) => {
console.log(`${name}: ${duration.toFixed(2)}ms`);
});
const avgDuration = benchmarks.reduce((sum, { duration }) => sum + duration, 0) / benchmarks.length;
console.log(`Average: ${avgDuration.toFixed(2)}ms`);
});
});
// backend/test/security/auth-security.test.js
const request = require('supertest');
const app = require('../../src/app');
describe('Authentication Security', () => {
describe('Login Security', () => {
it('should prevent brute force attacks', async () => {
const loginData = {
email: 'test@example.com',
password: 'wrongpassword'
};
// Attempt multiple failed logins
for (let i = 0; i < 10; i++) {
await request(app)
.post('/api/auth/login')
.send(loginData)
.expect(401);
}
// Should be locked out after too many attempts
await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'correctpassword'
})
.expect(429); // Too Many Requests
});
it('should validate JWT tokens properly', async () => {
// Test with malformed token
await request(app)
.get('/api/users/profile')
.set('Authorization', 'Bearer malformed-token')
.expect(401);
// Test with expired token
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.invalid';
await request(app)
.get('/api/users/profile')
.set('Authorization', `Bearer ${expiredToken}`)
.expect(401);
});
it('should prevent SQL injection in login', async () => {
const maliciousInput = "'; DROP TABLE users; --";
await request(app)
.post('/api/auth/login')
.send({
email: maliciousInput,
password: 'password'
})
.expect(401);
// Verify users table still exists
const response = await request(app)
.get('/api/health')
.expect(200);
expect(response.body.data.status).toBe('healthy');
});
});
});
// backend/test/security/input-validation.test.js
const request = require('supertest');
const app = require('../../src/app');
describe('Input Validation Security', () => {
describe('XSS Protection', () => {
it('should sanitize XSS attempts in user input', async () => {
const xssPayload = '<script>alert("xss")</script>';
const response = await request(app)
.post('/api/support/tickets')
.send({
subject: xssPayload,
description: xssPayload,
priority: 'medium',
category: 'technical'
})
.set('Authorization', 'Bearer valid-token')
.expect(200);
// Verify XSS payload is sanitized
expect(response.body.data.subject).not.toContain('<script>');
expect(response.body.data.description).not.toContain('<script>');
});
});
describe('CSRF Protection', () => {
it('should require CSRF token for state-changing operations', async () => {
// Test without CSRF token
await request(app)
.post('/api/users/profile')
.send({ name: 'Updated Name' })
.set('Authorization', 'Bearer valid-token')
.expect(403); // Forbidden
});
});
describe('Rate Limiting', () => {
it('should rate limit API requests', async () => {
// Make many requests quickly
const promises = Array(100).fill().map(() =>
request(app)
.get('/api/health')
);
const responses = await Promise.all(promises);
const rateLimitedResponses = responses.filter(res => res.status === 429);
expect(rateLimitedResponses.length).toBeGreaterThan(0);
});
});
});
// jest.config.js
module.exports = {
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
},
'./src/services/': {
branches: 90,
functions: 90,
lines: 90,
statements: 90
}
},
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.test.{js,jsx,ts,tsx}',
'!src/**/__tests__/**/*'
],
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/test/setup.js']
};
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:backend": "jest backend",
"test:frontend": "jest frontend",
"test:e2e": "cypress run",
"test:performance": "artillery run artillery-config.yml",
"test:security": "jest --testPathPattern=security"
}
}
# .github/workflows/test.yml
name: Test Suite
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test-backend:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: password
POSTGRES_USER: hostingco
POSTGRES_DB: hostingco_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: |
backend/package-lock.json
frontend/package-lock.json
- name: Install dependencies
run: |
npm run install:all
- name: Run backend tests
run: |
cd backend
npm run test:coverage
env:
DATABASE_URL: postgresql://hostingco:password@localhost:5432/hostingco_test
REDIS_URL: redis://localhost:6379
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./backend/coverage/lcov.info
test-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: |
cd frontend
npm ci
- name: Run frontend tests
run: |
cd frontend
npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./frontend/coverage/lcov.info
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm run install:all
- name: Start services
run: |
npm run start:all &
sleep 30
- name: Run E2E tests
uses: cypress-io/github-action@v5
with:
working-directory: frontend
wait-on: 'http://localhost:3000'
wait-on-timeout: 120
- name: Upload screenshots
uses: actions/upload-artifact@v3
if: failure()
with:
name: cypress-screenshots
path: frontend/cypress/screenshots
performance-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm run install:all
- name: Start services
run: |
npm run start:all &
sleep 30
- name: Install Artillery
run: npm install -g artillery
- name: Run performance tests
run: artillery run artillery-config.yml
# Complete pre-release test suite
./scripts/test.sh all --coverage --report --threshold 80
# Performance testing
./scripts/test-performance.sh
# Security testing
./scripts/test-security.sh
# Cross-browser testing
./scripts/test-e2e.sh --browsers chrome,firefox,safari
[ ] Check performance metrics
:::bash
./scripts/test.sh all --coverage --compare
./scripts/test-watch.sh all
[ ] Optimize slow tests
:::bash
./scripts/test.sh all --coverage --report --verbose
./scripts/test-coverage.sh all --compare --html
# Issue: Database connection failed
# Solution: Check test database status
sudo systemctl status postgresql
createdb hostingco_test
# Issue: Port conflicts
# Solution: Kill processes using test ports
lsof -ti:3000 | xargs kill -9
lsof -ti:3003 | xargs kill -9
# Issue: Test timeouts
# Solution: Increase timeout values
export CYPRESS_DEFAULT_COMMAND_TIMEOUT=10000
export CYPRESS_REQUEST_TIMEOUT=10000
# Issue: Low coverage
# Solution: Identify uncovered files
npm run test:coverage -- --coverageReporters=text
# Issue: Coverage threshold not met
# Solution: Review coverage report and add tests
open coverage/lcov-report/index.html
Last updated: $(date)