Files

29 lines
4.1 KiB
JavaScript
Raw Permalink Normal View History

// Smoke test restricted to the independent embedded handoff identity. No binding,
// provider registration, firmware upload, or OTA instruction is performed.
const fs=require('node:fs'),crypto=require('node:crypto'),net=require('node:net'),assert=require('node:assert/strict');
const {sign}=require('../docs/embedded-signing-reference.cjs');
function text(v){const b=Buffer.from(v),h=Buffer.alloc(2);h.writeUInt16BE(b.length);return Buffer.concat([h,b]);}
function packet(type,body){const h=[type];let n=body.length;do{let b=n%128;n=Math.floor(n/128);if(n)b|=128;h.push(b);}while(n);return Buffer.concat([Buffer.from(h),body]);}
(async()=>{
const init=JSON.parse(fs.readFileSync(process.argv[2],'utf8').replace(/^\uFEFF/,''));assert.equal(init.factory_id,'KP01-020000260914');assert.equal(init.origin,'https://82.157.201.114');
async function post(path,body){const raw=JSON.stringify(body);const s=sign(Buffer.from(init.device_key_hex,'hex'),{path,raw,deviceId:init.factory_id,keyVersion:init.key_version,timestamp:Date.now(),nonce:crypto.randomBytes(16).toString('base64url'),requestId:crypto.randomUUID()});const r=await fetch(init.origin+path,{method:'POST',headers:s.headers,body:raw,redirect:'error',signal:AbortSignal.timeout(15000)});return {status:r.status,body:await r.json()};}
const state=await post('/api/v1/device/ble-state',{});assert.equal(state.body.data.bound,false);
const response=await post('/api/v1/device/mqtt-config',{});assert.equal(response.status,200);const config=response.body.data;assert.equal(config.broker_url,'mqtt://82.157.201.114:1883');
const missing=await post('/api/v1/device/ota/report',{task_id:crypto.randomUUID(),status:'failed',message:'smoke test - nonexistent task'});assert.equal(missing.status,409);assert.equal(missing.body.code,'OTA_NOT_FOUND');
const host=new URL(config.broker_url);const tunnel=process.env.MQTT_TUNNEL_PORT;if(tunnel)assert(/^\d{4,5}$/.test(tunnel));
const socket=net.connect({host:tunnel?'127.0.0.1':host.hostname,port:Number(tunnel||host.port)});
let buffer=Buffer.alloc(0),queue=[],waiter;
const receive=()=>new Promise((resolve,reject)=>{if(queue.length){resolve(queue.shift());return;}const timer=setTimeout(()=>{waiter=null;reject(new Error('MQTT response timeout'));},8000);waiter={resolve:v=>{clearTimeout(timer);waiter=null;resolve(v);},reject:e=>{clearTimeout(timer);waiter=null;reject(e);}};});
socket.on('data',b=>{buffer=Buffer.concat([buffer,b]);while(buffer.length>1){let size=0,m=1,i=1,done=false;for(;i<buffer.length&&i<5;i++){const v=buffer[i];size+=(v&127)*m;m*=128;if(!(v&128)){i++;done=true;break;}}if(!done||buffer.length<i+size)return;const p={type:buffer[0],body:buffer.subarray(i,i+size)};buffer=buffer.subarray(i+size);if(waiter)waiter.resolve(p);else queue.push(p);}});
socket.on('error',e=>{if(waiter)waiter.reject(e);});
try{
await new Promise((resolve,reject)=>{const timer=setTimeout(()=>{socket.destroy();reject(new Error('MQTT TCP timeout'));},10000);socket.once('connect',()=>{clearTimeout(timer);resolve();});socket.once('error',e=>{clearTimeout(timer);reject(e);});});
socket.write(packet(0x10,Buffer.concat([text('MQTT'),Buffer.from([4,0xc2,0,60]),text(config.client_id),text(config.username),text(config.password)])));
let p=await receive();assert.equal(p.type,0x20);assert.equal(p.body[1],0);
socket.write(packet(0x82,Buffer.concat([Buffer.from([0,1]),text(config.ota_topic),Buffer.from([1])])));p=await receive();assert.equal(p.type,0x90);assert.equal(p.body[2],1);
socket.write(packet(0x32,Buffer.concat([text(config.status_topic),Buffer.from([0,2]),Buffer.from(JSON.stringify({online:true,firmware_version:'handoff-mqtt-simulator',version_code:0,ota_supported:true}))])));p=await receive();assert.equal(p.type,0x40);
socket.write(Buffer.from([0xe0,0]));
console.log(JSON.stringify({device_id:init.factory_id,config_status:response.status,transport_encrypted:false,mqtt_connected:true,ota_subscription:true,heartbeat_ack:true,ota_report_route:true,real_device_modified:false}));
}finally{socket.end();socket.destroy();}
})().catch(e=>{console.error('MQTT probe failed:',e.code||e.name,'No credentials printed.');process.exitCode=1;});