13#include <catch2/catch_test_macros.hpp>
34TEST_CASE(
"C-ECHO: pacs_system SCP with DCMTK echoscu",
"[dcmtk][interop][echo]") {
36 SKIP(
"DCMTK not installed - skipping interoperability test");
42 SKIP(
"pacs_system does not support real TCP DICOM connections yet - "
43 "accept_worker closes connections immediately. See Issue #XXX.");
48 const std::string ae_title =
"PACS_ECHO_SCP";
52 REQUIRE(server.
start());
59 SECTION(
"Basic echo succeeds") {
62 INFO(
"stdout: " << result.stdout_output);
63 INFO(
"stderr: " << result.stderr_output);
65 REQUIRE(result.success());
68 SECTION(
"Echo with custom calling AE title") {
70 "localhost", port, ae_title,
"CUSTOM_SCU");
72 INFO(
"stdout: " << result.stdout_output);
73 INFO(
"stderr: " << result.stderr_output);
75 REQUIRE(result.success());
78 SECTION(
"Multiple consecutive echoes") {
79 for (
int i = 0; i < 5; ++i) {
82 INFO(
"Iteration: " << i);
83 INFO(
"stdout: " << result.stdout_output);
84 INFO(
"stderr: " << result.stderr_output);
86 REQUIRE(result.success());
90 SECTION(
"Echo with short timeout succeeds") {
92 "localhost", port, ae_title,
"ECHOSCU", std::chrono::seconds{5});
94 REQUIRE(result.success());
102TEST_CASE(
"C-ECHO: DCMTK storescp with pacs_system SCU",
"[dcmtk][interop][echo]") {
104 SKIP(
"DCMTK not installed - skipping interoperability test");
109 SKIP(
"pacs_system does not support real TCP DICOM connections yet - "
110 "association uses in-memory transport only. See Issue #XXX.");
115 const std::string ae_title =
"DCMTK_SCP";
120 REQUIRE(dcmtk_server.is_running());
127 SECTION(
"pacs_system SCU sends C-ECHO successfully") {
130 "localhost", port, ae_title,
"PACS_SCU",
133 REQUIRE(connect_result.is_ok());
134 auto& assoc = connect_result.value();
139 REQUIRE(context_id.has_value());
143 auto send_result = assoc.send_dimse(*context_id, echo_rq);
144 REQUIRE(send_result.is_ok());
147 auto recv_result = assoc.receive_dimse();
148 REQUIRE(recv_result.is_ok());
150 auto& [recv_ctx, echo_rsp] = recv_result.value();
151 REQUIRE(echo_rsp.command() == command_field::c_echo_rsp);
152 REQUIRE(echo_rsp.status() == status_success);
155 SECTION(
"Multiple consecutive echoes from pacs_system") {
157 "localhost", port, ae_title,
"PACS_SCU",
160 REQUIRE(connect_result.is_ok());
161 auto& assoc = connect_result.value();
164 REQUIRE(context_id.has_value());
166 for (
int i = 0; i < 5; ++i) {
169 auto send_result = assoc.send_dimse(*context_id, echo_rq);
170 REQUIRE(send_result.is_ok());
172 auto recv_result = assoc.receive_dimse();
173 REQUIRE(recv_result.is_ok());
175 auto& [recv_ctx, echo_rsp] = recv_result.value();
176 INFO(
"Iteration: " << i);
177 REQUIRE(echo_rsp.command() == command_field::c_echo_rsp);
178 REQUIRE(echo_rsp.status() == status_success);
187TEST_CASE(
"C-ECHO: DCMTK echoscp with pacs_system SCU",
"[dcmtk][interop][echo]") {
189 SKIP(
"DCMTK not installed - skipping interoperability test");
194 SKIP(
"pacs_system does not support real TCP DICOM connections yet - "
195 "association uses in-memory transport only. See Issue #XXX.");
200 const std::string ae_title =
"DCMTK_ECHO";
203 REQUIRE(dcmtk_server.is_running());
210 SECTION(
"pacs_system SCU succeeds with DCMTK echoscp") {
212 "localhost", port, ae_title,
"PACS_SCU",
215 REQUIRE(connect_result.is_ok());
216 auto& assoc = connect_result.value();
219 REQUIRE(context_id.has_value());
222 auto send_result = assoc.send_dimse(*context_id, echo_rq);
223 REQUIRE(send_result.is_ok());
225 auto recv_result = assoc.receive_dimse();
226 REQUIRE(recv_result.is_ok());
228 auto& [recv_ctx, echo_rsp] = recv_result.value();
229 REQUIRE(echo_rsp.command() == command_field::c_echo_rsp);
230 REQUIRE(echo_rsp.status() == status_success);
238TEST_CASE(
"C-ECHO: Concurrent echo operations",
"[dcmtk][interop][echo][stress]") {
240 SKIP(
"DCMTK not installed");
245 SKIP(
"pacs_system does not support real TCP DICOM connections yet");
249 const std::string ae_title =
"STRESS_SCP";
253 REQUIRE(server.
start());
260 SECTION(
"5 concurrent DCMTK echoscu clients") {
261 constexpr int num_clients = 5;
262 std::vector<std::future<dcmtk_result>> futures;
263 futures.reserve(num_clients);
265 for (
int i = 0; i < num_clients; ++i) {
266 futures.push_back(std::async(std::launch::async, [&, i]() {
268 "localhost", port, ae_title,
269 "CLIENT_" + std::to_string(i));
274 for (
size_t i = 0; i < futures.size(); ++i) {
275 auto result = futures[i].get();
277 INFO(
"Client " << i <<
" stdout: " << result.stdout_output);
278 INFO(
"Client " << i <<
" stderr: " << result.stderr_output);
280 REQUIRE(result.success());
284 SECTION(
"5 concurrent pacs_system SCU clients") {
285 constexpr int num_clients = 5;
286 std::vector<std::future<bool>> futures;
287 futures.reserve(num_clients);
289 for (
int i = 0; i < num_clients; ++i) {
290 futures.push_back(std::async(std::launch::async, [&, i]() {
292 "localhost", port, ae_title,
293 "PACS_CLIENT_" + std::to_string(i),
296 if (!connect_result.is_ok()) {
300 auto& assoc = connect_result.value();
302 if (!context_id.has_value()) {
307 auto send_result = assoc.send_dimse(*context_id, echo_rq);
308 if (!send_result.is_ok()) {
312 auto recv_result = assoc.receive_dimse();
313 if (!recv_result.is_ok()) {
317 auto& [recv_ctx, echo_rsp] = recv_result.value();
318 return echo_rsp.command() == command_field::c_echo_rsp &&
324 for (
size_t i = 0; i < futures.size(); ++i) {
325 bool success = futures[i].get();
326 INFO(
"Client " << i);
336TEST_CASE(
"C-ECHO: Connection error handling",
"[dcmtk][interop][echo][error]") {
338 SKIP(
"DCMTK not installed");
343 SKIP(
"pacs_system does not support real TCP DICOM connections yet");
346 SECTION(
"echoscu to non-existent server fails gracefully") {
353 "localhost", port,
"NONEXISTENT",
354 "ECHOSCU", std::chrono::seconds{5});
357 REQUIRE_FALSE(result.success());
360 SECTION(
"pacs_system SCU to non-existent server fails gracefully") {
365 std::this_thread::sleep_for(std::chrono::milliseconds{100});
369 SKIP(
"Port " + std::to_string(port) +
" is unexpectedly in use");
373 "localhost", port,
"NONEXISTENT",
"PACS_SCU",
377 REQUIRE_FALSE(connect_result.is_ok());
385TEST_CASE(
"C-ECHO: Protocol verification",
"[dcmtk][interop][echo][protocol]") {
387 SKIP(
"DCMTK not installed");
392 SKIP(
"pacs_system does not support real TCP DICOM connections yet");
396 const std::string ae_title =
"PROTOCOL_SCP";
400 REQUIRE(server.
start());
406 SECTION(
"Verification SOP Class negotiation") {
410 REQUIRE(result.success());
415 SECTION(
"Association release after echo") {
418 REQUIRE(result.success());
423 REQUIRE(result2.success());
static bool is_port_listening(uint16_t port, const std::string &host="127.0.0.1")
Check if a port is currently listening.
static network::Result< network::association > connect(const std::string &host, uint16_t port, const std::string &called_ae, const std::string &calling_ae=test_scu_ae_title, const std::vector< std::string > &sop_classes={"1.2.840.10008.1.1"})
Connect to a test server.
RAII wrapper for temporary test directory.
RAII wrapper for a test DICOM server.
bool start()
Start the server and wait for it to be ready.
void register_service(std::shared_ptr< Service > service)
Register a service provider.
DIMSE message encoding and decoding.
bool supports_real_tcp_dicom()
Check if pacs_system supports real TCP DICOM connections.
uint16_t find_available_port(uint16_t start=default_test_port, int max_attempts=200)
Find an available port for testing.
bool wait_for(Func &&condition, std::chrono::milliseconds timeout, std::chrono::milliseconds interval=std::chrono::milliseconds{50})
Wait for a condition with timeout.
std::chrono::milliseconds server_ready_timeout()
Port listening timeout for pacs_system servers (5s normal, 30s CI)
TEST_CASE("test_data_generator::ct generates valid CT dataset", "[data_generator][ct]")
std::chrono::milliseconds dcmtk_server_ready_timeout()
Port listening timeout for DCMTK servers (10s normal, 60s CI)
auto make_c_echo_rq(uint16_t message_id, std::string_view sop_class_uid="1.2.840.10008.1.1") -> dimse_message
Create a C-ECHO request message.
constexpr status_code status_success
Operation completed successfully.
constexpr std::string_view verification_sop_class_uid
Verification SOP Class UID (1.2.840.10008.1.1)
Common test fixtures and utilities for integration tests.
DICOM Verification SCP service (C-ECHO handler)