PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
test_connectivity.cpp File Reference

Scenario 1: Basic Connectivity Tests. More...

#include "test_fixtures.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include "kcenon/pacs/network/dimse/dimse_message.h"
#include "kcenon/pacs/services/verification_scp.h"
#include <thread>
Include dependency graph for test_connectivity.cpp:

Go to the source code of this file.

Functions

 TEST_CASE ("C-ECHO basic connectivity", "[connectivity][echo]")
 
 TEST_CASE ("Multiple sequential C-ECHO requests", "[connectivity][echo]")
 
 TEST_CASE ("Multiple concurrent associations", "[connectivity][echo]")
 
 TEST_CASE ("Connection to non-existent server fails gracefully", "[connectivity][error]")
 
 TEST_CASE ("Wrong AE title handling", "[connectivity][ae_title]")
 
 TEST_CASE ("Association timeout handling", "[connectivity][timeout]")
 

Detailed Description

Scenario 1: Basic Connectivity Tests.

Tests basic DICOM connectivity using C-ECHO service. Validates that Echo SCP and SCU can communicate successfully.

See also
Issue #111 - Integration Test Suite

Test Workflow:

  1. Start Echo SCP
  2. Run Echo SCU -> Verify success
  3. Stop Echo SCP

Definition in file test_connectivity.cpp.

Function Documentation

◆ TEST_CASE() [1/6]

TEST_CASE ( "Association timeout handling" ,
"" [connectivity][timeout] )

Definition at line 223 of file test_connectivity.cpp.

223 {
224 auto port = find_available_port();
225 test_server server(port, "ECHO_SCP");
226 server.register_service(std::make_shared<verification_scp>());
227
228 REQUIRE(server.start());
229
230 auto connect_result = test_association::connect(
231 "localhost", port, server.ae_title(), "ECHO_SCU",
232 {std::string(verification_sop_class_uid)}
233 );
234 REQUIRE(connect_result.is_ok());
235 auto& assoc = connect_result.value();
236
237 // Try to receive without sending - should timeout
238 auto short_timeout = std::chrono::milliseconds{100};
239 auto recv_result = assoc.receive_dimse(short_timeout);
240
241 // Should timeout (error result)
242 REQUIRE(recv_result.is_err());
243
244 // Abort connection (since we didn't properly communicate)
245 assoc.abort();
246
247 server.stop();
248}
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 a test DICOM server.
uint16_t find_available_port(uint16_t start=default_test_port, int max_attempts=200)
Find an available port for testing.

References kcenon::pacs::integration_test::test_server::ae_title(), kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::test_server::start(), and kcenon::pacs::integration_test::test_server::stop().

Here is the call graph for this function:

◆ TEST_CASE() [2/6]

TEST_CASE ( "C-ECHO basic connectivity" ,
"" [connectivity][echo] )

Definition at line 35 of file test_connectivity.cpp.

35 {
36 SECTION("Echo SCP accepts connection and responds to C-ECHO") {
37 // Step 1: Start Echo SCP
38 auto port = find_available_port();
39 test_server server(port, "ECHO_SCP");
40 server.register_service(std::make_shared<verification_scp>());
41
42 REQUIRE(server.start());
43 REQUIRE(server.is_running());
44
45 // Step 2: Connect and send C-ECHO
46 auto connect_result = test_association::connect(
47 "localhost",
48 port,
49 server.ae_title(),
50 "ECHO_SCU",
51 {std::string(verification_sop_class_uid)}
52 );
53
54 REQUIRE(connect_result.is_ok());
55 auto& assoc = connect_result.value();
56
57 // Verify we have accepted context for verification
58 REQUIRE(assoc.has_accepted_context(verification_sop_class_uid));
59
60 auto context_id_opt = assoc.accepted_context_id(verification_sop_class_uid);
61 REQUIRE(context_id_opt.has_value());
62 uint8_t context_id = *context_id_opt;
63
64 // Create and send C-ECHO request
66 auto send_result = assoc.send_dimse(context_id, echo_rq);
67 REQUIRE(send_result.is_ok());
68
69 // Receive C-ECHO response
70 auto recv_result = assoc.receive_dimse(default_timeout());
71 REQUIRE(recv_result.is_ok());
72
73 auto& [recv_context_id, echo_rsp] = recv_result.value();
74 REQUIRE(echo_rsp.command() == command_field::c_echo_rsp);
75 REQUIRE(echo_rsp.status() == status_success);
76
77 // Release association gracefully
78 auto release_result = assoc.release(default_timeout());
79 REQUIRE(release_result.is_ok());
80
81 // Step 3: Stop server
82 server.stop();
83 REQUIRE_FALSE(server.is_running());
84 }
85}
std::chrono::milliseconds default_timeout()
Default timeout for test operations (5s normal, 30s 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 std::string_view verification_sop_class_uid
Verification SOP Class UID (1.2.840.10008.1.1)

References kcenon::pacs::integration_test::test_server::ae_title(), kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::default_timeout(), kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::test_server::is_running(), kcenon::pacs::network::dimse::make_c_echo_rq(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::test_server::start(), kcenon::pacs::integration_test::test_server::stop(), and kcenon::pacs::services::verification_sop_class_uid.

Here is the call graph for this function:

◆ TEST_CASE() [3/6]

TEST_CASE ( "Connection to non-existent server fails gracefully" ,
"" [connectivity][error] )

Definition at line 181 of file test_connectivity.cpp.

181 {
182 // Try to connect to a port that's not listening
183 auto connect_result = test_association::connect(
184 "localhost",
185 find_available_port() + 1000, // Very unlikely to be in use
186 "NONEXISTENT",
187 "ECHO_SCU",
188 {std::string(verification_sop_class_uid)}
189 );
190
191 REQUIRE(connect_result.is_err());
192}

References kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::find_available_port(), and kcenon::pacs::services::verification_sop_class_uid.

Here is the call graph for this function:

◆ TEST_CASE() [4/6]

TEST_CASE ( "Multiple concurrent associations" ,
"" [connectivity][echo] )

Definition at line 123 of file test_connectivity.cpp.

123 {
124 auto port = find_available_port();
125 test_server server(port, "ECHO_SCP");
126 server.register_service(std::make_shared<verification_scp>());
127
128 REQUIRE(server.start());
129
130 constexpr int num_associations = 5;
131 std::vector<std::thread> threads;
132 std::atomic<int> success_count{0};
133
134 for (int i = 0; i < num_associations; ++i) {
135 threads.emplace_back([&server, &success_count, port, i]() {
136 auto connect_result = test_association::connect(
137 "localhost", port, server.ae_title(),
138 "ECHO_SCU_" + std::to_string(i),
139 {std::string(verification_sop_class_uid)}
140 );
141
142 if (connect_result.is_err()) {
143 return;
144 }
145
146 auto& assoc = connect_result.value();
147 auto context_id_opt = assoc.accepted_context_id(verification_sop_class_uid);
148 if (!context_id_opt) {
149 return;
150 }
151
153 auto send_result = assoc.send_dimse(*context_id_opt, echo_rq);
154 if (send_result.is_err()) {
155 return;
156 }
157
158 auto recv_result = assoc.receive_dimse(default_timeout());
159 if (recv_result.is_ok()) {
160 auto& [ctx, rsp] = recv_result.value();
161 if (rsp.command() == command_field::c_echo_rsp &&
162 rsp.status() == status_success) {
163 ++success_count;
164 }
165 }
166
167 (void)assoc.release(default_timeout());
168 });
169 }
170
171 // Wait for all threads
172 for (auto& t : threads) {
173 t.join();
174 }
175
176 server.stop();
177
178 REQUIRE(success_count == num_associations);
179}

References kcenon::pacs::integration_test::test_server::ae_title(), kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::default_timeout(), kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::network::dimse::make_c_echo_rq(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::test_server::start(), kcenon::pacs::integration_test::test_server::stop(), and kcenon::pacs::services::verification_sop_class_uid.

Here is the call graph for this function:

◆ TEST_CASE() [5/6]

TEST_CASE ( "Multiple sequential C-ECHO requests" ,
"" [connectivity][echo] )

Definition at line 87 of file test_connectivity.cpp.

87 {
88 auto port = find_available_port();
89 test_server server(port, "ECHO_SCP");
90 server.register_service(std::make_shared<verification_scp>());
91
92 REQUIRE(server.start());
93
94 // Connect once
95 auto connect_result = test_association::connect(
96 "localhost", port, server.ae_title(), "ECHO_SCU",
97 {std::string(verification_sop_class_uid)}
98 );
99 REQUIRE(connect_result.is_ok());
100 auto& assoc = connect_result.value();
101
102 auto context_id = *assoc.accepted_context_id(verification_sop_class_uid);
103
104 // Send multiple C-ECHO requests on same association
105 constexpr int echo_count = 5;
106 for (int i = 0; i < echo_count; ++i) {
107 auto echo_rq = make_c_echo_rq(static_cast<uint16_t>(i + 1), verification_sop_class_uid);
108 auto send_result = assoc.send_dimse(context_id, echo_rq);
109 REQUIRE(send_result.is_ok());
110
111 auto recv_result = assoc.receive_dimse(default_timeout());
112 REQUIRE(recv_result.is_ok());
113
114 auto& [recv_ctx, echo_rsp] = recv_result.value();
115 REQUIRE(echo_rsp.command() == command_field::c_echo_rsp);
116 REQUIRE(echo_rsp.status() == status_success);
117 }
118
119 (void)assoc.release(default_timeout());
120 server.stop();
121}

References kcenon::pacs::integration_test::test_server::ae_title(), kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::default_timeout(), kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::network::dimse::make_c_echo_rq(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::test_server::start(), kcenon::pacs::integration_test::test_server::stop(), and kcenon::pacs::services::verification_sop_class_uid.

Here is the call graph for this function:

◆ TEST_CASE() [6/6]

TEST_CASE ( "Wrong AE title handling" ,
"" [connectivity][ae_title] )

Definition at line 194 of file test_connectivity.cpp.

194 {
195 auto port = find_available_port();
196 test_server server(port, "CORRECT_AE");
197 server.register_service(std::make_shared<verification_scp>());
198
199 REQUIRE(server.start());
200
201 // Note: In DICOM, AE title mismatch is typically handled during
202 // association negotiation. The server may accept or reject based
203 // on configuration. This test validates the connection behavior.
204 auto connect_result = test_association::connect(
205 "localhost",
206 port,
207 "WRONG_AE", // Wrong AE title
208 "ECHO_SCU",
209 {std::string(verification_sop_class_uid)}
210 );
211
212 // The result depends on server configuration
213 // Some servers reject, some accept any AE title
214 // We just verify the operation completes without crash
215 if (connect_result.is_ok()) {
216 auto& assoc = connect_result.value();
217 (void)assoc.release(default_timeout());
218 }
219
220 server.stop();
221}

References kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::default_timeout(), kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::test_server::start(), kcenon::pacs::integration_test::test_server::stop(), and kcenon::pacs::services::verification_sop_class_uid.

Here is the call graph for this function: