changeset 678:72f186c739d0 large-queries

merged default -> large-queries
author Alain Mazy <am@orthanc.team>
date Thu, 05 Sep 2024 18:49:09 +0200
parents 599ff47f609d (diff) 85c1447fa86b (current diff)
children db7cf82a881b 60cdae616275
files NewTests/Authorization/test_authorization.py Tests/Tests.py
diffstat 4 files changed, 241 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/NewTests/Authorization/test_authorization.py	Wed Sep 04 12:57:56 2024 +0200
+++ b/NewTests/Authorization/test_authorization.py	Thu Sep 05 18:49:09 2024 +0200
@@ -259,10 +259,19 @@
             i = o.get_binary(f"dicom-web/studies/{self.label_a_study_dicom_id}/series/{self.label_a_series_dicom_id}/instances/{self.label_a_instance_dicom_id}")
             self.assert_is_forbidden(lambda: o.get_binary(f"dicom-web/studies/{self.label_b_study_dicom_id}/series/{self.label_b_series_dicom_id}/instances/{self.label_b_instance_dicom_id}"))
 
+            i = o.get_json(f"dicom-web/studies/{self.label_a_study_dicom_id}/series?includefield=00080021%2C00080031%2C0008103E%2C00200011")
+            self.assert_is_forbidden(lambda: o.get_json(f"dicom-web/studies/{self.label_b_study_dicom_id}/series?includefield=00080021%2C00080031%2C0008103E%2C00200011"))
+
             o.get_json(f"/system")
             o.get_json(f"/plugins")
             o.get_json(f"/plugins/dicom-web")
 
+        if o_admin.is_plugin_version_at_least("authorization", 0, 7, 2):
+            # also check that this works with the admin user !
+            i = o_admin.get_json(f"dicom-web/studies/{self.label_a_study_dicom_id}/instances")
+            i = o_admin.get_binary(f"dicom-web/studies/{self.label_a_study_dicom_id}/series/{self.label_a_series_dicom_id}/instances/{self.label_a_instance_dicom_id}")
+            i = o_admin.get_json(f"dicom-web/studies/{self.label_a_study_dicom_id}/series?includefield=00080021%2C00080031%2C0008103E%2C00200011")
+
 
     def test_uploader_a(self):
         o_admin = OrthancApiClient(self.o._root_url, headers={"user-token-key": "token-admin"})
@@ -331,6 +340,7 @@
         o.get_binary(f"dicom-web/studies/{self.label_a_study_dicom_id}")
         o.get_json(f"dicom-web/studies/{self.label_a_study_dicom_id}/metadata")
         o.get_json(f"dicom-web/studies/{self.label_a_study_dicom_id}/series")
+        o.get_json(f"dicom-web/studies/{self.label_a_study_dicom_id}/series?includefield=00080021%2C00080031%2C0008103E%2C00200011")
         o.get_binary(f"dicom-web/studies/{self.label_a_study_dicom_id}/series/{self.label_a_series_dicom_id}")
         o.get_json(f"dicom-web/studies/{self.label_a_study_dicom_id}/series/{self.label_a_series_dicom_id}/metadata")
         o.get_binary(f"dicom-web/studies/{self.label_a_study_dicom_id}/series/{self.label_a_series_dicom_id}/instances/{self.label_a_instance_dicom_id}")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NewTests/Housekeeper/test_housekeeper2.py	Thu Sep 05 18:49:09 2024 +0200
@@ -0,0 +1,136 @@
+import unittest
+import time
+from helpers import OrthancTestCase, Helpers
+
+from orthanc_api_client import OrthancApiClient, generate_test_dicom_file
+
+import pathlib
+here = pathlib.Path(__file__).parent.resolve()
+
+
+class TestHousekeeper2(OrthancTestCase):
+
+    @classmethod
+    def prepare(cls):
+        print('-------------- preparing TestHousekeeper2 tests')
+
+        cls.clear_storage(storage_name="housekeeper2")
+
+        cls.launch_orthanc_to_prepare_db(
+            config_name="housekeeper2_preparation",
+            storage_name="housekeeper2",
+            config={
+                "Housekeeper": {
+                    "Enable": False
+                }
+            },
+            plugins=Helpers.plugins
+        )
+
+        # upload a study and keep track of data before housekeeper runs
+        cls.o.upload_folder(here / "../../Database/Knix/Loc")
+
+        cls.instance_before, cls.series_before, cls.study_before, cls.patient_before, cls.instance_metadata_before = cls.get_infos()
+
+        cls.kill_orthanc()
+        time.sleep(3)
+
+        # generate config for orthanc-under-tests (change StorageCompression and add ExtraMainDicomTags)
+        config_path = cls.generate_configuration(
+            config_name="housekeeper2_under_test",
+            storage_name="housekeeper2",
+            config={
+                "IngestTranscoding": "1.2.840.10008.1.2.4.80",
+                "ExtraMainDicomTags": {
+                    "Patient" : ["PatientWeight", "PatientAge"],
+                    "Study": ["NameOfPhysiciansReadingStudy"],
+                    "Series": ["ScanOptions"],
+                    "Instance": ["Rows", "Columns", "DerivationCodeSequence"]
+                },
+                "Housekeeper": {
+                    "Enable": True
+                },
+                "KeepAliveTimeout": 2
+            },
+            plugins=Helpers.plugins
+        )
+
+        print('-------------- prepared TestHousekeeper2 tests')
+        if Helpers.break_after_preparation:
+            print(f"++++ It is now time to start your Orthanc under tests with configuration file '{config_path}' +++++")
+            input("Press Enter to continue")
+        else:
+            print('-------------- launching TestHousekeeper2 tests')
+            cls.launch_orthanc_under_tests(
+                config_path=config_path,
+                config_name="housekeeper2_under_test",
+                storage_name="housekeeper2",
+                plugins=Helpers.plugins,
+                enable_verbose=False
+            )
+
+        print('-------------- waiting for orthanc-under-tests to be available')
+        cls.o.wait_started()
+        
+        completed = False
+        while not completed:
+            print('-------------- waiting for housekeeper2 to finish processing')
+            time.sleep(1)
+            housekeeper_status = cls.o.get_json("plugins/housekeeper/status")
+            completed = (housekeeper_status["LastProcessedConfiguration"]["IngestTranscoding"] == "1.2.840.10008.1.2.4.80") \
+                        and (housekeeper_status["LastChangeToProcess"] == housekeeper_status["LastProcessedChange"])
+
+
+    @classmethod
+    def get_infos(cls):
+        instance_id = cls.o.lookup(
+            needle="1.2.840.113619.2.176.2025.1499492.7040.1171286241.704",
+            filter="Instance"
+        )[0]
+
+        instance_info = cls.o.get_json(endpoint=f"instances/{instance_id}")
+        
+        series_id = instance_info["ParentSeries"]
+        series_info = cls.o.get_json(endpoint=f"series/{series_id}")
+        
+        study_id = series_info["ParentStudy"]
+        study_info = cls.o.get_json(endpoint=f"studies/{study_id}")
+
+        patient_id = study_info["ParentPatient"]
+        patient_info = cls.o.get_json(endpoint=f"patients/{patient_id}")
+
+        instance_metadata = cls.o.get_json(endpoint=f"instances/{instance_id}/metadata?expand")
+        return instance_info, series_info, study_info, patient_info, instance_metadata
+
+
+
+    def test_before_after_reconstruction(self):
+        if self.o.is_orthanc_version_at_least(1, 12, 4):
+            # make sure it has run once !
+            housekeeper_status = self.o.get_json("housekeeper/status")
+            self.assertIsNotNone(housekeeper_status["LastTimeStarted"])
+
+            instance_after, series_after, study_after, patient_after, instance_metadata_after = self.get_infos()
+
+            # extra tags were not in DB before reconstruction
+            self.assertNotIn("Rows", self.instance_before["MainDicomTags"])
+            self.assertNotIn("DerivationCodeSequence", self.instance_before["MainDicomTags"])
+            self.assertNotIn("ScanOptions", self.series_before["MainDicomTags"])
+            self.assertNotIn("NameOfPhysiciansReadingStudy", self.study_before["MainDicomTags"])
+            self.assertNotIn("PatientWeight", self.patient_before["MainDicomTags"])
+
+            # extra tags are in  DB after reconstruction
+            self.assertIn("Rows", instance_after["MainDicomTags"])
+            self.assertIn("DerivationCodeSequence", instance_after["MainDicomTags"])
+            self.assertIn("ScanOptions", series_after["MainDicomTags"])
+            self.assertIn("NameOfPhysiciansReadingStudy", study_after["MainDicomTags"])
+            self.assertIn("PatientWeight", patient_after["MainDicomTags"])
+
+            # instance has been transcoded and we can still access the tags
+            self.assertTrue(self.instance_metadata_before["TransferSyntax"] != instance_metadata_after["TransferSyntax"]) 
+            self.o.instances.get_tags(instance_after["ID"])
+
+            # the reception date and other metadata have not been updated
+            self.assertEqual(self.instance_metadata_before["ReceptionDate"], instance_metadata_after["ReceptionDate"]) 
+            self.assertEqual(self.instance_metadata_before["Origin"], instance_metadata_after["Origin"]) 
+            self.assertNotEqual(self.instance_before["FileUuid"], instance_after["FileUuid"]) # files ID have changed
--- a/NewTests/README	Wed Sep 04 12:57:56 2024 +0200
+++ b/NewTests/README	Thu Sep 05 18:49:09 2024 +0200
@@ -56,6 +56,12 @@
                          --plugin=/home/alain/o/build/orthanc/libHousekeeper.so \
                          --break_after_preparation
 
+python3 NewTests/main.py --pattern=Housekeeper.test_housekeeper2.TestHousekeeper2.test_before_after_reconstruction \
+                         --orthanc_under_tests_exe=/home/alain/o/build/orthanc/Orthanc \
+                         --orthanc_under_tests_http_port=8043 \
+                         --plugin=/home/alain/o/build/orthanc/libHousekeeper.so \
+                         --break_after_preparation
+
 The test script will:
 - generate 2 configuration file in the `configurations` folder,
 - start your local Orthanc version to prepare the db with one of the configuration file, 
--- a/Tests/Tests.py	Wed Sep 04 12:57:56 2024 +0200
+++ b/Tests/Tests.py	Thu Sep 05 18:49:09 2024 +0200
@@ -627,6 +627,95 @@
         self.assertEqual(0, completed)
 
 
+    def test_changes_extended(self):
+        if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and DoGet(_REMOTE, '/system').get("Capabilities").get("HasExtendedChanges"):
+            # Check emptiness
+            c = DoGet(_REMOTE, '/changes')
+            self.assertEqual(0, len(c['Changes']))
+            #self.assertEqual(0, c['Last'])   # Not true anymore for Orthanc >= 1.5.2
+            self.assertTrue(c['Done'])
+            c = DoGet(_REMOTE, '/changes?last')
+            self.assertEqual(0, len(c['Changes']))
+            #self.assertEqual(0, c['Last'])   # Not true anymore for Orthanc >= 1.5.2
+            self.assertTrue(c['Done'])
+
+            # Add 1 instance
+            i = UploadInstance(_REMOTE, 'Brainix/Flair/IM-0001-0001.dcm')['ID']
+            c = DoGet(_REMOTE, '/changes')
+            begin = c['Last']
+            self.assertEqual(4, len(c['Changes']))
+            self.assertTrue(c['Done'])
+            self.assertEqual(c['Changes'][-1]['Seq'], c['Last'])
+
+            # Check the order in which the creation events are reported
+            self.assertEqual(c['Changes'][0]['ChangeType'], 'NewInstance')
+            self.assertEqual(c['Changes'][1]['ChangeType'], 'NewSeries')
+            self.assertEqual(c['Changes'][2]['ChangeType'], 'NewStudy')
+            self.assertEqual(c['Changes'][3]['ChangeType'], 'NewPatient')
+
+            c = DoGet(_REMOTE, '/changes?type=NewInstance')
+            self.assertEqual(1, len(c['Changes']))
+            self.assertEqual(begin-3, c['Last'])
+
+            c = DoGet(_REMOTE, '/changes?type=NewPatient')
+            self.assertEqual(1, len(c['Changes']))
+            self.assertEqual(begin, c['Last'])
+
+            UploadFolder(_REMOTE, 'Knee/T1')
+            UploadFolder(_REMOTE, 'Knee/T2')
+
+            # Request the 1000 first NewInstance changes  -> all 50 shall be reported
+            c = DoGet(_REMOTE, '/changes', { 'type': 'NewInstance', 'since' : begin, 'limit' : 1000 })
+            self.assertEqual(50, len(c['Changes']))
+            self.assertLess(begin, c['Changes'][0]['Seq'])
+            self.assertTrue(c['Done'])   #w e have got them all so it's DONE
+            lastFrom1000NewInstances = c['Last']
+            firstFrom1000NewInstances = c['First']
+            self.assertLess(firstFrom1000NewInstances, lastFrom1000NewInstances)
+
+            # Only the 10 first NewInstance changes  -> only 10 shall be reported
+            c = DoGet(_REMOTE, '/changes', { 'type': 'NewInstance', 'since' : begin, 'limit' : 10 })
+            self.assertEqual(10, len(c['Changes']))
+            self.assertFalse(c['Done'])
+            lastFrom10firstNewInstances = c['Last']
+            firstFrom10firstNewInstances = c['First']
+            self.assertLess(firstFrom10firstNewInstances, lastFrom10firstNewInstances)
+            self.assertLess(lastFrom10firstNewInstances, lastFrom1000NewInstances)
+            self.assertEqual(firstFrom10firstNewInstances, firstFrom1000NewInstances)
+
+            # between begin and begin+10 with a max of 10 and a filter -> less than 10 NewInstance since there are other changes in this range
+            c = DoGet(_REMOTE, '/changes', { 'type': 'NewInstance', 'since' : begin, 'to': begin+10, 'limit' : 10 })
+            self.assertLess(len(c['Changes']), 10)
+            self.assertTrue(c['Done'])  # we have received ALL NewInstance that are between since and to so we consider it's done
+            lastFrom10SubsetNewInstances = c['Last']
+            firstFrom10SubsetNewInstances = c['First']
+            self.assertLess(firstFrom10SubsetNewInstances, lastFrom10SubsetNewInstances)
+            self.assertLess(lastFrom10SubsetNewInstances, lastFrom10firstNewInstances)
+            self.assertEqual(firstFrom10SubsetNewInstances, firstFrom1000NewInstances)
+
+            # test with only 'to' -> all 50 NewInstance shall be reported
+            c = DoGet(_REMOTE, '/changes', { 'type': 'NewInstance', 'to': lastFrom1000NewInstances, 'limit' : 50 })
+            self.assertEqual(lastFrom1000NewInstances, c['Changes'][-1]['Seq'])
+            self.assertEqual(50, len(c['Changes']))
+            self.assertFalse(c['Done'])  # Done can not be used when working in reverse direction
+            lastFrom50Reverse = c['Last']
+            firstFrom50Reverse = c['First']
+            self.assertLess(firstFrom50Reverse, lastFrom50Reverse)
+            self.assertEqual(lastFrom50Reverse, lastFrom1000NewInstances)
+            self.assertEqual(firstFrom50Reverse, firstFrom1000NewInstances)
+
+            # test with only 'to' and limit to 10 NewInstance changes
+            c = DoGet(_REMOTE, '/changes', { 'type': 'NewInstance', 'to': lastFrom1000NewInstances, 'limit' : 10 })
+            self.assertEqual(lastFrom1000NewInstances, c['Changes'][-1]['Seq'])
+            self.assertEqual(10, len(c['Changes']))
+            self.assertFalse(c['Done'])  # Done can not be used when working in reverse direction
+            lastFrom10Reverse = c['Last']
+            firstFrom10Reverse = c['First']
+            self.assertLess(firstFrom10Reverse, lastFrom10Reverse)
+            self.assertEqual(lastFrom10Reverse, lastFrom1000NewInstances)
+            self.assertLessEqual(firstFrom50Reverse, firstFrom10Reverse)
+
+
     def test_archive(self):
         UploadInstance(_REMOTE, 'Knee/T1/IM-0001-0001.dcm')
         UploadInstance(_REMOTE, 'Knee/T2/IM-0001-0001.dcm')