/** * Admin Operations Load Test * * Simulates 20 concurrent admin users performing management operations * Tests: * - Login * - List collections (Pages, Posts, Portfolio) * - View/edit items * - Create new items * - Delete items * * NFR4 Requirements: * - p95 response time < 700ms (slightly more lenient for admin) * - Error rate < 1% * - 20 concurrent users sustained for 3 minutes */ import { check, group } from 'k6'; import { urls, thresholdGroups, config } from './lib/config.js'; import { AuthHelper, ApiHelper, thinkTime, testData } from './lib/helpers.js'; // Test configuration export const options = { stages: config.stages.adminOperations, thresholds: thresholdGroups.admin, ...config.requestOptions, }; // Store auth token per VU let authToken = null; let apiHelper = null; /** * Setup and login - runs once per VU */ export function setup() { console.log('=== Admin Operations Load Test ==='); console.log(`Target: ${config.baseUrl}`); console.log(`Admin: ${config.adminEmail}`); console.log('Starting test...'); } /** * Login function */ function login() { const auth = new AuthHelper(config.baseUrl); const { success } = auth.login(config.adminEmail, config.adminPassword); if (!success) { console.error('Login failed!'); return null; } apiHelper = new ApiHelper(config.baseUrl); apiHelper.setToken(auth.token); return auth.token; } /** * Main test scenario */ export default function () { // Login if not authenticated if (!authToken) { group('Admin Login', () => { authToken = login(); thinkTime(1, 2); }); if (!authToken) { // Cannot proceed without auth return; } } // Scenario 1: Browse collections (read operations) group('List Collections', () => { // List pages apiHelper.get('/pages', { limit: 10, depth: 1 }); thinkTime(0.5, 1); // List posts apiHelper.get('/posts', { limit: 10, depth: 1 }); thinkTime(0.5, 1); // List portfolio apiHelper.get('/portfolio', { limit: 10, depth: 1 }); thinkTime(0.5, 1); }); // Scenario 2: View specific items (read operations) group('View Items', () => { // Try to view first item from each collection try { const pages = apiHelper.get('/pages', { limit: 1, depth: 0 }); if (pages.status === 200 && pages.json('totalDocs') > 0) { const firstId = pages.json('docs')[0].id; apiHelper.get(`/pages/${firstId}`); thinkTime(1, 2); } const posts = apiHelper.get('/posts', { limit: 1, depth: 0 }); if (posts.status === 200 && posts.json('totalDocs') > 0) { const firstId = posts.json('docs')[0].id; apiHelper.get(`/posts/${firstId}`); thinkTime(1, 2); } } catch (e) { // Items might not exist console.log('No items to view:', e.message); } }); // Scenario 3: Create new content (write operations) group('Create Content', () => { // 20% chance to create a test post if (Math.random() < 0.2) { const newPost = { title: `Load Test Post ${testData.string(6)}`, content: testData.content(2), status: 'draft', // Save as draft to avoid publishing }; const res = apiHelper.post('/posts', newPost); if (res.status === 201 || res.status === 200) { const postId = res.json('doc')?.id; // Store for potential cleanup (in real scenario) if (postId) { console.log(`Created post: ${postId}`); } } thinkTime(2, 3); } }); // Scenario 4: Update content (write operations) group('Update Content', () => { // 30% chance to update a post if (Math.random() < 0.3) { try { // Get a random post const posts = apiHelper.get('/posts', { limit: 20, depth: 0, where: { status: { equals: 'draft' } } }); if (posts.status === 200 && posts.json('totalDocs') > 0) { const docs = posts.json('docs'); const randomPost = docs[Math.floor(Math.random() * docs.length)]; const postId = randomPost.id; // Update the post const updateData = { title: `Updated ${randomPost.title}`, }; apiHelper.put(`/posts/${postId}`, updateData); thinkTime(1, 2); } } catch (e) { console.log('Update failed:', e.message); } } }); // Scenario 5: Delete test content (cleanup operations) group('Delete Content', () => { // 10% chance to delete a draft post if (Math.random() < 0.1) { try { // Get draft posts (likely from load test) const posts = apiHelper.get('/posts', { limit: 10, depth: 0, where: { status: { equals: 'draft' }, title: { like: 'Load Test Post' }, }, }); if (posts.status === 200 && posts.json('totalDocs') > 0) { const docs = posts.json('docs'); const randomPost = docs[Math.floor(Math.random() * docs.length)]; const postId = randomPost.id; // Delete the post apiHelper.delete(`/posts/${postId}`); thinkTime(1, 2); } } catch (e) { console.log('Delete failed:', e.message); } } }); // Scenario 6: Use GraphQL API group('GraphQL Operations', () => { // 40% chance to use GraphQL if (Math.random() < 0.4) { const query = ` query { Posts(limit: 5) { docs { id title status } } } `; apiHelper.graphql(query); thinkTime(1, 2); } }); // Think time before next iteration thinkTime(3, 6); } /** * Teardown function - runs once after test */ export function teardown(data) { console.log('=== Admin Test Complete ==='); console.log('Check results above for:'); console.log('- p95 response time < 700ms'); console.log('- Error rate < 1%'); console.log('- 20 concurrent admin users sustained'); console.log('Note: Any draft posts created were left in the system for manual review'); }