/** * @file test_server.js * @brief Server Test Suite */ const http = require('http'); const fs = require('fs'); const path = require('path'); const ChunkServer = require('../server/server'); const PORT = 18080; const TEST_DIR = path.join(__dirname, 'test_files'); let passed = 0; let failed = 0; function assert(cond, msg) { if (cond) { console.log(`[PASS] ${msg}`); passed++; } else { console.log(`[FAIL] ${msg}`); failed++; } } /** * HTTP GET helper */ function httpRequest(options, expectedStatus = 200) { return new Promise((resolve, reject) => { const req = http.request(options, (res) => { let data = Buffer.alloc(0); res.on('data', chunk => { data = Buffer.concat([data, chunk]); }); res.on('end', () => { resolve({ statusCode: res.statusCode, headers: res.headers, body: data }); }); }); req.on('error', reject); req.end(); }); } /** * Setup test files */ function setupTestFiles() { if (!fs.existsSync(TEST_DIR)) { fs.mkdirSync(TEST_DIR); } // Create test file: 3072 bytes (3 chunks of 1024) const testData = Buffer.alloc(3072); for (let i = 0; i < 3072; i++) { testData[i] = i % 256; } fs.writeFileSync(path.join(TEST_DIR, 'test.bin'), testData); // Create small file: 100 bytes const smallData = Buffer.alloc(100); for (let i = 0; i < 100; i++) { smallData[i] = 0x42; } fs.writeFileSync(path.join(TEST_DIR, 'small.bin'), smallData); } /** * Cleanup test files */ function cleanupTestFiles() { try { fs.rmSync(TEST_DIR, { recursive: true, force: true }); } catch (e) { // Ignore } } /** * Test suite */ async function runTests() { console.log("========================================"); console.log(" Chunk Server Test Suite"); console.log("========================================\n"); // Setup setupTestFiles(); const server = new ChunkServer({ port: PORT, rootPath: TEST_DIR, chunkSize: 1024 }); await server.start(); console.log("\n--- Server Tests ---"); try { // Test 1: Get first chunk const res1 = await httpRequest({ hostname: 'localhost', port: PORT, path: '/ota?file=test.bin', method: 'GET' }); assert(res1.statusCode === 200, "First chunk returns 200"); assert(res1.headers['content-type'] === 'application/octet-stream', "Content-Type is octet-stream"); assert(res1.headers['x-file-size'] === '3072', "X-File-Size header present"); assert(res1.headers['x-total-chunks'] === '3', "X-Total-Chunks header present"); assert(res1.body.length === 2 + 1024 + 4, "First chunk size correct (header + payload + crc)"); // Verify chunk index in payload assert(res1.body.readUInt16LE(0) === 0, "Chunk index is 0"); // Test 2: Get second chunk const res2 = await httpRequest({ hostname: 'localhost', port: PORT, path: '/ota?file=test.bin&chunk=1', method: 'GET' }); assert(res2.statusCode === 206, "Second chunk returns 206"); assert(res2.body.readUInt16LE(0) === 1, "Chunk index is 1"); assert(res2.body.length === 2 + 1024 + 4, "Second chunk size correct"); // Test 3: Get last chunk (smaller) const res3 = await httpRequest({ hostname: 'localhost', port: PORT, path: '/ota?file=test.bin&chunk=2', method: 'GET' }); assert(res3.statusCode === 206, "Last chunk returns 206"); assert(res3.body.readUInt16LE(0) === 2, "Chunk index is 2"); assert(res3.body.length === 2 + 1024 + 4, "Last chunk size correct"); // Test 4: Invalid chunk index const res4 = await httpRequest({ hostname: 'localhost', port: PORT, path: '/ota?file=test.bin&chunk=10', method: 'GET' }); assert(res4.statusCode === 416, "Invalid chunk returns 416"); // Test 5: File not found const res5 = await httpRequest({ hostname: 'localhost', port: PORT, path: '/ota?file=nonexistent.bin', method: 'GET' }); assert(res5.statusCode === 404, "Missing file returns 404"); // Test 6: Missing file parameter const res6 = await httpRequest({ hostname: 'localhost', port: PORT, path: '/ota', method: 'GET' }); assert(res6.statusCode === 400, "Missing file param returns 400"); // Test 7: Small file (single chunk) const res7 = await httpRequest({ hostname: 'localhost', port: PORT, path: '/ota?file=small.bin', method: 'GET' }); assert(res7.statusCode === 200, "Small file returns 200"); assert(res7.headers['x-total-chunks'] === '1', "Small file has 1 chunk"); assert(res7.body.length === 2 + 100 + 4, "Small file chunk size correct"); // Test 8: Verify CRC32 const payload = res1.body.slice(2, 2 + 1024); const receivedCrc = res1.body.readUInt32LE(2 + 1024); const calculatedCrc = ChunkServer.crc32(payload); console.log(`Received CRC: ${receivedCrc.toString(16)}`); console.log(`Calculated CRC: ${calculatedCrc.toString(16)}`); console.log(`Calculated >>>0: ${(calculatedCrc >>> 0).toString(16)}`); assert(receivedCrc === (calculatedCrc >>> 0), "CRC32 verification passed"); } catch (err) { console.error("Test error:", err); failed++; } finally { await server.stop(); cleanupTestFiles(); } console.log("\n========================================"); console.log(` Results: ${passed} passed, ${failed} failed`); console.log("========================================"); process.exit(failed === 0 ? 0 : 1); } runTests().catch(err => { console.error("Fatal error:", err); process.exit(1); });