# HG changeset patch # User Alain Mazy # Date 1725555257 -7200 # Node ID db7cf82a881bd542a4a1b2cda6a057c74c031771 # Parent 85c1447fa86b6e00cee86f23f115202d3ce377f5# Parent 72f186c739d051603a166de489ff946d9926b2b0 merged large-queries -> default diff -r 85c1447fa86b -r db7cf82a881b NewTests/Authorization/test_authorization.py --- a/NewTests/Authorization/test_authorization.py Wed Sep 04 12:57:56 2024 +0200 +++ b/NewTests/Authorization/test_authorization.py Thu Sep 05 18:54:17 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}") diff -r 85c1447fa86b -r db7cf82a881b NewTests/Housekeeper/test_housekeeper2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/NewTests/Housekeeper/test_housekeeper2.py Thu Sep 05 18:54:17 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 diff -r 85c1447fa86b -r db7cf82a881b NewTests/README --- a/NewTests/README Wed Sep 04 12:57:56 2024 +0200 +++ b/NewTests/README Thu Sep 05 18:54:17 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, diff -r 85c1447fa86b -r db7cf82a881b Tests/Tests.py --- a/Tests/Tests.py Wed Sep 04 12:57:56 2024 +0200 +++ b/Tests/Tests.py Thu Sep 05 18:54:17 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')