From c8d749f3c423989dbf04faf213e5069269f26aff Mon Sep 17 00:00:00 2001 From: Sebastian Diaz Date: Tue, 21 Jan 2025 09:34:26 +0100 Subject: [PATCH] enhance order submission integration tests (#4121) enhance order submission integration tests --- cg/services/orders/storing/service.py | 2 +- tests/conftest.py | 2 +- .../order_to_submit_fixtures.py | 31 +++-- ...d_api_fixtures.py => services_fixtures.py} | 10 +- .../orders_fixtures/store_fixtures.py | 65 +---------- .../orders_fixtures/store_service_fixtures.py | 48 +++++--- .../fixtures/cgweb_orders/FAIL_balsamic.json | 61 ++++++++++ tests/fixtures/cgweb_orders/sarscov2.json | 4 +- .../store_service/test_fastq_order_service.py | 84 +++++++++----- .../test_generic_order_store_service.py | 109 ++++++++++++++---- .../test_metagenome_store_service.py | 45 ++++++-- .../test_microbial_store_order_service.py | 102 ++++++++-------- .../test_pacbio_order_service.py | 28 ++--- .../test_pool_order_store_service.py | 43 +++---- .../orders/store_service/test_registry.py | 70 +++++++++++ .../orders/submitter/test_order_submitter.py | 69 +++++++---- .../test_validation_service.py | 21 ++++ 17 files changed, 518 insertions(+), 276 deletions(-) rename tests/fixture_plugins/orders_fixtures/{services_and_api_fixtures.py => services_fixtures.py} (78%) create mode 100644 tests/fixtures/cgweb_orders/FAIL_balsamic.json create mode 100644 tests/services/orders/store_service/test_registry.py create mode 100644 tests/services/orders/validation_service/test_validation_service.py diff --git a/cg/services/orders/storing/service.py b/cg/services/orders/storing/service.py index b4a5c675e4..4c8b4fe6f5 100644 --- a/cg/services/orders/storing/service.py +++ b/cg/services/orders/storing/service.py @@ -22,7 +22,7 @@ def store_order(self, order: Order): pass @staticmethod - def _fill_in_sample_ids(samples: list[Sample], lims_map: dict): + def _fill_in_sample_ids(samples: list[Sample], lims_map: dict) -> None: """Fill in LIMS sample ids.""" for sample in samples: LOG.debug(f"{sample.name}: link sample to LIMS") diff --git a/tests/conftest.py b/tests/conftest.py index c11a794ed8..b268bf2696 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -123,7 +123,7 @@ "tests.fixture_plugins.orders_fixtures.order_form_fixtures", "tests.fixture_plugins.orders_fixtures.order_to_submit_fixtures", "tests.fixture_plugins.orders_fixtures.order_fixtures", - "tests.fixture_plugins.orders_fixtures.services_and_api_fixtures", + "tests.fixture_plugins.orders_fixtures.services_fixtures", "tests.fixture_plugins.orders_fixtures.store_fixtures", "tests.fixture_plugins.orders_fixtures.store_service_fixtures", "tests.fixture_plugins.pacbio_fixtures.context_fixtures", diff --git a/tests/fixture_plugins/orders_fixtures/order_to_submit_fixtures.py b/tests/fixture_plugins/orders_fixtures/order_to_submit_fixtures.py index 80e4a3356d..554887ec87 100644 --- a/tests/fixture_plugins/orders_fixtures/order_to_submit_fixtures.py +++ b/tests/fixture_plugins/orders_fixtures/order_to_submit_fixtures.py @@ -7,10 +7,12 @@ from cg.constants.constants import FileFormat from cg.io.controller import ReadFile +# Valid orders + @pytest.fixture(scope="session") def balsamic_order_to_submit(cgweb_orders_dir: Path) -> dict: - """Load an example cancer order.""" + """Load an example Balsamic order.""" return ReadFile.get_content_from_file( file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "balsamic.json") ) @@ -18,7 +20,7 @@ def balsamic_order_to_submit(cgweb_orders_dir: Path) -> dict: @pytest.fixture(scope="session") def fastq_order_to_submit(cgweb_orders_dir) -> dict: - """Load an example FASTQ order.""" + """Load an example Fastq order.""" return ReadFile.get_content_from_file( file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "fastq.json") ) @@ -34,7 +36,7 @@ def fluffy_order_to_submit(cgweb_orders_dir: Path) -> dict: @pytest.fixture(scope="session") def metagenome_order_to_submit(cgweb_orders_dir: Path) -> dict: - """Load an example metagenome order.""" + """Load an example Metagenome order.""" return ReadFile.get_content_from_file( file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "metagenome.json") ) @@ -42,7 +44,7 @@ def metagenome_order_to_submit(cgweb_orders_dir: Path) -> dict: @pytest.fixture(scope="session") def microbial_fastq_order_to_submit(cgweb_orders_dir: Path) -> dict: - """Load an example microbial order.""" + """Load an example Microbial fastq order.""" return ReadFile.get_content_from_file( file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "microbial_fastq.json") ) @@ -50,7 +52,7 @@ def microbial_fastq_order_to_submit(cgweb_orders_dir: Path) -> dict: @pytest.fixture(scope="session") def microbial_order_to_submit(cgweb_orders_dir: Path) -> dict: - """Load an example microbial order.""" + """Load an example Microsalt order.""" return ReadFile.get_content_from_file( file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "microsalt.json") ) @@ -66,7 +68,7 @@ def mip_dna_order_to_submit(cgweb_orders_dir: Path) -> dict: @pytest.fixture(scope="session") def mip_rna_order_to_submit(cgweb_orders_dir: Path) -> dict: - """Load an example RNA order.""" + """Load an example MIP-RNA order.""" return ReadFile.get_content_from_file( file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "mip_rna.json") ) @@ -90,7 +92,7 @@ def rml_order_to_submit(cgweb_orders_dir: Path) -> dict: @pytest.fixture(scope="session") def rnafusion_order_to_submit(cgweb_orders_dir: Path) -> dict: - """Load an example RNA order.""" + """Load an example RNA Fusion order.""" return ReadFile.get_content_from_file( file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "rnafusion.json") ) @@ -98,7 +100,7 @@ def rnafusion_order_to_submit(cgweb_orders_dir: Path) -> dict: @pytest.fixture(scope="session") def sarscov2_order_to_submit(cgweb_orders_dir: Path) -> dict: - """Load an example sarscov2 order.""" + """Load an example Mutant order.""" return ReadFile.get_content_from_file( file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "sarscov2.json") ) @@ -114,7 +116,18 @@ def taxprofiler_order_to_submit(cgweb_orders_dir: Path) -> dict: @pytest.fixture(scope="session") def tomte_order_to_submit(cgweb_orders_dir: Path) -> dict: - """Load an example TOMTE order.""" + """Load an example Tomte order.""" return ReadFile.get_content_from_file( file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "tomte.json") ) + + +# Invalid orders + + +@pytest.fixture(scope="session") +def invalid_balsamic_order_to_submit(cgweb_orders_dir: Path) -> dict: + """Load an invalid example Balsamic order.""" + return ReadFile.get_content_from_file( + file_format=FileFormat.JSON, file_path=Path(cgweb_orders_dir, "FAIL_balsamic.json") + ) diff --git a/tests/fixture_plugins/orders_fixtures/services_and_api_fixtures.py b/tests/fixture_plugins/orders_fixtures/services_fixtures.py similarity index 78% rename from tests/fixture_plugins/orders_fixtures/services_and_api_fixtures.py rename to tests/fixture_plugins/orders_fixtures/services_fixtures.py index 0b725db605..e6de30a4d8 100644 --- a/tests/fixture_plugins/orders_fixtures/services_and_api_fixtures.py +++ b/tests/fixture_plugins/orders_fixtures/services_fixtures.py @@ -18,8 +18,8 @@ def freshdesk_client() -> FreshdeskClient: @pytest.fixture -def order_validation_service(store_with_all_test_applications: Store) -> OrderValidationService: - return OrderValidationService(store_with_all_test_applications) +def order_validation_service(store_to_submit_and_validate_orders: Store) -> OrderValidationService: + return OrderValidationService(store_to_submit_and_validate_orders) @pytest.fixture(scope="function") @@ -42,6 +42,8 @@ def ticket_handler(store: Store, freshdesk_client: FreshdeskClient) -> TicketHan @pytest.fixture def storing_service_registry( - store_with_all_test_applications: Store, lims_api: MockLimsAPI + store_to_submit_and_validate_orders: Store, lims_api: MockLimsAPI ) -> StoringServiceRegistry: - return setup_storing_service_registry(lims=lims_api, status_db=store_with_all_test_applications) + return setup_storing_service_registry( + lims=lims_api, status_db=store_to_submit_and_validate_orders + ) diff --git a/tests/fixture_plugins/orders_fixtures/store_fixtures.py b/tests/fixture_plugins/orders_fixtures/store_fixtures.py index 652aa03dfd..e5f86d461e 100644 --- a/tests/fixture_plugins/orders_fixtures/store_fixtures.py +++ b/tests/fixture_plugins/orders_fixtures/store_fixtures.py @@ -2,74 +2,17 @@ import pytest -from cg.constants.sequencing import SeqLibraryPrepCategory from cg.models.orders.constants import OrderType from cg.services.orders.storing.constants import MAF_ORDER_ID -from cg.services.orders.validation.workflows.balsamic.models.order import BalsamicOrder -from cg.services.orders.validation.workflows.mip_dna.models.order import MipDnaOrder -from cg.store.models import Application, ApplicationVersion, Customer, Order +from cg.store.models import ApplicationVersion, Customer, Order from cg.store.store import Store from tests.store_helpers import StoreHelpers @pytest.fixture -def balsamic_submit_store( - base_store: Store, balsamic_order: BalsamicOrder, helpers: StoreHelpers +def store_to_submit_and_validate_orders( + store: Store, helpers: StoreHelpers, customer_id: str ) -> Store: - for _, _, sample in balsamic_order.enumerated_new_samples: - if not base_store.get_application_by_tag(sample.application): - application_version = helpers.ensure_application_version( - store=base_store, application_tag=sample.application - ) - base_store.session.add(application_version) - base_store.session.commit() - return base_store - - -@pytest.fixture -def store_with_rml_applications(store: Store, helpers: StoreHelpers) -> Store: - app_tags: list[str] = ["RMLP15R100", "RMLP15R200", "RMLP15R400", "RMLP15R500"] - for tag in app_tags: - helpers.ensure_application_version( - store=store, - application_tag=tag, - prep_category=SeqLibraryPrepCategory.READY_MADE_LIBRARY, - ) - helpers.ensure_customer(store=store, customer_id="cust000") - return store - - -@pytest.fixture -def metagenome_storing_store(base_store: Store, helpers: StoreHelpers) -> Store: - metagenome_application: Application = helpers.ensure_application_version( - store=base_store, application_tag="METPCFR030" - ).application - metagenome_application.order_types = [OrderType.METAGENOME] - metagenome_application: Application = helpers.ensure_application_version( - store=base_store, application_tag="METWPFR030" - ).application - metagenome_application.order_types = [OrderType.METAGENOME] - customer = base_store.get_customer_by_internal_id("cust000") - helpers.ensure_user(store=base_store, customer=customer) - return base_store - - -@pytest.fixture -def mip_dna_submit_store( - base_store: Store, mip_dna_order: MipDnaOrder, helpers: StoreHelpers -) -> Store: - for _, _, sample in mip_dna_order.enumerated_new_samples: - if not base_store.get_application_by_tag(sample.application): - application_version = helpers.ensure_application_version( - store=base_store, application_tag=sample.application - ) - base_store.session.add(application_version) - base_store.session.commit() - return base_store - - -@pytest.fixture -def store_with_all_test_applications(store: Store, helpers: StoreHelpers) -> Store: app_tags: dict[str, list[OrderType]] = { "PANKTTR100": [OrderType.BALSAMIC], "WGSPCFC030": [OrderType.FASTQ, OrderType.MIP_DNA], @@ -92,7 +35,7 @@ def store_with_all_test_applications(store: Store, helpers: StoreHelpers) -> Sto store=store, application_tag=tag ) application_version.application.order_types = orders - customer: Customer = helpers.ensure_customer(store=store, customer_id="cust000") + customer: Customer = helpers.ensure_customer(store=store, customer_id=customer_id) helpers.ensure_user(store=store, customer=customer) helpers.ensure_panel(store=store, panel_abbreviation="AID") helpers.ensure_panel(store=store, panel_abbreviation="Ataxi") diff --git a/tests/fixture_plugins/orders_fixtures/store_service_fixtures.py b/tests/fixture_plugins/orders_fixtures/store_service_fixtures.py index 2c78fdd8cd..9a7b86c9aa 100644 --- a/tests/fixture_plugins/orders_fixtures/store_service_fixtures.py +++ b/tests/fixture_plugins/orders_fixtures/store_service_fixtures.py @@ -16,57 +16,67 @@ from cg.services.orders.storing.implementations.pool_order_service import StorePoolOrderService from cg.store.store import Store from tests.mocks.limsmock import MockLimsAPI -from tests.store_helpers import StoreHelpers @pytest.fixture -def store_generic_order_service(base_store: Store, lims_api: MockLimsAPI) -> StoreCaseOrderService: - return StoreCaseOrderService(status_db=base_store, lims_service=OrderLimsService(lims_api)) +def store_generic_order_service( + store_to_submit_and_validate_orders: Store, lims_api: MockLimsAPI +) -> StoreCaseOrderService: + return StoreCaseOrderService( + status_db=store_to_submit_and_validate_orders, lims_service=OrderLimsService(lims_api) + ) @pytest.fixture def store_pool_order_service( - store_with_rml_applications: Store, lims_api: MockLimsAPI + store_to_submit_and_validate_orders: Store, lims_api: MockLimsAPI ) -> StorePoolOrderService: return StorePoolOrderService( - status_db=store_with_rml_applications, lims_service=OrderLimsService(lims_api) + status_db=store_to_submit_and_validate_orders, lims_service=OrderLimsService(lims_api) ) @pytest.fixture -def store_fastq_order_service(base_store: Store, lims_api: MockLimsAPI) -> StoreFastqOrderService: - return StoreFastqOrderService(status_db=base_store, lims_service=OrderLimsService(lims_api)) +def store_fastq_order_service( + store_to_submit_and_validate_orders: Store, lims_api: MockLimsAPI +) -> StoreFastqOrderService: + return StoreFastqOrderService( + status_db=store_to_submit_and_validate_orders, lims_service=OrderLimsService(lims_api) + ) @pytest.fixture -def store_pacbio_order_service(base_store: Store, lims_api: MockLimsAPI) -> StorePacBioOrderService: - return StorePacBioOrderService(status_db=base_store, lims_service=OrderLimsService(lims_api)) +def store_pacbio_order_service( + store_to_submit_and_validate_orders: Store, lims_api: MockLimsAPI +) -> StorePacBioOrderService: + return StorePacBioOrderService( + status_db=store_to_submit_and_validate_orders, lims_service=OrderLimsService(lims_api) + ) @pytest.fixture def store_metagenome_order_service( - metagenome_storing_store: Store, lims_api: MockLimsAPI, helpers: StoreHelpers + store_to_submit_and_validate_orders: Store, lims_api: MockLimsAPI ) -> StoreMetagenomeOrderService: return StoreMetagenomeOrderService( - status_db=metagenome_storing_store, lims_service=OrderLimsService(lims_api) + status_db=store_to_submit_and_validate_orders, lims_service=OrderLimsService(lims_api) ) @pytest.fixture def store_microbial_order_service( - base_store: Store, lims_api: MockLimsAPI, helpers: StoreHelpers + store_to_submit_and_validate_orders: Store, + lims_api: MockLimsAPI, ) -> StoreMicrobialOrderService: - helpers.ensure_application_version(store=base_store, application_tag="MWRNXTR003") - helpers.ensure_application_version(store=base_store, application_tag="MWXNXTR003") - helpers.ensure_application_version(store=base_store, application_tag="VWGNXTR001") - - return StoreMicrobialOrderService(status_db=base_store, lims_service=OrderLimsService(lims_api)) + return StoreMicrobialOrderService( + status_db=store_to_submit_and_validate_orders, lims_service=OrderLimsService(lims_api) + ) @pytest.fixture def store_microbial_fastq_order_service( - base_store: Store, lims_api: MockLimsAPI + store_to_submit_and_validate_orders: Store, lims_api: MockLimsAPI ) -> StoreMicrobialFastqOrderService: return StoreMicrobialFastqOrderService( - status_db=base_store, lims_service=OrderLimsService(lims_api) + status_db=store_to_submit_and_validate_orders, lims_service=OrderLimsService(lims_api) ) diff --git a/tests/fixtures/cgweb_orders/FAIL_balsamic.json b/tests/fixtures/cgweb_orders/FAIL_balsamic.json new file mode 100644 index 0000000000..f75ae2ddcd --- /dev/null +++ b/tests/fixtures/cgweb_orders/FAIL_balsamic.json @@ -0,0 +1,61 @@ +{ + "cases": [ + { + "cohorts": null, + "name": "B", + "panels": null, + "priority": "FAIL", + "samples": [ + { + "age_at_sampling": "17.2", + "application": "PANKTTR100", + "capture_kit": "GMCKsolid", + "comment": "This is a sample comment", + "concentration_ng_ul": null, + "container": "96 well plate", + "container_name": "BalsamicPlate", + "control": "FAIL", + "data_analysis": null, + "data_delivery": null, + "elution_buffer": "Tris-HCl", + "family_name": null, + "father": null, + "formalin_fixation_time": "15", + "mother": null, + "name": "BalsamicSample", + "phenotype_groups": [ + "PhGroup" + ], + "phenotype_terms": [ + "PhTerm" + ], + "post_formalin_fixation_time": "3", + "priority": null, + "quantity": null, + "reference_genome": null, + "require_qc_ok": false, + "sex": "male", + "source": "cytology (FFPE)", + "source_comment": null, + "status": null, + "subject_id": "Subject1", + "tissue_block_size": "large", + "tumour": true, + "tumour_purity": "13", + "volume": 42, + "well_position": "A:1" + } + ], + "synopsis": "A synopsis" + } + ], + "comment": null, + "customer": "cust000", + "data_analysis": "balsamic", + "data_delivery": null, + "delivery_type": "analysis-scout", + "name": "BalsamicOrder", + "project_type": "balsamic", + "ticket": null, + "user_id": 1 +} \ No newline at end of file diff --git a/tests/fixtures/cgweb_orders/sarscov2.json b/tests/fixtures/cgweb_orders/sarscov2.json index 4def8c2d39..72c765d01a 100644 --- a/tests/fixtures/cgweb_orders/sarscov2.json +++ b/tests/fixtures/cgweb_orders/sarscov2.json @@ -35,7 +35,7 @@ "internal_id": null, "lab_code": null, "mother": null, - "name": "sarscov2sample1", + "name": "control-positive", "organism": "SARS-CoV-2", "organism_other": "", "original_lab": "Karolinska University Hospital Solna", @@ -94,7 +94,7 @@ "internal_id": null, "lab_code": null, "mother": null, - "name": "sarscov2sample2", + "name": "control-negative", "organism": "SARS-CoV-2", "organism_other": "", "original_lab": "Synlab Medilab", diff --git a/tests/services/orders/store_service/test_fastq_order_service.py b/tests/services/orders/store_service/test_fastq_order_service.py index e34ed4b7d4..d42b1cd186 100644 --- a/tests/services/orders/store_service/test_fastq_order_service.py +++ b/tests/services/orders/store_service/test_fastq_order_service.py @@ -1,3 +1,9 @@ +""" +Module to test the store_order_data_in_status_db method of the StoreFastqOrderService class. +The function store_order_data_in_status_db is never expected to fail, as its input order should +have always been validated before calling the function. +""" + from cg.constants import DataDelivery, Workflow from cg.constants.sequencing import SeqLibraryPrepCategory from cg.services.orders.storing.constants import MAF_ORDER_ID @@ -5,29 +11,37 @@ from cg.services.orders.validation.workflows.fastq.models.order import FastqOrder from cg.store.models import Application, Case, Order, Sample from cg.store.store import Store +from tests.store_helpers import StoreHelpers def test_store_order_data_in_status_db( - base_store: Store, + store_to_submit_and_validate_orders: Store, store_fastq_order_service: StoreFastqOrderService, fastq_order: FastqOrder, + ticket_id_as_int: int, ): - """Test that a Fastq order is stored in the database.""" - # GIVEN a basic store with no samples and a fastq order - assert not base_store._get_query(table=Sample).first() - assert base_store._get_query(table=Case).count() == 0 + """Test that a Fastq order with two WGS samples, one being tumour, is stored in the database.""" + + # GIVEN a fastq order with two WGS samples, the first one being a tumour sample - # WHEN storing an order with two WGS samples, the first one being a tumour sample + # GIVEN a basic store with no samples nor cases + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert store_to_submit_and_validate_orders._get_query(table=Case).count() == 0 + + # WHEN storing the order new_samples: list[Sample] = store_fastq_order_service.store_order_data_in_status_db(fastq_order) + # THEN it should store the order + assert store_to_submit_and_validate_orders.get_order_by_ticket_id(ticket_id_as_int) + # THEN it should store the samples - assert len(new_samples) == 2 - assert len(base_store._get_query(table=Sample).all()) == 2 + db_samples: list[Sample] = store_to_submit_and_validate_orders._get_query(table=Sample).all() + assert set(new_samples) == set(db_samples) # THEN it should create one case for the analysis and one MAF case - cases: list[Case] = base_store._get_query(table=Case).all() + cases: list[Case] = store_to_submit_and_validate_orders._get_query(table=Case).all() assert len(cases) == 2 - assert len(new_samples[0].links) == 2 + assert len(db_samples[0].links) == 2 assert cases[0].data_analysis == Workflow.MIP_DNA assert cases[1].data_analysis == Workflow.RAW_DATA @@ -35,27 +49,29 @@ def test_store_order_data_in_status_db( assert cases[1].data_delivery in [DataDelivery.FASTQ, DataDelivery.NO_DELIVERY] # THEN the sample sex should be stored - assert new_samples[0].sex == "male" + assert db_samples[0].sex == "male" # THEN the MAF order should have one case linked to the tumour negative sample - maf_order: Order = base_store.get_order_by_id(MAF_ORDER_ID) + maf_order: Order = store_to_submit_and_validate_orders.get_order_by_id(MAF_ORDER_ID) maf_cases: list[Case] = maf_order.cases assert len(maf_cases) == 1 assert not maf_cases[0].samples[0].is_tumour def test_store_fastq_samples_non_tumour_wgs_to_mip_maf_case( - base_store: Store, fastq_order: FastqOrder, store_fastq_order_service: StoreFastqOrderService + store_to_submit_and_validate_orders: Store, + fastq_order: FastqOrder, + store_fastq_order_service: StoreFastqOrderService, ): """Test that a non-tumour WGS sample creates a MAF case with MIP as data analysis.""" - # GIVEN a basic store with no samples - assert not base_store._get_query(table=Sample).first() - assert base_store._get_query(table=Case).count() == 0 + # GIVEN a basic store with no samples nor cases + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert store_to_submit_and_validate_orders._get_query(table=Case).count() == 0 # GIVEN a fastq order with the first sample being a non-tumour WGS sample - base_store.get_application_by_tag(fastq_order.samples[0].application).prep_category = ( - SeqLibraryPrepCategory.WHOLE_GENOME_SEQUENCING - ) + store_to_submit_and_validate_orders.get_application_by_tag( + fastq_order.samples[0].application + ).prep_category = SeqLibraryPrepCategory.WHOLE_GENOME_SEQUENCING fastq_order.samples[0].tumour = False # WHEN storing the order @@ -69,19 +85,19 @@ def test_store_fastq_samples_non_tumour_wgs_to_mip_maf_case( def test_store_fastq_samples_tumour_wgs_to_fastq_no_maf_case( - base_store: Store, + store_to_submit_and_validate_orders: Store, fastq_order: FastqOrder, store_fastq_order_service: StoreFastqOrderService, ): """Test that a tumour WGS sample does not create MAF cases.""" # GIVEN a basic store with no samples - assert not base_store._get_query(table=Sample).first() - assert base_store._get_query(table=Case).count() == 0 + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert store_to_submit_and_validate_orders._get_query(table=Case).count() == 0 # GIVEN a fastq order with the second sample being a tumour WGS sample - base_store.get_application_by_tag(fastq_order.samples[0].application).prep_category = ( - SeqLibraryPrepCategory.WHOLE_GENOME_SEQUENCING - ) + store_to_submit_and_validate_orders.get_application_by_tag( + fastq_order.samples[0].application + ).prep_category = SeqLibraryPrepCategory.WHOLE_GENOME_SEQUENCING fastq_order.samples[1].tumour = True # WHEN storing the order @@ -95,20 +111,26 @@ def test_store_fastq_samples_tumour_wgs_to_fastq_no_maf_case( def test_store_fastq_samples_non_wgs_no_maf_case( - base_store: Store, + store_to_submit_and_validate_orders: Store, fastq_order: FastqOrder, store_fastq_order_service: StoreFastqOrderService, + helpers: StoreHelpers, ): """Test that an order with non-WGS samples creates no MAF cases.""" # GIVEN a basic store with no samples - assert not base_store._get_query(table=Sample).first() - assert base_store._get_query(table=Case).count() == 0 + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert store_to_submit_and_validate_orders._get_query(table=Case).count() == 0 - # GIVEN a fastq order with a non-WGS samples + # GIVEN that the store has application versions for the non-WGS workflow non_wgs_prep_category = SeqLibraryPrepCategory.WHOLE_EXOME_SEQUENCING - non_wgs_applications: Application = base_store._get_query(table=Application).filter( - Application.prep_category == non_wgs_prep_category + helpers.ensure_application_version( + store=store_to_submit_and_validate_orders, prep_category=non_wgs_prep_category ) + + # GIVEN a fastq order with a non-WGS samples + non_wgs_applications: Application = store_to_submit_and_validate_orders._get_query( + table=Application + ).filter(Application.prep_category == non_wgs_prep_category) assert non_wgs_applications for sample in fastq_order.samples: sample.application = non_wgs_applications[0].tag diff --git a/tests/services/orders/store_service/test_generic_order_store_service.py b/tests/services/orders/store_service/test_generic_order_store_service.py index 0c8e52f86f..efd9742ad4 100644 --- a/tests/services/orders/store_service/test_generic_order_store_service.py +++ b/tests/services/orders/store_service/test_generic_order_store_service.py @@ -1,29 +1,35 @@ -"""Module to test the StoreGenericOrderService class.""" +""" +Module to test the store_order_data_in_status_db method of the StoreGenericOrderService class. +The function store_order_data_in_status_db is never expected to fail, as its input order should +have always been validated before calling the function. +""" from cg.constants import DataDelivery, Priority, Workflow from cg.services.orders.storing.implementations.case_order_service import StoreCaseOrderService from cg.services.orders.validation.workflows.balsamic.models.order import BalsamicOrder from cg.services.orders.validation.workflows.mip_dna.models.order import MipDnaOrder from cg.services.orders.validation.workflows.mip_rna.models.order import MipRnaOrder -from cg.store.models import Sample +from cg.services.orders.validation.workflows.rna_fusion.models.order import RnaFusionOrder +from cg.services.orders.validation.workflows.tomte.models.order import TomteOrder +from cg.store.models import Case, Sample from cg.store.store import Store -def test_store_mip( - mip_dna_submit_store: Store, +def test_store_mip_order( + store_to_submit_and_validate_orders: Store, mip_dna_order: MipDnaOrder, store_generic_order_service: StoreCaseOrderService, ): - # GIVEN a basic store with no samples or nothing in it + scout order - assert not mip_dna_submit_store._get_query(table=Sample).first() - assert not mip_dna_submit_store.get_cases() + # GIVEN a basic store with no samples nor cases + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert not store_to_submit_and_validate_orders.get_cases() # WHEN storing the order - new_families = store_generic_order_service.store_order_data_in_status_db(mip_dna_order) + new_cases: list[Case] = store_generic_order_service.store_order_data_in_status_db(mip_dna_order) # THEN it should create and link samples and the case - assert len(new_families) == 2 - new_case = new_families[0] + assert len(new_cases) == 2 + new_case = new_cases[0] assert new_case.name == "MipCase1" assert set(new_case.panels) == {"AID"} assert new_case.priority_human == Priority.standard.name @@ -49,19 +55,19 @@ def test_store_mip( assert new_link.sample.subject_id == "Subject3" -def test_store_mip_rna( - base_store: Store, +def test_store_mip_rna_order( + store_to_submit_and_validate_orders: Store, mip_rna_order: MipRnaOrder, store_generic_order_service: StoreCaseOrderService, ): - # GIVEN a basic store with no samples or nothing in it + rna order + # GIVEN a basic store with no samples nor cases rna_application_tag = "RNAPOAR025" - assert not base_store._get_query(table=Sample).first() - assert not base_store.get_cases() - assert base_store.get_application_by_tag(tag=rna_application_tag) + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert not store_to_submit_and_validate_orders.get_cases() + assert store_to_submit_and_validate_orders.get_application_by_tag(tag=rna_application_tag) # WHEN storing a MIP-RNA order containing 1 case with 2 samples and 1 case with only 1 sample - new_cases = store_generic_order_service.store_order_data_in_status_db(mip_rna_order) + new_cases: list[Case] = store_generic_order_service.store_order_data_in_status_db(mip_rna_order) # THEN it should create and link samples and the casing assert len(new_cases) == 2 @@ -75,22 +81,25 @@ def test_store_mip_rna( assert new_link.sample.application_version.application.tag == rna_application_tag -def test_store_cancer_samples( - balsamic_submit_store: Store, +def test_store_balsamic_order( + store_to_submit_and_validate_orders: Store, balsamic_order: BalsamicOrder, store_generic_order_service: StoreCaseOrderService, ): + # GIVEN a Balsamic order - # GIVEN a basic store with no samples and a cancer order - assert not balsamic_submit_store._get_query(table=Sample).first() - assert not balsamic_submit_store.get_cases() + # GIVEN a store with no samples nor cases + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert not store_to_submit_and_validate_orders.get_cases() # WHEN storing the order - new_families = store_generic_order_service.store_order_data_in_status_db(balsamic_order) + new_cases: list[Case] = store_generic_order_service.store_order_data_in_status_db( + balsamic_order + ) # THEN it should create and link samples and the case - assert len(new_families) == 1 - new_case = new_families[0] + assert len(new_cases) == 1 + new_case = new_cases[0] assert new_case.name == "BalsamicCase" assert new_case.data_analysis in [ Workflow.BALSAMIC, @@ -108,3 +117,53 @@ def test_store_cancer_samples( assert new_link.sample.application_version.application.tag == "PANKTTR100" assert new_link.sample.comment == "This is a sample comment" assert new_link.sample.is_tumour + + +def test_store_rna_fusion_order( + store_to_submit_and_validate_orders: Store, + rnafusion_order: RnaFusionOrder, + store_generic_order_service: StoreCaseOrderService, +): + # GIVEN a store with no samples nor cases + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert not store_to_submit_and_validate_orders.get_cases() + + # WHEN storing a RNA Fusion order + new_cases = store_generic_order_service.store_order_data_in_status_db(rnafusion_order) + + # THEN it should create and link samples and the casing + assert len(new_cases) == 2 + first_case = new_cases[0] + + assert len(first_case.links) == 1 + new_link = first_case.links[0] + assert first_case.data_analysis == Workflow.RNAFUSION + assert first_case.data_delivery == str(DataDelivery.FASTQ_ANALYSIS) + assert new_link.sample.name == "sample1-rna-t1" + assert new_link.sample.application_version.application.tag == "RNAPOAR025" + assert new_link + + +def test_store_tomte_order( + store_to_submit_and_validate_orders: Store, + tomte_order: TomteOrder, + store_generic_order_service: StoreCaseOrderService, +): + # GIVEN a store with no samples nor cases + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert not store_to_submit_and_validate_orders.get_cases() + + # WHEN storing a Tomte order + new_cases = store_generic_order_service.store_order_data_in_status_db(tomte_order) + + # THEN it should create and link samples and the casing + assert len(new_cases) == 1 + first_case = new_cases[0] + + assert len(first_case.links) == 4 + new_link = first_case.links[0] + assert first_case.data_analysis == Workflow.TOMTE + assert first_case.data_delivery == str(DataDelivery.FASTQ_ANALYSIS) + assert new_link.sample.name == "sample1" + assert new_link.sample.application_version.application.tag == "RNAPOAR025" + assert new_link diff --git a/tests/services/orders/store_service/test_metagenome_store_service.py b/tests/services/orders/store_service/test_metagenome_store_service.py index 73b326cb95..9da88631e4 100644 --- a/tests/services/orders/store_service/test_metagenome_store_service.py +++ b/tests/services/orders/store_service/test_metagenome_store_service.py @@ -1,20 +1,49 @@ +""" +Module to test the store_order_data_in_status_db method of the StoreMetagenomeOrderService class. +The function store_order_data_in_status_db is never expected to fail, as its input order should +have always been validated before calling the function. +""" + +import pytest + from cg.services.orders.storing.implementations.metagenome_order_service import ( StoreMetagenomeOrderService, ) from cg.services.orders.validation.workflows.metagenome.models.order import MetagenomeOrder +from cg.services.orders.validation.workflows.taxprofiler.models.order import TaxprofilerOrder from cg.store.models import Sample +from cg.store.store import Store -def test_store_metagenome_samples( - metagenome_order: MetagenomeOrder, +@pytest.mark.parametrize( + "order_fixture", + ["metagenome_order", "taxprofiler_order"], + ids=["Metagenome", "Taxprofiler"], +) +def test_store_metagenome_order_data_in_status_db( + order_fixture: str, store_metagenome_order_service: StoreMetagenomeOrderService, + store_to_submit_and_validate_orders: Store, + ticket_id_as_int: int, + request: pytest.FixtureRequest, ): - # GIVEN a basic store with no samples and a valid metagenome order with two samples + # GIVEN an order + order: MetagenomeOrder | TaxprofilerOrder = request.getfixturevalue(order_fixture) + + # GIVEN a store with no samples nor cases + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert not store_to_submit_and_validate_orders.get_cases() # WHEN storing the order - response: list[Sample] = store_metagenome_order_service.store_order_data_in_status_db( - metagenome_order - ) + new_samples: list[Sample] = store_metagenome_order_service.store_order_data_in_status_db(order) + + # THEN the samples should have been stored + db_samples: list[Sample] = store_to_submit_and_validate_orders._get_query(table=Sample).all() + assert set(new_samples) == set(db_samples) + + # THEN the samples should have the correct application tag + for sample in db_samples: + assert sample.application_version.application.tag in ["METWPFR030", "METPCFR030"] - # THEN the response should contain the two samples - assert len(response) == 2 + # THEN the order should be stored + assert store_to_submit_and_validate_orders.get_order_by_ticket_id(ticket_id_as_int) diff --git a/tests/services/orders/store_service/test_microbial_store_order_service.py b/tests/services/orders/store_service/test_microbial_store_order_service.py index a800380644..2699ae5af6 100644 --- a/tests/services/orders/store_service/test_microbial_store_order_service.py +++ b/tests/services/orders/store_service/test_microbial_store_order_service.py @@ -1,3 +1,9 @@ +""" +Module to test the store_order_data_in_status_db method of the StoreMicrobialOrderService class. +The function store_order_data_in_status_db is never expected to fail, as its input order should +have always been validated before calling the function. +""" + from cg.constants import DataDelivery from cg.constants.constants import Workflow from cg.models.orders.sample_base import ControlEnum @@ -6,72 +12,68 @@ ) from cg.services.orders.validation.workflows.microsalt.models.order import MicrosaltOrder from cg.services.orders.validation.workflows.mutant.models.order import MutantOrder -from cg.store.models import Case, Customer, Sample +from cg.store.models import Case, Sample from cg.store.store import Store -def test_store_microbial_samples( - base_store: Store, +def test_store_microsalt_order_data_in_status_db( + store_to_submit_and_validate_orders: Store, microsalt_order: MicrosaltOrder, store_microbial_order_service: StoreMicrobialOrderService, ): - # GIVEN a basic store with no samples and a microbial order and one Organism - assert not base_store._get_query(table=Sample).first() - assert base_store._get_query(table=Case).count() == 0 - assert base_store.get_all_organisms().count() == 1 - - # WHEN storing the order - new_samples = store_microbial_order_service.store_order_data_in_status_db(microsalt_order) + # GIVEN a store with no samples nor cases + assert store_to_submit_and_validate_orders._get_query(table=Sample).count() == 0 + assert not store_to_submit_and_validate_orders.get_cases() - # THEN it should store the samples under a case (case) and the used previously unknown - # organisms - assert new_samples - assert base_store._get_query(table=Case).count() == 1 - assert len(new_samples) == 5 - assert len(base_store._get_query(table=Sample).all()) == 5 - assert base_store.get_all_organisms().count() == 4 - - -def test_store_microbial_case_data_analysis_stored( - base_store: Store, - microsalt_order: MicrosaltOrder, - store_microbial_order_service: StoreMicrobialOrderService, -): - # GIVEN a basic store with no samples and a microbial order and one Organism - assert not base_store._get_query(table=Sample).first() - assert base_store._get_query(table=Case).count() == 0 + # GIVEN that the store has no organisms + assert store_to_submit_and_validate_orders.get_all_organisms().count() == 0 # WHEN storing the order - store_microbial_order_service.store_order_data_in_status_db(microsalt_order) + new_samples: list[Sample] = store_microbial_order_service.store_order_data_in_status_db( + microsalt_order + ) + + # THEN it should store the samples under a case + db_samples: list[Sample] = store_to_submit_and_validate_orders._get_query(table=Sample).all() + assert set(new_samples) == set(db_samples) + case_from_sample: Case = db_samples[0].links[0].case + db_case: Case = store_to_submit_and_validate_orders.get_cases()[0] + assert db_case == case_from_sample - # THEN store the samples under a case with the microbial data_analysis type on case level - assert len(base_store._get_query(table=Sample).all()) > 0 - assert base_store._get_query(table=Case).count() == 1 + # THEN it should store the organisms + assert store_to_submit_and_validate_orders.get_all_organisms().count() > 0 - microbial_case = base_store.get_cases()[0] - assert microbial_case.data_analysis == Workflow.MICROSALT - assert microbial_case.data_delivery == str(DataDelivery.FASTQ_QC_ANALYSIS) + # THEN the case should have the correct data analysis and data delivery + assert db_case.data_analysis == Workflow.MICROSALT + assert db_case.data_delivery == str(DataDelivery.FASTQ_QC_ANALYSIS) -def test_store_items_in_status_control_has_stored_value( +def test_store_mutant_order_data_control_has_stored_value( + mutant_order: MutantOrder, sarscov2_order_to_submit: dict, - base_store: Store, + store_to_submit_and_validate_orders: Store, store_microbial_order_service: StoreMicrobialOrderService, ): - # GIVEN sarscov2 order with three samples with control value - order = MutantOrder.model_validate(sarscov2_order_to_submit) - order._generated_ticket_id = 123456 - control_value = ControlEnum.positive - for sample in order.samples: - sample.control = control_value + # GIVEN a Mutant order with one positive and one negative control + + # GIVEN a store with no samples nor cases + assert store_to_submit_and_validate_orders._get_query(table=Sample).count() == 0 + assert not store_to_submit_and_validate_orders.get_cases() # WHEN storing the order - store_microbial_order_service.store_order_data_in_status_db(order) + new_samples: list[Sample] = store_microbial_order_service.store_order_data_in_status_db( + mutant_order + ) + + # THEN it should store the samples under a case + db_samples: list[Sample] = store_to_submit_and_validate_orders._get_query(table=Sample).all() + assert set(new_samples) == set(db_samples) + case_from_sample: Case = db_samples[0].links[0].case + db_case: Case = store_to_submit_and_validate_orders.get_cases()[0] + assert db_case == case_from_sample - # THEN control should exist on the sample in the store - customer: Customer = base_store.get_customer_by_internal_id(customer_internal_id=order.customer) - for sample in order.samples: - stored_sample: Sample = base_store.get_sample_by_customer_and_name( - customer_entry_id=[customer.id], sample_name=sample.name - ) - assert stored_sample.control == control_value + # THEN the control samples should have the correct control value + positive: Sample = store_to_submit_and_validate_orders.get_sample_by_name("control-positive") + assert positive.control == ControlEnum.positive + negative: Sample = store_to_submit_and_validate_orders.get_sample_by_name("control-negative") + assert negative.control == ControlEnum.negative diff --git a/tests/services/orders/store_service/test_pacbio_order_service.py b/tests/services/orders/store_service/test_pacbio_order_service.py index a43a23417d..312d8ce3ee 100644 --- a/tests/services/orders/store_service/test_pacbio_order_service.py +++ b/tests/services/orders/store_service/test_pacbio_order_service.py @@ -1,33 +1,23 @@ from cg.constants import DataDelivery, Workflow -from cg.constants.sequencing import SeqLibraryPrepCategory from cg.models.orders.sample_base import SexEnum from cg.services.orders.storing.implementations.pacbio_order_service import StorePacBioOrderService from cg.services.orders.validation.workflows.pacbio_long_read.models.order import PacbioOrder from cg.store.models import Case, Order, Sample from cg.store.store import Store -from tests.store_helpers import StoreHelpers -def test_store_order_data_in_status_db( - base_store: Store, +def test_store_pacbio_order_data_in_status_db( + store_to_submit_and_validate_orders: Store, pacbio_order: PacbioOrder, store_pacbio_order_service: StorePacBioOrderService, - helpers: StoreHelpers, ): """Test that a PacBio order is stored in the database.""" # GIVEN a valid Pacbio order and a Pacbio store service # GIVEN a basic store with no samples, cases and only a MAF order - assert not base_store._get_query(table=Sample).first() - assert base_store._get_query(table=Case).count() == 0 - assert base_store._get_query(table=Order).count() == 1 - - # GIVEN that a Pacbio application exists in the store - helpers.ensure_application_version( - store=base_store, - application_tag="LWPBELB070", - prep_category=SeqLibraryPrepCategory.WHOLE_GENOME_SEQUENCING, - ) + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert store_to_submit_and_validate_orders._get_query(table=Case).count() == 0 + assert store_to_submit_and_validate_orders._get_query(table=Order).count() == 1 # WHEN storing the order new_samples: list[Sample] = store_pacbio_order_service.store_order_data_in_status_db( @@ -35,19 +25,19 @@ def test_store_order_data_in_status_db( ) # THEN it should store the order - assert base_store._get_query(table=Order).count() == 2 + assert store_to_submit_and_validate_orders._get_query(table=Order).count() == 2 # THEN it should store the samples and create a case for each sample assert len(new_samples) == 3 - assert len(base_store._get_query(table=Sample).all()) == 3 - assert base_store._get_query(table=Case).count() == 3 + assert len(store_to_submit_and_validate_orders._get_query(table=Sample).all()) == 3 + assert store_to_submit_and_validate_orders._get_query(table=Case).count() == 3 for new_sample in new_samples: # THEN the sample sex should be stored assert new_sample.sex == SexEnum.male # THEN the sample should have a relationship with a case assert len(new_sample.links) == 1 case_link = new_sample.links[0] - assert case_link.case in base_store.get_cases() + assert case_link.case in store_to_submit_and_validate_orders.get_cases() # THEN the analysis for the case should be RAW_DATA assert case_link.case.data_analysis == Workflow.RAW_DATA # THEN the delivery type for the case should be BAM or NO_DELIVERY diff --git a/tests/services/orders/store_service/test_pool_order_store_service.py b/tests/services/orders/store_service/test_pool_order_store_service.py index 7e646659e8..42a8da0c74 100644 --- a/tests/services/orders/store_service/test_pool_order_store_service.py +++ b/tests/services/orders/store_service/test_pool_order_store_service.py @@ -1,10 +1,15 @@ +""" +Module to test the store_order_data_in_status_db method of the StorePoolOrderService class. +The function store_order_data_in_status_db is never expected to fail, as its input order should +have always been validated before calling the function. +""" + import pytest from cg.constants import Workflow -from cg.constants.sequencing import SeqLibraryPrepCategory from cg.services.orders.storing.implementations.pool_order_service import StorePoolOrderService from cg.services.orders.validation.models.order_aliases import OrderWithIndexedSamples -from cg.store.models import ApplicationVersion, Case, CaseSample, Order, Pool, Sample +from cg.store.models import Case, CaseSample, Pool, Sample from cg.store.store import Store @@ -13,8 +18,8 @@ [("rml_order", Workflow.RAW_DATA), ("fluffy_order", Workflow.FLUFFY)], ids=["RML", "Fluffy"], ) -def test_store_order_data_in_status_db( - store_with_rml_applications: Store, +def test_store_pool_order_data_in_status_db( + store_to_submit_and_validate_orders: Store, order_fixture: str, ticket_id: str, store_pool_order_service: StorePoolOrderService, @@ -25,20 +30,11 @@ def test_store_order_data_in_status_db( # GIVEN a valid Fluffy or RML order order: OrderWithIndexedSamples = request.getfixturevalue(order_fixture) - # GIVEN a store with no samples, pools, cases nor orders - assert store_with_rml_applications._get_query(table=Sample).count() == 0 - assert store_with_rml_applications._get_query(table=Pool).count() == 0 - assert store_with_rml_applications._get_query(table=Case).count() == 0 - assert store_with_rml_applications._get_query(table=CaseSample).count() == 0 - assert store_with_rml_applications._get_query(table=Order).count() == 0 - - # GIVEN that the store has application versions for the RML workflow - apps: list[ApplicationVersion] = store_with_rml_applications._get_query( - table=ApplicationVersion - ).all() - assert len(apps) == 4 - for app in apps: - assert app.application.prep_category == SeqLibraryPrepCategory.READY_MADE_LIBRARY + # GIVEN a store with no samples, pools, nor cases + assert store_to_submit_and_validate_orders._get_query(table=Sample).count() == 0 + assert store_to_submit_and_validate_orders._get_query(table=Pool).count() == 0 + assert store_to_submit_and_validate_orders._get_query(table=Case).count() == 0 + assert store_to_submit_and_validate_orders._get_query(table=CaseSample).count() == 0 # WHEN storing the order new_pools: list[Pool] = store_pool_order_service.store_order_data_in_status_db(order=order) @@ -48,7 +44,7 @@ def test_store_order_data_in_status_db( assert isinstance(new_pools[0], Pool) # THEN the pools should be stored in the database - db_pools: list[Pool] = store_with_rml_applications._get_query(table=Pool).all() + db_pools: list[Pool] = store_to_submit_and_validate_orders._get_query(table=Pool).all() assert len(db_pools) == 4 assert set(new_pools) == set(db_pools) @@ -59,15 +55,14 @@ def test_store_order_data_in_status_db( assert pool.ticket == ticket_id # THEN the order should be stored, have the correct ticket id - new_order = store_with_rml_applications._get_query(table=Order).first() - assert new_order.ticket_id == int(ticket_id) + assert store_to_submit_and_validate_orders.get_order_by_ticket_id(int(ticket_id)) # THEN it should store the samples and create a case for each sample - new_samples: list[Sample] = store_with_rml_applications._get_query(table=Sample).all() - new_cases: list[Case] = store_with_rml_applications._get_query(table=Case).all() + new_samples: list[Sample] = store_to_submit_and_validate_orders._get_query(table=Sample).all() + new_cases: list[Case] = store_to_submit_and_validate_orders._get_query(table=Case).all() assert len(new_samples) == 4 assert len(new_cases) == 4 - assert store_with_rml_applications._get_query(table=CaseSample).count() == 4 + assert store_to_submit_and_validate_orders._get_query(table=CaseSample).count() == 4 # THEN the samples are not set for invoicing for sample in new_samples: diff --git a/tests/services/orders/store_service/test_registry.py b/tests/services/orders/store_service/test_registry.py new file mode 100644 index 0000000000..a762a874de --- /dev/null +++ b/tests/services/orders/store_service/test_registry.py @@ -0,0 +1,70 @@ +import pytest + +from cg.models.orders.constants import OrderType +from cg.services.orders.storing.service import StoreOrderService +from cg.services.orders.storing.service_registry import StoringServiceRegistry + + +@pytest.mark.parametrize( + "order_type, storing_service_fixture", + [ + (OrderType.BALSAMIC, "store_generic_order_service"), + (OrderType.BALSAMIC_QC, "store_generic_order_service"), + (OrderType.FASTQ, "store_fastq_order_service"), + (OrderType.FLUFFY, "store_pool_order_service"), + (OrderType.METAGENOME, "store_metagenome_order_service"), + (OrderType.MICROBIAL_FASTQ, "store_microbial_fastq_order_service"), + (OrderType.MICROSALT, "store_microbial_order_service"), + (OrderType.MIP_DNA, "store_generic_order_service"), + (OrderType.MIP_RNA, "store_generic_order_service"), + (OrderType.PACBIO_LONG_READ, "store_pacbio_order_service"), + (OrderType.RML, "store_pool_order_service"), + (OrderType.RNAFUSION, "store_generic_order_service"), + (OrderType.SARS_COV_2, "store_microbial_order_service"), + (OrderType.TAXPROFILER, "store_metagenome_order_service"), + (OrderType.TOMTE, "store_generic_order_service"), + ], + ids=[ + "balsamic", + "balsamic_qc", + "fastq", + "fluffy", + "metagenome", + "microbial_fastq", + "microbial", + "mip_dna", + "mip_rna", + "pacbio_long_read", + "rml", + "rnafusion", + "sars_cov_2", + "taxprofiler", + "tomte", + ], +) +def test_get_storing_service( + storing_service_registry: StoringServiceRegistry, + order_type: OrderType, + storing_service_fixture: str, + request: pytest.FixtureRequest, +): + """Test that getting a storing service returns the correct service for any known order type.""" + # GIVEN a storing service registry + + # WHEN getting a storing service for a known order type + storing_service: StoreOrderService = storing_service_registry.get_storing_service(order_type) + + # THEN the correct storing service should be returned + expected_storing_service: StoreOrderService = request.getfixturevalue(storing_service_fixture) + assert isinstance(storing_service, type(expected_storing_service)) + + +def test_get_storing_registry_unknown_order_type(storing_service_registry: StoringServiceRegistry): + """Test that getting a storing service for an unknown order type raises a ValueError.""" + # GIVEN a storing service registry + + # WHEN getting a storing service for an unknown order type + + # THEN it should raise a ValueError + with pytest.raises(ValueError): + storing_service_registry.get_storing_service(order_type="non_existing_order_type") diff --git a/tests/services/orders/submitter/test_order_submitter.py b/tests/services/orders/submitter/test_order_submitter.py index 7db66f0236..57edb4458c 100644 --- a/tests/services/orders/submitter/test_order_submitter.py +++ b/tests/services/orders/submitter/test_order_submitter.py @@ -6,12 +6,17 @@ from cg.clients.freshdesk.models import TicketResponse from cg.exc import TicketCreationError from cg.models.orders.constants import OrderType +from cg.services.orders.constants import ORDER_TYPE_WORKFLOW_MAP +from cg.services.orders.storing.constants import MAF_ORDER_ID from cg.services.orders.submitter.service import OrderSubmitter from cg.services.orders.validation.errors.validation_errors import ValidationErrors from cg.services.orders.validation.models.order import Order from cg.services.orders.validation.models.order_with_cases import OrderWithCases from cg.services.orders.validation.models.order_with_samples import OrderWithSamples -from cg.store.models import Case, Pool, Sample, User +from cg.services.orders.validation.workflows.mip_dna.models.order import MipDnaOrder +from cg.store.models import Case +from cg.store.models import Order as DbOrder +from cg.store.models import Pool, Sample, User from cg.store.store import Store @@ -66,18 +71,29 @@ def mock_freshdesk_reply_to_ticket(mock_reply_to_ticket: callable): ], ) def test_submit_order( - store_with_all_test_applications: Store, + store_to_submit_and_validate_orders: Store, monkeypatch: pytest.MonkeyPatch, order_type: OrderType, order_fixture: str, order_submitter: OrderSubmitter, ticket_id: str, + customer_id: str, request: pytest.FixtureRequest, ): """Test submitting a valid order of each ordertype.""" # GIVEN an order order: Order = request.getfixturevalue(order_fixture) + # GIVEN a store without samples, cases, or pools + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() + assert not store_to_submit_and_validate_orders._get_query(table=Case).first() + assert not store_to_submit_and_validate_orders._get_query(table=Pool).first() + + # GIVEN that the only order in store is a MAF order + orders: list[DbOrder] = store_to_submit_and_validate_orders._get_query(table=DbOrder).all() + assert len(orders) == 1 + assert orders[0].id == MAF_ORDER_ID + # GIVEN a ticketing system that returns a ticket number with ( patch( @@ -94,46 +110,54 @@ def test_submit_order( monkeypatch_process_lims(monkeypatch=monkeypatch, order=order) # GIVEN a registered user - user: User = store_with_all_test_applications._get_query(table=User).first() + user: User = store_to_submit_and_validate_orders._get_query(table=User).first() # GIVEN the dict representation of the order and a store without samples raw_order = order.model_dump(by_alias=True) - assert not store_with_all_test_applications._get_query(table=Sample).first() + assert not store_to_submit_and_validate_orders._get_query(table=Sample).first() # WHEN submitting the order result = order_submitter.submit(order_type=order_type, raw_order=raw_order, user=user) - # THEN the result should contain the ticket number for the order + # THEN the result should contain the project data + assert result["project"]["id"] == "ADM1234" + + # THEN the records should contain the appropriate ticket id, customer id and data analysis + is_pool_order: bool = False for record in result["records"]: + assert record.customer.internal_id == customer_id if isinstance(record, Pool): assert record.ticket == ticket_id + is_pool_order = True elif isinstance(record, Sample): assert record.original_ticket == ticket_id elif isinstance(record, Case): + assert record.data_analysis == ORDER_TYPE_WORKFLOW_MAP[order_type] for link_obj in record.links: assert link_obj.sample.original_ticket == ticket_id + # THEN the order should be stored in the database + assert store_to_submit_and_validate_orders.get_order_by_ticket_id(ticket_id=int(ticket_id)) + + # THEN the samples should be stored in the database + assert store_to_submit_and_validate_orders._get_query(table=Sample).first() + + # THEN the cases should be stored in the database + assert store_to_submit_and_validate_orders._get_query(table=Case).first() + + # THEN the pools should be stored in the database if applicable + if is_pool_order: + assert store_to_submit_and_validate_orders._get_query(table=Pool).first() + -@pytest.mark.parametrize( - "order_type, order_fixture", - [ - (OrderType.MIP_DNA, "mip_dna_order"), - (OrderType.MIP_RNA, "mip_rna_order"), - (OrderType.BALSAMIC, "balsamic_order"), - ], -) def test_submit_ticketexception( order_submitter: OrderSubmitter, - order_type: OrderType, - order_fixture: str, - request: pytest.FixtureRequest, + mip_dna_order: MipDnaOrder, ): # GIVEN an order - order: OrderWithCases = request.getfixturevalue(order_fixture) - raw_order = order.model_dump() - raw_order["ticket_number"] = "#123456" - raw_order["project_type"] = order_type + raw_order = mip_dna_order.model_dump() + raw_order["project_type"] = mip_dna_order.order_type # GIVEN a registered user user: User = order_submitter.validation_service.store._get_query(table=User).first() @@ -149,9 +173,10 @@ def test_submit_ticketexception( return_value=ValidationErrors(), ), ): - # GIVEN an order that does not have a name (ticket_nr) # WHEN the order is submitted and a TicketCreationError raised # THEN the TicketCreationError is not excepted with pytest.raises(TicketCreationError): - order_submitter.submit(raw_order=raw_order, user=user, order_type=order_type) + order_submitter.submit( + raw_order=raw_order, user=user, order_type=mip_dna_order.order_type + ) diff --git a/tests/services/orders/validation_service/test_validation_service.py b/tests/services/orders/validation_service/test_validation_service.py new file mode 100644 index 0000000000..16add7d335 --- /dev/null +++ b/tests/services/orders/validation_service/test_validation_service.py @@ -0,0 +1,21 @@ +import pytest + +from cg.exc import OrderError +from cg.models.orders.constants import OrderType +from cg.services.orders.validation.service import OrderValidationService + + +def test_parse_and_validate_pydantic_error( + order_validation_service: OrderValidationService, invalid_balsamic_order_to_submit: dict +): + # GIVEN a raw order that will fail validation and a validation service + + # WHEN parsing and validating the order + + # THEN an OrderError should be raised + with pytest.raises(OrderError): + order_validation_service.parse_and_validate( + raw_order=invalid_balsamic_order_to_submit, + order_type=OrderType.BALSAMIC, + user_id=1, + )