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

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

21 

22import os 

23import unittest 

24 

25import yaml 

26from pydantic import ValidationError 

27 

28from felis.datamodel import ( 

29 CheckConstraint, 

30 Column, 

31 DataType, 

32 ForeignKeyConstraint, 

33 Index, 

34 Schema, 

35 SchemaVersion, 

36 Table, 

37 UniqueConstraint, 

38) 

39 

40TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

41TEST_YAML = os.path.join(TESTDIR, "data", "test.yml") 

42 

43 

44class DataModelTestCase(unittest.TestCase): 

45 """Test validation a test schema from a YAML file.""" 

46 

47 schema_obj: Schema 

48 

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) 

54 

55 

56class ColumnTestCase(unittest.TestCase): 

57 """Test the `Column` class.""" 

58 

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() 

64 

65 # Setting only name should throw an exception. 

66 with self.assertRaises(ValidationError): 

67 Column(name="testColumn") 

68 

69 # Setting name and id should throw an exception from missing datatype. 

70 with self.assertRaises(ValidationError): 

71 Column(name="testColumn", id="#test_id") 

72 

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

79 

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

86 

87 # Setting a bad IVOA UCD should throw an error. 

88 with self.assertRaises(ValidationError): 

89 Column(**data, ivoa_ucd="bad") 

90 

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

94 

95 units_data = data.copy() 

96 

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) 

101 

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

106 

107 units_data = data.copy() 

108 

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) 

113 

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

118 

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) 

123 

124 

125class ConstraintTestCase(unittest.TestCase): 

126 """Test the `UniqueConstraint`, `Index`, `CheckCosntraint`, and 

127 `ForeignKeyConstraint` classes. 

128 """ 

129 

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() 

135 

136 # Setting only name should throw an exception. 

137 with self.assertRaises(ValidationError): 

138 UniqueConstraint(name="testConstraint") 

139 

140 # Setting name and id should throw an exception from missing columns. 

141 with self.assertRaises(ValidationError): 

142 UniqueConstraint(name="testConstraint", id="#test_id") 

143 

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']") 

150 

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']") 

157 

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() 

163 

164 # Setting only name should throw an exception. 

165 with self.assertRaises(ValidationError): 

166 Index(name="testConstraint") 

167 

168 # Setting name and id should throw an exception from missing columns. 

169 with self.assertRaises(ValidationError): 

170 Index(name="testConstraint", id="#test_id") 

171 

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']") 

178 

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']") 

185 

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

190 

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() 

196 

197 # Setting only name should throw an exception. 

198 with self.assertRaises(ValidationError): 

199 ForeignKeyConstraint(name="testConstraint") 

200 

201 # Setting name and id should throw an exception from missing columns. 

202 with self.assertRaises(ValidationError): 

203 ForeignKeyConstraint(name="testConstraint", id="#test_id") 

204 

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 ) 

216 

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 ) 

231 

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() 

237 

238 # Setting only name should throw an exception. 

239 with self.assertRaises(ValidationError): 

240 CheckConstraint(name="testConstraint") 

241 

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

246 

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

253 

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

264 

265 

266class TableTestCase(unittest.TestCase): 

267 """Test the `Table` class.""" 

268 

269 def test_validation(self) -> None: 

270 # Default initialization should throw an exception. 

271 with self.assertRaises(ValidationError): 

272 Table() 

273 

274 # Setting only name should throw an exception. 

275 with self.assertRaises(ValidationError): 

276 Table(name="testTable") 

277 

278 # Setting name and id should throw an exception from missing columns. 

279 with self.assertRaises(ValidationError): 

280 Index(name="testTable", id="#test_id") 

281 

282 testCol = Column(name="testColumn", id="#test_id", datatype="string") 

283 

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']") 

290 

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]) 

295 

296 

297class SchemaTestCase(unittest.TestCase): 

298 """Test the `Schema` class.""" 

299 

300 def test_validation(self) -> None: 

301 # Default initialization should throw an exception. 

302 with self.assertRaises(ValidationError): 

303 Schema() 

304 

305 # Setting only name should throw an exception. 

306 with self.assertRaises(ValidationError): 

307 Schema(name="testSchema") 

308 

309 # Setting name and id should throw an exception from missing columns. 

310 with self.assertRaises(ValidationError): 

311 Schema(name="testSchema", id="#test_id") 

312 

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]) 

315 

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']") 

322 

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]) 

327 

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]) 

331 

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 ) 

348 

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]) 

354 

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) 

358 

359 

360class SchemaVersionTest(unittest.TestCase): 

361 """Test the `SchemaVersion` class.""" 

362 

363 def test_validation(self) -> None: 

364 # Default initialization should throw an exception. 

365 with self.assertRaises(ValidationError): 

366 SchemaVersion() 

367 

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

372 

373 

374if __name__ == "__main__": 374 ↛ 375line 374 didn't jump to line 375, because the condition on line 374 was never true

375 unittest.main()