Files
ridebus-admin/backend/test/boardDevices.integration.test.js
Zakhar Timoshenko 360b684019 feat: implement board device service with CRUD operations and validation
- Add boardDeviceService for managing device pairing, status, claiming, and revocation.
- Introduce boardDeviceSettings for default settings and validation logic.
- Create integration tests for device lifecycle, concurrent claims, and updates.
- Add unit tests for device pairing, claiming, and settings validation.
- Implement API tests for device endpoints with JWT authentication.
- Develop in-memory repository for testing purposes.
- Create frontend components for managing devices, including a form for device settings.
- Style DevicesPage with CSS for better user experience.
2026-10-10 00:47:26 +03:00

25 lines
2.9 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs/promises');
const path = require('node:path');
const { Pool } = require('pg');
const { createBoardDeviceRepository } = require('../src/services/boardDeviceRepository');
const { createBoardDeviceService } = require('../src/services/boardDeviceService');
const { defaults } = require('../src/services/boardDeviceSettings');
test('PostgreSQL device lifecycle, concurrent claims/updates, city isolation and retry after a lost reply', { skip: !process.env.BOARD_DEVICE_TEST_DATABASE_URL }, async () => {
const url = process.env.BOARD_DEVICE_TEST_DATABASE_URL; assert.match(new URL(url).pathname, /test/i, 'Use a dedicated test database');
const admin = new Pool({ connectionString: url }); const schema = `board_device_test_${Date.now()}`; let pool;
try {
await admin.query(`CREATE SCHEMA ${schema}`); pool = new Pool({ connectionString: url, options: `-c search_path=${schema},public` });
await pool.query('CREATE TABLE cities(id INTEGER PRIMARY KEY,name TEXT,is_active BOOLEAN); CREATE TABLE users(id INTEGER PRIMARY KEY); CREATE TABLE stops(id INTEGER PRIMARY KEY,city_id INTEGER REFERENCES cities(id),name TEXT,description TEXT); CREATE TABLE routes(id INTEGER PRIMARY KEY,city_id INTEGER,route_number TEXT,name TEXT); CREATE TABLE route_stops(route_id INTEGER,stop_id INTEGER); INSERT INTO cities VALUES(1,\'Полоцк\',true),(2,\'Новополоцк\',true); INSERT INTO users VALUES(1); INSERT INTO stops VALUES(1,1,\'Швейная фабрика\',\'на центр\'),(2,2,\'Другая\',\'конечная\');');
const sql = await fs.readFile(path.join(__dirname, '../migrations/016_board_devices.sql'), 'utf8'); await pool.query(sql); await pool.query(sql);
const service = createBoardDeviceService(createBoardDeviceRepository(pool)); const p = await service.start({ width: 1920 });
const claims = await Promise.allSettled([service.claim(1,{code:p.code,name:'A',settings:defaults},1),service.claim(1,{code:p.code,name:'B',settings:defaults},1)]);
assert.equal(claims.filter(r=>r.status==='fulfilled').length,1); const row=(await service.status(p.token)).device;
assert.equal((await service.status(p.token)).device.id,row.id); assert.equal((await service.list(2)).length,0);
const updates=await Promise.allSettled([service.update(row.id,1,{name:'C',settings:defaults,revision:1},1),service.update(row.id,1,{name:'D',settings:defaults,revision:1},1)]);assert.equal(updates.filter(r=>r.status==='fulfilled').length,1);
await service.heartbeat(p.token,{appliedRevision:2});assert.equal((await service.list(1))[0].appliedRevision,2);
await assert.rejects(service.revoke(row.id,2,1),e=>e.status===404);await service.revoke(row.id,1,1);await assert.rejects(service.status(p.token),e=>e.status===410);
} finally { if(pool) await pool.end();await admin.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`);await admin.end(); }
});