From 549d59d9de76abe643296aaac6d8b250a3e218f4 Mon Sep 17 00:00:00 2001 From: Sebastian Diaz Date: Tue, 14 Jan 2025 10:29:15 +0100 Subject: [PATCH] add new mutant validation rule (#4075) ## Description Closes https://github.com/Clinical-Genomics/improve-order-flow/issues/102 Fixes the sample name validation rule for Mutant orders to exclude control samples from the name check ### Added - Validation function `validate_non_control_sample_names_available` that verifies that sample names used in an order are not in the store excluding control samples - Utils function `get_sample_name_not_available_errors` used in new validation function - Mutant order fixture - aliases to group samples with organism and samples orders with control samples - tests for new validation functions ### Changed - Refactored validation function `validate_sample_names_available` to use the new utils function ### Fixed - Changed some balsamic structure in the order validation service - Reverted some deletions in json order fixtures from previous prs --- .../errors/sample_errors.py | 5 ++ .../models/order_aliases.py | 12 ++- .../models/sample_aliases.py | 3 - .../rules/sample/rules.py | 37 +++++--- .../rules/sample/utils.py | 44 +++++++++- .../balsamic/{rules/case_sample => }/rules.py | 2 +- .../balsamic/{rules/case_sample => }/utils.py | 0 .../workflows/balsamic/validation_rules.py | 2 +- .../workflows/metagenome/validation_rules.py | 4 +- .../microbial_fastq/validation_rules.py | 4 +- .../workflows/microsalt/validation_rules.py | 4 +- .../workflows/mutant/__init__.py | 0 .../workflows/mutant/validation_rules.py | 4 +- .../workflows/taxprofiler/validation_rules.py | 4 +- .../orders_fixtures/order_fixtures.py | 9 ++ tests/fixtures/cgweb_orders/fluffy.json | 22 +++++ tests/fixtures/cgweb_orders/rml.json | 25 ++++++ .../sample_rules/test_sample_rules.py | 87 ++++++++++++++++++- ...est_case_sample_rules.py => test_rules.py} | 2 +- 19 files changed, 238 insertions(+), 32 deletions(-) rename cg/services/order_validation_service/workflows/balsamic/{rules/case_sample => }/rules.py (89%) rename cg/services/order_validation_service/workflows/balsamic/{rules/case_sample => }/utils.py (100%) delete mode 100644 cg/services/order_validation_service/workflows/mutant/__init__.py rename tests/services/order_validation_service/workflows/balsamic/{test_case_sample_rules.py => test_rules.py} (97%) diff --git a/cg/services/order_validation_service/errors/sample_errors.py b/cg/services/order_validation_service/errors/sample_errors.py index 47262fd747..7be56eecca 100644 --- a/cg/services/order_validation_service/errors/sample_errors.py +++ b/cg/services/order_validation_service/errors/sample_errors.py @@ -61,6 +61,11 @@ class SampleNameNotAvailableError(SampleError): message: str = "Sample name already used in previous order" +class SampleNameNotAvailableControlError(SampleError): + field: str = "name" + message: str = "Sample name already in use. Only control samples are allowed repeated names" + + class ContainerNameRepeatedError(SampleError): field: str = "container_name" message: str = "Tube names must be unique among samples" diff --git a/cg/services/order_validation_service/models/order_aliases.py b/cg/services/order_validation_service/models/order_aliases.py index e9d3bf16c4..d87312c471 100644 --- a/cg/services/order_validation_service/models/order_aliases.py +++ b/cg/services/order_validation_service/models/order_aliases.py @@ -1,7 +1,17 @@ from cg.services.order_validation_service.workflows.fluffy.models.order import FluffyOrder +from cg.services.order_validation_service.workflows.metagenome.models.sample import MetagenomeSample +from cg.services.order_validation_service.workflows.microbial_fastq.models.order import ( + MicrobialFastqOrder, +) from cg.services.order_validation_service.workflows.microsalt.models.order import MicrosaltOrder from cg.services.order_validation_service.workflows.mutant.models.order import MutantOrder from cg.services.order_validation_service.workflows.rml.models.order import RmlOrder +from cg.services.order_validation_service.workflows.taxprofiler.models.sample import ( + TaxprofilerSample, +) OrderWithIndexedSamples = FluffyOrder | RmlOrder -OrderWithNonHumanSamples = MutantOrder | MicrosaltOrder +OrderWithSamplesFromOrganism = MutantOrder | MicrosaltOrder +OrderWithControlSamples = ( + MetagenomeSample | MicrobialFastqOrder | MicrosaltOrder | MutantOrder | TaxprofilerSample +) diff --git a/cg/services/order_validation_service/models/sample_aliases.py b/cg/services/order_validation_service/models/sample_aliases.py index b424c1aeee..a7e41836c5 100644 --- a/cg/services/order_validation_service/models/sample_aliases.py +++ b/cg/services/order_validation_service/models/sample_aliases.py @@ -4,10 +4,8 @@ ) from cg.services.order_validation_service.workflows.fastq.models.sample import FastqSample from cg.services.order_validation_service.workflows.fluffy.models.sample import FluffySample -from cg.services.order_validation_service.workflows.microsalt.models.sample import MicrosaltSample from cg.services.order_validation_service.workflows.mip_dna.models.sample import MipDnaSample from cg.services.order_validation_service.workflows.mip_rna.models.sample import MipRnaSample -from cg.services.order_validation_service.workflows.mutant.models.sample import MutantSample from cg.services.order_validation_service.workflows.rml.models.sample import RmlSample from cg.services.order_validation_service.workflows.rna_fusion.models.sample import RnaFusionSample from cg.services.order_validation_service.workflows.tomte.models.sample import TomteSample @@ -15,7 +13,6 @@ HumanSample = ( BalsamicSample | BalsamicUmiSample | FastqSample | MipDnaSample | RnaFusionSample | TomteSample ) -NonHumanSample = MutantSample | MicrosaltSample IndexedSample = FluffySample | RmlSample diff --git a/cg/services/order_validation_service/rules/sample/rules.py b/cg/services/order_validation_service/rules/sample/rules.py index b247acfb36..6695fb4b3d 100644 --- a/cg/services/order_validation_service/rules/sample/rules.py +++ b/cg/services/order_validation_service/rules/sample/rules.py @@ -1,4 +1,5 @@ from cg.models.orders.constants import OrderType +from cg.models.orders.sample_base import ControlEnum from cg.services.order_validation_service.errors.sample_errors import ( ApplicationArchivedError, ApplicationNotCompatibleError, @@ -22,14 +23,16 @@ WellPositionRmlMissingError, ) from cg.services.order_validation_service.models.order_aliases import ( + OrderWithControlSamples, OrderWithIndexedSamples, - OrderWithNonHumanSamples, + OrderWithSamplesFromOrganism, ) from cg.services.order_validation_service.models.sample_aliases import IndexedSample from cg.services.order_validation_service.rules.sample.utils import ( PlateSamplesValidator, get_indices_for_repeated_sample_names, get_indices_for_tube_repeated_container_name, + get_sample_name_not_available_errors, has_multiple_applications, has_multiple_priorities, is_container_name_missing, @@ -155,11 +158,11 @@ def validate_concentration_required_if_skip_rc( def validate_organism_exists( - order: OrderWithNonHumanSamples, store: Store, **kwargs + order: OrderWithSamplesFromOrganism, store: Store, **kwargs ) -> list[OrganismDoesNotExistError]: """ Validate that the organisms of all samples in the order exist in the database. - Only applicable to order types with non-human samples. + Only applicable to Microsalt and Mutant orders. """ errors: list[OrganismDoesNotExistError] = [] for sample_index, sample in order.enumerated_samples: @@ -208,16 +211,24 @@ def validate_sample_names_available( ) -> list[SampleNameNotAvailableError]: """ Validate that the sample names do not exists in the database under the same customer. - Applicable to all order types. + Applicable to all orders without control samples. """ - errors: list[SampleNameNotAvailableError] = [] - customer = store.get_customer_by_internal_id(order.customer) - for sample_index, sample in order.enumerated_samples: - if store.get_sample_by_customer_and_name( - sample_name=sample.name, customer_entry_id=[customer.id] - ): - error = SampleNameNotAvailableError(sample_index=sample_index) - errors.append(error) + errors: list[SampleNameNotAvailableError] = get_sample_name_not_available_errors( + order=order, store=store, has_order_control=False + ) + return errors + + +def validate_non_control_sample_names_available( + order: OrderWithControlSamples, store: Store, **kwargs +) -> list[SampleNameNotAvailableError]: + """ + Validate that non-control sample names do not exists in the database under the same customer. + Applicable to all orders with control samples. + """ + errors: list[SampleNameNotAvailableError] = get_sample_name_not_available_errors( + order=order, store=store, has_order_control=True + ) return errors @@ -226,7 +237,7 @@ def validate_sample_names_unique( ) -> list[SampleNameRepeatedError]: """ Validate that all the sample names are unique within the order. - Applicable to all order types except Mutant orders. + Applicable to all order types. """ sample_indices: list[int] = get_indices_for_repeated_sample_names(order) return [SampleNameRepeatedError(sample_index=sample_index) for sample_index in sample_indices] diff --git a/cg/services/order_validation_service/rules/sample/utils.py b/cg/services/order_validation_service/rules/sample/utils.py index 602f7b6a7e..d94181c482 100644 --- a/cg/services/order_validation_service/rules/sample/utils.py +++ b/cg/services/order_validation_service/rules/sample/utils.py @@ -1,13 +1,16 @@ import re from collections import Counter -from cg.models.orders.sample_base import ContainerEnum +from cg.models.orders.sample_base import ContainerEnum, ControlEnum from cg.services.order_validation_service.constants import ALLOWED_SKIP_RC_BUFFERS from cg.services.order_validation_service.errors.sample_errors import ( BufferInvalidError, ConcentrationInvalidIfSkipRCError, ConcentrationRequiredError, OccupiedWellError, + SampleError, + SampleNameNotAvailableControlError, + SampleNameNotAvailableError, WellPositionMissingError, ) from cg.services.order_validation_service.models.order_with_samples import OrderWithSamples @@ -79,6 +82,45 @@ def get_indices_for_repeated_sample_names(order: OrderWithSamples) -> list[int]: return indices +def get_sample_name_not_available_errors( + order: OrderWithSamples, store: Store, has_order_control: bool +) -> list[SampleError]: + """Return errors for non-control samples with names already used in the database.""" + errors: list[SampleError] = [] + customer = store.get_customer_by_internal_id(order.customer) + for sample_index, sample in order.enumerated_samples: + if store.get_sample_by_customer_and_name( + sample_name=sample.name, customer_entry_id=[customer.id] + ): + if is_sample_name_allowed_to_be_repeated(has_control=has_order_control, sample=sample): + continue + error = get_appropriate_sample_name_available_error( + has_control=has_order_control, sample_index=sample_index + ) + errors.append(error) + return errors + + +def is_sample_name_allowed_to_be_repeated(has_control: bool, sample: Sample) -> bool: + """ + Return whether a sample name can be used if it is already in the database. + This is the case when the order has control samples and the sample is a control. + """ + return has_control and sample.control in [ControlEnum.positive, ControlEnum.negative] + + +def get_appropriate_sample_name_available_error( + has_control: bool, sample_index: int +) -> SampleError: + """ + Return the appropriate error for a sample name that is not available based on whether the + order has control samples or not. + """ + if has_control: + return SampleNameNotAvailableControlError(sample_index=sample_index) + return SampleNameNotAvailableError(sample_index=sample_index) + + def is_tube_container_name_redundant(sample: Sample, counter: Counter) -> bool: return sample.container == ContainerEnum.tube and counter.get(sample.container_name) > 1 diff --git a/cg/services/order_validation_service/workflows/balsamic/rules/case_sample/rules.py b/cg/services/order_validation_service/workflows/balsamic/rules.py similarity index 89% rename from cg/services/order_validation_service/workflows/balsamic/rules/case_sample/rules.py rename to cg/services/order_validation_service/workflows/balsamic/rules.py index e7c6b177fd..62893f5ab1 100644 --- a/cg/services/order_validation_service/workflows/balsamic/rules/case_sample/rules.py +++ b/cg/services/order_validation_service/workflows/balsamic/rules.py @@ -1,6 +1,6 @@ from cg.services.order_validation_service.errors.case_sample_errors import CaptureKitMissingError from cg.services.order_validation_service.workflows.balsamic.models.order import BalsamicOrder -from cg.services.order_validation_service.workflows.balsamic.rules.case_sample.utils import ( +from cg.services.order_validation_service.workflows.balsamic.utils import ( is_sample_missing_capture_kit, ) from cg.store.store import Store diff --git a/cg/services/order_validation_service/workflows/balsamic/rules/case_sample/utils.py b/cg/services/order_validation_service/workflows/balsamic/utils.py similarity index 100% rename from cg/services/order_validation_service/workflows/balsamic/rules/case_sample/utils.py rename to cg/services/order_validation_service/workflows/balsamic/utils.py diff --git a/cg/services/order_validation_service/workflows/balsamic/validation_rules.py b/cg/services/order_validation_service/workflows/balsamic/validation_rules.py index d76ed55d44..edf280bb57 100644 --- a/cg/services/order_validation_service/workflows/balsamic/validation_rules.py +++ b/cg/services/order_validation_service/workflows/balsamic/validation_rules.py @@ -25,7 +25,7 @@ validate_well_positions_required, validate_wells_contain_at_most_one_sample, ) -from cg.services.order_validation_service.workflows.balsamic.rules.case_sample.rules import ( +from cg.services.order_validation_service.workflows.balsamic.rules import ( validate_capture_kit_panel_requirement, ) diff --git a/cg/services/order_validation_service/workflows/metagenome/validation_rules.py b/cg/services/order_validation_service/workflows/metagenome/validation_rules.py index a8c826e9e9..7f390c914b 100644 --- a/cg/services/order_validation_service/workflows/metagenome/validation_rules.py +++ b/cg/services/order_validation_service/workflows/metagenome/validation_rules.py @@ -3,7 +3,7 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, - validate_sample_names_available, + validate_non_control_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, @@ -18,7 +18,7 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, - validate_sample_names_available, + validate_non_control_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, diff --git a/cg/services/order_validation_service/workflows/microbial_fastq/validation_rules.py b/cg/services/order_validation_service/workflows/microbial_fastq/validation_rules.py index 3fd0038334..e353813be9 100644 --- a/cg/services/order_validation_service/workflows/microbial_fastq/validation_rules.py +++ b/cg/services/order_validation_service/workflows/microbial_fastq/validation_rules.py @@ -3,7 +3,7 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, - validate_sample_names_available, + validate_non_control_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, @@ -18,7 +18,7 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, - validate_sample_names_available, + validate_non_control_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, diff --git a/cg/services/order_validation_service/workflows/microsalt/validation_rules.py b/cg/services/order_validation_service/workflows/microsalt/validation_rules.py index e667ff10fe..c226a3a0ce 100644 --- a/cg/services/order_validation_service/workflows/microsalt/validation_rules.py +++ b/cg/services/order_validation_service/workflows/microsalt/validation_rules.py @@ -3,8 +3,8 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, + validate_non_control_sample_names_available, validate_organism_exists, - validate_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, @@ -19,8 +19,8 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, + validate_non_control_sample_names_available, validate_organism_exists, - validate_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, diff --git a/cg/services/order_validation_service/workflows/mutant/__init__.py b/cg/services/order_validation_service/workflows/mutant/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cg/services/order_validation_service/workflows/mutant/validation_rules.py b/cg/services/order_validation_service/workflows/mutant/validation_rules.py index b9a0934689..86010d13a5 100644 --- a/cg/services/order_validation_service/workflows/mutant/validation_rules.py +++ b/cg/services/order_validation_service/workflows/mutant/validation_rules.py @@ -3,8 +3,8 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, + validate_non_control_sample_names_available, validate_organism_exists, - validate_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, @@ -19,9 +19,9 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, + validate_non_control_sample_names_available, validate_organism_exists, validate_volume_required, - validate_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, diff --git a/cg/services/order_validation_service/workflows/taxprofiler/validation_rules.py b/cg/services/order_validation_service/workflows/taxprofiler/validation_rules.py index f39934e469..631acf4577 100644 --- a/cg/services/order_validation_service/workflows/taxprofiler/validation_rules.py +++ b/cg/services/order_validation_service/workflows/taxprofiler/validation_rules.py @@ -3,7 +3,7 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, - validate_sample_names_available, + validate_non_control_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, @@ -18,7 +18,7 @@ validate_application_exists, validate_applications_not_archived, validate_container_name_required, - validate_sample_names_available, + validate_non_control_sample_names_available, validate_sample_names_unique, validate_tube_container_name_unique, validate_volume_interval, diff --git a/tests/fixture_plugins/orders_fixtures/order_fixtures.py b/tests/fixture_plugins/orders_fixtures/order_fixtures.py index 42ed7557b0..d474e39218 100644 --- a/tests/fixture_plugins/orders_fixtures/order_fixtures.py +++ b/tests/fixture_plugins/orders_fixtures/order_fixtures.py @@ -12,6 +12,7 @@ from cg.services.order_validation_service.workflows.microsalt.models.order import MicrosaltOrder from cg.services.order_validation_service.workflows.mip_dna.models.order import MipDnaOrder from cg.services.order_validation_service.workflows.mip_rna.models.order import MipRnaOrder +from cg.services.order_validation_service.workflows.mutant.models.order import MutantOrder from cg.services.order_validation_service.workflows.pacbio_long_read.models.order import PacbioOrder from cg.services.order_validation_service.workflows.rml.models.order import RmlOrder @@ -88,6 +89,14 @@ def mip_rna_order(mip_rna_order_to_submit: dict) -> MipRnaOrder: return mip_rna_order +@pytest.fixture +def mutant_order(sarscov2_order_to_submit: dict, ticket_id_as_int: int) -> MutantOrder: + """Parse mutant order example.""" + order = MutantOrder.model_validate(sarscov2_order_to_submit) + order._generated_ticket_id = ticket_id_as_int + return order + + @pytest.fixture def pacbio_order(pacbio_order_to_submit: dict, ticket_id_as_int: int) -> PacbioOrder: order = PacbioOrder.model_validate(pacbio_order_to_submit) diff --git a/tests/fixtures/cgweb_orders/fluffy.json b/tests/fixtures/cgweb_orders/fluffy.json index 5059a42037..672313ccce 100644 --- a/tests/fixtures/cgweb_orders/fluffy.json +++ b/tests/fixtures/cgweb_orders/fluffy.json @@ -35,6 +35,8 @@ "lab_code": null, "mother": null, "name": "fluffysample1", + "organism": null, + "organism_other": null, "original_lab": null, "original_lab_address": null, "phenotype_groups": null, @@ -56,6 +58,10 @@ "source": null, "status": null, "subject_id": null, + "tissue_block_size": null, + "tumour": null, + "tumour_purity": null, + "verified_organism": null, "volume": "20", "well_position": null, "well_position_rml": "A:1" @@ -111,6 +117,10 @@ "source": null, "status": null, "subject_id": null, + "tissue_block_size": null, + "tumour": null, + "tumour_purity": null, + "verified_organism": null, "volume": "21", "well_position": null, "well_position_rml": "" @@ -143,6 +153,8 @@ "lab_code": null, "mother": null, "name": "fluffysample3", + "organism": null, + "organism_other": null, "original_lab": null, "original_lab_address": null, "phenotype_groups": null, @@ -164,6 +176,10 @@ "source": null, "status": null, "subject_id": null, + "tissue_block_size": null, + "tumour": null, + "tumour_purity": null, + "verified_organism": null, "volume": "22", "well_position": null, "well_position_rml": "" @@ -196,6 +212,8 @@ "lab_code": null, "mother": null, "name": "fluffysample4", + "organism": null, + "organism_other": null, "original_lab": null, "original_lab_address": null, "phenotype_groups": null, @@ -217,6 +235,10 @@ "source": null, "status": null, "subject_id": null, + "tissue_block_size": null, + "tumour": null, + "tumour_purity": null, + "verified_organism": null, "volume": "23", "well_position": null, "well_position_rml": "A:1" diff --git a/tests/fixtures/cgweb_orders/rml.json b/tests/fixtures/cgweb_orders/rml.json index 97b8c95507..e4cf212dca 100644 --- a/tests/fixtures/cgweb_orders/rml.json +++ b/tests/fixtures/cgweb_orders/rml.json @@ -35,6 +35,8 @@ "lab_code": null, "mother": null, "name": "rmlsample1", + "organism": null, + "organism_other": null, "original_lab": null, "original_lab_address": null, "phenotype_groups": null, @@ -56,6 +58,10 @@ "source": null, "status": null, "subject_id": null, + "tissue_block_size": null, + "tumour": null, + "tumour_purity": null, + "verified_organism": null, "volume": "20", "well_position": null, "well_position_rml": "A:1" @@ -88,6 +94,8 @@ "lab_code": null, "mother": null, "name": "rmlsample2", + "organism": null, + "organism_other": null, "original_lab": null, "original_lab_address": null, "phenotype_groups": null, @@ -99,6 +107,7 @@ "priority": "clinical_trials", "quantity": null, "reagent_label": "C01 - D701-D503 (ATTACTCG-CCTATCCT)", + "reference_genome": null, "region": null, "region_code": null, "require_qc_ok": false, @@ -108,6 +117,10 @@ "source": null, "status": null, "subject_id": null, + "tissue_block_size": null, + "tumour": null, + "tumour_purity": null, + "verified_organism": null, "volume": "21", "well_position": null, "well_position_rml": "A:1" @@ -140,6 +153,8 @@ "lab_code": null, "mother": null, "name": "rmlsample3", + "organism": null, + "organism_other": null, "original_lab": null, "original_lab_address": null, "phenotype_groups": null, @@ -161,6 +176,10 @@ "source": null, "status": null, "subject_id": null, + "tissue_block_size": null, + "tumour": null, + "tumour_purity": null, + "verified_organism": null, "volume": "22", "well_position": null, "well_position_rml": "A:1" @@ -193,6 +212,8 @@ "lab_code": null, "mother": null, "name": "rmlsample4", + "organism": null, + "organism_other": null, "original_lab": null, "original_lab_address": null, "phenotype_groups": null, @@ -214,6 +235,10 @@ "source": null, "status": null, "subject_id": null, + "tissue_block_size": null, + "tumour": null, + "tumour_purity": null, + "verified_organism": null, "volume": "23", "well_position": null, "well_position_rml": "A:1" diff --git a/tests/services/order_validation_service/sample_rules/test_sample_rules.py b/tests/services/order_validation_service/sample_rules/test_sample_rules.py index e6c234309d..a218d52f9a 100644 --- a/tests/services/order_validation_service/sample_rules/test_sample_rules.py +++ b/tests/services/order_validation_service/sample_rules/test_sample_rules.py @@ -1,4 +1,4 @@ -from cg.models.orders.sample_base import ContainerEnum, PriorityEnum +from cg.models.orders.sample_base import ContainerEnum, ControlEnum, PriorityEnum from cg.services.order_validation_service.constants import ElutionBuffer from cg.services.order_validation_service.errors.sample_errors import ( BufferInvalidError, @@ -8,6 +8,7 @@ ContainerNameRepeatedError, PoolApplicationError, PoolPriorityError, + SampleNameNotAvailableControlError, SampleNameNotAvailableError, VolumeRequiredError, WellFormatError, @@ -18,6 +19,7 @@ validate_concentration_interval_if_skip_rc, validate_concentration_required_if_skip_rc, validate_container_name_required, + validate_non_control_sample_names_available, validate_pools_contain_one_application, validate_pools_contain_one_priority, validate_sample_names_available, @@ -27,10 +29,13 @@ validate_well_position_rml_format, ) from cg.services.order_validation_service.workflows.fastq.models.order import FastqOrder +from cg.services.order_validation_service.workflows.fluffy.models.order import FluffyOrder from cg.services.order_validation_service.workflows.microsalt.models.order import MicrosaltOrder +from cg.services.order_validation_service.workflows.mutant.models.order import MutantOrder from cg.services.order_validation_service.workflows.rml.models.order import RmlOrder from cg.store.models import Sample from cg.store.store import Store +from tests.store_helpers import StoreHelpers def test_sample_names_available(valid_microsalt_order: MicrosaltOrder, sample_store: Store): @@ -69,6 +74,86 @@ def test_validate_tube_container_name_unique(valid_microsalt_order: MicrosaltOrd assert errors[1].sample_index == 1 +def test_validate_sample_names_available( + fluffy_order: FluffyOrder, store: Store, helpers: StoreHelpers +): + """ + Test that an order without any control sample that has a sample name already existing in the + database returns an error. + """ + + # GIVEN an order without control with a sample name already in the database + sample_name: str = fluffy_order.samples[0].name + helpers.add_sample( + store=store, + name=sample_name, + customer_id=fluffy_order.customer, + ) + + # WHEN validating that the sample names are available to the customer + errors = validate_sample_names_available(order=fluffy_order, store=store) + + # THEN an error should be returned + assert errors + + # THEN the error should concern the reused sample name + assert isinstance(errors[0], SampleNameNotAvailableError) + + +def test_validate_non_control_sample_names_available( + mutant_order: MutantOrder, store: Store, helpers: StoreHelpers +): + """ + Test that an order with a control sample name already existing in the database returns no error. + """ + + # GIVEN an order with a control sample + sample = mutant_order.samples[0] + assert sample.control == ControlEnum.positive + + # GIVEN that there is a sample in the database with the same name + helpers.add_sample( + store=store, + name=sample.name, + customer_id=mutant_order.customer, + ) + + # WHEN validating that the sample names are available to the customer + errors = validate_non_control_sample_names_available(order=mutant_order, store=store) + + # THEN no error should be returned because it is a control sample + assert not errors + + +def test_validate_non_control_sample_names_available_non_control_sample_name( + mutant_order: MutantOrder, store: Store, helpers: StoreHelpers +): + """ + Test that an order with a non-control sample name already existing in the database returns an + error. + """ + + # GIVEN an order with a non-control sample + sample = mutant_order.samples[2] + assert sample.control == ControlEnum.not_control + + # GIVEN that there is a sample in the database with the same name + helpers.add_sample( + store=store, + name=sample.name, + customer_id=mutant_order.customer, + ) + + # WHEN validating that the sample names are available to the customer + errors = validate_non_control_sample_names_available(order=mutant_order, store=store) + + # THEN an error should be returned + assert errors + + # THEN the error should concern the reused sample name + assert isinstance(errors[0], SampleNameNotAvailableControlError) + + def test_validate_well_position_format(valid_microsalt_order: MicrosaltOrder): # GIVEN an order with a sample with an invalid well position diff --git a/tests/services/order_validation_service/workflows/balsamic/test_case_sample_rules.py b/tests/services/order_validation_service/workflows/balsamic/test_rules.py similarity index 97% rename from tests/services/order_validation_service/workflows/balsamic/test_case_sample_rules.py rename to tests/services/order_validation_service/workflows/balsamic/test_rules.py index e81054a92c..0d73c09bcd 100644 --- a/tests/services/order_validation_service/workflows/balsamic/test_case_sample_rules.py +++ b/tests/services/order_validation_service/workflows/balsamic/test_rules.py @@ -1,6 +1,6 @@ from cg.services.order_validation_service.errors.case_sample_errors import CaptureKitMissingError from cg.services.order_validation_service.workflows.balsamic.models.order import BalsamicOrder -from cg.services.order_validation_service.workflows.balsamic.rules.case_sample.rules import ( +from cg.services.order_validation_service.workflows.balsamic.rules import ( validate_capture_kit_panel_requirement, ) from cg.store.models import Application