Coverage for tests/test_cli.py: 94%

267 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-11-04 01:32 -0800

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 logging 

23import os 

24import shutil 

25import tempfile 

26import unittest 

27from typing import Any 

28 

29import yaml 

30from sqlalchemy import create_engine, text 

31 

32import felis.tap_schema as tap_schema 

33from felis.datamodel import Schema 

34from felis.metadata import MetaDataBuilder 

35from felis.tests.run_cli import run_cli 

36 

37TEST_DIR = os.path.abspath(os.path.dirname(__file__)) 

38TEST_YAML = os.path.join(TEST_DIR, "data", "test.yml") 

39TEST_SALES_YAML = os.path.join(TEST_DIR, "data", "sales.yaml") 

40 

41 

42class CliTestCase(unittest.TestCase): 

43 """Tests for CLI commands.""" 

44 

45 def setUp(self) -> None: 

46 """Set up a temporary directory for tests.""" 

47 self.tmpdir = tempfile.mkdtemp(dir=TEST_DIR) 

48 self.sqlite_url = f"sqlite:///{self.tmpdir}/db.sqlite3" 

49 print(f"Using temporary directory: {self.tmpdir}") 

50 

51 def tearDown(self) -> None: 

52 """Clean up temporary directory.""" 

53 shutil.rmtree(self.tmpdir, ignore_errors=True) 

54 

55 def test_invalid_command(self) -> None: 

56 """Test for invalid command.""" 

57 run_cli(["invalid"], expect_error=True) 

58 

59 def test_help(self) -> None: 

60 """Test for help command.""" 

61 run_cli(["--help"], print_output=True) 

62 

63 def test_create(self) -> None: 

64 """Test for create command.""" 

65 run_cli(["create", f"--engine-url={self.sqlite_url}", TEST_YAML]) 

66 

67 def test_create_with_echo(self) -> None: 

68 """Test for create command.""" 

69 run_cli(["create", "--echo", f"--engine-url={self.sqlite_url}", TEST_YAML]) 

70 

71 def test_create_with_dry_run(self) -> None: 

72 """Test for ``create --dry-run`` command.""" 

73 run_cli(["create", "--schema-name=main", f"--engine-url={self.sqlite_url}", "--dry-run", TEST_YAML]) 

74 

75 def test_create_with_ignore_constraints(self) -> None: 

76 """Test ``--ignore-constraints`` flag of ``create`` command.""" 

77 run_cli( 

78 [ 

79 "create", 

80 "--schema-name=main", 

81 "--ignore-constraints", 

82 f"--engine-url={self.sqlite_url}", 

83 "--dry-run", 

84 TEST_YAML, 

85 ] 

86 ) 

87 

88 def test_browser_with_config(self) -> None: 

89 """Test browser command with a site configuration file.""" 

90 output_dir = os.path.join(self.tmpdir, "site") 

91 for name in ("theme.css", "local.css"): 

92 with open(os.path.join(self.tmpdir, name), "w", encoding="utf-8") as f: 

93 f.write(":root { --color-link: red; }\n") 

94 config_path = os.path.join(self.tmpdir, "site.yaml") 

95 with open(config_path, "w", encoding="utf-8") as f: 

96 f.write("title: SDM Schemas\nstylesheets:\n - theme.css\n - local.css\n") 

97 run_cli(["browser", "--output-dir", output_dir, "--config", config_path, TEST_YAML]) 

98 self.assertTrue(os.path.exists(os.path.join(output_dir, "assets", "custom-1-theme.css"))) 

99 self.assertTrue(os.path.exists(os.path.join(output_dir, "assets", "custom-2-local.css"))) 

100 with open(os.path.join(output_dir, "index.html"), encoding="utf-8") as f: 

101 self.assertIn("<title>SDM Schemas</title>", f.read()) 

102 

103 def test_browser_with_invalid_config(self) -> None: 

104 """Test browser command with an invalid site configuration file.""" 

105 config_path = os.path.join(self.tmpdir, "site.yaml") 

106 with open(config_path, "w", encoding="utf-8") as f: 

107 f.write("unknown: setting\n") 

108 run_cli( 

109 [ 

110 "browser", 

111 "--output-dir", 

112 os.path.join(self.tmpdir, "site"), 

113 "--config", 

114 config_path, 

115 TEST_YAML, 

116 ], 

117 expect_error=True, 

118 ) 

119 

120 def test_browser_with_malformed_config(self) -> None: 

121 """Test browser command with a site configuration file that isn't 

122 valid YAML: it fails with an error message, not a traceback. 

123 """ 

124 config_path = os.path.join(self.tmpdir, "site.yaml") 

125 with open(config_path, "w", encoding="utf-8") as f: 

126 f.write("title: [unclosed\n") 

127 run_cli( 

128 [ 

129 "browser", 

130 "--output-dir", 

131 os.path.join(self.tmpdir, "site"), 

132 "--config", 

133 config_path, 

134 TEST_YAML, 

135 ], 

136 expect_error=True, 

137 ) 

138 

139 def test_browser_resource(self) -> None: 

140 """Test browser command with a schema given as a resource URI.""" 

141 output_dir = os.path.join(self.tmpdir, "site") 

142 run_cli( 

143 [ 

144 "browser", 

145 "--output-dir", 

146 output_dir, 

147 "resource://felis/config/tap_schema/tap_schema_std.yaml", 

148 ], 

149 id_generation=True, 

150 ) 

151 self.assertTrue(os.path.isfile(os.path.join(output_dir, "schemas", "tap-schema", "index.html"))) 

152 

153 def test_browser_missing_file(self) -> None: 

154 """Test browser command with a schema file that doesn't exist.""" 

155 run_cli( 

156 [ 

157 "browser", 

158 "--output-dir", 

159 os.path.join(self.tmpdir, "site"), 

160 os.path.join(self.tmpdir, "missing.yaml"), 

161 ], 

162 expect_error=True, 

163 ) 

164 

165 def test_browser_unresolved_column(self) -> None: 

166 """Test browser command with an index on a column that isn't in the 

167 schema: it fails with an error message. 

168 """ 

169 schema_path = os.path.join(self.tmpdir, "refs.yaml") 

170 with open(schema_path, "w", encoding="utf-8") as f: 

171 f.write( 

172 "name: refs\n" 

173 "'@id': '#refs'\n" 

174 "tables:\n" 

175 " - name: data\n" 

176 " '@id': '#data'\n" 

177 " columns:\n" 

178 " - {name: id, '@id': '#data.id', datatype: int, description: Key}\n" 

179 " indexes:\n" 

180 " - {name: idx_missing, '@id': '#idx_missing', columns: ['#data.missing']}\n" 

181 ) 

182 run_cli( 

183 ["browser", "--output-dir", os.path.join(self.tmpdir, "site"), schema_path], expect_error=True 

184 ) 

185 

186 def test_browser_non_yaml_file(self) -> None: 

187 """Test browser command with an input file that isn't a schema.""" 

188 bad_file = os.path.join(self.tmpdir, "not_yaml.txt") 

189 with open(bad_file, "w", encoding="utf-8") as f: 

190 f.write("hello") 

191 run_cli( 

192 ["browser", "--output-dir", os.path.join(self.tmpdir, "site"), bad_file], 

193 expect_error=True, 

194 ) 

195 

196 def test_validate(self) -> None: 

197 """Test validate command.""" 

198 run_cli(["validate", TEST_YAML]) 

199 

200 def test_validate_with_log_file(self) -> None: 

201 """Test validate command with log file.""" 

202 log_file = os.path.join(self.tmpdir, "validate.log") 

203 run_cli([f"--log-file={log_file}", "validate", TEST_YAML], log_level=logging.DEBUG, print_cmd=True) 

204 if not os.path.exists(log_file): 204 ↛ 205line 204 didn't jump to line 205 because the condition on line 204 was never true

205 self.fail("Log file was not created") 

206 if os.path.getsize(log_file) == 0: 206 ↛ 207line 206 didn't jump to line 207 because the condition on line 206 was never true

207 self.fail("Log file is empty") 

208 

209 def test_validate_with_id_generation(self) -> None: 

210 """Test that loading a schema with IDs works if ID generation is 

211 enabled. This is the default behavior. 

212 """ 

213 test_yaml = os.path.join(TEST_DIR, "data", "test_id_generation.yaml") 

214 run_cli(["--id-generation", "validate", test_yaml]) 

215 

216 def test_validate_with_id_generation_error(self) -> None: 

217 """Test that loading a schema without IDs fails if ID generation is not 

218 enabled. 

219 """ 

220 test_yaml = os.path.join(TEST_DIR, "data", "test_id_generation.yaml") 

221 run_cli(["--no-id-generation", "validate", test_yaml], expect_error=True) 

222 

223 def test_validate_with_extra_checks(self) -> None: 

224 """Test schema validation flags.""" 

225 run_cli( 

226 [ 

227 "validate", 

228 "--check-description", 

229 "--check-tap-principal", 

230 "--check-tap-table-indexes", 

231 TEST_YAML, 

232 ] 

233 ) 

234 

235 def test_create_with_initialize_and_drop_error(self) -> None: 

236 """Test that initialize and drop can't be used together.""" 

237 run_cli(["create", "--initialize", "--drop", TEST_YAML], expect_error=True) 

238 

239 def test_load_tap_schema(self) -> None: 

240 """Test load-tap-schema command.""" 

241 # Create the TAP_SCHEMA database. 

242 tap_schema_path = tap_schema.TableManager.get_tap_schema_std_path() 

243 run_cli(["--id-generation", "create", f"--engine-url={self.sqlite_url}", tap_schema_path]) 

244 

245 # Load the TAP_SCHEMA data. 

246 run_cli(["load-tap-schema", f"--engine-url={self.sqlite_url}", TEST_YAML]) 

247 

248 def test_load_tap_schema_with_dry_run_and_output_file(self) -> None: 

249 """Test load-tap-schema command with dry run and output file.""" 

250 output_sql = os.path.join(self.tmpdir, "tap_schema.sql") 

251 run_cli( 

252 [ 

253 "load-tap-schema", 

254 "--engine-url=mysql://", 

255 "--dry-run", 

256 "--tap-schema-index=1", 

257 "--tap-tables-postfix=11", 

258 "--force-unbounded-arraysize", 

259 f"--output-file={output_sql}", 

260 TEST_YAML, 

261 ] 

262 ) 

263 if not os.path.exists(output_sql): 263 ↛ 264line 263 didn't jump to line 264 because the condition on line 263 was never true

264 self.fail("Output SQL file was not created") 

265 if os.path.getsize(output_sql) == 0: 265 ↛ 266line 265 didn't jump to line 266 because the condition on line 265 was never true

266 self.fail("Output SQL file is empty") 

267 

268 def test_init_tap_schema(self) -> None: 

269 """Test init-tap-schema command.""" 

270 run_cli(["init-tap-schema", f"--engine-url={self.sqlite_url}"]) 

271 

272 def test_init_tap_schema_mock(self) -> None: 

273 """Test init-tap-schema command with a mock URL, which should throw 

274 an error, as this is not supported. 

275 """ 

276 run_cli(["init-tap-schema", "sqlite://"], expect_error=True) 

277 

278 def test_init_tap_schema_with_extensions(self) -> None: 

279 """Test init-tap-schema command with default extensions.""" 

280 run_cli( 

281 [ 

282 "init-tap-schema", 

283 f"--engine-url={self.sqlite_url}", 

284 "--extensions", 

285 "resource://felis/config/tap_schema/tap_schema_extensions.yaml", 

286 ] 

287 ) 

288 

289 def test_init_tap_schema_with_custom_extensions(self) -> None: 

290 """Test init-tap-schema command with custom extensions file.""" 

291 extensions_file = os.path.join(self.tmpdir, "custom_extensions.yaml") 

292 extensions_content = """ 

293 name: TAP_SCHEMA 

294 tables: 

295 - name: schemas 

296 columns: 

297 - name: field1 

298 datatype: char 

299 length: 64 

300 description: A custom field 

301 """ 

302 with open(extensions_file, "w") as f: 

303 f.write(extensions_content) 

304 

305 run_cli(["init-tap-schema", f"--engine-url={self.sqlite_url}", "--extensions", extensions_file]) 

306 

307 def test_diff(self) -> None: 

308 """Test for ``diff`` command.""" 

309 test_diff1 = os.path.join(TEST_DIR, "data", "test_diff1.yaml") 

310 test_diff2 = os.path.join(TEST_DIR, "data", "test_diff2.yaml") 

311 

312 run_cli(["diff", test_diff1, test_diff2]) 

313 

314 def test_diff_database(self) -> None: 

315 """Test for ``diff`` command with database.""" 

316 test_diff1 = os.path.join(TEST_DIR, "data", "test_diff1.yaml") 

317 test_diff2 = os.path.join(TEST_DIR, "data", "test_diff2.yaml") 

318 

319 engine = create_engine(self.sqlite_url) 

320 metadata_db = MetaDataBuilder(Schema.from_uri(test_diff1), apply_schema_to_metadata=False).build() 

321 metadata_db.create_all(engine) 

322 engine.dispose() 

323 

324 run_cli(["diff", f"--engine-url={self.sqlite_url}", test_diff2]) 

325 

326 def test_diff_alembic(self) -> None: 

327 """Test for ``diff`` command with ``--alembic`` comparator option.""" 

328 test_diff1 = os.path.join(TEST_DIR, "data", "test_diff1.yaml") 

329 test_diff2 = os.path.join(TEST_DIR, "data", "test_diff2.yaml") 

330 run_cli(["diff", "--comparator", "alembic", test_diff1, test_diff2], print_output=True) 

331 

332 def test_diff_error(self) -> None: 

333 """Test for ``diff`` command with bad arguments.""" 

334 test_diff1 = os.path.join(TEST_DIR, "data", "test_diff1.yaml") 

335 run_cli(["diff", test_diff1], expect_error=True) 

336 

337 def test_diff_error_on_change(self) -> None: 

338 """Test for ``diff`` command with ``--error-on-change`` flag.""" 

339 test_diff1 = os.path.join(TEST_DIR, "data", "test_diff1.yaml") 

340 test_diff2 = os.path.join(TEST_DIR, "data", "test_diff2.yaml") 

341 run_cli(["diff", "--error-on-change", test_diff1, test_diff2], expect_error=True, print_output=True) 

342 

343 def test_dump_yaml(self) -> None: 

344 """Test for ``dump`` command with YAML output.""" 

345 with tempfile.NamedTemporaryFile(delete=True, suffix=".yaml") as temp_file: 

346 run_cli(["dump", TEST_YAML, temp_file.name], print_output=True) 

347 

348 @classmethod 

349 def _check_strip_ids(cls, obj: Any) -> None: 

350 """ 

351 Recursively check that a dict/list structure has no attributes with key 

352 '@id'. Raises a ValueError if any '@id' key is found. This is used to 

353 check the output of the `--strip-ids` option in the `dump` command. 

354 """ 

355 if isinstance(obj, dict): 

356 for k, v in obj.items(): 

357 if k == "@id": 357 ↛ 358line 357 didn't jump to line 358 because the condition on line 357 was never true

358 raise ValueError("Found forbidden key '@id'") 

359 cls._check_strip_ids(v) 

360 elif isinstance(obj, list): 

361 for item in obj: 

362 cls._check_strip_ids(item) 

363 

364 def test_dump_yaml_with_strip_ids(self) -> None: 

365 """Test for ``dump`` command with YAML output and stripped IDs.""" 

366 with tempfile.NamedTemporaryFile(delete=True, suffix=".yaml") as temp_file: 

367 run_cli(["dump", "--strip-ids", TEST_YAML, temp_file.name], print_output=True) 

368 dumped_data = temp_file.read().decode("utf-8") 

369 try: 

370 # Load the dumped YAML data to check for '@id' keys. 

371 data = yaml.safe_load(dumped_data) 

372 self._check_strip_ids(data) 

373 except ValueError: 

374 self.fail("Dumped YAML contains forbidden key '@id'") 

375 

376 def test_dump_json(self) -> None: 

377 """Test for ``dump`` command with JSON output.""" 

378 with tempfile.NamedTemporaryFile(delete=True, suffix=".json") as temp_file: 

379 run_cli(["dump", TEST_YAML, temp_file.name], print_output=True) 

380 

381 def test_dump_with_dereference_resources_and_sort_columns(self) -> None: 

382 """Test dump with both --dereference-resources and --sort-columns.""" 

383 # Define a source schema with columns in non-alphabetical order 

384 source_schema_content = """ 

385name: base_schema 

386tables: 

387- name: base_table 

388 columns: 

389 - name: zebra_col 

390 datatype: string 

391 length: 32 

392 - name: alpha_col 

393 datatype: int 

394 - name: middle_col 

395 datatype: float 

396""" 

397 source_path = os.path.join(self.tmpdir, "base_schema.yaml") 

398 with open(source_path, "w") as f: 

399 f.write(source_schema_content.strip()) 

400 

401 # Define a referencing schema that pulls columns via columnRefs 

402 ref_schema_content = f""" 

403name: derived_schema 

404resources: 

405 base_schema: 

406 uri: {source_path} 

407tables: 

408- name: derived_table 

409 columnRefs: 

410 base_schema: 

411 base_table: 

412 zebra_col: 

413 alpha_col: 

414 middle_col: 

415""" 

416 ref_path = os.path.join(self.tmpdir, "derived_schema.yaml") 

417 with open(ref_path, "w") as f: 

418 f.write(ref_schema_content.strip()) 

419 

420 with tempfile.NamedTemporaryFile(delete=True, suffix=".yaml", dir=self.tmpdir) as temp_file: 

421 run_cli( 

422 [ 

423 "dump", 

424 "--dereference-resources", 

425 "--sort-columns", 

426 ref_path, 

427 temp_file.name, 

428 ], 

429 print_output=True, 

430 ) 

431 dumped_data = temp_file.read().decode("utf-8") 

432 data = yaml.safe_load(dumped_data) 

433 

434 # Verify resources are dereferenced (no columnRefs remain) 

435 for table in data.get("tables", []): 

436 self.assertNotIn("columnRefs", table) 

437 # Verify columns are present and sorted alphabetically 

438 columns = table.get("columns", []) 

439 self.assertGreater(len(columns), 0) 

440 names = [col["name"] for col in columns] 

441 self.assertEqual(names, sorted(names)) 

442 

443 def test_dump_json_with_strip_ids(self) -> None: 

444 """Test for ``dump`` command with JSON output.""" 

445 with tempfile.NamedTemporaryFile(delete=True, suffix=".json") as temp_file: 

446 run_cli(["dump", "--strip-ids", TEST_YAML, temp_file.name], print_output=True) 

447 dumped_data = temp_file.read().decode("utf-8") 

448 try: 

449 # Load the dumped YAML data to check for '@id' keys. 

450 data = yaml.safe_load(dumped_data) 

451 self._check_strip_ids(data) 

452 except ValueError: 

453 self.fail("Dumped YAML contains forbidden key '@id'") 

454 

455 @classmethod 

456 def _check_columns_sorted(cls, data: dict[str, Any]) -> None: 

457 """Check that columns in each table are sorted alphabetically by 

458 name. 

459 """ 

460 for table in data.get("tables", []): 

461 columns = table.get("columns", []) 

462 names = [col["name"] for col in columns] 

463 assert names == sorted(names), f"Columns not sorted in table {table.get('name')}: {names}" 

464 

465 def test_dump_yaml_with_sort_columns(self) -> None: 

466 """Test for ``dump`` command with YAML output and sorted columns.""" 

467 with tempfile.NamedTemporaryFile(delete=True, suffix=".yaml") as temp_file: 

468 run_cli(["dump", "--sort-columns", TEST_YAML, temp_file.name], print_output=True) 

469 dumped_data = temp_file.read().decode("utf-8") 

470 data = yaml.safe_load(dumped_data) 

471 self._check_columns_sorted(data) 

472 

473 def test_dump_json_with_sort_columns(self) -> None: 

474 """Test for ``dump`` command with JSON output and sorted columns.""" 

475 with tempfile.NamedTemporaryFile(delete=True, suffix=".json") as temp_file: 

476 run_cli(["dump", "--sort-columns", TEST_YAML, temp_file.name], print_output=True) 

477 dumped_data = temp_file.read().decode("utf-8") 

478 data = yaml.safe_load(dumped_data) 

479 self._check_columns_sorted(data) 

480 

481 def test_dump_with_invalid_file_extension_error(self) -> None: 

482 """Test for ``dump`` command with JSON output.""" 

483 run_cli(["dump", TEST_YAML, "out.bad"], expect_error=True) 

484 

485 def test_create_and_drop_indexes(self) -> None: 

486 """Test creating and dropping indexes using CLI commands with 

487 SQLite; no checking for the existence of the indexes is done on the 

488 database because other test cases cover that functionality 

489 sufficiently. 

490 """ 

491 # Create database without indexes 

492 run_cli(["create", "--skip-indexes", f"--engine-url={self.sqlite_url}", TEST_SALES_YAML]) 

493 

494 # Create the indexes using CLI 

495 run_cli( 

496 ["create-indexes", f"--engine-url={self.sqlite_url}", TEST_SALES_YAML, "--schema-name", "main"] 

497 ) 

498 

499 # Create the indexes again; should not cause an error 

500 run_cli( 

501 ["create-indexes", f"--engine-url={self.sqlite_url}", TEST_SALES_YAML, "--schema-name", "main"] 

502 ) 

503 

504 # Drop the indexes using CLI 

505 run_cli(["drop-indexes", f"--engine-url={self.sqlite_url}", TEST_SALES_YAML, "--schema-name", "main"]) 

506 

507 def test_generate_and_load_sql(self) -> None: 

508 """Test generating SQL and then executing it on a SQLite database.""" 

509 generated_sql = os.path.join(self.tmpdir, "generated.sql") 

510 

511 try: 

512 # Generate SQL DDL from schema using mock connection 

513 run_cli( 

514 [ 

515 "create", 

516 "--engine-url=sqlite://", 

517 f"--output-file={generated_sql}", 

518 f"{TEST_YAML}", 

519 ] 

520 ) 

521 

522 # Verify the SQL file was generated 

523 self.assertTrue(os.path.exists(generated_sql), "Generated SQL file should exist") 

524 

525 # Read the generated SQL 

526 with open(generated_sql) as f: 

527 sql = f.read() 

528 

529 # Verify SQL content is not empty 

530 self.assertGreater(len(sql.strip()), 0, "Generated SQL should not be empty") 

531 

532 # Execute the SQL against a real database 

533 engine = create_engine(self.sqlite_url) 

534 with engine.connect() as connection: 

535 with connection.begin(): 

536 # Split SQL into individual statements for execution since 

537 # SQLite can only execute one statement at a time 

538 statements = [stmt.strip() for stmt in sql.split(";") if stmt.strip()] 

539 for statement in statements: 

540 if statement: # Skip empty statements 540 ↛ 539line 540 didn't jump to line 539 because the condition on line 540 was always true

541 connection.execute(text(statement)) 

542 

543 # Verify that all expected tables were actually created 

544 with engine.connect() as connection: 

545 # Load the schema to get expected table names 

546 schema = Schema.from_uri(TEST_YAML, context={"id_generation": True}) 

547 expected_table_names = {table.name for table in schema.tables} 

548 

549 # Get all tables that were created in the database 

550 result = connection.execute(text("SELECT name FROM sqlite_master WHERE type='table'")) 

551 created_table_names = {row[0] for row in result.fetchall()} 

552 

553 # Verify all expected tables were created 

554 self.assertTrue( 

555 expected_table_names.issubset(created_table_names), 

556 f"Missing tables: {expected_table_names - created_table_names}. " 

557 f"Expected: {sorted(expected_table_names)}, " 

558 f"Created: {sorted(created_table_names)}", 

559 ) 

560 

561 engine.dispose() 

562 

563 except Exception as e: 

564 self.fail(f"Test failed with exception: {e}") 

565 

566 

567class ColumnRefsTestCase(unittest.TestCase): 

568 """Test handling of column references in CLI.""" 

569 

570 def setUp(self) -> None: 

571 """Set up a temporary directory for tests.""" 

572 self.temp_dir = tempfile.mkdtemp(dir=TEST_DIR) 

573 self.sqlite_url = f"sqlite:///{self.temp_dir}/db.sqlite3" 

574 

575 # Write out source schema file 

576 source_schema_content = """ 

577name: source_schema 

578tables: 

579- name: source_table 

580 columns: 

581 - name: ref_col1 

582 datatype: int 

583 - name: ref_col2 

584 datatype: string 

585 length: 64 

586 - name: ref_col3 

587 datatype: float 

588""" 

589 source_schema_path = os.path.join(self.temp_dir, "source_schema.yaml") 

590 with open(source_schema_path, "w") as f: 

591 f.write(source_schema_content.strip()) 

592 

593 # Write out referencing schema file 

594 ref_schema_content = """ 

595name: ref_schema 

596resources: 

597 source_schema: 

598 uri: {resource_path} 

599tables: 

600- name: ref_table 

601 columnRefs: 

602 source_schema: 

603 source_table: 

604 ref_col1: 

605 ref_col2: 

606 overrides: 

607 tap:column_index: 15 

608 col3: 

609 ref_name: ref_col3 

610""" 

611 self.ref_schema_path = os.path.join(self.temp_dir, "ref_schema.yaml") 

612 ref_content = ref_schema_content.format(resource_path=source_schema_path) 

613 with open(self.ref_schema_path, "w") as f: 

614 f.write(ref_content.strip()) 

615 

616 def tearDown(self) -> None: 

617 """Clean up temporary directory.""" 

618 shutil.rmtree(self.temp_dir, ignore_errors=True) 

619 

620 def test_validate_with_column_ref_index_increment(self) -> None: 

621 """Test that passing a valid value for column reference index increment 

622 works. 

623 """ 

624 run_cli( 

625 [ 

626 "--column-ref-index-increment=1", 

627 "validate", 

628 self.ref_schema_path, 

629 ] 

630 ) 

631 

632 def test_validate_with_column_ref_index_increment_error(self) -> None: 

633 """Test that passing an invalid value for column reference index raises 

634 an error. 

635 """ 

636 run_cli( 

637 [ 

638 "--column-ref-index-increment=-1", 

639 "validate", 

640 self.ref_schema_path, 

641 ], 

642 expect_error=True, 

643 ) 

644 

645 def test_load_tap_schema_with_column_refs(self) -> None: 

646 """Test load-tap-schema command with column reference index 

647 increment. 

648 """ 

649 # Create the TAP_SCHEMA database 

650 run_cli(["init-tap-schema", f"--engine-url={self.sqlite_url}"]) 

651 

652 # Load the TAP_SCHEMA data that includes column references 

653 run_cli( 

654 [ 

655 "load-tap-schema", 

656 f"--engine-url={self.sqlite_url}", 

657 self.ref_schema_path, 

658 ] 

659 ) 

660 

661 def test_load_tap_schema_with_column_ref_index_increment(self) -> None: 

662 """Test load-tap-schema command with column reference index 

663 increment. 

664 """ 

665 # Create the TAP_SCHEMA database 

666 run_cli(["init-tap-schema", f"--engine-url={self.sqlite_url}"]) 

667 

668 # Load the TAP_SCHEMA data that includes column reference index 

669 # increment 

670 run_cli( 

671 [ 

672 "--column-ref-index-increment=1", 

673 "load-tap-schema", 

674 f"--engine-url={self.sqlite_url}", 

675 self.ref_schema_path, 

676 ] 

677 ) 

678 

679 

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

681 unittest.main()