Coverage for tests/test_datamodel.py: 11%
169 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-23 10:44 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-23 10:44 +0000
1# This file is part of felis.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22import os
23import unittest
25import yaml
26from pydantic import ValidationError
28from felis.datamodel import (
29 CheckConstraint,
30 Column,
31 DataType,
32 ForeignKeyConstraint,
33 Index,
34 Schema,
35 SchemaVersion,
36 Table,
37 UniqueConstraint,
38)
40TESTDIR = os.path.abspath(os.path.dirname(__file__))
41TEST_YAML = os.path.join(TESTDIR, "data", "test.yml")
44class DataModelTestCase(unittest.TestCase):
45 """Test validation a test schema from a YAML file."""
47 schema_obj: Schema
49 def test_validation(self) -> None:
50 """Load test file and validate it using the data model."""
51 with open(TEST_YAML) as test_yaml:
52 data = yaml.safe_load(test_yaml)
53 self.schema_obj = Schema.model_validate(data)
56class ColumnTestCase(unittest.TestCase):
57 """Test the `Column` class."""
59 def test_validation(self) -> None:
60 """Test validation of the `Column` class."""
61 # Default initialization should throw an exception.
62 with self.assertRaises(ValidationError):
63 Column()
65 # Setting only name should throw an exception.
66 with self.assertRaises(ValidationError):
67 Column(name="testColumn")
69 # Setting name and id should throw an exception from missing datatype.
70 with self.assertRaises(ValidationError):
71 Column(name="testColumn", id="#test_id")
73 # Setting name, id, and datatype should not throw an exception and
74 # should load data correctly.
75 col = Column(name="testColumn", id="#test_id", datatype="string")
76 self.assertEqual(col.name, "testColumn", "name should be 'testColumn'")
77 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
78 self.assertEqual(col.datatype, DataType.STRING.value, "datatype should be 'DataType.STRING'")
80 # Creating from data dictionary should work and load data correctly.
81 data = {"name": "testColumn", "id": "#test_id", "datatype": "string"}
82 col = Column(**data)
83 self.assertEqual(col.name, "testColumn", "name should be 'testColumn'")
84 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
85 self.assertEqual(col.datatype, DataType.STRING.value, "datatype should be 'DataType.STRING'")
87 # Setting a bad IVOA UCD should throw an error.
88 with self.assertRaises(ValidationError):
89 Column(**data, ivoa_ucd="bad")
91 # Setting a valid IVOA UCD should not throw an error.
92 col = Column(**data, ivoa_ucd="meta.id")
93 self.assertEqual(col.ivoa_ucd, "meta.id", "ivoa_ucd should be 'meta.id'")
95 units_data = data.copy()
97 # Setting a bad IVOA unit should throw an error.
98 units_data["ivoa:unit"] = "bad"
99 with self.assertRaises(ValidationError):
100 Column(**units_data)
102 # Setting a valid IVOA unit should not throw an error.
103 units_data["ivoa:unit"] = "m"
104 col = Column(**units_data)
105 self.assertEqual(col.ivoa_unit, "m", "ivoa_unit should be 'm'")
107 units_data = data.copy()
109 # Setting a bad FITS TUNIT should throw an error.
110 units_data["fits:tunit"] = "bad"
111 with self.assertRaises(ValidationError):
112 Column(**units_data)
114 # Setting a valid FITS TUNIT should not throw an error.
115 units_data["fits:tunit"] = "m"
116 col = Column(**units_data)
117 self.assertEqual(col.fits_tunit, "m", "fits_tunit should be 'm'")
119 # Setting both IVOA unit and FITS TUNIT should throw an error.
120 units_data["ivoa:unit"] = "m"
121 with self.assertRaises(ValidationError):
122 Column(**units_data)
125class ConstraintTestCase(unittest.TestCase):
126 """Test the `UniqueConstraint`, `Index`, `CheckCosntraint`, and
127 `ForeignKeyConstraint` classes.
128 """
130 def test_unique_constraint_validation(self) -> None:
131 """Test validation of the `UniqueConstraint` class."""
132 # Default initialization should throw an exception.
133 with self.assertRaises(ValidationError):
134 UniqueConstraint()
136 # Setting only name should throw an exception.
137 with self.assertRaises(ValidationError):
138 UniqueConstraint(name="testConstraint")
140 # Setting name and id should throw an exception from missing columns.
141 with self.assertRaises(ValidationError):
142 UniqueConstraint(name="testConstraint", id="#test_id")
144 # Setting name, id, and columns should not throw an exception and
145 # should load data correctly.
146 col = UniqueConstraint(name="testConstraint", id="#test_id", columns=["testColumn"])
147 self.assertEqual(col.name, "testConstraint", "name should be 'testConstraint'")
148 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
149 self.assertEqual(col.columns, ["testColumn"], "columns should be ['testColumn']")
151 # Creating from data dictionary should work and load data correctly.
152 data = {"name": "testConstraint", "id": "#test_id", "columns": ["testColumn"]}
153 col = UniqueConstraint(**data)
154 self.assertEqual(col.name, "testConstraint", "name should be 'testConstraint'")
155 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
156 self.assertEqual(col.columns, ["testColumn"], "columns should be ['testColumn']")
158 def test_index_validation(self) -> None:
159 """Test validation of the `Index` class."""
160 # Default initialization should throw an exception.
161 with self.assertRaises(ValidationError):
162 Index()
164 # Setting only name should throw an exception.
165 with self.assertRaises(ValidationError):
166 Index(name="testConstraint")
168 # Setting name and id should throw an exception from missing columns.
169 with self.assertRaises(ValidationError):
170 Index(name="testConstraint", id="#test_id")
172 # Setting name, id, and columns should not throw an exception and
173 # should load data correctly.
174 col = Index(name="testConstraint", id="#test_id", columns=["testColumn"])
175 self.assertEqual(col.name, "testConstraint", "name should be 'testConstraint'")
176 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
177 self.assertEqual(col.columns, ["testColumn"], "columns should be ['testColumn']")
179 # Creating from data dictionary should work and load data correctly.
180 data = {"name": "testConstraint", "id": "#test_id", "columns": ["testColumn"]}
181 col = Index(**data)
182 self.assertEqual(col.name, "testConstraint", "name should be 'testConstraint'")
183 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
184 self.assertEqual(col.columns, ["testColumn"], "columns should be ['testColumn']")
186 # Setting both columns and expressions on an index should throw an
187 # exception.
188 with self.assertRaises(ValidationError):
189 Index(name="testConstraint", id="#test_id", columns=["testColumn"], expressions=["1+2"])
191 def test_foreign_key_validation(self) -> None:
192 """Test validation of the `ForeignKeyConstraint` class."""
193 # Default initialization should throw an exception.
194 with self.assertRaises(ValidationError):
195 ForeignKeyConstraint()
197 # Setting only name should throw an exception.
198 with self.assertRaises(ValidationError):
199 ForeignKeyConstraint(name="testConstraint")
201 # Setting name and id should throw an exception from missing columns.
202 with self.assertRaises(ValidationError):
203 ForeignKeyConstraint(name="testConstraint", id="#test_id")
205 # Setting name, id, and columns should not throw an exception and
206 # should load data correctly.
207 col = ForeignKeyConstraint(
208 name="testConstraint", id="#test_id", columns=["testColumn"], referenced_columns=["testColumn"]
209 )
210 self.assertEqual(col.name, "testConstraint", "name should be 'testConstraint'")
211 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
212 self.assertEqual(col.columns, ["testColumn"], "columns should be ['testColumn']")
213 self.assertEqual(
214 col.referenced_columns, ["testColumn"], "referenced_columns should be ['testColumn']"
215 )
217 # Creating from data dictionary should work and load data correctly.
218 data = {
219 "name": "testConstraint",
220 "id": "#test_id",
221 "columns": ["testColumn"],
222 "referenced_columns": ["testColumn"],
223 }
224 col = ForeignKeyConstraint(**data)
225 self.assertEqual(col.name, "testConstraint", "name should be 'testConstraint'")
226 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
227 self.assertEqual(col.columns, ["testColumn"], "columns should be ['testColumn']")
228 self.assertEqual(
229 col.referenced_columns, ["testColumn"], "referenced_columns should be ['testColumn']"
230 )
232 def test_check_constraint_validation(self) -> None:
233 """Check validation of the `CheckConstraint` class."""
234 # Default initialization should throw an exception.
235 with self.assertRaises(ValidationError):
236 CheckConstraint()
238 # Setting only name should throw an exception.
239 with self.assertRaises(ValidationError):
240 CheckConstraint(name="testConstraint")
242 # Setting name and id should throw an exception from missing
243 # expression.
244 with self.assertRaises(ValidationError):
245 CheckConstraint(name="testConstraint", id="#test_id")
247 # Setting name, id, and expression should not throw an exception and
248 # should load data correctly.
249 col = CheckConstraint(name="testConstraint", id="#test_id", expression="1+2")
250 self.assertEqual(col.name, "testConstraint", "name should be 'testConstraint'")
251 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
252 self.assertEqual(col.expression, "1+2", "expression should be '1+2'")
254 # Creating from data dictionary should work and load data correctly.
255 data = {
256 "name": "testConstraint",
257 "id": "#test_id",
258 "expression": "1+2",
259 }
260 col = CheckConstraint(**data)
261 self.assertEqual(col.name, "testConstraint", "name should be 'testConstraint'")
262 self.assertEqual(col.id, "#test_id", "id should be '#test_id'")
263 self.assertEqual(col.expression, "1+2", "expression should be '1+2'")
266class TableTestCase(unittest.TestCase):
267 """Test the `Table` class."""
269 def test_validation(self) -> None:
270 # Default initialization should throw an exception.
271 with self.assertRaises(ValidationError):
272 Table()
274 # Setting only name should throw an exception.
275 with self.assertRaises(ValidationError):
276 Table(name="testTable")
278 # Setting name and id should throw an exception from missing columns.
279 with self.assertRaises(ValidationError):
280 Index(name="testTable", id="#test_id")
282 testCol = Column(name="testColumn", id="#test_id", datatype="string")
284 # Setting name, id, and columns should not throw an exception and
285 # should load data correctly.
286 tbl = Table(name="testTable", id="#test_id", columns=[testCol])
287 self.assertEqual(tbl.name, "testTable", "name should be 'testTable'")
288 self.assertEqual(tbl.id, "#test_id", "id should be '#test_id'")
289 self.assertEqual(tbl.columns, [testCol], "columns should be ['testColumn']")
291 # Creating a table with duplicate column names should raise an
292 # exception.
293 with self.assertRaises(ValidationError):
294 Table(name="testTable", id="#test_id", columns=[testCol, testCol])
297class SchemaTestCase(unittest.TestCase):
298 """Test the `Schema` class."""
300 def test_validation(self) -> None:
301 # Default initialization should throw an exception.
302 with self.assertRaises(ValidationError):
303 Schema()
305 # Setting only name should throw an exception.
306 with self.assertRaises(ValidationError):
307 Schema(name="testSchema")
309 # Setting name and id should throw an exception from missing columns.
310 with self.assertRaises(ValidationError):
311 Schema(name="testSchema", id="#test_id")
313 test_col = Column(name="testColumn", id="#test_col_id", datatype="string")
314 test_tbl = Table(name="testTable", id="#test_tbl_id", columns=[test_col])
316 # Setting name, id, and columns should not throw an exception and
317 # should load data correctly.
318 sch = Schema(name="testSchema", id="#test_sch_id", tables=[test_tbl])
319 self.assertEqual(sch.name, "testSchema", "name should be 'testSchema'")
320 self.assertEqual(sch.id, "#test_sch_id", "id should be '#test_sch_id'")
321 self.assertEqual(sch.tables, [test_tbl], "tables should be ['testTable']")
323 # Creating a schema with duplicate table names should raise an
324 # exception.
325 with self.assertRaises(ValidationError):
326 Schema(name="testSchema", id="#test_id", tables=[test_tbl, test_tbl])
328 # Using an undefined YAML field should raise an exception.
329 with self.assertRaises(ValidationError):
330 Schema(**{"name": "testSchema", "id": "#test_sch_id", "bad_field": "1234"}, tables=[test_tbl])
332 # Creating a schema containing duplicate IDs should raise an error.
333 with self.assertRaises(ValidationError):
334 Schema(
335 name="testSchema",
336 id="#test_sch_id",
337 tables=[
338 Table(
339 name="testTable",
340 id="#test_tbl_id",
341 columns=[
342 Column(name="testColumn", id="#test_col_id", datatype="string"),
343 Column(name="testColumn2", id="#test_col_id", datatype="string"),
344 ],
345 )
346 ],
347 )
349 def test_id_map(self) -> None:
350 """Test that the id_map is properly populated."""
351 test_col = Column(name="testColumn", id="#test_col_id", datatype="string")
352 test_tbl = Table(name="testTable", id="#test_table_id", columns=[test_col])
353 sch = Schema(name="testSchema", id="#test_schema_id", tables=[test_tbl])
355 for id in ["#test_col_id", "#test_table_id", "#test_schema_id"]:
356 # Test that the id_map contains the expected id.
357 sch.get_object_by_id(id)
360class SchemaVersionTest(unittest.TestCase):
361 """Test the `SchemaVersion` class."""
363 def test_validation(self) -> None:
364 # Default initialization should throw an exception.
365 with self.assertRaises(ValidationError):
366 SchemaVersion()
368 # Setting current should not throw an exception and should load data
369 # correctly.
370 sv = SchemaVersion(current="1.0.0")
371 self.assertEqual(sv.current, "1.0.0", "current should be '1.0.0'")
374if __name__ == "__main__": 374 ↛ 375line 374 didn't jump to line 375, because the condition on line 374 was never true
375 unittest.main()