|
4 | 4 |
|
5 | 5 | import pytest |
6 | 6 | from _pytest.fixtures import FixtureRequest |
7 | | -from unittest.mock import patch |
| 7 | +from unittest.mock import patch, MagicMock |
8 | 8 |
|
9 | 9 | from sqlmesh.core.config.connection import ( |
10 | 10 | BigQueryConnectionConfig, |
@@ -455,6 +455,110 @@ def test_duckdb(make_config): |
455 | 455 | assert not config.is_recommended_for_state_sync |
456 | 456 |
|
457 | 457 |
|
| 458 | +@patch("duckdb.connect") |
| 459 | +def test_duckdb_multiple_secrets(mock_connect, make_config): |
| 460 | + """Test that multiple secrets are correctly converted to CREATE SECRET SQL statements.""" |
| 461 | + mock_cursor = MagicMock() |
| 462 | + mock_connection = MagicMock() |
| 463 | + mock_connection.cursor.return_value = mock_cursor |
| 464 | + mock_connection.execute = mock_cursor.execute |
| 465 | + mock_connect.return_value = mock_connection |
| 466 | + |
| 467 | + # Create config with 2 secrets |
| 468 | + config = make_config( |
| 469 | + type="duckdb", |
| 470 | + secrets=[ |
| 471 | + { |
| 472 | + "type": "s3", |
| 473 | + "region": "us-east-1", |
| 474 | + "key_id": "my_aws_key", |
| 475 | + "secret": "my_aws_secret", |
| 476 | + }, |
| 477 | + { |
| 478 | + "type": "azure", |
| 479 | + "account_name": "myaccount", |
| 480 | + "account_key": "myaccountkey", |
| 481 | + }, |
| 482 | + ], |
| 483 | + ) |
| 484 | + |
| 485 | + assert isinstance(config, DuckDBConnectionConfig) |
| 486 | + assert len(config.secrets) == 2 |
| 487 | + |
| 488 | + # Create cursor which triggers _cursor_init |
| 489 | + cursor = config.create_engine_adapter().cursor |
| 490 | + |
| 491 | + execute_calls = [call[0][0] for call in mock_cursor.execute.call_args_list] |
| 492 | + create_secret_calls = [call for call in execute_calls if call.startswith("CREATE SECRET")] |
| 493 | + |
| 494 | + # Should have exactly 2 CREATE SECRET calls |
| 495 | + assert len(create_secret_calls) == 2 |
| 496 | + |
| 497 | + # Verify the SQL for the first secret (S3) |
| 498 | + assert ( |
| 499 | + create_secret_calls[0] |
| 500 | + == "CREATE SECRET (type 's3', region 'us-east-1', key_id 'my_aws_key', secret 'my_aws_secret');" |
| 501 | + ) |
| 502 | + |
| 503 | + # Verify the SQL for the second secret (Azure) |
| 504 | + assert ( |
| 505 | + create_secret_calls[1] |
| 506 | + == "CREATE SECRET (type 'azure', account_name 'myaccount', account_key 'myaccountkey');" |
| 507 | + ) |
| 508 | + |
| 509 | + |
| 510 | +@patch("duckdb.connect") |
| 511 | +def test_duckdb_named_secrets(mock_connect, make_config): |
| 512 | + """Test that named secrets are correctly converted to CREATE SECRET SQL statements.""" |
| 513 | + mock_cursor = MagicMock() |
| 514 | + mock_connection = MagicMock() |
| 515 | + mock_connection.cursor.return_value = mock_cursor |
| 516 | + mock_connection.execute = mock_cursor.execute |
| 517 | + mock_connect.return_value = mock_connection |
| 518 | + |
| 519 | + # Create config with named secrets using dictionary format |
| 520 | + config = make_config( |
| 521 | + type="duckdb", |
| 522 | + secrets={ |
| 523 | + "my_s3_secret": { |
| 524 | + "type": "s3", |
| 525 | + "region": "us-east-1", |
| 526 | + "key_id": "my_aws_key", |
| 527 | + "secret": "my_aws_secret", |
| 528 | + }, |
| 529 | + "my_azure_secret": { |
| 530 | + "type": "azure", |
| 531 | + "account_name": "myaccount", |
| 532 | + "account_key": "myaccountkey", |
| 533 | + }, |
| 534 | + }, |
| 535 | + ) |
| 536 | + |
| 537 | + assert isinstance(config, DuckDBConnectionConfig) |
| 538 | + assert len(config.secrets) == 2 |
| 539 | + |
| 540 | + # Create cursor which triggers _cursor_init |
| 541 | + cursor = config.create_engine_adapter().cursor |
| 542 | + |
| 543 | + execute_calls = [call[0][0] for call in mock_cursor.execute.call_args_list] |
| 544 | + create_secret_calls = [call for call in execute_calls if call.startswith("CREATE SECRET")] |
| 545 | + |
| 546 | + # Should have exactly 2 CREATE SECRET calls |
| 547 | + assert len(create_secret_calls) == 2 |
| 548 | + |
| 549 | + # Verify the SQL for the first secret (S3) includes the secret name |
| 550 | + assert ( |
| 551 | + create_secret_calls[0] |
| 552 | + == "CREATE SECRET my_s3_secret (type 's3', region 'us-east-1', key_id 'my_aws_key', secret 'my_aws_secret');" |
| 553 | + ) |
| 554 | + |
| 555 | + # Verify the SQL for the second secret (Azure) includes the secret name |
| 556 | + assert ( |
| 557 | + create_secret_calls[1] |
| 558 | + == "CREATE SECRET my_azure_secret (type 'azure', account_name 'myaccount', account_key 'myaccountkey');" |
| 559 | + ) |
| 560 | + |
| 561 | + |
458 | 562 | @pytest.mark.parametrize( |
459 | 563 | "kwargs1, kwargs2, shared_adapter", |
460 | 564 | [ |
@@ -643,6 +747,33 @@ def test_duckdb_attach_ducklake_catalog(make_config): |
643 | 747 | assert "DATA_INLINING_ROW_LIMIT 10" in ducklake_catalog.to_sql("ducklake") |
644 | 748 |
|
645 | 749 |
|
| 750 | +def test_ryan(make_config): |
| 751 | + config = make_config( |
| 752 | + type="duckdb", |
| 753 | + catalogs={ |
| 754 | + "ducklake": DuckDBAttachOptions( |
| 755 | + type="ducklake", |
| 756 | + path="ducklake:postgres:dbname=ducklake_catalog host=127.0.0.1 user=postgres password=postgres", |
| 757 | + data_path="s3://191110674922-test/ducklake/", |
| 758 | + encrypted=True, |
| 759 | + data_inlining_row_limit=10, |
| 760 | + ), |
| 761 | + }, |
| 762 | + ) |
| 763 | + assert isinstance(config, DuckDBConnectionConfig) |
| 764 | + ducklake_catalog = config.catalogs.get("ducklake") |
| 765 | + assert ducklake_catalog is not None |
| 766 | + assert ducklake_catalog.type == "ducklake" |
| 767 | + assert ducklake_catalog.path == "catalog.ducklake" |
| 768 | + assert ducklake_catalog.data_path == "/tmp/ducklake_data" |
| 769 | + assert ducklake_catalog.encrypted is True |
| 770 | + assert ducklake_catalog.data_inlining_row_limit == 10 |
| 771 | + # Check that the generated SQL includes DATA_PATH |
| 772 | + assert "DATA_PATH '/tmp/ducklake_data'" in ducklake_catalog.to_sql("ducklake") |
| 773 | + assert "ENCRYPTED" in ducklake_catalog.to_sql("ducklake") |
| 774 | + assert "DATA_INLINING_ROW_LIMIT 10" in ducklake_catalog.to_sql("ducklake") |
| 775 | + |
| 776 | + |
646 | 777 | def test_duckdb_attach_options(): |
647 | 778 | options = DuckDBAttachOptions( |
648 | 779 | type="postgres", path="dbname=postgres user=postgres host=127.0.0.1", read_only=True |
|
0 commit comments