Coverage for testing/classes.py: 84%

43 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2026-02-05 06:46 -0600

1""" 

2crate_anon/testing/classes.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CRATE. 

10 

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. 

15 

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. 

20 

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/>. 

23 

24=============================================================================== 

25 

26Test classes for more complex tests e.g. where a database session is required. 

27 

28""" 

29 

30import logging 

31from typing import Generator, TYPE_CHECKING 

32from unittest import TestCase 

33 

34from faker import Faker 

35import pytest 

36from sqlalchemy.engine.base import Engine 

37 

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) 

45 

46if TYPE_CHECKING: 

47 from sqlalchemy.orm.session import Session 

48 

49 

50class CrateTestCase(TestCase): 

51 def setUp(self) -> None: 

52 super().setUp() 

53 

54 self.fake = Faker("en_GB") 

55 register_all_providers(self.fake) 

56 

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}" 

66 

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 ) 

71 

72 

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 """ 

79 

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 

90 

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 ) 

101 

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 

109 

110 

111@pytest.mark.usefixtures("setup") 

112class DatabaseTestCase(CommonDatabaseTestCase): 

113 """ 

114 Base class for testing with a database. 

115 

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 """ 

122 

123 

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 """ 

131 

132 

133class DemoDatabaseTestCase(DatabaseTestCase): 

134 """ 

135 Base class for use with test factories such as 

136 DemoPatientFactory 

137 """