Files
website-enchun-mgr/apps/backend/tests/k6/public-browsing.js
pkupuk 7fd73e0e3d Implement Sprint 1 stories: collections, RBAC, audit logging, load testing
Complete 6 Sprint 1 stories for Epic 1 web migration infrastructure.

Portfolio Collection:
- Add 7 fields: title, slug, url, image, description, websiteType, tags
- Configure R2 storage and authenticated access control

Categories Collection:
- Add nameEn, order, textColor, backgroundColor fields
- Add color picker UI configuration

Posts Collection:
- Add excerpt with 200 char limit and ogImage for social sharing
- Add showInFooter checkbox and status select (draft/review/published)

Role-Based Access Control:
- Add role field to Users collection (admin/editor)
- Create adminOnly and authenticated access functions
- Apply access rules to Portfolio, Categories, Posts, Users collections

Audit Logging System (NFR9):
- Create Audit collection with timestamps for 90-day retention
- Add auditLogger utility for login/logout/content change tracking
- Add auditChange and auditGlobalChange hooks to all collections and globals
- Add cleanupAuditLogs job with 90-day retention policy

Load Testing Framework (NFR4):
- Add k6 load testing with 3 scripts: public-browsing, admin-operations, api-performance
- Configure targets: p95 < 500ms, error rate < 1%, 100 concurrent users
- Add verification script and comprehensive documentation

Other Changes:
- Remove unused Form blocks
- Add Header/Footer audit hooks
- Regenerate Payload TypeScript types
2026-01-31 17:20:35 +08:00

120 lines
3.0 KiB
JavaScript

/**
* Public Browsing Load Test
*
* Simulates 100 concurrent users browsing public pages
* Tests:
* - Homepage
* - About page
* - Solutions page
* - Portfolio list
* - Blog list
* - Contact page
*
* NFR4 Requirements:
* - p95 response time < 500ms
* - Error rate < 1%
* - 100 concurrent users sustained for 2 minutes
*/
import { check, group } from 'k6';
import { SharedArray } from 'k6/data';
import { urls, thresholdGroups, config } from './lib/config.js';
import { PageHelper, thinkTime, pickRandom } from './lib/helpers.js';
// Test configuration
export const options = {
stages: config.stages.publicBrowsing,
thresholds: thresholdGroups.public,
...config.requestOptions,
};
// Page list to browse
const publicPages = [
urls.home,
urls.about,
urls.solutions,
urls.portfolio,
urls.blog,
urls.contact,
];
// Initialize page helper
const pageHelper = new PageHelper(config.baseUrl);
/**
* Main test scenario
*/
export default function () {
// Scenario 1: Browse homepage (most common)
group('Browse Homepage', () => {
pageHelper.loadPage(urls.home);
thinkTime(2, 4); // 2-4 seconds thinking
});
// Scenario 2: Browse random pages (weighted)
group('Browse Random Pages', () => {
// Browse 3-6 random pages
const pageCount = Math.floor(Math.random() * 4) + 3;
for (let i = 0; i < pageCount; i++) {
const randomPage = pickRandom(publicPages);
pageHelper.loadPage(randomPage);
thinkTime(1, 3); // 1-3 seconds thinking
}
});
// Scenario 3: Navigate to contact (conversion intent)
group('Navigate to Contact', () => {
// 20% chance to visit contact page
if (Math.random() < 0.2) {
pageHelper.loadPage(urls.contact);
thinkTime(3, 5); // More time on contact page
}
});
// Scenario 4: Deep dive into portfolio or blog
group('Deep Dive', () => {
// 30% chance to deep dive
if (Math.random() < 0.3) {
const section = Math.random() > 0.5 ? 'portfolio' : 'blog';
if (section === 'portfolio') {
// Browse portfolio items
pageHelper.loadPage(urls.portfolio);
thinkTime(1, 2);
// Note: In real scenario, we would click individual items
// This requires parsing the page to get item URLs
} else {
// Browse blog posts
pageHelper.loadPage(urls.blog);
thinkTime(1, 2);
// Note: In real scenario, we would click individual posts
}
}
});
// Small think time before next iteration
thinkTime(2, 5);
}
/**
* Setup function - runs once before test
*/
export function setup() {
console.log('=== Public Browsing Load Test ===');
console.log(`Target: ${config.baseUrl}`);
console.log(`Pages to browse: ${publicPages.length}`);
console.log('Starting test...');
}
/**
* Teardown function - runs once after test
*/
export function teardown(data) {
console.log('=== Test Complete ===');
console.log('Check results above for:');
console.log('- p95 response time < 500ms');
console.log('- Error rate < 1%');
console.log('- 100 concurrent users sustained');
}