- Implemented device pairing with a four-digit code and TTL for remote settings. - Introduced automatic checks for device status and configuration validation. - Developed UI components for device setup and management, including error handling and retry options. - Created a device bridge for secure communication between the device and the server. - Added tests for managed state validation and device pairing logic. - Established a local end-to-end fixture for testing device service and API interactions.
44 lines
4.6 KiB
JavaScript
44 lines
4.6 KiB
JavaScript
// Local end-to-end fixture: real device service, router, admin UI and board bridge.
|
|
// The database and transport data are explicit test fixtures. Never deploy this file.
|
|
import { createRequire } from 'node:module';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { createApp } from '../../server/index.mjs';
|
|
import { createDeviceBridge } from '../../server/device.mjs';
|
|
import { createDemo } from '../../src/lib/demo.ts';
|
|
const apiRequire = createRequire(new URL('../../../polotsk-transit-api/backend/package.json', import.meta.url));
|
|
process.env.JWT_SECRET = 'local-board-fixture-only-never-production';
|
|
apiRequire.cache[apiRequire.resolve('./src/config/database.js')] = { exports: { query: async () => ({ rows: [] }) } };
|
|
const express = apiRequire('express'), jwt = apiRequire('jsonwebtoken');
|
|
const { createBoardDevicesRouter } = apiRequire('./src/routes/boardDevices.js');
|
|
const { createBoardDeviceService } = apiRequire('./src/services/boardDeviceService.js');
|
|
const { memoryRepository } = apiRequire('./test/helpers/boardDeviceMemoryRepository.js');
|
|
const repository = memoryRepository(), service = createBoardDeviceService(repository);
|
|
const api = express(); api.use(express.json());
|
|
let managementUnavailable = false;
|
|
api.post('/__fixture/connection', (req,res) => { managementUnavailable = req.body.offline === true; res.json({ offline: managementUnavailable }); });
|
|
api.use('/api/v1/board-devices', (_req,res,next) => managementUnavailable ? res.status(503).json({error:'fixture_offline'}) : next());
|
|
const user = { id: 1, username: 'board-fixture', full_name: 'Тестовый оператор', role: 'admin' };
|
|
api.post('/api/v1/auth/login', (req, res) => { if(req.body.username !== 'board-fixture' || req.body.password !== 'fixture-only') return res.status(401).json({error:'Тестовый логин: board-fixture / fixture-only'}); res.json({ user, accessToken:jwt.sign(user,process.env.JWT_SECRET,{expiresIn:'1h'}),refreshToken:'local-fixture-refresh' }); });
|
|
api.get('/api/v1/auth/me', (_req,res)=>res.json(user));
|
|
api.post('/api/v1/auth/logout', (_req,res)=>res.json({ok:true}));
|
|
api.use('/api/v1/board-devices', createBoardDevicesRouter(service, { cityMiddleware: (req,res,next) => { const id=Number(req.get('X-City-Id')||1);if(![1,2].includes(id))return res.status(404).json({error:'City not found'});req.city={id};next(); } }));
|
|
api.get('/api/v1/cities', (_req,res)=>res.json([{id:1,name:'Полоцк',slug:'polotsk',emblem:'🏛️'},{id:2,name:'Новополоцк',slug:'novopolotsk',emblem:'🏙️'}]));
|
|
api.get('/api/v1/stops', (req,res)=>res.json(repository.stops.filter(s=>s.city_id===Number(req.get('X-City-Id')||1))));
|
|
api.get('/api/v1/routes', (req,res)=>res.json(repository.routesData.filter(s=>s.city_id===Number(req.get('X-City-Id')||1))));
|
|
api.get('/api/v1/alerts/active',(_req,res)=>res.json([]));api.get('/api/v1/holidays/upcoming',(_req,res)=>res.json([]));api.get('/api/v1/sync/status',(_req,res)=>res.json({status:'ready',database:{schedules_count:2}}));
|
|
const adminDist=fileURLToPath(new URL('../../../polotsk-transit-api/frontend/dist/',import.meta.url));
|
|
api.use(express.static(adminDist));api.get('*',async(_req,res)=>res.type('html').send((await readFile(`${adminDist}/index.html`,'utf8')).replace('</body>','<div style="position:fixed;bottom:6px;right:10px;z-index:1000;background:#fff8;padding:4px;font-size:11px">Тестовый стенд · без рабочей БД</div></body>')));
|
|
await new Promise(r=>api.listen(5190,'127.0.0.1',r));
|
|
const makeClient=({cityId=1,cityName='Полоцк',defaultStopId=1,stopName='Швейная фабрика'}={})=>({
|
|
config:()=>({cityId,cityName,defaultStopId,stopName,configured:true,refreshSeconds:20,timeZone:'Europe/Minsk',passengerBaseUrl:'https://pwa.ridebus.by/'}),
|
|
stops:async()=>repository.stops.filter(s=>s.city_id===cityId),
|
|
board:async stopId=>({...createDemo(),cityId,cityName,stop:{id:stopId,name:repository.stops.find(s=>s.id===stopId)?.name||stopName,direction:'Тестовые данные'},mode:'live'}),
|
|
timetable:async()=>{throw new Error('no timetable in fixture');},coordinates:async()=>null,
|
|
});
|
|
const bridge=createDeviceBridge({enabled:true,baseUrl:'http://127.0.0.1:5190/',adminUrl:'http://127.0.0.1:5190/devices'});
|
|
const board=createApp(makeClient(),{weather:async()=>{throw new Error('no weather in fixture');}},bridge,makeClient);
|
|
const boardDist=fileURLToPath(new URL('../../dist/',import.meta.url));board.use(express.static(boardDist));board.get('/{*path}',(_req,res)=>res.sendFile(`${boardDist}/index.html`));
|
|
await new Promise(r=>board.listen(5191,'127.0.0.1',r));
|
|
console.log('Device flow fixture: admin http://127.0.0.1:5190/devices; board http://127.0.0.1:5191/');
|