Coverage for testing/classes.py: 84%
43 statements
« prev ^ index » next coverage.py v7.8.0, created at 2026-02-05 06:46 -0600
« prev ^ index » next coverage.py v7.8.0, created at 2026-02-05 06:46 -0600
1"""
2crate_anon/testing/classes.py
4===============================================================================
6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CRATE.
11 CRATE is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
16 CRATE is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with CRATE. If not, see <https://www.gnu.org/licenses/>.
24===============================================================================
26Test classes for more complex tests e.g. where a database session is required.
28"""
30import logging
31from typing import Generator, TYPE_CHECKING
32from unittest import TestCase
34from faker import Faker
35import pytest
36from sqlalchemy.engine.base import Engine
38from crate_anon.testing.providers import register_all_providers
39from crate_anon.testing.factories import (
40 AnonTestBaseFactory,
41 SecretBaseFactory,
42 SourceTestBaseFactory,
43 set_sqlalchemy_session_on_all_factories,
44)
46if TYPE_CHECKING:
47 from sqlalchemy.orm.session import Session
50class CrateTestCase(TestCase):
51 def setUp(self) -> None:
52 super().setUp()
54 self.fake = Faker("en_GB")
55 register_all_providers(self.fake)
57 def assert_logged(
58 self,
59 logger_name: str,
60 level: int,
61 expected_message: str,
62 logging_cm: Generator[None, None, None],
63 ) -> None:
64 level_name = logging.getLevelName(level)
65 search = f"{level_name}:{logger_name}:{expected_message}"
67 self.assertTrue(
68 any(search in line for line in logging_cm.output),
69 msg=f"Failed to find '{search}' in {logging_cm.output}",
70 )
73class CommonDatabaseTestCase(CrateTestCase):
74 """
75 Base class for testing with a database. Do not inherit from this directly,
76 use one of the below subclasses instead, which will be associated with
77 pytest fixtures for engine, session etc.
78 """
80 anon_dbsession: "Session"
81 secret_dbsession: "Session"
82 source_dbsession: "Session"
83 anon_engine: Engine
84 secret_engine: Engine
85 source_engine: Engine
86 databases_on_disk: bool
87 anon_db_filename: str
88 secret_db_filename: str
89 source_db_filename: str
91 def setUp(self) -> None:
92 set_sqlalchemy_session_on_all_factories(
93 AnonTestBaseFactory, self.anon_dbsession
94 )
95 set_sqlalchemy_session_on_all_factories(
96 SecretBaseFactory, self.secret_dbsession
97 )
98 set_sqlalchemy_session_on_all_factories(
99 SourceTestBaseFactory, self.source_dbsession
100 )
102 def set_echo(self, echo: bool) -> None:
103 """
104 Changes the database echo status.
105 """
106 self.anon_engine.echo = echo
107 self.secret_engine.echo = echo
108 self.source_engine.echo = echo
111@pytest.mark.usefixtures("setup")
112class DatabaseTestCase(CommonDatabaseTestCase):
113 """
114 Base class for testing with a database.
116 The pytest fixtures defined in conftest.py run each test in a transaction,
117 rolling back the transaction at the end of the test. This all works fine,
118 unless one of the tests encounters a DatabaseError and the transaction
119 needs to be rolled back. In this case we need the approach taken by
120 SlowSecretDatabaseTestCase below.
121 """
124@pytest.mark.usefixtures("slow_secret_setup")
125class SlowSecretDatabaseTestCase(CommonDatabaseTestCase):
126 """
127 Like DatabaseTestCase but we create and drop all of the tables for the
128 secret database every time a test is run. Potentially slow if there are
129 lots of tables.
130 """
133class DemoDatabaseTestCase(DatabaseTestCase):
134 """
135 Base class for use with test factories such as
136 DemoPatientFactory
137 """