Coverage for cc_modules/tests/cc_sqla_coltypes_tests.py: 25%
114 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/cc_modules/tests/cc_sqla_coltypes_tests.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
13 CamCOPS is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
18 CamCOPS is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
26===============================================================================
27"""
29import datetime
30from typing import Union
31import unittest
33from cardinal_pythonlib.datetimefunc import coerce_to_pendulum
34from cardinal_pythonlib.sqlalchemy.session import SQLITE_MEMORY_URL
35import pendulum
36from pendulum import DateTime as Pendulum, Duration
37import phonenumbers
38from semantic_version import Version
39from sqlalchemy.engine import create_engine
40from sqlalchemy.sql.expression import select
41from sqlalchemy.sql.functions import func
42from sqlalchemy.sql.schema import Column, MetaData, Table
43from sqlalchemy.sql.sqltypes import DateTime, Integer
45from camcops_server.cc_modules.cc_sqla_coltypes import (
46 isotzdatetime_to_utcdatetime,
47 PendulumDateTimeAsIsoTextColType,
48 PendulumDurationAsIsoTextColType,
49 PhoneNumberColType,
50 SemanticVersionColType,
51 unknown_field_to_utcdatetime,
52)
55# =============================================================================
56# Unit testing
57# =============================================================================
60class SqlaColtypesTest(unittest.TestCase):
61 """
62 Unit tests.
63 """
65 # don't inherit from ExtendedTestCase; circular import
67 def setUp(self) -> None:
68 super().setUp()
70 engine = create_engine(SQLITE_MEMORY_URL, echo=True)
71 self.meta = MetaData()
72 self.meta.bind = engine # adds execute() method to select() etc.
73 # ... https://docs.sqlalchemy.org/en/latest/core/connections.html
75 @staticmethod
76 def _assert_dt_equal(
77 a: Union[datetime.datetime, Pendulum],
78 b: Union[datetime.datetime, Pendulum],
79 ) -> None:
80 # Accept that one may have been truncated or rounded to milliseconds.
81 a = coerce_to_pendulum(a)
82 b = coerce_to_pendulum(b)
83 diff = a - b
84 assert diff.microseconds < 1000, f"{a!r} != {b!r}"
86 def test_iso_datetime_field(self) -> None:
87 id_colname = "id"
88 dt_local_colname = "dt_local"
89 dt_utc_colname = "dt_utc"
90 iso_colname = "iso"
91 id_col = Column(id_colname, Integer, primary_key=True)
92 dt_local_col = Column(dt_local_colname, DateTime)
93 dt_utc_col = Column(dt_utc_colname, DateTime)
94 iso_col = Column(iso_colname, PendulumDateTimeAsIsoTextColType)
96 table = Table(
97 "testtable", self.meta, id_col, dt_local_col, dt_utc_col, iso_col
98 )
99 table.create()
101 now = Pendulum.now()
102 now_utc = now.in_tz(pendulum.UTC)
103 yesterday = now.subtract(days=1)
104 yesterday_utc = yesterday.in_tz(pendulum.UTC)
106 table.insert().values(
107 [
108 {
109 id_colname: 1,
110 dt_local_colname: now,
111 dt_utc_colname: now_utc,
112 iso_colname: now,
113 },
114 {
115 id_colname: 2,
116 dt_local_colname: yesterday,
117 dt_utc_colname: yesterday_utc,
118 iso_colname: yesterday,
119 },
120 ]
121 ).execute()
122 select_fields = [
123 id_col,
124 dt_local_col,
125 dt_utc_col,
126 iso_col,
127 func.length(dt_local_col).label("len_dt_local_col"),
128 func.length(dt_utc_col).label("len_dt_utc_col"),
129 func.length(iso_col).label("len_iso_col"),
130 isotzdatetime_to_utcdatetime(iso_col).label("iso_to_utcdt"),
131 unknown_field_to_utcdatetime(dt_utc_col).label(
132 "uk_utcdt_to_utcdt"
133 ),
134 unknown_field_to_utcdatetime(iso_col).label("uk_iso_to_utc_dt"),
135 ]
136 rows = list(
137 select(select_fields).select_from(table).order_by(id_col).execute()
138 )
139 self._assert_dt_equal(rows[0][dt_local_col], now)
140 self._assert_dt_equal(rows[0][dt_utc_col], now_utc)
141 self._assert_dt_equal(rows[0][iso_colname], now)
142 self._assert_dt_equal(rows[0]["iso_to_utcdt"], now_utc)
143 self._assert_dt_equal(rows[0]["uk_utcdt_to_utcdt"], now_utc)
144 self._assert_dt_equal(rows[0]["uk_iso_to_utc_dt"], now_utc)
145 self._assert_dt_equal(rows[1][dt_local_col], yesterday)
146 self._assert_dt_equal(rows[1][dt_utc_col], yesterday_utc)
147 self._assert_dt_equal(rows[1][iso_colname], yesterday)
148 self._assert_dt_equal(rows[1]["iso_to_utcdt"], yesterday_utc)
149 self._assert_dt_equal(rows[1]["uk_utcdt_to_utcdt"], yesterday_utc)
150 self._assert_dt_equal(rows[1]["uk_iso_to_utc_dt"], yesterday_utc)
152 @staticmethod
153 def _assert_duration_equal(a: Duration, b: Duration) -> None:
154 assert a.total_seconds() == b.total_seconds(), f"{a!r} != {b!r}"
156 def test_iso_duration_field(self) -> None:
157 id_colname = "id"
158 duration_colname = "duration_iso"
159 id_col = Column(id_colname, Integer, primary_key=True)
160 duration_col = Column(
161 duration_colname, PendulumDurationAsIsoTextColType
162 )
164 table = Table("testtable", self.meta, id_col, duration_col)
165 table.create()
167 d1 = Duration(years=1, months=3, seconds=3, microseconds=4)
168 d2 = Duration(seconds=987.654321)
169 d3 = Duration(days=-5)
171 table.insert().values(
172 [
173 {id_colname: 1, duration_colname: d1},
174 {id_colname: 2, duration_colname: d2},
175 {id_colname: 3, duration_colname: d3},
176 ]
177 ).execute()
178 select_fields = [id_col, duration_col]
179 rows = list(
180 select(select_fields).select_from(table).order_by(id_col).execute()
181 )
182 self._assert_duration_equal(rows[0][duration_col], d1)
183 self._assert_duration_equal(rows[1][duration_col], d2)
184 self._assert_duration_equal(rows[2][duration_col], d3)
186 @staticmethod
187 def _assert_version_equal(a: Version, b: Version) -> None:
188 assert a == b, f"{a!r} != {b!r}"
190 def test_semantic_version_field(self) -> None:
191 id_colname = "id"
192 version_colname = "version"
193 id_col = Column(id_colname, Integer, primary_key=True)
194 version_col = Column(version_colname, SemanticVersionColType)
196 table = Table("testtable", self.meta, id_col, version_col)
197 table.create()
199 v1 = Version("1.1.0")
200 v2 = Version("2.0.1")
201 v3 = Version("14.0.0")
203 table.insert().values(
204 [
205 {id_colname: 1, version_colname: v1},
206 {id_colname: 2, version_colname: v2},
207 {id_colname: 3, version_colname: v3},
208 ]
209 ).execute()
210 select_fields = [id_col, version_col]
211 rows = list(
212 select(select_fields).select_from(table).order_by(id_col).execute()
213 )
214 self._assert_version_equal(rows[0][version_col], v1)
215 self._assert_version_equal(rows[1][version_col], v2)
216 self._assert_version_equal(rows[2][version_col], v3)
218 def test_phone_number_field(self) -> None:
219 id_colname = "id"
220 phone_number_colname = "phone_number"
221 id_col = Column(id_colname, Integer, primary_key=True)
222 phone_number_col = Column(phone_number_colname, PhoneNumberColType)
224 table = Table("testtable", self.meta, id_col, phone_number_col)
225 table.create()
227 # https://en.wikipedia.org/wiki/Fictitious_telephone_number
228 p1 = phonenumbers.parse("+44 (0)113 496 0123")
229 p2 = phonenumbers.parse("+33 1 99 00 12 34 56")
230 p3 = phonenumbers.parse("07700 900123", "GB")
231 p4 = None
233 table.insert().values(
234 [
235 {id_colname: 1, phone_number_colname: p1},
236 {id_colname: 2, phone_number_colname: p2},
237 {id_colname: 3, phone_number_colname: p3},
238 {id_colname: 4, phone_number_colname: p4},
239 ]
240 ).execute()
241 select_fields = [id_col, phone_number_col]
242 rows = list(
243 select(select_fields).select_from(table).order_by(id_col).execute()
244 )
245 self.assertEqual(rows[0][phone_number_col], p1)
246 self.assertEqual(rows[1][phone_number_col], p2)
247 self.assertEqual(rows[2][phone_number_col], p3)
248 self.assertIsNone(rows[3][phone_number_col])