2323from preset_cli .cli .superset .main import superset_cli
2424from preset_cli .cli .superset .sync .native .command import (
2525 ResourceType ,
26+ add_password_to_config ,
2627 import_resources ,
2728 import_resources_individually ,
2829 load_user_modules ,
29- prompt_for_passwords ,
3030 raise_helper ,
3131 verify_db_connectivity ,
3232)
3333from preset_cli .exceptions import ErrorLevel , ErrorPayload , SupersetError
3434
3535
36- def test_prompt_for_passwords (mocker : MockerFixture ) -> None :
36+ def test_add_password_to_config (mocker : MockerFixture ) -> None :
3737 """
38- Test ``prompt_for_passwords ``.
38+ Test ``add_password_to_config ``.
3939 """
4040 getpass = mocker .patch ("preset_cli.cli.superset.sync.native.command.getpass" )
4141
42- config = {"sqlalchemy_uri" : "postgresql://user:XXXXXXXXXX@host:5432/db" }
42+ config = {
43+ "sqlalchemy_uri" : "postgresql://user:XXXXXXXXXX@host:5432/db" ,
44+ "uuid" : "uuid1" ,
45+ }
4346 path = Path ("/path/to/root/databases/psql.yaml" )
44- prompt_for_passwords (path , config )
47+ add_password_to_config (path , config , {} )
4548
4649 getpass .getpass .assert_called_with (f"Please provide the password for { path } : " )
4750
4851 config ["password" ] = "password123"
4952 getpass .reset_mock ()
50- prompt_for_passwords (path , config )
53+ add_password_to_config (path , config , {} )
5154 getpass .getpass .assert_not_called ()
5255
56+ getpass .reset_mock ()
57+ add_password_to_config (path , config , {"uuid1" : "password321" })
58+ getpass .getpass .assert_not_called ()
59+ # db_password takes precedence over config["password"]
60+ assert config == {
61+ "sqlalchemy_uri" : "postgresql://user:XXXXXXXXXX@host:5432/db" ,
62+ "uuid" : "uuid1" ,
63+ "password" : "password321" ,
64+ }
65+
5366
5467def test_import_resources (mocker : MockerFixture ) -> None :
5568 """
@@ -418,8 +431,8 @@ def test_native_password_prompt(mocker: MockerFixture, fs: FakeFilesystem) -> No
418431 client .get_databases .return_value = []
419432 mocker .patch ("preset_cli.cli.superset.sync.native.command.import_resources" )
420433 mocker .patch ("preset_cli.cli.superset.main.UsernamePasswordAuth" )
421- prompt_for_passwords = mocker .patch (
422- "preset_cli.cli.superset.sync.native.command.prompt_for_passwords " ,
434+ add_password_to_config = mocker .patch (
435+ "preset_cli.cli.superset.sync.native.command.add_password_to_config " ,
423436 )
424437
425438 runner = CliRunner ()
@@ -430,9 +443,9 @@ def test_native_password_prompt(mocker: MockerFixture, fs: FakeFilesystem) -> No
430443 catch_exceptions = False ,
431444 )
432445 assert result .exit_code == 0
433- prompt_for_passwords .assert_called ()
446+ add_password_to_config .assert_called ()
434447
435- prompt_for_passwords .reset_mock ()
448+ add_password_to_config .reset_mock ()
436449 client .get_databases .return_value = [
437450 {"uuid" : "uuid1" },
438451 ]
@@ -442,7 +455,7 @@ def test_native_password_prompt(mocker: MockerFixture, fs: FakeFilesystem) -> No
442455 catch_exceptions = False ,
443456 )
444457 assert result .exit_code == 0
445- prompt_for_passwords .assert_not_called ()
458+ add_password_to_config .assert_not_called ()
446459 client .get_uuids .assert_not_called ()
447460
448461
@@ -610,8 +623,8 @@ def test_native_legacy_instance(mocker: MockerFixture, fs: FakeFilesystem) -> No
610623 client .get_uuids .return_value = {1 : "uuid1" }
611624 mocker .patch ("preset_cli.cli.superset.sync.native.command.import_resources" )
612625 mocker .patch ("preset_cli.cli.superset.main.UsernamePasswordAuth" )
613- prompt_for_passwords = mocker .patch (
614- "preset_cli.cli.superset.sync.native.command.prompt_for_passwords " ,
626+ add_password_to_config = mocker .patch (
627+ "preset_cli.cli.superset.sync.native.command.add_password_to_config " ,
615628 )
616629
617630 runner = CliRunner ()
@@ -622,7 +635,7 @@ def test_native_legacy_instance(mocker: MockerFixture, fs: FakeFilesystem) -> No
622635 catch_exceptions = False ,
623636 )
624637 assert result .exit_code == 0
625- prompt_for_passwords .assert_not_called ()
638+ add_password_to_config .assert_not_called ()
626639
627640
628641def test_load_user_modules (mocker : MockerFixture , fs : FakeFilesystem ) -> None :
@@ -2001,3 +2014,171 @@ def test_native_invalid_asset_type(mocker: MockerFixture, fs: FakeFilesystem) ->
20012014
20022015 assert result .exit_code == 2
20032016 assert "Invalid value for '--asset-type'" in result .output
2017+
2018+
2019+ def test_native_with_db_passwords (mocker : MockerFixture , fs : FakeFilesystem ) -> None :
2020+ """
2021+ Test the ``native`` command while passing db passwords in the command.
2022+ """
2023+ root = Path ("/path/to/root" )
2024+ fs .create_dir (root )
2025+ sqlalchemy_uri = {
2026+ "sqlalchemy_uri" : "postgresql://user:XXXXXXXXXX@host:5432/db" ,
2027+ }
2028+ unmasked_uri = {
2029+ "sqlalchemy_uri" : "postgresql://user:unmasked@host:5432/db" ,
2030+ }
2031+
2032+ db_configs = {
2033+ "db_config_masked_no_password" : {
2034+ "uuid" : "uuid1" ,
2035+ ** sqlalchemy_uri ,
2036+ },
2037+ "other_db_config_masked_no_password" : {
2038+ "uuid" : "uuid2" ,
2039+ ** sqlalchemy_uri ,
2040+ },
2041+ "db_config_masked_with_password" : {
2042+ "uuid" : "uuid3" ,
2043+ "password" : "directpwd!" ,
2044+ ** sqlalchemy_uri ,
2045+ },
2046+ "other_db_config_masked_with_password" : {
2047+ "uuid" : "uuid4" ,
2048+ "password" : "directpwd!again" ,
2049+ ** sqlalchemy_uri ,
2050+ },
2051+ "db_config_unmasked" : {
2052+ "uuid" : "uuid5" ,
2053+ ** unmasked_uri ,
2054+ },
2055+ "other_db_config_unmasked" : {
2056+ "uuid" : "uuid6" ,
2057+ ** unmasked_uri ,
2058+ },
2059+ "db_config_unmasked_with_password" : {
2060+ "uuid" : "uuid7" ,
2061+ "password" : "unmaskedpwd!" ,
2062+ ** unmasked_uri ,
2063+ },
2064+ "final_db_config_unmasked_with_password" : {
2065+ "uuid" : "uuid8" ,
2066+ "password" : "unmaskedpwd!again" ,
2067+ ** unmasked_uri ,
2068+ },
2069+ }
2070+
2071+ for file_name , content in db_configs .items ():
2072+ fs .create_file (
2073+ root / f"databases/{ file_name } .yaml" ,
2074+ contents = yaml .dump (content ),
2075+ )
2076+
2077+ SupersetClient = mocker .patch (
2078+ "preset_cli.cli.superset.sync.native.command.SupersetClient" ,
2079+ )
2080+ client = SupersetClient ()
2081+ client .get_databases .return_value = []
2082+ import_resources = mocker .patch (
2083+ "preset_cli.cli.superset.sync.native.command.import_resources" ,
2084+ )
2085+ mocker .patch ("preset_cli.cli.superset.main.UsernamePasswordAuth" )
2086+ mocker .patch ("preset_cli.cli.superset.sync.native.command.verify_db_connectivity" )
2087+ getpass = mocker .patch ("preset_cli.cli.superset.sync.native.command.getpass" )
2088+ getpass .getpass .return_value = "pwd_from_prompt"
2089+
2090+ runner = CliRunner ()
2091+ result = runner .invoke (
2092+ superset_cli ,
2093+ [
2094+ "https://superset.example.org/" ,
2095+ "sync" ,
2096+ "native" ,
2097+ str (root ),
2098+ "--db-password" ,
2099+ "uuid1=pwd_from_command=1" ,
2100+ "--db-password" ,
2101+ "uuid3=pwd_from_command=2" ,
2102+ "--db-password" ,
2103+ "uuid5=pwd_from_command=3" ,
2104+ "--db-password" ,
2105+ "uuid7=pwd_from_command=4" ,
2106+ ],
2107+ catch_exceptions = False ,
2108+ )
2109+ assert result .exit_code == 0
2110+ contents = {
2111+ "bundle/databases/db_config_masked_no_password.yaml" : yaml .dump (
2112+ {
2113+ "uuid" : "uuid1" ,
2114+ ** sqlalchemy_uri ,
2115+ "password" : "pwd_from_command=1" ,
2116+ "is_managed_externally" : False ,
2117+ },
2118+ ),
2119+ "bundle/databases/other_db_config_masked_no_password.yaml" : yaml .dump (
2120+ {
2121+ "uuid" : "uuid2" ,
2122+ ** sqlalchemy_uri ,
2123+ "password" : "pwd_from_prompt" ,
2124+ "is_managed_externally" : False ,
2125+ },
2126+ ),
2127+ "bundle/databases/db_config_masked_with_password.yaml" : yaml .dump (
2128+ {
2129+ "uuid" : "uuid3" ,
2130+ ** sqlalchemy_uri ,
2131+ "password" : "pwd_from_command=2" ,
2132+ "is_managed_externally" : False ,
2133+ },
2134+ ),
2135+ "bundle/databases/other_db_config_masked_with_password.yaml" : yaml .dump (
2136+ {
2137+ "uuid" : "uuid4" ,
2138+ ** sqlalchemy_uri ,
2139+ "password" : "directpwd!again" ,
2140+ "is_managed_externally" : False ,
2141+ },
2142+ ),
2143+ "bundle/databases/db_config_unmasked.yaml" : yaml .dump (
2144+ {
2145+ "uuid" : "uuid5" ,
2146+ ** unmasked_uri ,
2147+ "password" : "pwd_from_command=3" ,
2148+ "is_managed_externally" : False ,
2149+ },
2150+ ),
2151+ "bundle/databases/other_db_config_unmasked.yaml" : yaml .dump (
2152+ {
2153+ "uuid" : "uuid6" ,
2154+ ** unmasked_uri ,
2155+ "is_managed_externally" : False ,
2156+ },
2157+ ),
2158+ "bundle/databases/db_config_unmasked_with_password.yaml" : yaml .dump (
2159+ {
2160+ "uuid" : "uuid7" ,
2161+ ** unmasked_uri ,
2162+ "password" : "pwd_from_command=4" ,
2163+ "is_managed_externally" : False ,
2164+ },
2165+ ),
2166+ "bundle/databases/final_db_config_unmasked_with_password.yaml" : yaml .dump (
2167+ {
2168+ "uuid" : "uuid8" ,
2169+ ** unmasked_uri ,
2170+ "password" : "unmaskedpwd!again" ,
2171+ "is_managed_externally" : False ,
2172+ },
2173+ ),
2174+ }
2175+
2176+ import_resources .assert_called_once_with (
2177+ contents ,
2178+ client ,
2179+ False ,
2180+ ResourceType .ASSET ,
2181+ )
2182+ getpass .getpass .assert_called_once_with (
2183+ "Please provide the password for databases/other_db_config_masked_no_password.yaml: " ,
2184+ )
0 commit comments