Add local header testing infrastructure
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Your Name 2025-03-31 04:58:51 -04:00
parent 0c3c133431
commit cc0142f000
3 changed files with 91 additions and 0 deletions

View File

@ -8,6 +8,7 @@
"test": "npm run test:lighthouse && npm run test:playwright",
"test:playwright": "npx playwright test",
"test:lighthouse": "node tests/lighthouse.js",
"test:headers": "playwright test tests/headers.spec.js",
"setup": "npx playwright install"
},
"repository": {
@ -23,7 +24,13 @@
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.42.1",
"chrome-launcher": "^1.1.2",
"lighthouse": "^11.4.0",
"puppeteer": "^22.4.1"
},
"dependencies": {
"express": "^4.18.2",
"lighthouse": "^11.6.0",
"playwright": "^1.42.1"
}
}

49
tests/headers.spec.js Normal file
View File

@ -0,0 +1,49 @@
const { test, expect } = require('@playwright/test');
test.describe('Security Headers Tests', () => {
test('should have all required security headers', async ({ page }) => {
// Navigate to the page
await page.goto('http://localhost:8080');
// Get response headers
const response = await page.waitForResponse('http://localhost:8080');
const headers = response.headers();
// Define required headers and their expected values
const requiredHeaders = {
'Content-Security-Policy': expect.stringContaining("default-src 'self'"),
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': expect.stringContaining('geolocation=()'),
'Strict-Transport-Security': expect.stringContaining('max-age=31536000'),
};
// Check each required header
for (const [header, expectedValue] of Object.entries(requiredHeaders)) {
const headerValue = headers[header.toLowerCase()];
expect(headerValue).toBeDefined();
if (typeof expectedValue === 'string') {
expect(headerValue).toBe(expectedValue);
} else {
expect(headerValue).toMatch(expectedValue);
}
}
});
test('should have correct CSP directives', async ({ page }) => {
await page.goto('http://localhost:8080');
const response = await page.waitForResponse('http://localhost:8080');
const headers = response.headers();
const csp = headers['content-security-policy'];
// Check for essential CSP directives
expect(csp).toContain("default-src 'self'");
expect(csp).toContain("script-src 'self' 'unsafe-inline'");
expect(csp).toContain("style-src 'self' 'unsafe-inline'");
expect(csp).toContain("img-src 'self' data: https: http:");
expect(csp).toContain("font-src 'self'");
expect(csp).toContain("connect-src 'self'");
});
});

35
tests/server.js Normal file
View File

@ -0,0 +1,35 @@
const express = require('express');
const path = require('path');
const app = express();
const port = 8080;
// Security headers middleware
app.use((req, res, next) => {
// Content Security Policy
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https: http:; " +
"font-src 'self'; " +
"connect-src 'self'"
);
// Other security headers
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});
// Serve static files from the docker/resume directory
app.use(express.static(path.join(__dirname, '../docker/resume')));
app.listen(port, () => {
console.log(`Local development server running at http://localhost:${port}`);
});