import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'; import { S7Client } from '../src/client.js'; import { S7DataItem } from '../src/protocol/s7-protocol.js'; import { S7Server } from '../src/server.js'; import { S7AreaDB, S7AreaMK, S7AreaPE, S7AreaPA, S7AreaCT, S7AreaTM, S7WLByte, S7WLWord, S7WLDWord, S7WLBit, S7WLCounter, S7WLTimer, srvAreaDB, srvAreaMK, srvAreaPE, srvAreaPA, srvAreaCT, srvAreaTM, CONNTYPE_PG, CONNTYPE_OP, CONNTYPE_BASIC, OperationRead, OperationWrite, } from '../src/protocol/constants.js'; // Use a non-privileged port for testing (port 102 requires admin on some systems) const TEST_PORT = 1102; describe('S7Client + S7Server Integration', () => { let server: S7Server; let client: S7Client; let dbBuffer: Buffer; let mkBuffer: Buffer; let peBuffer: Buffer; let paBuffer: Buffer; let ctBuffer: Buffer; let tmBuffer: Buffer; beforeAll(async () => { server = new S7Server(); client = new S7Client(); // Register areas dbBuffer = Buffer.alloc(1024, 0); mkBuffer = Buffer.alloc(256, 0); peBuffer = Buffer.alloc(256, 0); paBuffer = Buffer.alloc(256, 0); ctBuffer = Buffer.alloc(256, 0); tmBuffer = Buffer.alloc(256, 0); expect(server.RegisterArea(srvAreaDB, 1, dbBuffer)).toBe(0); expect(server.RegisterArea(srvAreaMK, 0, mkBuffer)).toBe(0); expect(server.RegisterArea(srvAreaPE, 0, peBuffer)).toBe(0); expect(server.RegisterArea(srvAreaPA, 0, paBuffer)).toBe(0); expect(server.RegisterArea(srvAreaCT, 0, ctBuffer)).toBe(0); expect(server.RegisterArea(srvAreaTM, 0, tmBuffer)).toBe(0); // Start server on test port // We need to override the port - server listens on 102 by default // For testing, we'll start a custom server await new Promise((resolve, reject) => { server.StartToPort('127.0.0.1', TEST_PORT, (err) => { if (err) reject(new Error(`Server start failed: ${err}`)); else resolve(); }); }); }); afterAll(() => { client.Disconnect(); server.Stop(); }); describe('Connection', () => { it('should connect to server', async () => { await new Promise((resolve, reject) => { client.ConnectToPort('127.0.0.1', TEST_PORT, 0, 1, (err) => { if (err) reject(new Error(`Connect failed: ${err}`)); else resolve(); }); }); expect(client.Connected()).toBe(true); }); it('should report PDU length after connect', () => { expect(client.PDULength()).toBeGreaterThan(0); }); it('should disconnect', () => { client.Disconnect(); expect(client.Connected()).toBe(false); }); it('should reconnect', async () => { await new Promise((resolve, reject) => { client.ConnectToPort('127.0.0.1', TEST_PORT, 0, 1, (err) => { if (err) reject(new Error(`Reconnect failed: ${err}`)); else resolve(); }); }); expect(client.Connected()).toBe(true); }); }); describe('Data I/O - DBRead/DBWrite', () => { it('should write and read DB data', async () => { const writeData = Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]); await new Promise((resolve, reject) => { client.DBWrite(1, 0, 4, writeData, (err) => { if (err) reject(new Error(`DBWrite failed: ${err}`)); else resolve(); }); }); const readData = await new Promise((resolve, reject) => { client.DBRead(1, 0, 4, (err, data) => { if (err) reject(new Error(`DBRead failed: ${err}`)); else resolve(data!); }); }); expect(readData).toEqual(writeData); }); it('should read written data from server buffer directly', () => { // Verify the data was actually written to the server's buffer expect(dbBuffer[0]).toBe(0xDE); expect(dbBuffer[1]).toBe(0xAD); expect(dbBuffer[2]).toBe(0xBE); expect(dbBuffer[3]).toBe(0xEF); }); it('should write data at offset', async () => { const writeData = Buffer.from([0x11, 0x22]); await new Promise((resolve, reject) => { client.DBWrite(1, 10, 2, writeData, (err) => { if (err) reject(new Error(`DBWrite offset failed: ${err}`)); else resolve(); }); }); const readData = await new Promise((resolve, reject) => { client.DBRead(1, 10, 2, (err, data) => { if (err) reject(new Error(`DBRead offset failed: ${err}`)); else resolve(data!); }); }); expect(readData).toEqual(writeData); }); }); describe('Data I/O - ReadArea/WriteArea', () => { it('should write and read MK area', async () => { const writeData = Buffer.from([0xAA, 0xBB]); await new Promise((resolve, reject) => { client.WriteArea(S7AreaMK, 0, 0, 2, S7WLByte, writeData, (err) => { if (err) reject(new Error(`WriteArea MK failed: ${err}`)); else resolve(); }); }); const readData = await new Promise((resolve, reject) => { client.ReadArea(S7AreaMK, 0, 0, 2, S7WLByte, (err, data) => { if (err) reject(new Error(`ReadArea MK failed: ${err}`)); else resolve(data!); }); }); expect(readData).toEqual(writeData); }); it('should write and read PE area', async () => { const writeData = Buffer.from([0x55]); await new Promise((resolve, reject) => { client.WriteArea(S7AreaPE, 0, 0, 1, S7WLByte, writeData, (err) => { if (err) reject(new Error(`WriteArea PE failed: ${err}`)); else resolve(); }); }); const readData = await new Promise((resolve, reject) => { client.ReadArea(S7AreaPE, 0, 0, 1, S7WLByte, (err, data) => { if (err) reject(new Error(`ReadArea PE failed: ${err}`)); else resolve(data!); }); }); expect(readData).toEqual(writeData); }); }); describe('MBRead/MBWrite', () => { it('should read/write MK area via convenience methods', async () => { const writeData = Buffer.from([0xCC, 0xDD]); await new Promise((resolve, reject) => { client.MBWrite(5, 2, writeData, (err) => { if (err) reject(new Error(`MBWrite failed: ${err}`)); else resolve(); }); }); const readData = await new Promise((resolve, reject) => { client.MBRead(5, 2, (err, data) => { if (err) reject(new Error(`MBRead failed: ${err}`)); else resolve(data!); }); }); expect(readData).toEqual(writeData); }); }); describe('EBRead/ABRead', () => { it('should read PE area via EBRead', async () => { // Set up data first peBuffer[0] = 0x42; const readData = await new Promise((resolve, reject) => { client.EBRead(0, 1, (err, data) => { if (err) reject(new Error(`EBRead failed: ${err}`)); else resolve(data!); }); }); expect(readData[0]).toBe(0x42); }); it('should write PA area via ABWrite', async () => { const writeData = Buffer.from([0x77]); await new Promise((resolve, reject) => { client.ABWrite(0, 1, writeData, (err) => { if (err) reject(new Error(`ABWrite failed: ${err}`)); else resolve(); }); }); expect(paBuffer[0]).toBe(0x77); }); }); describe('Properties', () => { it('should report Connected as true', () => { expect(client.Connected()).toBe(true); }); it('should report LastError as 0', () => { expect(client.LastError()).toBe(0); }); it('should return error text', () => { const text = client.ErrorText(0); expect(text).toBe('No error'); }); it('should return error text for known error', () => { const text = client.ErrorText(0x00010000); expect(text).toBe('ISO connection error'); }); }); describe('Constants', () => { it('should have correct area constants on instance', () => { expect(client.S7AreaDB).toBe(0x84); expect(client.S7AreaMK).toBe(0x83); expect(client.S7AreaPE).toBe(0x81); expect(client.S7AreaPA).toBe(0x82); expect(client.S7AreaCT).toBe(0x1C); expect(client.S7AreaTM).toBe(0x1D); }); it('should have correct WordLen constants on instance', () => { expect(client.S7WLBit).toBe(0x01); expect(client.S7WLByte).toBe(0x02); expect(client.S7WLWord).toBe(0x04); expect(client.S7WLDWord).toBe(0x06); expect(client.S7WLReal).toBe(0x08); expect(client.S7WLCounter).toBe(0x1C); expect(client.S7WLTimer).toBe(0x1D); }); it('should have correct constants on class', () => { expect(S7Client.S7AreaDB).toBe(0x84); expect(S7Client.S7WLByte).toBe(0x02); }); it('should have connection type constants', () => { expect(client.CONNTYPE_PG).toBe(0x01); expect(client.CONNTYPE_OP).toBe(0x02); expect(client.CONNTYPE_BASIC).toBe(0x03); }); }); describe('Connection params', () => { it('should set connection type', async () => { // Must disconnect first to set connection type client.Disconnect(); const result = client.SetConnectionType(CONNTYPE_PG); expect(result).toBe(true); // Reconnect for subsequent tests await new Promise((resolve, reject) => { client.ConnectToPort('127.0.0.1', TEST_PORT, 0, 1, (err) => { if (err) reject(new Error(`Reconnect failed: ${err}`)); else resolve(); }); }); }); it('should get/set params', () => { client.SetParam(10, 960); // PDU request expect(client.GetParam(10)).toBe(960); }); }); describe('Server', () => { it('should report server status as running', () => { expect(server.ServerStatus()).toBe(0x01); // SrvRunning }); it('should report clients count', () => { expect(server.ClientsCount()).toBeGreaterThanOrEqual(1); }); it('should get/set CPU status', () => { server.SetCpuStatus(0x08); // Run expect(server.GetCpuStatus()).toBe(0x08); }); it('should get/set events mask', () => { server.SetEventsMask(0xFFFFFFFF); expect(server.GetEventsMask()).toBe(0xFFFFFFFF); server.SetEventsMask(0x00000000); expect(server.GetEventsMask()).toBe(0x00000000); server.SetEventsMask(0xFFFFFFFF); // restore }); it('should return error text', () => { expect(server.ErrorText(0)).toBe('No error'); }); it('should get/set area', () => { const newBuf = Buffer.alloc(512, 0); const result = server.SetArea(srvAreaDB, 1, newBuf); expect(result).toBe(0); const got = server.GetArea(srvAreaDB, 1); expect(got).toBe(newBuf); // Restore original server.SetArea(srvAreaDB, 1, dbBuffer); }); it('should return error for unknown area', () => { const result = server.GetArea(99, 99); expect(result).toBeNull(); }); it('should unregister area', () => { const testBuf = Buffer.alloc(100, 0); expect(server.RegisterArea(srvAreaDB, 99, testBuf)).toBe(0); expect(server.UnregisterArea(srvAreaDB, 99)).toBe(0); expect(server.GetArea(srvAreaDB, 99)).toBeNull(); }); it('should return error when registering duplicate area', () => { const testBuf = Buffer.alloc(100, 0); expect(server.RegisterArea(srvAreaDB, 1, testBuf)).toBe(0x00300000); // errSrvAreaAlreadyExists }); it('should return error when unregistering unknown area', () => { expect(server.UnregisterArea(srvAreaDB, 999)).toBe(0x00400000); // errSrvUnknownArea }); it('should return error for null buffer', () => { expect(server.RegisterArea(srvAreaDB, 98, null as any)).toBe(0x00200000); // errSrvDBNullPointer }); it('should support LockArea/UnlockArea (no-op)', () => { server.LockArea(srvAreaDB, 1); server.UnlockArea(srvAreaDB, 1); // No error means success }); it('should return last error', () => { expect(server.LastError()).toBeGreaterThanOrEqual(0); }); }); describe('Events', () => { it('should emit event on server start', async () => { const events: any[] = []; const handler = (evt: any) => events.push(evt); server.on('event', handler); server.SetEventsMask(0xFFFFFFFF); // Trigger an event by doing a read (must await!) await new Promise((resolve) => { client.DBRead(1, 0, 4, () => resolve()); }); server.off('event', handler); server.SetEventsMask(0xFFFFFFFF); }); }); describe('Error handling', () => { it('should return error when reading from unregistered DB', async () => { // DB 2 is not registered const readData = await new Promise((resolve) => { client.DBRead(2, 0, 4, (err, data) => { // Should return data filled with zeros (item not available) resolve(data ?? null); }); }); // When area not found, server returns data filled with zeros and result 0x0A // Due to error, data might be null or a zero buffer if (readData) { expect(readData.length).toBe(4); } }); }); describe('ReadMultiVars / WriteMultiVars', () => { it('should read multiple areas in one request', async () => { // Write some data to MK and DB first await new Promise((resolve, reject) => { client.WriteArea(S7AreaMK, 0, 0, 4, S7WLByte, Buffer.from([0x11, 0x22, 0x33, 0x44]), (err) => { if (err) reject(new Error(`WriteArea failed: ${err}`)); else resolve(); }); }); await new Promise((resolve, reject) => { client.DBWrite(1, 0, 4, Buffer.from([0x55, 0x66, 0x77, 0x88]), (err) => { if (err) reject(new Error(`DBWrite failed: ${err}`)); else resolve(); }); }); const items: S7DataItem[] = [ { Area: S7AreaDB, DBNumber: 1, Start: 0, Amount: 4, WordLen: S7WLByte }, { Area: S7AreaMK, DBNumber: 0, Start: 0, Amount: 4, WordLen: S7WLByte }, ]; const result = await new Promise((resolve, reject) => { client.ReadMultiVars(items, (err, items) => { if (err) reject(new Error(`ReadMultiVars failed: ${err}`)); else resolve(items!); }); }); expect(result.length).toBe(2); expect(result[0].Data).toEqual(Buffer.from([0x55, 0x66, 0x77, 0x88])); expect(result[1].Data).toEqual(Buffer.from([0x11, 0x22, 0x33, 0x44])); }); it('should write multiple areas in one request', async () => { const items: S7DataItem[] = [ { Area: S7AreaDB, DBNumber: 1, Start: 20, Amount: 2, WordLen: S7WLByte, Data: Buffer.from([0xCA, 0xFE]) }, { Area: S7AreaMK, DBNumber: 0, Start: 20, Amount: 2, WordLen: S7WLByte, Data: Buffer.from([0xBA, 0xBE]) }, ]; const results = await new Promise((resolve, reject) => { client.WriteMultiVars(items, (err, results) => { if (err) reject(new Error(`WriteMultiVars failed: ${err}`)); else resolve(results!); }); }); expect(results.length).toBe(2); expect(results[0]).toBe(0xFF); // Success expect(results[1]).toBe(0xFF); // Success // Verify data was written expect(dbBuffer[20]).toBe(0xCA); expect(dbBuffer[21]).toBe(0xFE); expect(mkBuffer[20]).toBe(0xBA); expect(mkBuffer[21]).toBe(0xBE); }); }); describe('Promise-based API', () => { it('should support Promise-based DBRead/DBWrite', async () => { const writeData = Buffer.from([0x01, 0x02, 0x03]); await client.DBWrite(1, 100, 3, writeData); const readData = await client.DBRead(1, 100, 3); expect(readData).toEqual(writeData); }); it('should support Promise-based ReadArea/WriteArea', async () => { const writeData = Buffer.from([0xAA, 0xBB]); await client.WriteArea(S7AreaMK, 0, 30, 2, S7WLByte, writeData); const readData = await client.ReadArea(S7AreaMK, 0, 30, 2, S7WLByte); expect(readData).toEqual(writeData); }); }); describe('Word length variants', () => { it('should read/write word (16-bit)', async () => { const writeData = Buffer.alloc(2); writeData.writeUInt16BE(0x1234, 0); await client.WriteArea(S7AreaMK, 0, 40, 1, S7WLWord, writeData); const readData = await client.ReadArea(S7AreaMK, 0, 40, 1, S7WLWord) as Buffer; expect(readData.readUInt16BE(0)).toBe(0x1234); }); it('should read/write dword (32-bit)', async () => { const writeData = Buffer.alloc(4); writeData.writeUInt32BE(0xDEADBEEF, 0); await client.WriteArea(S7AreaDB, 1, 50, 1, S7WLDWord, writeData); const readData = await client.ReadArea(S7AreaDB, 1, 50, 1, S7WLDWord) as Buffer; expect(readData.readUInt32BE(0)).toBe(0xDEADBEEF); }); }); describe('EBWrite / ABRead', () => { it('should write PE area via EBWrite', async () => { const writeData = Buffer.from([0x99]); await client.EBWrite(0, 1, writeData); expect(peBuffer[0]).toBe(0x99); }); it('should read PA area via ABRead', async () => { paBuffer[0] = 0xAB; const readData = await client.ABRead(0, 1) as Buffer; expect(readData[0]).toBe(0xAB); }); }); describe('TMRead / TMWrite', () => { it('should write and read Timer area', async () => { const writeData = Buffer.alloc(2); writeData.writeUInt16BE(0x5678, 0); await client.TMWrite(0, 1, writeData); const readData = await client.TMRead(0, 1) as Buffer; expect(readData.readUInt16BE(0)).toBe(0x5678); }); }); describe('CTRead / CTWrite', () => { it('should write and read Counter area', async () => { const writeData = Buffer.alloc(2); writeData.writeUInt16BE(0x9ABC, 0); await client.CTWrite(0, 1, writeData); const readData = await client.CTRead(0, 1) as Buffer; expect(readData.readUInt16BE(0)).toBe(0x9ABC); }); }); describe('Client Properties (from docs)', () => { it('should return ExecTime >= 0', () => { expect(client.ExecTime()).toBeGreaterThanOrEqual(0); }); it('should return PDURequested', () => { expect(client.PDURequested()).toBeGreaterThan(0); }); it('should return PDULength', () => { expect(client.PDULength()).toBeGreaterThan(0); }); it('should return LastError', () => { // LastError may be non-zero from previous operations // After a successful operation, we verify LastError is accessible expect(typeof client.LastError()).toBe('number'); expect(client.LastError()).toBeGreaterThanOrEqual(0); }); it('should return ErrorText for various errors', () => { expect(client.ErrorText(0)).toBe('No error'); expect(client.ErrorText(0x00010000)).toBe('ISO connection error'); expect(typeof client.ErrorText(0xFFFFFFFF)).toBe('string'); }); }); describe('SetConnectionParams', () => { it('should set connection params when disconnected', async () => { client.Disconnect(); const result = client.SetConnectionParams('127.0.0.1', 0x0100, 0x0201); expect(result).toBe(true); // Reconnect await new Promise((resolve, reject) => { client.ConnectToPort('127.0.0.1', TEST_PORT, 0, 1, (err) => { if (err) reject(new Error(`Reconnect failed: ${err}`)); else resolve(); }); }); }); }); describe('ConnectionType constants (from docs)', () => { it('should have CONNTYPE_PG = 0x01', () => { expect(S7Client.CONNTYPE_PG).toBe(0x01); expect(S7Client.CONNTYPE_OP).toBe(0x02); expect(S7Client.CONNTYPE_BASIC).toBe(0x03); }); }); describe('GetParam values (from docs)', () => { it('should return RemotePort (param 2)', () => { expect(client.GetParam(2)).toBe(TEST_PORT); }); it('should return PDURequest (param 10)', () => { expect(client.GetParam(10)).toBeGreaterThan(0); }); it('should set and get RecvTimeout (param 5)', () => { client.SetParam(5, 10000); expect(client.GetParam(5)).toBe(10000); }); }); describe('Block type constants (from docs)', () => { it('should have correct Block_OB..Block_SFB values', () => { expect(S7Client.Block_OB).toBe(0x38); expect(S7Client.Block_DB).toBe(0x41); expect(S7Client.Block_SDB).toBe(0x42); expect(S7Client.Block_FC).toBe(0x43); expect(S7Client.Block_SFC).toBe(0x44); expect(S7Client.Block_FB).toBe(0x45); expect(S7Client.Block_SFB).toBe(0x46); }); it('should have correct SubBlk values', () => { expect(S7Client.SubBlk_OB).toBe(0x08); expect(S7Client.SubBlk_DB).toBe(0x0A); expect(S7Client.SubBlk_FC).toBe(0x0C); expect(S7Client.SubBlk_FB).toBe(0x0E); }); it('should have correct BlockLang values', () => { expect(S7Client.BlockLangAWL).toBe(0x01); expect(S7Client.BlockLangDB).toBe(0x05); expect(S7Client.BlockLangGRAPH).toBe(0x06); }); }); describe('CPU status constants (from docs)', () => { it('should have S7CpuStatusRun/Stop/Unknown', () => { expect(S7Client.S7CpuStatusUnknown).toBe(0x00); expect(S7Client.S7CpuStatusRun).toBe(0x08); expect(S7Client.S7CpuStatusStop).toBe(0x04); }); }); // ---- Tests from README.md Server Example ---- describe('Server EventText (README example)', () => { it('should return text description for a server event', async () => { const events: any[] = []; const handler = (evt: any) => events.push(evt); server.on('event', handler); server.SetEventsMask(0xFFFFFFFF); // Trigger an event by reading await new Promise((resolve) => { client.DBRead(1, 0, 4, () => resolve()); }); server.off('event', handler); // At least one event should have been captured expect(events.length).toBeGreaterThan(0); // EventText should return a string for each event for (const evt of events) { const text = server.EventText(evt); expect(typeof text).toBe('string'); expect(text.length).toBeGreaterThan(0); } // Test specific known event codes const text = server.EventText({ EvtCode: 0x00000001, EvtRetCode: 0, EvtParam1: 0, EvtParam2: 0 }); expect(text).toContain('Server started'); const textRead = server.EventText({ EvtCode: 0x00020000, EvtRetCode: 0, EvtParam1: 0, EvtParam2: 0 }); expect(textRead).toContain('Data read'); }); }); describe('Server StartTo / Stop (README example)', () => { it('should start server with StartTo on specific IP', async () => { // Server is already running; verify ServerStatus is Running expect(server.ServerStatus()).toBe(0x01); // SrvRunning }); it('should stop and restart server', async () => { // Disconnect client first client.Disconnect(); expect(client.Connected()).toBe(false); // Stop the server await new Promise((resolve) => { server.Stop((err) => { expect(err).toBeNull(); resolve(); }); }); expect(server.ServerStatus()).toBe(0x00); // SrvStopped // Restart with StartToPort await new Promise((resolve, reject) => { server.StartToPort('127.0.0.1', TEST_PORT, (err) => { if (err) reject(new Error(`Server restart failed: ${err}`)); else resolve(); }); }); expect(server.ServerStatus()).toBe(0x01); // SrvRunning // Reconnect client await new Promise((resolve, reject) => { client.ConnectToPort('127.0.0.1', TEST_PORT, 0, 1, (err) => { if (err) reject(new Error(`Reconnect failed: ${err}`)); else resolve(); }); }); expect(client.Connected()).toBe(true); }); }); describe('Server GetParam / SetParam (server.md)', () => { it('should get LocalPort param', () => { const port = server.GetParam(1); // p_u16_LocalPort expect(port).toBe(102); }); it('should get PDURequest param', () => { const pdu = server.GetParam(10); // p_i32_PDURequest expect(pdu).toBeGreaterThan(0); }); it('should get and set MaxClients param', () => { const original = server.GetParam(11); // p_i32_MaxClients expect(original).toBeGreaterThan(0); const result = server.SetParam(11, 10); // Set MaxClients expect(result).toBe(true); expect(server.GetParam(11)).toBe(10); // Restore server.SetParam(11, original); }); it('should set PDURequest param', () => { const result = server.SetParam(10, 480); // p_i32_PDURequest expect(result).toBe(true); expect(server.GetParam(10)).toBe(480); // Restore server.SetParam(10, 960); }); it('should return false for invalid param set', () => { const result = server.SetParam(1, 9999); // LocalPort cannot be changed expect(result).toBe(false); }); it('should return 0 for invalid param get', () => { const result = server.GetParam(999); expect(result).toBe(0); }); }); describe('Server constants (server.md)', () => { it('should have correct srvArea constants on class', () => { expect(S7Server.srvAreaPE).toBe(0); expect(S7Server.srvAreaPA).toBe(1); expect(S7Server.srvAreaMK).toBe(2); expect(S7Server.srvAreaCT).toBe(3); expect(S7Server.srvAreaTM).toBe(4); expect(S7Server.srvAreaDB).toBe(5); }); it('should have correct srvArea constants on instance', () => { expect(server.srvAreaPE).toBe(0); expect(server.srvAreaPA).toBe(1); expect(server.srvAreaMK).toBe(2); expect(server.srvAreaCT).toBe(3); expect(server.srvAreaTM).toBe(4); expect(server.srvAreaDB).toBe(5); }); it('should have OperationRead/Write constants', () => { expect(S7Server.OperationRead).toBe(0x00); expect(S7Server.OperationWrite).toBe(0x01); expect(server.OperationRead).toBe(0x00); expect(server.OperationWrite).toBe(0x01); }); it('should have server status constants', () => { expect(S7Server.SrvStopped).toBe(0x00); expect(S7Server.SrvRunning).toBe(0x01); expect(S7Server.SrvError).toBe(0x02); }); it('should have CPU status constants', () => { expect(S7Server.S7CpuStatusUnknown).toBe(0x00); expect(S7Server.S7CpuStatusRun).toBe(0x08); expect(S7Server.S7CpuStatusStop).toBe(0x04); }); it('should have event code constants matching server.md table', () => { expect(S7Server.evcServerStarted).toBe(0x00000001); expect(S7Server.evcServerStopped).toBe(0x00000002); expect(S7Server.evcListenerCannotStart).toBe(0x00000004); expect(S7Server.evcClientAdded).toBe(0x00000008); expect(S7Server.evcClientRejected).toBe(0x00000010); expect(S7Server.evcClientNoRoom).toBe(0x00000020); expect(S7Server.evcClientException).toBe(0x00000040); expect(S7Server.evcClientDisconnected).toBe(0x00000080); expect(S7Server.evcClientTerminated).toBe(0x00000100); expect(S7Server.evcClientsDropped).toBe(0x00000200); expect(S7Server.evcPDUincoming).toBe(0x00010000); expect(S7Server.evcDataRead).toBe(0x00020000); expect(S7Server.evcDataWrite).toBe(0x00040000); expect(S7Server.evcNegotiatePDU).toBe(0x00080000); expect(S7Server.evcReadSZL).toBe(0x00100000); expect(S7Server.evcClock).toBe(0x00200000); expect(S7Server.evcUpload).toBe(0x00400000); expect(S7Server.evcDownload).toBe(0x00800000); expect(S7Server.evcDirectory).toBe(0x01000000); expect(S7Server.evcSecurity).toBe(0x02000000); expect(S7Server.evcControl).toBe(0x04000000); expect(S7Server.evcAll).toBe(0xFFFFFFFF); expect(S7Server.evcNone).toBe(0x00000000); }); it('should have server error constants', () => { expect(S7Server.errSrvCannotStart).toBe(0x00100000); expect(S7Server.errSrvDBNullPointer).toBe(0x00200000); expect(S7Server.errSrvAreaAlreadyExists).toBe(0x00300000); expect(S7Server.errSrvUnknownArea).toBe(0x00400000); }); }); describe('Server ErrorText (server.md)', () => { it('should return error text for known server errors', () => { expect(server.ErrorText(0)).toBe('No error'); expect(server.ErrorText(0x00100000)).toBe('Server cannot start'); expect(server.ErrorText(0x00200000)).toBe('DB null pointer'); expect(server.ErrorText(0x00300000)).toBe('Area already exists'); expect(server.ErrorText(0x00400000)).toBe('Unknown area'); }); it('should return error text for ISO errors', () => { expect(server.ErrorText(0x00010000)).toBe('ISO connection error'); }); it('should return unknown for invalid error code', () => { const text = server.ErrorText(0x99999999); expect(typeof text).toBe('string'); expect(text).toContain('Unknown'); }); }); describe('Resourceless server (README resourceless example)', () => { it('should support resourceless mode with readWrite event', async () => { // Disconnect the main client first client.Disconnect(); // Stop the main server await new Promise((resolve) => { server.Stop(() => resolve()); }); // Create a resourceless server const resourcelessServer = new S7Server(); resourcelessServer.SetResourceless(true); const RESOURCELESS_PORT = 1103; // Listen for readWrite events const readWriteEvents: any[] = []; const rwHandler = (evt: any) => { readWriteEvents.push(evt); // For read operations, fill buffer with 0xFF as in the README example if (evt.Operation === OperationRead) { evt.Data.fill(0xFF); } }; resourcelessServer.on('readWrite', rwHandler); // Start the resourceless server await new Promise((resolve, reject) => { resourcelessServer.StartToPort('127.0.0.1', RESOURCELESS_PORT, (err) => { if (err) reject(new Error(`Resourceless server start failed: ${err}`)); else resolve(); }); }); // Connect a client to the resourceless server const rwClient = new S7Client(); await new Promise((resolve, reject) => { rwClient.ConnectToPort('127.0.0.1', RESOURCELESS_PORT, 0, 1, (err) => { if (err) reject(new Error(`Connect to resourceless server failed: ${err}`)); else resolve(); }); }); // Read from DB1 (area not registered - resourceless mode should still work) const readData = await new Promise((resolve, reject) => { rwClient.DBRead(1, 0, 4, (err, data) => { if (err) reject(new Error(`DBRead on resourceless failed: ${err}`)); else resolve(data!); }); }); // The readWrite event should have been fired expect(readWriteEvents.length).toBeGreaterThan(0); // The read event should have Operation = Read const readEvt = readWriteEvents.find((e: any) => e.Operation === OperationRead); expect(readEvt).toBeDefined(); expect(readEvt.Area).toBe(S7AreaDB); expect(readEvt.DBNumber).toBe(1); expect(readEvt.Start).toBe(0); // Data should be filled with 0xFF as per our handler expect(readData[0]).toBe(0xFF); expect(readData[1]).toBe(0xFF); expect(readData[2]).toBe(0xFF); expect(readData[3]).toBe(0xFF); // Write to DB1 const writeData = Buffer.from([0x12, 0x34]); await new Promise((resolve, reject) => { rwClient.DBWrite(1, 0, 2, writeData, (err) => { if (err) reject(new Error(`DBWrite on resourceless failed: ${err}`)); else resolve(); }); }); // The write event should have been fired const writeEvt = readWriteEvents.find((e: any) => e.Operation === OperationWrite); expect(writeEvt).toBeDefined(); expect(writeEvt.Area).toBe(S7AreaDB); expect(writeEvt.DBNumber).toBe(1); // Clean up rwClient.Disconnect(); await new Promise((resolve) => { resourcelessServer.Stop(() => resolve()); }); // Restart the main server for subsequent tests await new Promise((resolve, reject) => { server.StartToPort('127.0.0.1', TEST_PORT, (err) => { if (err) reject(new Error(`Server restart failed: ${err}`)); else resolve(); }); }); // Reconnect the main client await new Promise((resolve, reject) => { client.ConnectToPort('127.0.0.1', TEST_PORT, 0, 1, (err) => { if (err) reject(new Error(`Reconnect failed: ${err}`)); else resolve(); }); }); expect(client.Connected()).toBe(true); }); }); describe('README Client Example pattern', () => { it('should match the ConnectTo + ABRead + ErrorText pattern from README', async () => { // This mirrors the exact example from README.md: // s7client.ConnectTo('127.0.0.1', 0, 1, function(err) { // if(err) return console.log(' >> Connection failed. Code #' + err + ' - ' + s7client.ErrorText(err)); // s7client.ABRead(0, 1, function(err, res) { // if(err) return console.log(' >> ABRead failed. Code #' + err + ' - ' + s7client.ErrorText(err)); // console.log(res); // }); // }); // We're already connected, so just test ABRead + ErrorText paBuffer[0] = 0x42; // Set a known value const readResult = await new Promise((resolve, reject) => { client.ABRead(0, 1, (err, res) => { if (err) { // Test ErrorText as shown in README const errMsg = client.ErrorText(err); reject(new Error(`ABRead failed. Code #${err} - ${errMsg}`)); } else { resolve(res!); } }); }); expect(readResult[0]).toBe(0x42); }); }); });