b1c3ec3af4
- Protocol definition with 6-byte header (chunk_idx: 2B, payload: N B, crc32: 4B) - Node.js server with chunk-based HTTP delivery - C client library with resumable download support - CRC32 implementation (IEEE 802.3) - Test suites for both server and CRC32 - 1024B default chunk size (0.59% overhead)
216 lines
6.3 KiB
JavaScript
216 lines
6.3 KiB
JavaScript
/**
|
|
* @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);
|
|
});
|