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

Catch2 round-trip integrity tests for conversion CLI tools. More...

#include <kcenon/pacs/core/dicom_file.h>
#include <kcenon/pacs/core/dicom_tag_constants.h>
#include <catch2/catch_test_macros.hpp>
#include <array>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <string>
#include <thread>
#include <vector>
#include <sys/wait.h>
Include dependency graph for test_cli_roundtrip.cpp:

Go to the source code of this file.

Functions

 TEST_CASE ("JSON round-trip preserves metadata", "[cli][roundtrip]")
 
 TEST_CASE ("XML round-trip preserves metadata", "[cli][roundtrip]")
 
 TEST_CASE ("Transfer Syntax conversion preserves tags", "[cli][roundtrip]")
 
 TEST_CASE ("Anonymization changes patient identifiers", "[cli][roundtrip]")
 
 TEST_CASE ("Tag modification updates values correctly", "[cli][roundtrip]")
 

Detailed Description

Catch2 round-trip integrity tests for conversion CLI tools.

Verifies data integrity through CLI conversion round-trips: DCM->JSON->DCM, DCM->XML->DCM, Transfer Syntax conversion, anonymization, and tag modification.

Uses the PACS library API to programmatically compare input/output datasets at the tag level.

See also
Issue #759 - Add Catch2 round-trip integrity tests for conversion CLIs
Issue #756 - Automated CLI Tool Testing (Epic)

Definition in file test_cli_roundtrip.cpp.

Function Documentation

◆ TEST_CASE() [1/5]

TEST_CASE ( "Anonymization changes patient identifiers" ,
"" [cli][roundtrip] )

Definition at line 268 of file test_cli_roundtrip.cpp.

268 {
269 temp_dir tmp;
270 auto src = test_data_dir / "ct_minimal.dcm";
271 auto anon_path = tmp.path / "anonymized.dcm";
272
273 REQUIRE(fs::exists(src));
274
275 // Anonymize
276 auto result = run_cli("dcm_anonymize",
277 {src.string(), anon_path.string()});
278 INFO("dcm_anonymize output: " << result.output);
279 REQUIRE(result.exit_code == 0);
280 REQUIRE(fs::exists(anon_path));
281
282 // Verify anonymization
283 auto original = read_dicom(src);
284 auto anonymized = read_dicom(anon_path);
285
286 auto orig_name = get_tag(original, kcenon::pacs::core::tags::patient_name);
287 auto anon_name = get_tag(anonymized, kcenon::pacs::core::tags::patient_name);
288
289 // Patient name must be different after anonymization
290 CHECK(orig_name != anon_name);
291
292 // Anonymized file must still be valid DICOM (modality preserved)
293 CHECK_FALSE(get_tag(anonymized, kcenon::pacs::core::tags::modality).empty());
294}
constexpr dicom_tag modality
Modality.
constexpr dicom_tag patient_name
Patient's Name.
@ empty
Z - Replace with zero-length value.

References kcenon::pacs::core::tags::modality, and kcenon::pacs::core::tags::patient_name.

◆ TEST_CASE() [2/5]

TEST_CASE ( "JSON round-trip preserves metadata" ,
"" [cli][roundtrip] )

Definition at line 161 of file test_cli_roundtrip.cpp.

161 {
162 temp_dir tmp;
163 auto src = test_data_dir / "ct_minimal.dcm";
164 auto json_path = tmp.path / "ct.json";
165 auto dcm_path = tmp.path / "roundtrip.dcm";
166
167 REQUIRE(fs::exists(src));
168
169 // DCM -> JSON
170 auto to_json = run_cli("dcm_to_json",
171 {src.string(), json_path.string(), "--pretty", "--no-pixel"});
172 INFO("dcm_to_json output: " << to_json.output);
173 REQUIRE(to_json.exit_code == 0);
174 REQUIRE(fs::exists(json_path));
175
176 // JSON -> DCM
177 auto to_dcm = run_cli("json_to_dcm",
178 {json_path.string(), dcm_path.string()});
179 INFO("json_to_dcm output: " << to_dcm.output);
180 REQUIRE(to_dcm.exit_code == 0);
181 REQUIRE(fs::exists(dcm_path));
182
183 // Compare tags
184 auto original = read_dicom(src);
185 auto roundtrip = read_dicom(dcm_path);
186
187 CHECK(get_tag(original, kcenon::pacs::core::tags::patient_name)
188 == get_tag(roundtrip, kcenon::pacs::core::tags::patient_name));
189 CHECK(get_tag(original, kcenon::pacs::core::tags::patient_id)
190 == get_tag(roundtrip, kcenon::pacs::core::tags::patient_id));
191 CHECK(get_tag(original, kcenon::pacs::core::tags::modality)
192 == get_tag(roundtrip, kcenon::pacs::core::tags::modality));
193 CHECK(get_tag(original, kcenon::pacs::core::tags::study_date)
194 == get_tag(roundtrip, kcenon::pacs::core::tags::study_date));
195}
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag study_date
Study Date.

References kcenon::pacs::core::tags::modality, kcenon::pacs::core::tags::patient_id, kcenon::pacs::core::tags::patient_name, and kcenon::pacs::core::tags::study_date.

◆ TEST_CASE() [3/5]

TEST_CASE ( "Tag modification updates values correctly" ,
"" [cli][roundtrip] )

Definition at line 296 of file test_cli_roundtrip.cpp.

296 {
297 temp_dir tmp;
298 auto src = test_data_dir / "ct_minimal.dcm";
299 auto modified_path = tmp.path / "modified.dcm";
300
301 REQUIRE(fs::exists(src));
302
303 const std::string new_name = "ROUNDTRIP^TEST";
304
305 // Modify PatientName
306 auto result = run_cli("dcm_modify",
307 {"-i", "(0010,0010)=" + new_name, "-o", modified_path.string(),
308 src.string()});
309 INFO("dcm_modify output: " << result.output);
310 REQUIRE(result.exit_code == 0);
311 REQUIRE(fs::exists(modified_path));
312
313 // Verify modification
314 auto modified = read_dicom(modified_path);
315 CHECK(get_tag(modified, kcenon::pacs::core::tags::patient_name) == new_name);
316
317 // Other tags should be unchanged
318 auto original = read_dicom(src);
319 CHECK(get_tag(original, kcenon::pacs::core::tags::patient_id)
320 == get_tag(modified, kcenon::pacs::core::tags::patient_id));
321 CHECK(get_tag(original, kcenon::pacs::core::tags::modality)
322 == get_tag(modified, kcenon::pacs::core::tags::modality));
323}
@ modified
Study modified on both sides.

References kcenon::pacs::core::tags::modality, kcenon::pacs::core::tags::patient_id, and kcenon::pacs::core::tags::patient_name.

◆ TEST_CASE() [4/5]

TEST_CASE ( "Transfer Syntax conversion preserves tags" ,
"" [cli][roundtrip] )

Definition at line 231 of file test_cli_roundtrip.cpp.

231 {
232 temp_dir tmp;
233 auto src = test_data_dir / "ct_minimal.dcm";
234 auto explicit_path = tmp.path / "explicit.dcm";
235 auto implicit_path = tmp.path / "implicit.dcm";
236
237 REQUIRE(fs::exists(src));
238
239 // Convert to Explicit VR LE
240 auto to_explicit = run_cli("dcm_conv",
241 {src.string(), explicit_path.string(), "--explicit"});
242 INFO("dcm_conv --explicit output: " << to_explicit.output);
243 REQUIRE(to_explicit.exit_code == 0);
244 REQUIRE(fs::exists(explicit_path));
245
246 // Convert back to Implicit VR LE
247 auto to_implicit = run_cli("dcm_conv",
248 {explicit_path.string(), implicit_path.string(), "--implicit"});
249 INFO("dcm_conv --implicit output: " << to_implicit.output);
250 REQUIRE(to_implicit.exit_code == 0);
251 REQUIRE(fs::exists(implicit_path));
252
253 // Verify both outputs are valid DICOM with preserved tags
254 auto original = read_dicom(src);
255 auto explicit_file = read_dicom(explicit_path);
256 auto implicit_file = read_dicom(implicit_path);
257
258 CHECK(get_tag(original, kcenon::pacs::core::tags::patient_name)
259 == get_tag(explicit_file, kcenon::pacs::core::tags::patient_name));
260 CHECK(get_tag(original, kcenon::pacs::core::tags::patient_name)
261 == get_tag(implicit_file, kcenon::pacs::core::tags::patient_name));
262 CHECK(get_tag(original, kcenon::pacs::core::tags::modality)
263 == get_tag(implicit_file, kcenon::pacs::core::tags::modality));
264 CHECK(get_tag(original, kcenon::pacs::core::tags::patient_id)
265 == get_tag(implicit_file, kcenon::pacs::core::tags::patient_id));
266}

References kcenon::pacs::core::tags::modality, kcenon::pacs::core::tags::patient_id, and kcenon::pacs::core::tags::patient_name.

◆ TEST_CASE() [5/5]

TEST_CASE ( "XML round-trip preserves metadata" ,
"" [cli][roundtrip] )

Definition at line 197 of file test_cli_roundtrip.cpp.

197 {
198 temp_dir tmp;
199 auto src = test_data_dir / "mr_minimal.dcm";
200 auto xml_path = tmp.path / "mr.xml";
201 auto dcm_path = tmp.path / "roundtrip.dcm";
202
203 REQUIRE(fs::exists(src));
204
205 // DCM -> XML
206 auto to_xml = run_cli("dcm_to_xml",
207 {src.string(), xml_path.string(), "--no-pixel"});
208 INFO("dcm_to_xml output: " << to_xml.output);
209 REQUIRE(to_xml.exit_code == 0);
210 REQUIRE(fs::exists(xml_path));
211
212 // XML -> DCM
213 auto to_dcm = run_cli("xml_to_dcm",
214 {xml_path.string(), dcm_path.string()});
215 INFO("xml_to_dcm output: " << to_dcm.output);
216 REQUIRE(to_dcm.exit_code == 0);
217 REQUIRE(fs::exists(dcm_path));
218
219 // Compare tags
220 auto original = read_dicom(src);
221 auto roundtrip = read_dicom(dcm_path);
222
223 CHECK(get_tag(original, kcenon::pacs::core::tags::patient_name)
224 == get_tag(roundtrip, kcenon::pacs::core::tags::patient_name));
225 CHECK(get_tag(original, kcenon::pacs::core::tags::patient_id)
226 == get_tag(roundtrip, kcenon::pacs::core::tags::patient_id));
227 CHECK(get_tag(original, kcenon::pacs::core::tags::modality)
228 == get_tag(roundtrip, kcenon::pacs::core::tags::modality));
229}

References kcenon::pacs::core::tags::modality, kcenon::pacs::core::tags::patient_id, and kcenon::pacs::core::tags::patient_name.