costco-grocery-list/backend/public/test-runner.js

148 lines
5.0 KiB
JavaScript

async function makeRequest(test) {
const apiUrl = document.getElementById('apiUrl').value;
const endpoint = typeof test.endpoint === 'function' ? test.endpoint() : test.endpoint;
const url = `${apiUrl}${endpoint}`;
const options = {
method: test.method,
headers: {
'Content-Type': 'application/json',
}
};
if (test.auth && authToken) {
options.headers['Authorization'] = `Bearer ${authToken}`;
}
if (test.body) {
options.body = JSON.stringify(typeof test.body === 'function' ? test.body() : test.body);
}
const response = await fetch(url, options);
const data = await response.json().catch(() => ({}));
return { data, status: response.status };
}
async function runTest(categoryIdx, testIdx) {
const test = tests[categoryIdx].tests[testIdx];
const testId = `test-${categoryIdx}-${testIdx}`;
const testEl = document.getElementById(testId);
const contentEl = document.getElementById(`${testId}-content`);
const toggleEl = document.getElementById(`${testId}-toggle`);
const resultEl = testEl.querySelector('.test-result');
if (test.skip && test.skip()) {
testEl.querySelector('.test-status').textContent = 'SKIPPED';
testEl.querySelector('.test-status').className = 'test-status pending';
resultEl.style.display = 'block';
resultEl.className = 'test-result';
resultEl.innerHTML = '⚠️ Prerequisites not met';
return 'skip';
}
testEl.className = 'test-case running';
testEl.querySelector('.test-status').textContent = 'RUNNING';
testEl.querySelector('.test-status').className = 'test-status running';
resultEl.style.display = 'none';
try {
const { data, status } = await makeRequest(test);
const expectFail = test.expectFail || false;
const passed = test.expect(data, status);
const success = expectFail ? !passed || status >= 400 : passed;
testEl.className = success ? 'test-case pass' : 'test-case fail';
testEl.querySelector('.test-status').textContent = success ? 'PASS' : 'FAIL';
testEl.querySelector('.test-status').className = `test-status ${success ? 'pass' : 'fail'}`;
// Determine status code class
let statusClass = 'status-5xx';
if (status >= 200 && status < 300) statusClass = 'status-2xx';
else if (status >= 300 && status < 400) statusClass = 'status-3xx';
else if (status >= 400 && status < 500) statusClass = 'status-4xx';
// Check expected fields if defined
let expectedFieldsHTML = '';
if (test.expectedFields) {
const fieldChecks = test.expectedFields.map(field => {
const exists = field.split('.').reduce((obj, key) => obj?.[key], data) !== undefined;
const icon = exists ? '✓' : '✗';
const className = exists ? 'pass' : 'fail';
return `<div class="field-check ${className}">${icon} ${field}</div>`;
}).join('');
expectedFieldsHTML = `
<div class="expected-section">
<div class="expected-label">Expected Fields:</div>
${fieldChecks}
</div>
`;
}
resultEl.style.display = 'block';
resultEl.className = 'test-result';
resultEl.innerHTML = `
<div style="margin-bottom: 8px;">
<span class="response-status ${statusClass}">HTTP ${status}</span>
<span style="color: #666;">${success ? '✓ Test passed' : '✗ Test failed'}</span>
</div>
${expectedFieldsHTML}
<div style="color: #666; font-size: 12px; margin-bottom: 4px;">Response:</div>
<div>${JSON.stringify(data, null, 2)}</div>
`;
if (success && test.onSuccess) {
test.onSuccess(data);
}
return success ? 'pass' : 'fail';
} catch (error) {
testEl.className = 'test-case fail';
testEl.querySelector('.test-status').textContent = 'ERROR';
testEl.querySelector('.test-status').className = 'test-status fail';
resultEl.style.display = 'block';
resultEl.className = 'test-error';
resultEl.innerHTML = `
<div style="font-weight: bold; margin-bottom: 8px;">❌ Network/Request Error</div>
<div>${error.message}</div>
${error.stack ? `<div style="margin-top: 8px; font-size: 11px; opacity: 0.7;">${error.stack}</div>` : ''}
`;
return 'fail';
}
}
async function runAllTests(event) {
resetState();
const button = event.target;
button.disabled = true;
button.textContent = '⏳ Running Tests...';
let totalTests = 0;
let passedTests = 0;
let failedTests = 0;
for (let i = 0; i < tests.length; i++) {
for (let j = 0; j < tests[i].tests.length; j++) {
const result = await runTest(i, j);
if (result !== 'skip') {
totalTests++;
if (result === 'pass') passedTests++;
if (result === 'fail') failedTests++;
}
}
}
document.getElementById('summary').style.display = 'flex';
document.getElementById('totalTests').textContent = totalTests;
document.getElementById('passedTests').textContent = passedTests;
document.getElementById('failedTests').textContent = failedTests;
button.disabled = false;
button.textContent = '▶ Run All Tests';
}