const assert = require('assert'); const http = require('http'); const mysql = require('mysql2/promise'); require('dotenv').config(); const BASE_URL = 'http://localhost:3001'; function request(method, path, body = null) { return new Promise((resolve, reject) => { const url = `${BASE_URL}${path}`; const options = { method: method, headers: { 'Content-Type': 'application/json' } }; const req = http.request(url, options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { try { const parsed = JSON.parse(data); resolve({ status: res.statusCode, body: parsed }); } catch (e) { resolve({ status: res.statusCode, body: data }); } }); }); req.on('error', (err) => reject(err)); if (body) { req.write(JSON.stringify(body)); } req.end(); }); } async function runTests() { console.log('๐Ÿงช Starting Audit TDD Tests...'); const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME, port: process.env.DB_PORT }); const connection = await pool.getConnection(); try { // Clean up any test records console.log('๐Ÿงน Cleaning up test records...'); await connection.query("DELETE FROM asset_audit_pending WHERE asset_code LIKE 'TEST-ASSET-%'"); // Check if test assets exist in asset_core & asset_location // We will use an existing asset or insert a dummy test asset const [testAssets] = await connection.query("SELECT id FROM asset_core WHERE asset_code = 'TEST-ASSET-001'"); let testAssetId; if (testAssets.length === 0) { console.log('โณ Inserting dummy test asset...'); testAssetId = 'test_asset_uuid_123456'; await connection.query(` INSERT INTO asset_core (id, asset_code, category, asset_type, asset_purpose) VALUES (?, 'TEST-ASSET-001', 'server', 'Server', 'TDD Test Server') `, [testAssetId]); await connection.query(` INSERT INTO asset_location (asset_id, location, location_detail, location_photo, loc_x, loc_y, is_active) VALUES (?, 'Initial Location', 'Initial Detail', 'initial.png', '10.00', '10.00', 1) `, [testAssetId]); } else { testAssetId = testAssets[0].id; } // 1. Test GET /api/physical-locations console.log('๐Ÿ‘‰ Test 1: GET /api/physical-locations'); const res1 = await request('GET', '/api/physical-locations'); assert.strictEqual(res1.status, 200, 'GET /api/physical-locations should return 200'); assert(Array.isArray(res1.body), 'Response should be an array of physical locations'); assert(res1.body.length > 0, 'Should return at least one physical location'); console.log(`โœ… Test 1 Passed: Found ${res1.body.length} physical locations.`); const sampleLocation = res1.body[0].location_code; // 2. Test POST /api/audit/scan console.log(`๐Ÿ‘‰ Test 2: POST /api/audit/scan (Location: ${sampleLocation}, Asset: TEST-ASSET-001)`); const res2 = await request('POST', '/api/audit/scan', { asset_code: 'TEST-ASSET-001', physical_location_code: sampleLocation }); assert.strictEqual(res2.status, 200, 'POST /api/audit/scan should return 200'); assert.strictEqual(res2.body.success, true, 'Response success should be true'); assert(res2.body.pending_id, 'Response should contain pending_id'); console.log(`โœ… Test 2 Passed: Pending scan registered with ID: ${res2.body.pending_id}`); const pendingId = res2.body.pending_id; // 3. Test GET /api/audit/pending console.log('๐Ÿ‘‰ Test 3: GET /api/audit/pending'); const res3 = await request('GET', '/api/audit/pending'); assert.strictEqual(res3.status, 200, 'GET /api/audit/pending should return 200'); assert(Array.isArray(res3.body), 'Response should be an array'); const pendingItem = res3.body.find(item => item.id === pendingId); assert(pendingItem, 'Pending list should contain the newly registered scan'); assert.strictEqual(pendingItem.asset_code, 'TEST-ASSET-001', 'Asset code should match'); assert.strictEqual(pendingItem.physical_location_code, sampleLocation, 'Location code should match'); assert.strictEqual(pendingItem.status, 'PENDING', 'Status should be PENDING'); console.log('โœ… Test 3 Passed: Newly registered scan found in pending list with correct details.'); // 4. Test POST /api/audit/approve console.log(`๐Ÿ‘‰ Test 4: POST /api/audit/approve (Pending ID: ${pendingId})`); const res4 = await request('POST', '/api/audit/approve', { pending_ids: [pendingId], processed_by: 'TDD-TESTER' }); assert.strictEqual(res4.status, 200, 'POST /api/audit/approve should return 200'); assert.strictEqual(res4.body.success, true, 'Response success should be true'); console.log('โœ… Test 4 Passed: Audit approved.'); // Verify database updates console.log('๐Ÿ” Verifying updates in database...'); const [pendingCheck] = await connection.query( 'SELECT status, processed_by FROM asset_audit_pending WHERE id = ?', [pendingId] ); assert.strictEqual(pendingCheck[0].status, 'APPROVED', 'Pending record status should be APPROVED'); assert.strictEqual(pendingCheck[0].processed_by, 'TDD-TESTER', 'Processed by should match'); const [locationCheck] = await connection.query( 'SELECT physical_location_code, location_photo, loc_x, loc_y FROM asset_location WHERE asset_id = ? AND is_active = 1', [testAssetId] ); const [physLoc] = await connection.query( 'SELECT map_image, map_x, map_y FROM physical_locations WHERE location_code = ?', [sampleLocation] ); assert.strictEqual(locationCheck[0].physical_location_code, sampleLocation, 'Asset location code should be updated'); assert.strictEqual(locationCheck[0].location_photo, physLoc[0].map_image, 'Asset map_image should be updated'); assert.strictEqual(parseFloat(locationCheck[0].loc_x).toFixed(2), parseFloat(physLoc[0].map_x).toFixed(2), 'Asset map_x should be updated'); assert.strictEqual(parseFloat(locationCheck[0].loc_y).toFixed(2), parseFloat(physLoc[0].map_y).toFixed(2), 'Asset map_y should be updated'); console.log('โœ… Database verification passed: Asset location and map coordinates updated successfully!'); // 5. Test GET /api/maps (Before modification) console.log('๐Ÿ‘‰ Test 5: GET /api/maps'); const res5 = await request('GET', '/api/maps'); assert.strictEqual(res5.status, 200, 'GET /api/maps should return 200'); assert(typeof res5.body === 'object' && res5.body !== null, 'Response should be a map config object'); console.log('โœ… Test 5 Passed: GET /api/maps returned valid object.'); // 6. Test POST /api/maps/save console.log('๐Ÿ‘‰ Test 6: POST /api/maps/save'); const testMapPath = 'img/location_photo/TDD_TEST_MAP.png'; const testBoxes = [ { x: '30.50', y: '40.25', w: '10.00', h: '12.00', asset_id: testAssetId }, { x: '50.00', y: '60.00', w: '5.00', h: '5.00', asset_id: null } ]; const res6 = await request('POST', '/api/maps/save', { path: testMapPath, boxes: testBoxes }); assert.strictEqual(res6.status, 200, 'POST /api/maps/save should return 200'); assert.strictEqual(res6.body.success, true, 'Save should be successful'); console.log('โœ… Test 6 Passed: Map coordinate save triggered successfully.'); // Verify DB update directly for physical_locations console.log('๐Ÿ” Verifying physical_locations update in database...'); const [physLocCheck] = await connection.query( 'SELECT location_code, map_x, map_y, map_w, map_h FROM physical_locations WHERE map_image = ? ORDER BY location_code', [testMapPath] ); assert.strictEqual(physLocCheck.length, 2, 'Should create 2 physical locations for the test map'); // First location has asset_id mapped assert.strictEqual(parseFloat(physLocCheck[0].map_x).toFixed(2), '30.50', 'First location X coord match'); assert.strictEqual(parseFloat(physLocCheck[0].map_y).toFixed(2), '40.25', 'First location Y coord match'); assert.strictEqual(parseFloat(physLocCheck[0].map_w).toFixed(2), '10.00', 'First location W size match'); assert.strictEqual(parseFloat(physLocCheck[0].map_h).toFixed(2), '12.00', 'First location H size match'); // Asset location coordinates sync check console.log('๐Ÿ” Verifying asset_location coordination sync in database...'); const [assetLocSyncCheck] = await connection.query( 'SELECT loc_x, loc_y, physical_location_code FROM asset_location WHERE asset_id = ? AND is_active = 1', [testAssetId] ); assert(assetLocSyncCheck.length > 0, 'Asset location should be active'); assert.strictEqual(parseFloat(assetLocSyncCheck[0].loc_x).toFixed(2), '30.50', 'Asset location X should sync'); assert.strictEqual(parseFloat(assetLocSyncCheck[0].loc_y).toFixed(2), '40.25', 'Asset location Y should sync'); assert.strictEqual(assetLocSyncCheck[0].physical_location_code, physLocCheck[0].location_code, 'Physical location code should match'); console.log('โœ… DB Verification for save: physical_locations and asset_location coordinates synced.'); // 7. Test GET /api/maps (After modification) console.log('๐Ÿ‘‰ Test 7: GET /api/maps (After saving)'); const res7 = await request('GET', '/api/maps'); assert.strictEqual(res7.status, 200, 'GET /api/maps should return 200'); assert(res7.body[testMapPath], 'Returned config should contain the newly saved test map'); const savedBoxes = res7.body[testMapPath]; assert.strictEqual(savedBoxes.length, 2, 'Saved boxes count match'); assert.strictEqual(savedBoxes[0].asset_id, testAssetId, 'First box asset_id match'); assert.strictEqual(savedBoxes[0].x, '30.50', 'First box X match'); assert.strictEqual(savedBoxes[1].asset_id, null, 'Second box asset_id is null'); console.log('โœ… Test 7 Passed: GET /api/maps returned updated configuration.'); // Clean up console.log('๐Ÿงน Cleaning up test assets...'); await connection.query("DELETE FROM asset_audit_pending WHERE asset_code = 'TEST-ASSET-001'"); await connection.query("DELETE FROM asset_location WHERE asset_id = ?", [testAssetId]); await connection.query("DELETE FROM asset_core WHERE id = ?", [testAssetId]); await connection.query("DELETE FROM physical_locations WHERE map_image = ?", [testMapPath]); console.log('๐ŸŽ‰ All TDD tests passed successfully!'); } catch (err) { console.error('โŒ TDD Test Suite Failed:', err.message); throw err; } finally { connection.release(); await pool.end(); } } runTests().catch(() => process.exit(1));