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

C-FIND (Query) interoperability tests with DCMTK. More...

#include <catch2/catch_test_macros.hpp>
#include "dcmtk_tool.h"
#include "test_fixtures.h"
#include "kcenon/pacs/core/dicom_dataset.h"
#include "kcenon/pacs/core/dicom_tag_constants.h"
#include "kcenon/pacs/encoding/transfer_syntax.h"
#include "kcenon/pacs/network/dimse/dimse_message.h"
#include "kcenon/pacs/services/query_scp.h"
#include "kcenon/pacs/services/storage_scp.h"
#include "kcenon/pacs/services/verification_scp.h"
#include <future>
#include <mutex>
#include <set>
#include <thread>
#include <vector>
Include dependency graph for test_dcmtk_find.cpp:

Go to the source code of this file.

Functions

 TEST_CASE ("C-FIND: pacs_system SCP with DCMTK findscu", "[dcmtk][interop][find]")
 
 TEST_CASE ("C-FIND: pacs_system SCU query operations", "[dcmtk][interop][find]")
 
 TEST_CASE ("C-FIND: Concurrent query operations", "[dcmtk][interop][find][stress]")
 
 TEST_CASE ("C-FIND: Connection error handling", "[dcmtk][interop][find][error]")
 
 TEST_CASE ("C-FIND: Query level variations", "[dcmtk][interop][find][levels]")
 
 TEST_CASE ("C-FIND: Special character handling", "[dcmtk][interop][find][special]")
 

Detailed Description

C-FIND (Query) interoperability tests with DCMTK.

Tests bidirectional C-FIND compatibility between pacs_system and DCMTK:

  • Scenario A: pacs_system SCP <- DCMTK findscu
  • Scenario B: DCMTK SCP <- pacs_system SCU (using association)
See also
Issue #453 - C-FIND Interoperability Test with DCMTK
Issue #449 - DCMTK Interoperability Test Automation Epic

Definition in file test_dcmtk_find.cpp.

Function Documentation

◆ TEST_CASE() [1/6]

TEST_CASE ( "C-FIND: Concurrent query operations" ,
"" [dcmtk][interop][find][stress] )

Definition at line 592 of file test_dcmtk_find.cpp.

592 : Concurrent query operations", "[dcmtk][interop][find][stress]") {
593 if (!dcmtk_tool::is_available()) {
594 SKIP("DCMTK not installed");
595 }
596
597 // Skip if real TCP DICOM connections are not supported yet
598 if (!supports_real_tcp_dicom()) {
599 SKIP("pacs_system does not support real TCP DICOM connections yet");
600 }
601
602 auto port = find_available_port();
603 const std::string ae_title = "STRESS_FIND_SCP";
604
605 test_query_database db;
606 for (int i = 0; i < 10; ++i) {
607 db.add_study(create_test_study(
608 "PAT" + std::to_string(i),
609 "PATIENT^" + std::to_string(i),
610 "20240" + std::to_string(100 + i),
611 "CT"));
612 }
613
614 test_server server(port, ae_title);
615
616 auto query_scp_ptr = std::make_shared<query_scp>();
617 query_scp_ptr->set_handler([&db](
618 query_level /* level */,
619 const dicom_dataset& query_keys,
620 const std::string& /* calling_ae */) -> std::vector<dicom_dataset> {
621 return db.find_studies(query_keys);
622 });
623 server.register_service(query_scp_ptr);
624 REQUIRE(server.start());
625
626 REQUIRE(wait_for([&]() {
627 return process_launcher::is_port_listening(port);
628 }, server_ready_timeout()));
629
630 SECTION("3 concurrent DCMTK findscu clients") {
631 constexpr int num_clients = 3;
632 std::vector<std::future<dcmtk_result>> futures;
633 futures.reserve(num_clients);
634
635 for (int i = 0; i < num_clients; ++i) {
636 futures.push_back(std::async(std::launch::async, [&, i]() {
637 std::vector<std::pair<std::string, std::string>> keys = {
638 {"PatientID", ""},
639 {"StudyInstanceUID", ""}
640 };
641 return dcmtk_tool::findscu(
642 "localhost", port, ae_title, "STUDY", keys,
643 "CLIENT_" + std::to_string(i));
644 }));
645 }
646
647 // All should succeed
648 for (size_t i = 0; i < futures.size(); ++i) {
649 auto result = futures[i].get();
650
651 INFO("Client " << i << " stdout: " << result.stdout_output);
652 INFO("Client " << i << " stderr: " << result.stderr_output);
653
654 REQUIRE(result.success());
655 }
656 }
657
658 SECTION("3 concurrent pacs_system SCU clients") {
659 constexpr int num_clients = 3;
660 std::vector<std::future<bool>> futures;
661 futures.reserve(num_clients);
662
663 for (int i = 0; i < num_clients; ++i) {
664 futures.push_back(std::async(std::launch::async, [&, i]() {
665 auto connect_result = test_association::connect(
666 "localhost", port, ae_title,
667 "PACS_CLIENT_" + std::to_string(i),
668 {std::string(study_root_find_sop_class_uid)});
669
670 if (!connect_result.is_ok()) return false;
671
672 auto& assoc = connect_result.value();
673 auto context_id = assoc.accepted_context_id(study_root_find_sop_class_uid);
674 if (!context_id.has_value()) return false;
675
676 dicom_dataset query_keys;
677 query_keys.set_string(tags::query_retrieve_level, vr_type::CS, "STUDY");
678 query_keys.set_string(tags::patient_id, vr_type::LO, "");
679 query_keys.set_string(tags::study_instance_uid, vr_type::UI, "");
680
681 auto find_rq = make_c_find_rq(1, study_root_find_sop_class_uid);
682 find_rq.set_dataset(std::move(query_keys));
683 auto send_result = assoc.send_dimse(*context_id, find_rq);
684 if (!send_result.is_ok()) return false;
685
686 // Receive all responses
687 while (true) {
688 auto recv_result = assoc.receive_dimse();
689 if (!recv_result.is_ok()) return false;
690
691 auto& [recv_ctx, rsp] = recv_result.value();
692 if (rsp.status() == status_success) break;
693 if (rsp.status() != status_pending) return false;
694 }
695
696 return true;
697 }));
698 }
699
700 // All should succeed
701 for (size_t i = 0; i < futures.size(); ++i) {
702 bool success = futures[i].get();
703 INFO("Client " << i);
704 REQUIRE(success);
705 }
706 }
707}
@ find
C-FIND query request/response.
const atna_coded_value query
Query (110112)

References kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::dcmtk_tool::findscu(), kcenon::pacs::integration_test::dcmtk_tool::is_available(), kcenon::pacs::integration_test::process_launcher::is_port_listening(), kcenon::pacs::network::dimse::make_c_find_rq(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::server_ready_timeout(), kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::integration_test::test_server::start(), kcenon::pacs::services::study_root_find_sop_class_uid, kcenon::pacs::network::success, kcenon::pacs::integration_test::supports_real_tcp_dicom(), and kcenon::pacs::integration_test::wait_for().

Here is the call graph for this function:

◆ TEST_CASE() [2/6]

TEST_CASE ( "C-FIND: Connection error handling" ,
"" [dcmtk][interop][find][error] )

Definition at line 713 of file test_dcmtk_find.cpp.

713 : Connection error handling", "[dcmtk][interop][find][error]") {
714 if (!dcmtk_tool::is_available()) {
715 SKIP("DCMTK not installed");
716 }
717
718 // Skip if real TCP DICOM connections are not supported yet
719 if (!supports_real_tcp_dicom()) {
720 SKIP("pacs_system does not support real TCP DICOM connections yet");
721 }
722
723 SECTION("findscu to non-existent server fails gracefully") {
724 auto port = find_available_port();
725
726 // Ensure nothing is listening
727 REQUIRE_FALSE(process_launcher::is_port_listening(port));
728
729 std::vector<std::pair<std::string, std::string>> keys = {
730 {"PatientID", ""},
731 {"StudyInstanceUID", ""}
732 };
733
734 auto result = dcmtk_tool::findscu(
735 "localhost", port, "NONEXISTENT", "STUDY", keys,
736 "FINDSCU", std::chrono::seconds{5});
737
738 // Should fail - no server listening
739 REQUIRE_FALSE(result.success());
740 }
741
742 SECTION("pacs_system SCU to non-existent server fails gracefully") {
743 // Use a high port range that's less likely to have conflicts
744 auto port = find_available_port(59000);
745
746 // Wait briefly and re-verify the port is truly free
747 std::this_thread::sleep_for(std::chrono::milliseconds{100});
748
749 // Ensure nothing is listening
750 if (process_launcher::is_port_listening(port)) {
751 SKIP("Port " + std::to_string(port) + " is unexpectedly in use");
752 }
753
754 auto connect_result = test_association::connect(
755 "localhost", port, "NONEXISTENT", "PACS_SCU",
756 {std::string(study_root_find_sop_class_uid)});
757
758 // Should fail - no server listening
759 REQUIRE_FALSE(connect_result.is_ok());
760 }
761}
@ error
Node returned an error.

References kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::dcmtk_tool::findscu(), kcenon::pacs::integration_test::dcmtk_tool::is_available(), kcenon::pacs::integration_test::process_launcher::is_port_listening(), kcenon::pacs::services::study_root_find_sop_class_uid, and kcenon::pacs::integration_test::supports_real_tcp_dicom().

Here is the call graph for this function:

◆ TEST_CASE() [3/6]

TEST_CASE ( "C-FIND: pacs_system SCP with DCMTK findscu" ,
"" [dcmtk][interop][find] )

Definition at line 251 of file test_dcmtk_find.cpp.

251 : pacs_system SCP with DCMTK findscu", "[dcmtk][interop][find]") {
252 if (!dcmtk_tool::is_available()) {
253 SKIP("DCMTK not installed - skipping interoperability test");
254 }
255
256 // Skip if real TCP DICOM connections are not supported yet
257 if (!supports_real_tcp_dicom()) {
258 SKIP("pacs_system does not support real TCP DICOM connections yet");
259 }
260
261 // Setup: Start pacs_system query server with test data
262 auto port = find_available_port();
263 const std::string ae_title = "PACS_FIND_SCP";
264
265 test_query_database db;
266
267 // Populate test data
268 db.add_study(create_test_study("PAT001", "SMITH^JOHN", "20231201", "CT"));
269 db.add_study(create_test_study("PAT002", "SMITH^JANE", "20231215", "MR"));
270 db.add_study(create_test_study("PAT003", "JONES^WILLIAM", "20240101", "CT"));
271 db.add_study(create_test_study("PAT004", "BROWN^ALICE", "20240115", "XA"));
272
273 test_server server(port, ae_title);
274
275 // Register query SCP with handler
276 auto query_scp_ptr = std::make_shared<query_scp>();
277 query_scp_ptr->set_handler([&db](
278 query_level level,
279 const dicom_dataset& query_keys,
280 const std::string& /* calling_ae */) -> std::vector<dicom_dataset> {
281
282 if (level == query_level::patient) {
283 return db.find_patients(query_keys);
284 }
285 return db.find_studies(query_keys);
286 });
287 server.register_service(query_scp_ptr);
288 server.register_service(std::make_shared<verification_scp>());
289
290 REQUIRE(server.start());
291
292 // Wait for server to be ready
293 REQUIRE(wait_for([&]() {
294 return process_launcher::is_port_listening(port);
295 }, server_ready_timeout()));
296
297 SECTION("Basic study-level query succeeds") {
298 std::vector<std::pair<std::string, std::string>> keys = {
299 {"PatientID", ""},
300 {"PatientName", ""},
301 {"StudyDate", ""},
302 {"StudyInstanceUID", ""}
303 };
304
305 auto result = dcmtk_tool::findscu(
306 "localhost", port, ae_title, "STUDY", keys);
307
308 INFO("stdout: " << result.stdout_output);
309 INFO("stderr: " << result.stderr_output);
310
311 REQUIRE(result.success());
312 }
313
314 SECTION("Query with PatientID filter") {
315 std::vector<std::pair<std::string, std::string>> keys = {
316 {"PatientID", "PAT001"},
317 {"PatientName", ""},
318 {"StudyInstanceUID", ""}
319 };
320
321 auto result = dcmtk_tool::findscu(
322 "localhost", port, ae_title, "STUDY", keys);
323
324 INFO("stdout: " << result.stdout_output);
325 INFO("stderr: " << result.stderr_output);
326
327 REQUIRE(result.success());
328 }
329
330 SECTION("Query with wildcard PatientName") {
331 std::vector<std::pair<std::string, std::string>> keys = {
332 {"PatientName", "SMITH*"},
333 {"PatientID", ""},
334 {"StudyInstanceUID", ""}
335 };
336
337 auto result = dcmtk_tool::findscu(
338 "localhost", port, ae_title, "STUDY", keys);
339
340 INFO("stdout: " << result.stdout_output);
341 INFO("stderr: " << result.stderr_output);
342
343 REQUIRE(result.success());
344 }
345
346 SECTION("Query with date range") {
347 std::vector<std::pair<std::string, std::string>> keys = {
348 {"StudyDate", "20231201-20231231"},
349 {"PatientID", ""},
350 {"PatientName", ""},
351 {"StudyInstanceUID", ""}
352 };
353
354 auto result = dcmtk_tool::findscu(
355 "localhost", port, ae_title, "STUDY", keys);
356
357 INFO("stdout: " << result.stdout_output);
358 INFO("stderr: " << result.stderr_output);
359
360 REQUIRE(result.success());
361 }
362
363 SECTION("Query with no matching results") {
364 std::vector<std::pair<std::string, std::string>> keys = {
365 {"PatientID", "NONEXISTENT"},
366 {"StudyInstanceUID", ""}
367 };
368
369 auto result = dcmtk_tool::findscu(
370 "localhost", port, ae_title, "STUDY", keys);
371
372 INFO("stdout: " << result.stdout_output);
373 INFO("stderr: " << result.stderr_output);
374
375 // Should succeed even with no results (returns 0 matches)
376 REQUIRE(result.success());
377 }
378
379 SECTION("Multiple consecutive queries") {
380 for (int i = 0; i < 3; ++i) {
381 std::vector<std::pair<std::string, std::string>> keys = {
382 {"PatientID", ""},
383 {"StudyInstanceUID", ""}
384 };
385
386 auto result = dcmtk_tool::findscu(
387 "localhost", port, ae_title, "STUDY", keys,
388 "FINDSCU_" + std::to_string(i));
389
390 INFO("Iteration: " << i);
391 INFO("stdout: " << result.stdout_output);
392 INFO("stderr: " << result.stderr_output);
393
394 REQUIRE(result.success());
395 }
396 }
397
398 SECTION("Patient-level query") {
399 std::vector<std::pair<std::string, std::string>> keys = {
400 {"PatientID", ""},
401 {"PatientName", ""},
402 {"PatientBirthDate", ""}
403 };
404
405 auto result = dcmtk_tool::findscu(
406 "localhost", port, ae_title, "PATIENT", keys);
407
408 INFO("stdout: " << result.stdout_output);
409 INFO("stderr: " << result.stderr_output);
410
411 REQUIRE(result.success());
412 }
413}
@ study
Study level - query study information.

References kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::dcmtk_tool::findscu(), kcenon::pacs::integration_test::dcmtk_tool::is_available(), kcenon::pacs::integration_test::process_launcher::is_port_listening(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::server_ready_timeout(), kcenon::pacs::integration_test::test_server::start(), kcenon::pacs::integration_test::supports_real_tcp_dicom(), and kcenon::pacs::integration_test::wait_for().

Here is the call graph for this function:

◆ TEST_CASE() [4/6]

TEST_CASE ( "C-FIND: pacs_system SCU query operations" ,
"" [dcmtk][interop][find] )

Definition at line 419 of file test_dcmtk_find.cpp.

419 : pacs_system SCU query operations", "[dcmtk][interop][find]") {
420 if (!dcmtk_tool::is_available()) {
421 SKIP("DCMTK not installed - skipping interoperability test");
422 }
423
424 // Skip if real TCP DICOM connections are not supported yet
425 if (!supports_real_tcp_dicom()) {
426 SKIP("pacs_system does not support real TCP DICOM connections yet");
427 }
428
429 // Setup: Start pacs_system server with query capability
430 auto port = find_available_port();
431 const std::string ae_title = "QUERY_SCP";
432
433 test_query_database db;
434 db.add_study(create_test_study("PAT001", "DOE^JOHN", "20240101", "CT"));
435 db.add_study(create_test_study("PAT002", "DOE^JANE", "20240115", "MR"));
436
437 test_server server(port, ae_title);
438
439 auto query_scp_ptr = std::make_shared<query_scp>();
440 query_scp_ptr->set_handler([&db](
441 query_level level,
442 const dicom_dataset& query_keys,
443 const std::string& /* calling_ae */) -> std::vector<dicom_dataset> {
444
445 if (level == query_level::patient) {
446 return db.find_patients(query_keys);
447 }
448 return db.find_studies(query_keys);
449 });
450 server.register_service(query_scp_ptr);
451 REQUIRE(server.start());
452
453 REQUIRE(wait_for([&]() {
454 return process_launcher::is_port_listening(port);
455 }, server_ready_timeout()));
456
457 SECTION("pacs_system SCU sends C-FIND successfully") {
458 auto connect_result = test_association::connect(
459 "localhost", port, ae_title, "PACS_SCU",
460 {std::string(study_root_find_sop_class_uid)});
461
462 REQUIRE(connect_result.is_ok());
463 auto& assoc = connect_result.value();
464
465 REQUIRE(assoc.has_accepted_context(study_root_find_sop_class_uid));
466 auto context_id = assoc.accepted_context_id(study_root_find_sop_class_uid);
467 REQUIRE(context_id.has_value());
468
469 // Create query keys
470 dicom_dataset query_keys;
471 query_keys.set_string(tags::query_retrieve_level, vr_type::CS, "STUDY");
472 query_keys.set_string(tags::patient_id, vr_type::LO, "");
473 query_keys.set_string(tags::patient_name, vr_type::PN, "");
474 query_keys.set_string(tags::study_instance_uid, vr_type::UI, "");
475
476 // Send C-FIND request
477 auto find_rq = make_c_find_rq(1, study_root_find_sop_class_uid);
478 find_rq.set_dataset(std::move(query_keys));
479 auto send_result = assoc.send_dimse(*context_id, find_rq);
480 REQUIRE(send_result.is_ok());
481
482 // Receive responses
483 std::vector<dicom_dataset> results;
484 while (true) {
485 auto recv_result = assoc.receive_dimse();
486 REQUIRE(recv_result.is_ok());
487
488 auto& [recv_ctx, rsp] = recv_result.value();
489 if (rsp.status() == status_success) {
490 break; // Final response
491 } else if (rsp.status() == status_pending) {
492 if (rsp.has_dataset()) {
493 auto ds_result = rsp.dataset();
494 if (ds_result.is_ok()) {
495 results.push_back(ds_result.value().get());
496 }
497 }
498 } else {
499 FAIL("Unexpected C-FIND response status");
500 }
501 }
502
503 // Should get 2 study results
504 REQUIRE(results.size() == 2);
505 }
506
507 SECTION("Query with specific PatientName filter") {
508 auto connect_result = test_association::connect(
509 "localhost", port, ae_title, "PACS_SCU",
510 {std::string(study_root_find_sop_class_uid)});
511
512 REQUIRE(connect_result.is_ok());
513 auto& assoc = connect_result.value();
514
515 auto context_id = assoc.accepted_context_id(study_root_find_sop_class_uid);
516 REQUIRE(context_id.has_value());
517
518 dicom_dataset query_keys;
519 query_keys.set_string(tags::query_retrieve_level, vr_type::CS, "STUDY");
520 query_keys.set_string(tags::patient_name, vr_type::PN, "DOE^JOHN");
521 query_keys.set_string(tags::study_instance_uid, vr_type::UI, "");
522
523 auto find_rq = make_c_find_rq(1, study_root_find_sop_class_uid);
524 find_rq.set_dataset(std::move(query_keys));
525 auto send_result = assoc.send_dimse(*context_id, find_rq);
526 REQUIRE(send_result.is_ok());
527
528 std::vector<dicom_dataset> results;
529 while (true) {
530 auto recv_result = assoc.receive_dimse();
531 REQUIRE(recv_result.is_ok());
532
533 auto& [recv_ctx, rsp] = recv_result.value();
534 if (rsp.status() == status_success) break;
535 if (rsp.status() == status_pending && rsp.has_dataset()) {
536 auto ds_result = rsp.dataset();
537 if (ds_result.is_ok()) {
538 results.push_back(ds_result.value().get());
539 }
540 }
541 }
542
543 // Should find exactly 1 matching study
544 REQUIRE(results.size() == 1);
545 REQUIRE(results[0].get_string(tags::patient_name) == "DOE^JOHN");
546 }
547
548 SECTION("Query with wildcard pattern") {
549 auto connect_result = test_association::connect(
550 "localhost", port, ae_title, "PACS_SCU",
551 {std::string(study_root_find_sop_class_uid)});
552
553 REQUIRE(connect_result.is_ok());
554 auto& assoc = connect_result.value();
555
556 auto context_id = assoc.accepted_context_id(study_root_find_sop_class_uid);
557 REQUIRE(context_id.has_value());
558
559 dicom_dataset query_keys;
560 query_keys.set_string(tags::query_retrieve_level, vr_type::CS, "STUDY");
561 query_keys.set_string(tags::patient_name, vr_type::PN, "DOE*");
562 query_keys.set_string(tags::study_instance_uid, vr_type::UI, "");
563
564 auto find_rq = make_c_find_rq(1, study_root_find_sop_class_uid);
565 find_rq.set_dataset(std::move(query_keys));
566 (void)assoc.send_dimse(*context_id, find_rq);
567
568 std::vector<dicom_dataset> results;
569 while (true) {
570 auto recv_result = assoc.receive_dimse();
571 if (recv_result.is_err()) break;
572
573 auto& [recv_ctx, rsp] = recv_result.value();
574 if (rsp.status() == status_success) break;
575 if (rsp.status() == status_pending && rsp.has_dataset()) {
576 auto ds_result = rsp.dataset();
577 if (ds_result.is_ok()) {
578 results.push_back(ds_result.value().get());
579 }
580 }
581 }
582
583 // Both DOE^JOHN and DOE^JANE should match
584 REQUIRE(results.size() == 2);
585 }
586}
constexpr dicom_tag status
Status.

References kcenon::pacs::integration_test::test_association::connect(), kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::dcmtk_tool::is_available(), kcenon::pacs::integration_test::process_launcher::is_port_listening(), kcenon::pacs::network::dimse::make_c_find_rq(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::server_ready_timeout(), kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::integration_test::test_server::start(), kcenon::pacs::services::study_root_find_sop_class_uid, kcenon::pacs::integration_test::supports_real_tcp_dicom(), and kcenon::pacs::integration_test::wait_for().

Here is the call graph for this function:

◆ TEST_CASE() [5/6]

TEST_CASE ( "C-FIND: Query level variations" ,
"" [dcmtk][interop][find][levels] )

Definition at line 767 of file test_dcmtk_find.cpp.

767 : Query level variations", "[dcmtk][interop][find][levels]") {
768 if (!dcmtk_tool::is_available()) {
769 SKIP("DCMTK not installed");
770 }
771
772 // Skip if real TCP DICOM connections are not supported yet
773 if (!supports_real_tcp_dicom()) {
774 SKIP("pacs_system does not support real TCP DICOM connections yet");
775 }
776
777 auto port = find_available_port();
778 const std::string ae_title = "LEVEL_TEST_SCP";
779
780 test_query_database db;
781 db.add_study(create_test_study("PAT001", "TEST^PATIENT", "20240101", "CT"));
782
783 test_server server(port, ae_title);
784
785 auto query_scp_ptr = std::make_shared<query_scp>();
786 query_scp_ptr->set_handler([&db](
787 query_level level,
788 const dicom_dataset& query_keys,
789 const std::string& /* calling_ae */) -> std::vector<dicom_dataset> {
790
791 if (level == query_level::patient) {
792 return db.find_patients(query_keys);
793 }
794 return db.find_studies(query_keys);
795 });
796 server.register_service(query_scp_ptr);
797 REQUIRE(server.start());
798
799 REQUIRE(wait_for([&]() {
800 return process_launcher::is_port_listening(port);
801 }, server_ready_timeout()));
802
803 SECTION("STUDY level query") {
804 std::vector<std::pair<std::string, std::string>> keys = {
805 {"PatientID", "PAT001"},
806 {"StudyInstanceUID", ""}
807 };
808
809 auto result = dcmtk_tool::findscu(
810 "localhost", port, ae_title, "STUDY", keys);
811
812 REQUIRE(result.success());
813 }
814
815 SECTION("PATIENT level query") {
816 std::vector<std::pair<std::string, std::string>> keys = {
817 {"PatientID", ""},
818 {"PatientName", ""}
819 };
820
821 auto result = dcmtk_tool::findscu(
822 "localhost", port, ae_title, "PATIENT", keys);
823
824 REQUIRE(result.success());
825 }
826}

References kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::dcmtk_tool::findscu(), kcenon::pacs::integration_test::dcmtk_tool::is_available(), kcenon::pacs::integration_test::process_launcher::is_port_listening(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::server_ready_timeout(), kcenon::pacs::integration_test::test_server::start(), kcenon::pacs::integration_test::supports_real_tcp_dicom(), and kcenon::pacs::integration_test::wait_for().

Here is the call graph for this function:

◆ TEST_CASE() [6/6]

TEST_CASE ( "C-FIND: Special character handling" ,
"" [dcmtk][interop][find][special] )

Definition at line 832 of file test_dcmtk_find.cpp.

832 : Special character handling", "[dcmtk][interop][find][special]") {
833 if (!dcmtk_tool::is_available()) {
834 SKIP("DCMTK not installed");
835 }
836
837 // Skip if real TCP DICOM connections are not supported yet
838 if (!supports_real_tcp_dicom()) {
839 SKIP("pacs_system does not support real TCP DICOM connections yet");
840 }
841
842 auto port = find_available_port();
843 const std::string ae_title = "SPECIAL_CHAR_SCP";
844
845 test_query_database db;
846 // Add study with special characters in patient name
847 db.add_study(create_test_study("PAT001", "O'BRIEN^MARY", "20240101", "CT"));
848 db.add_study(create_test_study("PAT002", "MÜLLER^HANS", "20240101", "MR"));
849
850 test_server server(port, ae_title);
851
852 auto query_scp_ptr = std::make_shared<query_scp>();
853 query_scp_ptr->set_handler([&db](
854 query_level /* level */,
855 const dicom_dataset& query_keys,
856 const std::string& /* calling_ae */) -> std::vector<dicom_dataset> {
857 return db.find_studies(query_keys);
858 });
859 server.register_service(query_scp_ptr);
860 REQUIRE(server.start());
861
862 REQUIRE(wait_for([&]() {
863 return process_launcher::is_port_listening(port);
864 }, server_ready_timeout()));
865
866 SECTION("Query with special characters in response") {
867 std::vector<std::pair<std::string, std::string>> keys = {
868 {"PatientID", "PAT001"},
869 {"PatientName", ""},
870 {"StudyInstanceUID", ""}
871 };
872
873 auto result = dcmtk_tool::findscu(
874 "localhost", port, ae_title, "STUDY", keys);
875
876 INFO("stdout: " << result.stdout_output);
877 INFO("stderr: " << result.stderr_output);
878
879 // Should succeed despite special characters
880 REQUIRE(result.success());
881 }
882}

References kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration_test::dcmtk_tool::findscu(), kcenon::pacs::integration_test::dcmtk_tool::is_available(), kcenon::pacs::integration_test::process_launcher::is_port_listening(), kcenon::pacs::integration_test::test_server::register_service(), kcenon::pacs::integration_test::server_ready_timeout(), kcenon::pacs::integration_test::test_server::start(), kcenon::pacs::integration_test::supports_real_tcp_dicom(), and kcenon::pacs::integration_test::wait_for().

Here is the call graph for this function: