diff --git a/AUTHORS b/AUTHORS index 06c8d9a17..c8a2d4d34 100644 --- a/AUTHORS +++ b/AUTHORS @@ -152,6 +152,7 @@ Contributors: * Shayan Golshani (shgol) * Tommi Kyntölä (kynde) * Diego + * Chris (ChrisJr404) Creator: -------- diff --git a/changelog.rst b/changelog.rst index 0c3905e38..784f36d10 100644 --- a/changelog.rst +++ b/changelog.rst @@ -3,6 +3,13 @@ Upcoming (TBD) Bug fixes: ---------- +* Fix ``--list-dsn`` and ``-D``/``--dsn`` not finding ``[alias_dsn]`` entries. + Both read the config with a bare ``load_config`` that skips the packaged + default, so on a fresh install (no config written yet) ``--list-dsn`` printed + a misleading "Invalid DSNs found" error and ``-D`` could crash on the missing + ``[main]`` section. They now load config the same way as the rest of pgcli + (``get_config``), which writes the default template if needed and always has + the expected sections ([issue 1489](https://github.com/dbcli/pgcli/issues/1489)). * Restore cursor shape behaviour for Emacs mode * Fix ``TypeError: cannot use a string pattern on a bytes-like object`` when completion metadata comes back as bytes (e.g. ``SQL_ASCII`` client encoding). diff --git a/pgcli/main.py b/pgcli/main.py index 8a9b1b024..bc7a16cb9 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -1575,7 +1575,7 @@ def cli( ) if list_dsn: try: - cfg = load_config(pgclirc, config_full_path) + cfg = get_config(pgclirc) for alias in cfg["alias_dsn"]: click.secho(alias + " : " + cfg["alias_dsn"][alias]) sys.exit(0) @@ -1627,7 +1627,7 @@ def cli( if list_databases or ping_database: database = "postgres" - cfg = load_config(pgclirc, config_full_path) + cfg = get_config(pgclirc) if dsn != "": try: dsn_config = cfg["alias_dsn"][dsn] diff --git a/tests/test_alias_dsn.py b/tests/test_alias_dsn.py new file mode 100644 index 000000000..f9de31c05 --- /dev/null +++ b/tests/test_alias_dsn.py @@ -0,0 +1,90 @@ +import pytest +from click.testing import CliRunner + +from pgcli.main import cli, PGCli + + +def write_config(tmp_path, body): + cfg = tmp_path / "config" + cfg.write_text(body) + return cfg + + +@pytest.fixture +def isolate_config(monkeypatch, tmp_path): + # Keep the real config dir out of the picture. get_config() writes its + # default template under here when a config file does not exist yet. + monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path)) + return tmp_path + + +def test_list_dsn_fresh_config(isolate_config): + # No config file exists yet. --list-dsn used to read the not-yet-written + # config before PGCli() created it, hit a KeyError on the missing + # [alias_dsn] section, and print "Invalid DSNs found" with exit code 1. + # It should just report no aliases and exit cleanly. + cfg = isolate_config / "config" + runner = CliRunner() + result = runner.invoke(cli, ["--pgclirc", str(cfg), "--list-dsn"]) + assert result.exit_code == 0 + assert "Invalid DSNs" not in result.output + assert result.output.strip() == "" + + +def test_list_dsn_lists_aliases(isolate_config): + cfg = write_config( + isolate_config, + "[alias_dsn]\n" + "foo = postgres://u:p@localhost:5432/foo\n" + "bar = postgres://u:p@localhost:5432/bar\n", + ) + runner = CliRunner() + result = runner.invoke(cli, ["--pgclirc", str(cfg), "--list-dsn"]) + assert result.exit_code == 0 + assert "foo : postgres://u:p@localhost:5432/foo" in result.output + assert "bar : postgres://u:p@localhost:5432/bar" in result.output + + +def test_dsn_alias_resolves(isolate_config, monkeypatch): + cfg = write_config( + isolate_config, + "[alias_dsn]\nfoo = postgres://u:p@localhost:5432/foo\n", + ) + captured = {} + + def fake_connect(self, *args, **kwargs): + # connect_uri() parses the alias URI and calls connect() with the + # resolved parts, so recording them proves the alias was found. + captured.update(kwargs) + + class DummyExec: + def run(self, cmd): + return [] + + def get_timezone(self): + return "UTC" + + def set_timezone(self, *a, **k): + pass + + self.pgexecute = DummyExec() + + monkeypatch.setattr(PGCli, "connect", fake_connect) + + runner = CliRunner() + result = runner.invoke(cli, ["--pgclirc", str(cfg), "-D", "foo", "--ping"]) + assert result.exit_code == 0 + assert "Could not find a DSN" not in result.output + assert captured.get("database") == "foo" + assert captured.get("host") == "localhost" + + +def test_dsn_alias_missing(isolate_config): + cfg = write_config( + isolate_config, + "[alias_dsn]\nfoo = postgres://u:p@localhost:5432/foo\n", + ) + runner = CliRunner() + result = runner.invoke(cli, ["--pgclirc", str(cfg), "-D", "does-not-exist"]) + assert result.exit_code == 1 + assert "Could not find a DSN with alias does-not-exist" in result.output