22
33#include " ProtocolAdapter/adapterclient.h"
44#include " ProtocolAdapter/adapterprocess.h"
5+ #include " util/result.h"
56
67#include < QJsonArray>
78#include < QJsonObject>
@@ -13,6 +14,11 @@ namespace {
1314constexpr int cSessionTimeoutMs = 10000 ;
1415constexpr int cReadTimeoutMs = 5000 ;
1516
17+ // Dummy adapter built-in server settings
18+ constexpr const char * DUMMY_IP = " 127.0.0.1" ;
19+ constexpr quint16 DUMMY_PORT = 5020 ;
20+ constexpr quint8 DUMMY_SLAVE_ID = 1 ;
21+
1622// ! Minimal adapter configuration with no real connections or devices.
1723// ! The dummymodbusadapter accepts this without attempting any real I/O.
1824QJsonObject minimalConfig ()
@@ -25,6 +31,30 @@ QJsonObject minimalConfig()
2531 return config;
2632}
2733
34+ // ! Full configuration pointing at the dummymodbusadapter's built-in TCP server.
35+ QJsonObject realConfig ()
36+ {
37+ QJsonObject connection;
38+ connection[" id" ] = 0 ;
39+ connection[" type" ] = QStringLiteral (" tcp" );
40+ connection[" ip" ] = DUMMY_IP;
41+ connection[" port" ] = static_cast <int >(DUMMY_PORT);
42+ connection[" persistent" ] = true ;
43+ connection[" timeout" ] = 1000 ;
44+
45+ QJsonObject device;
46+ device[" id" ] = 1 ;
47+ device[" connectionId" ] = 0 ;
48+ device[" slaveId" ] = static_cast <int >(DUMMY_SLAVE_ID);
49+
50+ QJsonObject config;
51+ config[" version" ] = 1 ;
52+ config[" general" ] = QJsonObject ();
53+ config[" connections" ] = QJsonArray ({ connection });
54+ config[" devices" ] = QJsonArray ({ device });
55+ return config;
56+ }
57+
2858} // namespace
2959
3060void TestDummyAdapter::describeReturnsRequiredFields ()
@@ -97,4 +127,91 @@ void TestDummyAdapter::fullLifecycleSessionStarts()
97127 client.stopSession ();
98128}
99129
130+ void TestDummyAdapter::readRegistersReturnsValidData ()
131+ {
132+ auto * process = new AdapterProcess ();
133+ AdapterClient client (process);
134+
135+ const QStringList registers = { " ${40001}" , " ${40002}" , " ${40010}" };
136+
137+ connect (&client, &AdapterClient::describeResult, &client,
138+ [&client, ®isters]() { client.provideConfig (realConfig (), registers); });
139+
140+ QSignalSpy spyStarted (&client, &AdapterClient::sessionStarted);
141+ QSignalSpy spyError (&client, &AdapterClient::sessionError);
142+
143+ client.prepareAdapter (QString::fromUtf8 (DUMMY_ADAPTER_EXECUTABLE));
144+
145+ QVERIFY2 (spyStarted.wait (cSessionTimeoutMs), " sessionStarted not emitted" );
146+ QCOMPARE (spyError.count (), 0 );
147+
148+ QSignalSpy spyData (&client, &AdapterClient::readDataResult);
149+ client.requestReadData ();
150+ QVERIFY2 (spyData.wait (cReadTimeoutMs), " readDataResult not emitted" );
151+
152+ const auto results = spyData.at (0 ).at (0 ).value <ResultDoubleList>();
153+ QCOMPARE (results.size (), registers.size ());
154+ // The dummymodbusadapter stores the 0-based register offset as the register value:
155+ // register 40001 → offset 0, register 40002 → offset 1, register 40010 → offset 9.
156+ QVERIFY2 (results[0 ].isValid (), " Expected SUCCESS for register 40001" );
157+ QCOMPARE (results[0 ].value (), 0.0 );
158+ QVERIFY2 (results[1 ].isValid (), " Expected SUCCESS for register 40002" );
159+ QCOMPARE (results[1 ].value (), 1.0 );
160+ QVERIFY2 (results[2 ].isValid (), " Expected SUCCESS for register 40010" );
161+ QCOMPARE (results[2 ].value (), 9.0 );
162+
163+ client.stopSession ();
164+ }
165+
166+ void TestDummyAdapter::multipleReadCyclesAllSucceed ()
167+ {
168+ auto * process = new AdapterProcess ();
169+ AdapterClient client (process);
170+
171+ const QStringList registers = { " ${40001}" , " ${40005}" };
172+
173+ connect (&client, &AdapterClient::describeResult, &client,
174+ [&client, ®isters]() { client.provideConfig (realConfig (), registers); });
175+
176+ QSignalSpy spyStarted (&client, &AdapterClient::sessionStarted);
177+ client.prepareAdapter (QString::fromUtf8 (DUMMY_ADAPTER_EXECUTABLE));
178+ QVERIFY2 (spyStarted.wait (cSessionTimeoutMs), " sessionStarted not emitted" );
179+
180+ QSignalSpy spyData (&client, &AdapterClient::readDataResult);
181+
182+ constexpr int cReadCycles = 3 ;
183+ for (int cycle = 0 ; cycle < cReadCycles; ++cycle)
184+ {
185+ client.requestReadData ();
186+ QVERIFY2 (spyData.wait (cReadTimeoutMs), " readDataResult not emitted" );
187+
188+ const auto results = spyData.takeLast ().at (0 ).value <ResultDoubleList>();
189+ QCOMPARE (results.size (), registers.size ());
190+ // The dummymodbusadapter stores the 0-based register offset as the value.
191+ QVERIFY2 (results[0 ].isValid (), " Expected SUCCESS for register 40001 on each cycle" );
192+ QCOMPARE (results[0 ].value (), 0.0 );
193+ QVERIFY2 (results[1 ].isValid (), " Expected SUCCESS for register 40005 on each cycle" );
194+ QCOMPARE (results[1 ].value (), 4.0 );
195+ }
196+
197+ client.stopSession ();
198+ }
199+
200+ void TestDummyAdapter::stopSessionEmitsSessionStopped ()
201+ {
202+ auto * process = new AdapterProcess ();
203+ AdapterClient client (process);
204+
205+ connect (&client, &AdapterClient::describeResult, &client,
206+ [&client]() { client.provideConfig (realConfig (), QStringList ()); });
207+
208+ QSignalSpy spyStarted (&client, &AdapterClient::sessionStarted);
209+ client.prepareAdapter (QString::fromUtf8 (DUMMY_ADAPTER_EXECUTABLE));
210+ QVERIFY2 (spyStarted.wait (cSessionTimeoutMs), " sessionStarted not emitted" );
211+
212+ QSignalSpy spyStopped (&client, &AdapterClient::sessionStopped);
213+ client.stopSession ();
214+ QVERIFY2 (spyStopped.wait (cSessionTimeoutMs), " sessionStopped not emitted after stopSession" );
215+ }
216+
100217QTEST_GUILESS_MAIN (TestDummyAdapter)
0 commit comments