diff --git a/coding_systems/base/tests/test_import_data_utils.py b/coding_systems/base/tests/test_import_data_utils.py index 046cb2b9..851d5c2b 100644 --- a/coding_systems/base/tests/test_import_data_utils.py +++ b/coding_systems/base/tests/test_import_data_utils.py @@ -9,29 +9,98 @@ from codelists.hierarchy import Hierarchy from codelists.models import Status from coding_systems.base.import_data_utils import update_codelist_version_compatibility +from coding_systems.base.tests.helpers import DynamicDatabaseTestCase from coding_systems.bnf.models import Concept from coding_systems.conftest import mock_migrate_coding_system from coding_systems.versioning.models import CodingSystemRelease, ReleaseState -@pytest.fixture -def bnf_csr(): - def _csr(valid_from=datetime(2022, 11, 1)): - return CodingSystemRelease.objects.create( - coding_system="bnf", - release_name="import-data", - valid_from=valid_from, - state=ReleaseState.READY, - ) - - return _csr +class BaseCodingSystemDynamicDatabaseTestCase(DynamicDatabaseTestCase): + @pytest.fixture + @staticmethod + def bnf_csr(): + def _csr(valid_from=datetime(2022, 11, 1)): + return CodingSystemRelease.objects.create( + coding_system="bnf", + release_name="import-data", + valid_from=valid_from, + state=ReleaseState.READY, + ) + + return _csr + + @pytest.fixture + @staticmethod + def bnf_review_version_with_search(bnf_version_with_search): + bnf_version_with_search.status = Status.UNDER_REVIEW + bnf_version_with_search.save() + yield bnf_version_with_search + + @pytest.fixture + def _get_bnf_release(self, _bnf_release): + self.bnf_release = _bnf_release + + @pytest.fixture + def _bnf_release(self, bnf_csr): + # Unusually, this setup needs to be done *before* the regular setUp() runs. + self.add_to_databases(self.db_alias) + # setup the database as a duplicate of the fixture one + csr = bnf_csr() + setup_db(csr) + yield csr + cleanup_db(csr) + @pytest.fixture + def _get_bnf_release_excl_last_concept(self, _bnf_release_excl_last_concept): + self.bnf_release = _bnf_release_excl_last_concept + + @pytest.fixture + def _bnf_release_excl_last_concept(self, bnf_csr): + # This addition needs to be done *before* the regular setUp() runs. + self.add_to_databases(self.db_alias) + # setup the database as a duplicate of the fixture one, omitting the last concept + csr = bnf_csr(datetime(2022, 10, 1)) + setup_db(csr, exclude_last_concept=True) + yield csr + cleanup_db(csr) -@pytest.fixture -def bnf_review_version_with_search(bnf_version_with_search): - bnf_version_with_search.status = Status.UNDER_REVIEW - bnf_version_with_search.save() - yield bnf_version_with_search + @pytest.fixture + def _bnf_releases(self, bnf_csr): + db_alias_additions = [ + "bnf_import-data_20190101", + "bnf_import-data_20220901", + "bnf_import-data_20221201", + ] + # This addition needs to be done *before* the regular setUp() runs. + self.add_to_databases(*db_alias_additions) + # setup multiple databases as duplicates of the fixture one, with different dates + + # earlier than the release the codelist versions are created with + csr_20190101 = bnf_csr(datetime(2019, 1, 1)) + setup_db(csr_20190101) + # earlier than bnf_release_excl_last_concept + csr_20220901 = bnf_csr(datetime(2022, 9, 1)) + setup_db(csr_20220901) + # later than bnf_release_excl_last_concept + csr_20221201 = bnf_csr(datetime(2022, 12, 1)) + setup_db(csr_20221201) + + yield + + for csr in [csr_20190101, csr_20220901, csr_20221201]: + cleanup_db(csr) + + @pytest.fixture + def _get_bnf_version_with_search(self, bnf_version_with_search): + self.bnf_version_with_search = bnf_version_with_search + + @pytest.fixture + def _get_bnf_review_version_with_search(self, bnf_review_version_with_search): + self.bnf_review_version_with_search = bnf_review_version_with_search + + @pytest.fixture + def _get_bnf_version_asthma(self, bnf_version_asthma): + self.bnf_version_asthma = bnf_version_asthma def setup_db(csr, exclude_last_concept=False): @@ -66,159 +135,151 @@ def cleanup_db(csr): cursor.execute("DROP TABLE bnf_concept") -@pytest.fixture -def bnf_release(bnf_csr): - # setup the database as a duplicate of the fixture one - csr = bnf_csr() - setup_db(csr) - yield csr - cleanup_db(csr) - - -@pytest.fixture -def bnf_release_excl_last_concept(bnf_csr): - # setup the database as a duplicate of the fixture one, omitting the last concept - csr = bnf_csr(datetime(2022, 10, 1)) - setup_db(csr, exclude_last_concept=True) - yield csr - cleanup_db(csr) - - -@pytest.fixture -def bnf_releases(bnf_csr): - # setup multiple databases as duplicates of the fixture one, with different dates - - # earlier than the release the codelist versions are created with - csr_20190101 = bnf_csr(datetime(2019, 1, 1)) - setup_db(csr_20190101) - # earlier than bnf_release_excl_last_concept - csr_20220901 = bnf_csr(datetime(2022, 9, 1)) - setup_db(csr_20220901) - # later than bnf_release_excl_last_concept - csr_20221201 = bnf_csr(datetime(2022, 12, 1)) - setup_db(csr_20221201) - - yield - - for csr in [csr_20190101, csr_20220901, csr_20221201]: - cleanup_db(csr) - - -def test_update_codelist_version_compatibility_no_searches( - bnf_version_asthma, coding_systems_tmp_path, bnf_release +class TestUpdateCodelistVersionCompatibilityNoSearches( + BaseCodingSystemDynamicDatabaseTestCase ): - # Draft versions are not checked for compatibility; this version is under review - assert bnf_version_asthma.status == Status.PUBLISHED - update_codelist_version_compatibility("bnf", bnf_release.database_alias) - # this version has no searches, but its hierarchy is identical - assert bnf_version_asthma.compatible_releases.exists() - + db_alias = "bnf_import-data_20221101" -def test_update_codelist_draft_version_excluded( - bnf_version_with_search, coding_systems_tmp_path, bnf_release -): - assert bnf_version_with_search.status == Status.DRAFT - update_codelist_version_compatibility("bnf", bnf_release.database_alias) - # this version has an identical hierarchy, and its search will return the same - # results with the new release, so it is compatible - assert not bnf_version_with_search.compatible_releases.exists() + @pytest.mark.usefixtures("_get_bnf_release", "_get_bnf_version_asthma") + def test_update_codelist_version_compatibility_no_searches(self): + # Draft versions are not checked for compatibility; this version is under review + assert self.bnf_version_asthma.status == Status.PUBLISHED + update_codelist_version_compatibility("bnf", self.bnf_release.database_alias) + # this version has no searches, but its hierarchy is identical + assert self.bnf_version_asthma.compatible_releases.exists() -def test_update_codelist_version_with_search( - bnf_review_version_with_search, coding_systems_tmp_path, bnf_release +class TestUpdateCodelistVersionDraftVersionExcluded( + BaseCodingSystemDynamicDatabaseTestCase ): - assert bnf_review_version_with_search.status == Status.UNDER_REVIEW - update_codelist_version_compatibility("bnf", bnf_release.database_alias) - # this version has an identical hierarchy, and its search will return the same - # results with the new release, so it is compatible - assert bnf_review_version_with_search.compatible_releases.first() == bnf_release + db_alias = "bnf_import-data_20221101" + + @pytest.mark.usefixtures("_get_bnf_release", "_get_bnf_version_with_search") + def test_update_codelist_draft_version_excluded(self): + assert self.bnf_version_with_search.status == Status.DRAFT + update_codelist_version_compatibility("bnf", self.bnf_release.database_alias) + # this version has an identical hierarchy, and its search will return the same + # results with the new release, so it is compatible + assert not self.bnf_version_with_search.compatible_releases.exists() + + +class TestUpdateCodelistVersionWithSearch(BaseCodingSystemDynamicDatabaseTestCase): + db_alias = "bnf_import-data_20221101" + + @pytest.mark.usefixtures("_get_bnf_release", "_get_bnf_review_version_with_search") + def test_update_codelist_version_with_search(self): + assert self.bnf_review_version_with_search.status == Status.UNDER_REVIEW + update_codelist_version_compatibility("bnf", self.bnf_release.database_alias) + # this version has an identical hierarchy, and its search will return the same + # results with the new release, so it is compatible + assert ( + self.bnf_review_version_with_search.compatible_releases.first() + == self.bnf_release + ) -def test_update_codelist_version_compatibility_with_mismatched_search( - bnf_review_version_with_search, - coding_systems_tmp_path, - bnf_release_excl_last_concept, +class TestUpdateCodelistVersionCompatibilityWithMismatchedSearch( + BaseCodingSystemDynamicDatabaseTestCase ): - # setup the db, but omit the last Concept from the existing db, so the search - # will return different results - update_codelist_version_compatibility( - "bnf", bnf_release_excl_last_concept.database_alias + db_alias = "bnf_import-data_20221001" + + @pytest.mark.usefixtures( + "_get_bnf_release_excl_last_concept", "_get_bnf_review_version_with_search" ) - # this version has a search, but the new release returns different results, - # so it is not compatible - assert not bnf_review_version_with_search.compatible_releases.exists() + def test_update_codelist_version_compatibility_with_mismatched_search(self): + # setup the db, but omit the last Concept from the existing db, so the search + # will return different results + update_codelist_version_compatibility("bnf", self.bnf_release.database_alias) + # this version has a search, but the new release returns different results, + # so it is not compatible + assert not self.bnf_review_version_with_search.compatible_releases.exists() -def test_update_codelist_version_compatibility_with_search_but_mismatched_hierarchy( - bnf_review_version_with_search, - coding_systems_tmp_path, - bnf_release_excl_last_concept, +class TestUpdateCodelistVersionCompatibilityWithSearchButMismatchedHierarchy( + BaseCodingSystemDynamicDatabaseTestCase ): - # setup the db, but omit the last Concept from the existing db - # Modify the search so that it returns just one code; the hierarchy will differ but - # search results will remain the same - bnf_review_version_with_search.searches.all().delete() - create_search( - draft=bnf_review_version_with_search, - code="0301012A0AAABAB", - codes=["0301012A0AAABAB"], - ) + db_alias = "bnf_import-data_20221001" - existing_hierarchy = bnf_review_version_with_search.hierarchy - hierarchy_with_new_release = Hierarchy.from_codes( - coding_system=CODING_SYSTEMS["bnf"]( - database_alias=bnf_release_excl_last_concept.database_alias - ), - codes=["0301012A0AAABAB"], + @pytest.mark.usefixtures( + "_get_bnf_release_excl_last_concept", "_get_bnf_review_version_with_search" ) - assert existing_hierarchy.nodes != hierarchy_with_new_release.nodes + def test_update_codelist_version_compatibility_with_search_but_mismatched_hierarchy( + self, + ): + # setup the db, but omit the last Concept from the existing db + # Modify the search so that it returns just one code; the hierarchy will differ but + # search results will remain the same + self.bnf_review_version_with_search.searches.all().delete() + create_search( + draft=self.bnf_review_version_with_search, + code="0301012A0AAABAB", + codes=["0301012A0AAABAB"], + ) - update_codelist_version_compatibility( - "bnf", bnf_release_excl_last_concept.database_alias - ) - # this version has a search that returns identical results in the new release - # but the hierarchies differ, so it is not compatible - assert not bnf_review_version_with_search.compatible_releases.exists() + existing_hierarchy = self.bnf_review_version_with_search.hierarchy + hierarchy_with_new_release = Hierarchy.from_codes( + coding_system=CODING_SYSTEMS["bnf"]( + database_alias=self.bnf_release.database_alias + ), + codes=["0301012A0AAABAB"], + ) + assert existing_hierarchy.nodes != hierarchy_with_new_release.nodes + + update_codelist_version_compatibility("bnf", self.bnf_release.database_alias) + # this version has a search that returns identical results in the new release + # but the hierarchies differ, so it is not compatible + assert not self.bnf_review_version_with_search.compatible_releases.exists() -def test_save_codelist_draft_updates_compatibility( - bnf_version_with_search, coding_systems_tmp_path, bnf_release +class TestSaveCodelistDraftUpdatesCompatibility( + BaseCodingSystemDynamicDatabaseTestCase ): - assert bnf_version_with_search.status == Status.DRAFT - assert not bnf_version_with_search.compatible_releases.exists() - save_draft_for_review(draft=bnf_version_with_search) + db_alias = "bnf_import-data_20221001" - assert bnf_version_with_search.compatible_releases.exists() + @pytest.mark.usefixtures("_get_bnf_release", "_get_bnf_version_with_search") + def test_save_codelist_draft_updates_compatibility(self): + assert self.bnf_version_with_search.status == Status.DRAFT + assert not self.bnf_version_with_search.compatible_releases.exists() + save_draft_for_review(draft=self.bnf_version_with_search) + assert self.bnf_version_with_search.compatible_releases.exists() -def test_save_codelist_draft_updates_compatibility_multiple_releases( - bnf_version_with_search, - coding_systems_tmp_path, - bnf_releases, - bnf_release_excl_last_concept, + +# This test is particularly unusual compared with the other coding systems +# tests that use dynamic databases, in that multiple databases are used. +class TestSaveCodelistDraftUpdatesCompatibilityMultipleReleases( + BaseCodingSystemDynamicDatabaseTestCase ): - # In this test, we have a draft created with release `bnf_test_20200101` - # The `bnf_releases` fixture gives us 3 other releases, which are duplicates - # of the data in `bnf_test_20200101`, so would all be compatible - # These have dates 20190901, 20221001, 20221201 - # bnf_release_excl_last_concept is not compatible, and has date 20221101 - - # When the draft version is saved for review, releases with more recent valid_from - # dates are checked for compatibility in order from oldest to newest. If a release - # is found to be incompatible, no later releases are checked. - # - # i.e. for this draft, releases are checked in this order: - # (bnf_import-data_20190901 is skipped because it's earlier than the draft's cs release) - # 1) bnf_import-data_20220901: compatible - # 2) bnf_import-data_20221101: incompatible - # 3) bnf_import-data_20221201: compatible (but later than an incompatible release, so not checked) - - assert bnf_version_with_search.status == Status.DRAFT - assert not bnf_version_with_search.compatible_releases.exists() - save_draft_for_review(draft=bnf_version_with_search) - - assert bnf_version_with_search.compatible_releases.count() == 1 - assert ( - bnf_version_with_search.compatible_releases.first().database_alias - == "bnf_import-data_20220901" + db_alias = "bnf_import-data_20190101" + + @pytest.mark.usefixtures( + "_bnf_releases", + "_get_bnf_release_excl_last_concept", + "_get_bnf_version_with_search", ) + def test_save_codelist_draft_updates_compatibility_multiple_releases(self): + # In this test, we have a draft created with release `bnf_test_20200101` + # The `bnf_releases` fixture gives us 3 other releases, which are duplicates + # of the data in `bnf_test_20200101`, so would all be compatible + # These have dates 20190901, 20221001, 20221201 + # bnf_release_excl_last_concept is not compatible, and has date 20221101 + + # When the draft version is saved for review, releases with more recent valid_from + # dates are checked for compatibility in order from oldest to newest. If a release + # is found to be incompatible, no later releases are checked. + # + # i.e. for this draft, releases are checked in this order: + # (bnf_import-data_20190901 is skipped because it's earlier than the draft's cs release) + # 1) bnf_import-data_20220901: compatible + # 2) bnf_import-data_20221101: incompatible + # 3) bnf_import-data_20221201: compatible (but later than an incompatible release, so not checked) + + assert self.bnf_version_with_search.status == Status.DRAFT + assert not self.bnf_version_with_search.compatible_releases.exists() + save_draft_for_review(draft=self.bnf_version_with_search) + + assert self.bnf_version_with_search.compatible_releases.count() == 1 + assert ( + self.bnf_version_with_search.compatible_releases.first().database_alias + == "bnf_import-data_20220901" + )