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

Integration tests for XA Storage SOP Classes. More...

#include "test_fixtures.h"
#include "kcenon/pacs/storage/file_storage.h"
#include <catch2/catch_test_macros.hpp>
#include <iostream>
Include dependency graph for test_xa_storage.cpp:

Go to the source code of this file.

Functions

 TEST_CASE ("XA Storage Integration", "[integration][xa][storage]")
 

Variables

constexpr dicom_tag number_of_frames {0x0028, 0x0008}
 

Detailed Description

Integration tests for XA Storage SOP Classes.

Verifies XA Image Storage, Enhanced XA, and related workflows. Covers Issue #135 scenarios.

Definition in file test_xa_storage.cpp.

Function Documentation

◆ TEST_CASE()

TEST_CASE ( "XA Storage Integration" ,
"" [integration][xa][storage] )

Definition at line 102 of file test_xa_storage.cpp.

102 {
103 // Setup test environment
104 auto port = find_available_port();
105 xa_pacs_server server(port, "XA_SCP");
106 REQUIRE(server.initialize());
107 REQUIRE(server.start());
108
109 SECTION("Scenario 1: Basic XA Storage") {
110 // Create XA dataset
111 auto ds = generate_xa_dataset();
112 auto instance_uid = ds.get_string(tags::sop_instance_uid);
113
114 // Connect and store
115 auto assoc_result = test_association::connect(
116 "127.0.0.1", port, "XA_SCP", "TEST_SCU",
117 {"1.2.840.10008.5.1.4.1.1.12.1", "1.2.840.10008.1.1"});
118 REQUIRE(assoc_result.is_ok());
119
120 auto association = std::move(assoc_result.value());
121
122 // Verify connection with C-ECHO
124 if (!context_id_opt) {
125 // Known issue: Global negotiation failure
126 WARN("Verification SOP Class not accepted (Global Issue)");
127 } else {
129 auto send_result = association.send_dimse(*context_id_opt, echo_rq);
130 CHECK(send_result.is_ok());
131 auto recv_result = association.receive_dimse(std::chrono::seconds{5});
132 CHECK(recv_result.is_ok());
133 }
134
135 storage_scu scu;
136 auto store_result = scu.store(association, ds);
137 if (store_result.is_err()) {
138 FAIL("Store failed: " << store_result.error().message);
139 }
140 CHECK(store_result.value().is_success()); // Success
141
142 // Verify file exists
143 auto stored_path = server.storage_path() / (instance_uid + ".dcm");
144 CHECK(std::filesystem::exists(stored_path));
145
146 // Release association
147 [[maybe_unused]] auto release_result = association.release();
148 }
149
150 SECTION("Scenario 2: XA IOD Validation") {
151 // Valid dataset
152 auto valid_ds = generate_xa_dataset();
153
154 // Connect
155 auto assoc_result = test_association::connect(
156 "127.0.0.1", port, "XA_SCP", "TEST_SCU",
157 {"1.2.840.10008.5.1.4.1.1.12.1"});
158 REQUIRE(assoc_result.is_ok());
159 auto association = std::move(assoc_result.value());
160
161 // Store valid
162 storage_scu scu;
163 auto result_valid = scu.store(association, valid_ds);
164 if (result_valid.is_err()) {
165 FAIL("Store valid failed: " << result_valid.error().message);
166 }
167 CHECK(result_valid.value().is_success());
168
169 // Release association
170 [[maybe_unused]] auto release_result = association.release();
171
172 // TODO: Implement invalid dataset test once dataset API is confirmed.
173 }
174
175 SECTION("Scenario 3: Multi-frame XA Storage") {
176 auto ds = generate_xa_dataset();
177 ds.set_string(number_of_frames, vr_type::IS, "10");
178 // Update pixel data for 10 frames
179 std::vector<uint16_t> pixel_data(512 * 512 * 10, 128);
180 ds.insert(dicom_element(tags::pixel_data, vr_type::OW,
181 std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(pixel_data.data()), pixel_data.size() * sizeof(uint16_t))));
182
183 auto assoc_result = test_association::connect(
184 "127.0.0.1", port, "XA_SCP", "TEST_SCU",
185 {"1.2.840.10008.5.1.4.1.1.12.1"});
186 REQUIRE(assoc_result.is_ok());
187 auto association = std::move(assoc_result.value());
188
189 storage_scu scu;
190 auto result = scu.store(association, ds);
191 if (result.is_err()) {
192 FAIL("Multi-frame store failed: " << result.error().message);
193 }
194 CHECK(result.value().is_success());
195
196 // Release association
197 [[maybe_unused]] auto release_result = association.release();
198 }
199
200 SECTION("Scenario 4: XA Specific Attributes") {
201 auto ds = generate_xa_dataset();
202 // Verify we can set/get XA specific tags
203
204 auto assoc_result = test_association::connect(
205 "127.0.0.1", port, "XA_SCP", "TEST_SCU",
206 {"1.2.840.10008.5.1.4.1.1.12.1"});
207 REQUIRE(assoc_result.is_ok());
208 auto association = std::move(assoc_result.value());
209
210 storage_scu scu;
211 auto result = scu.store(association, ds);
212 if (result.is_err()) {
213 FAIL("XA attributes store failed: " << result.error().message);
214 }
215 CHECK(result.value().is_success());
216
217 // Release association
218 [[maybe_unused]] auto release_result = association.release();
219 }
220
221 // Stop server explicitly
222 server.stop();
223}
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.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
Result< std::monostate > release(duration timeout=default_timeout)
Gracefully release the association.
Result< std::pair< uint8_t, dimse::dimse_message > > receive_dimse(duration timeout=default_timeout)
Receive a DIMSE message.
std::optional< uint8_t > accepted_context_id(std::string_view abstract_syntax) const
Get the presentation context ID for an abstract syntax.
network::Result< store_result > store(network::association &assoc, const core::dicom_dataset &dataset)
Store a single DICOM dataset.
constexpr dicom_tag pixel_data
Pixel Data.
uint16_t find_available_port(uint16_t start=default_test_port, int max_attempts=200)
Find an available port for testing.
core::dicom_dataset generate_xa_dataset(const std::string &study_uid="")
Generate a XA (X-Ray Angiographic) image dataset for testing.
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)
Result of a C-STORE operation.
Definition storage_scu.h:43
bool is_success() const noexcept
Check if the store operation was successful.
Definition storage_scu.h:54
constexpr dicom_tag number_of_frames

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::generate_xa_dataset(), kcenon::pacs::services::store_result::is_success(), kcenon::pacs::network::dimse::make_c_echo_rq(), number_of_frames, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::release(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::storage_scu::store(), and kcenon::pacs::services::verification_sop_class_uid.

Here is the call graph for this function:

Variable Documentation

◆ number_of_frames