Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,37 @@ def project(tmp_path: Path) -> ProjectBuilder:
return ProjectBuilder(tmp_path)


@pytest.fixture
def below_project(project: ProjectBuilder, monkeypatch: pytest.MonkeyPatch) -> Path:
"""A working directory inside the project, so discovery has to walk up.

The commands that read a project take it from the working directory. A test
about such a command, rather than about the loader it calls, therefore has
to run from somewhere in the tree instead of at its root.
"""
directory = project.root / "somewhere"
directory.mkdir()
monkeypatch.chdir(directory)
return directory


@pytest.fixture
def outside_any_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""A working directory with no gete.yaml above it.

For the commands that have to answer before there is a project at all.
"""
directory = tmp_path / "elsewhere"
directory.mkdir()
# A gete.yaml anywhere above would be found by the walk up, and every test
# that means "there is no project" would pass without proving anything.
assert not any(
(parent / "gete.yaml").exists() for parent in (directory, *directory.parents)
)
monkeypatch.chdir(directory)
return directory


class FakeGcp:
"""Answers from a table of (method, url) and records every write."""

Expand Down
46 changes: 20 additions & 26 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,15 @@
from gete.errors import DeclarationError


@pytest.mark.usefixtures("below_project")
def test_validate_exits_zero_when_everything_is_fine(project: ProjectBuilder) -> None:
project.write_agent("mail-triage")
runner = CliRunner()
# A directory below the project root; validate walks up to gete.yaml.
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(main, ["validate"])
result = CliRunner().invoke(main, ["validate"])
assert result.exit_code == 0, result.output
assert "1 agent" in result.output


@pytest.mark.usefixtures("below_project")
def test_validate_lists_every_problem_and_exits_one(project: ProjectBuilder) -> None:
project.write_agent(
"mail-triage",
Expand All @@ -29,22 +28,20 @@ def test_validate_lists_every_problem_and_exits_one(project: ProjectBuilder) ->
"runtime": {"agent_engine": {"env": {"GOOGLE_CLOUD_PROJECT": "x"}}},
},
)
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(main, ["validate"])
result = CliRunner().invoke(main, ["validate"])
assert result.exit_code == 1
assert "salesforce" in result.output
assert "GOOGLE_CLOUD_PROJECT" in result.output


@pytest.mark.usefixtures("outside_any_project")
def test_validate_reports_a_missing_project_file() -> None:
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(main, ["validate"])
result = CliRunner().invoke(main, ["validate"])
assert result.exit_code == 1
assert "gete.yaml" in result.output


@pytest.mark.usefixtures("below_project")
def test_register_passes_the_authorizations_to_reset_through(
project: ProjectBuilder, monkeypatch: pytest.MonkeyPatch
) -> None:
Expand All @@ -63,19 +60,17 @@ def fake_register(
monkeypatch.setattr("gete.cli.register_project", fake_register)
monkeypatch.setattr("gete.gcp.GcpClient", lambda quota_project: object())
project.write_agent("finance", {"connections": ["freee"]})
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(
main,
[
"register",
"finance",
"--reset-authorization",
"finance-freee",
"--reset-authorization",
"finance-github",
],
)
result = CliRunner().invoke(
main,
[
"register",
"finance",
"--reset-authorization",
"finance-freee",
"--reset-authorization",
"finance-github",
],
)
assert result.exit_code == 0, result.output
assert seen == {"names": ["finance"], "reset": ["finance-freee", "finance-github"]}

Expand All @@ -86,6 +81,7 @@ def test_version_is_shown() -> None:
assert result.output.startswith("gete, version ")


@pytest.mark.usefixtures("below_project")
def test_a_failure_inside_the_import_check_is_a_message_not_a_traceback(
project: ProjectBuilder, monkeypatch: pytest.MonkeyPatch
) -> None:
Expand All @@ -94,9 +90,7 @@ def boom(directory: Path, **kwargs: Any) -> None:

monkeypatch.setattr("gete.importcheck.import_check", boom)
project.write_agent("mail-triage")
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(main, ["validate", "--import-check"])
result = CliRunner().invoke(main, ["validate", "--import-check"])
assert result.exit_code == 1
assert "requirements.txt cannot be read" in result.output
assert not isinstance(result.exception, DeclarationError)
55 changes: 26 additions & 29 deletions tests/test_cli_archive.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
"""gete archive from the command line."""
"""gete archive from the command line.

archive takes the agent directory as an argument and finds the project by
walking up from it, so these tests pass absolute paths and never have to move
the working directory.
"""

import hashlib
from pathlib import Path
Expand All @@ -14,12 +19,10 @@ def test_archive_writes_the_file_and_prints_the_hash(
) -> None:
project.write_agent("mail-triage")
out = tmp_path / "out" / "mail-triage.tar.gz"
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(
main,
["archive", str(project.agents_dir / "mail-triage"), "--out", str(out)],
)
result = CliRunner().invoke(
main,
["archive", str(project.agents_dir / "mail-triage"), "--out", str(out)],
)
assert result.exit_code == 0, result.output
assert hashlib.sha256(out.read_bytes()).hexdigest() in result.output

Expand All @@ -28,17 +31,15 @@ def test_archive_refuses_an_agent_that_does_not_validate(
project: ProjectBuilder, tmp_path: Path
) -> None:
project.write_agent("mail-triage", {"connections": ["nope"]})
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(
main,
[
"archive",
str(project.agents_dir / "mail-triage"),
"--out",
str(tmp_path / "a.tgz"),
],
)
result = CliRunner().invoke(
main,
[
"archive",
str(project.agents_dir / "mail-triage"),
"--out",
str(tmp_path / "a.tgz"),
],
)
assert result.exit_code == 1
assert "nope" in result.output

Expand All @@ -48,21 +49,17 @@ def test_external_mode_needs_no_directory_argument(project: ProjectBuilder) -> N
import json

directory = project.write_agent("mail-triage")
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(
main,
["archive", "--external"],
input=json.dumps({"directory": str(directory)}),
)
result = CliRunner().invoke(
main,
["archive", "--external"],
input=json.dumps({"directory": str(directory)}),
)
assert result.exit_code == 0, result.output
payload = json.loads(result.output)
assert set(payload) == {"archive", "sha256"}


def test_plain_mode_still_requires_the_directory(project: ProjectBuilder) -> None:
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(main, ["archive"])
def test_plain_mode_still_requires_the_directory() -> None:
result = CliRunner().invoke(main, ["archive"])
assert result.exit_code != 0
assert "DIRECTORY" in result.output
27 changes: 14 additions & 13 deletions tests/test_cli_terraform.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
"""gete terraform from the command line."""

import pytest
from click.testing import CliRunner
from conftest import ProjectBuilder

from gete.cli import main


@pytest.mark.usefixtures("below_project")
def test_terraform_writes_files_then_check_passes(project: ProjectBuilder) -> None:
project.write_agent("mail-triage")
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
written = runner.invoke(main, ["terraform", "--out", str(project.root / "tf")])
assert written.exit_code == 0, written.output
assert (project.root / "tf" / "mail_triage.tf").is_file()
checked = runner.invoke(
main, ["terraform", "--out", str(project.root / "tf"), "--check"]
)
written = runner.invoke(main, ["terraform", "--out", str(project.root / "tf")])
assert written.exit_code == 0, written.output
assert (project.root / "tf" / "mail_triage.tf").is_file()
checked = runner.invoke(
main, ["terraform", "--out", str(project.root / "tf"), "--check"]
)
assert checked.exit_code == 0, checked.output


@pytest.mark.usefixtures("below_project")
def test_check_exits_one_and_names_the_stale_file(project: ProjectBuilder) -> None:
project.write_agent("mail-triage")
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
runner.invoke(main, ["terraform", "--out", str(project.root / "tf")])
project.write_agent("mail-triage", {"display_name": "Renamed"})
checked = runner.invoke(
main, ["terraform", "--out", str(project.root / "tf"), "--check"]
)
runner.invoke(main, ["terraform", "--out", str(project.root / "tf")])
project.write_agent("mail-triage", {"display_name": "Renamed"})
checked = runner.invoke(
main, ["terraform", "--out", str(project.root / "tf"), "--check"]
)
assert checked.exit_code == 1
assert "mail_triage.tf" in checked.output
31 changes: 16 additions & 15 deletions tests/test_connections_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import Any

import pytest
from click.testing import CliRunner
from conftest import ProjectBuilder

Expand Down Expand Up @@ -60,22 +61,20 @@ def test_the_description_shows_the_menu_next_to_the_default_scopes() -> None:
assert "optional scopes" in output


@pytest.mark.usefixtures("below_project")
def test_cli_prints_one_line_per_connection(project: ProjectBuilder) -> None:
write_connections(project)
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(main, ["connections"])
result = CliRunner().invoke(main, ["connections"])
assert result.exit_code == 0, result.output
lines = [line for line in result.output.splitlines() if line.strip()]
assert any(line.startswith("freee") for line in lines)
assert any("retired" in line and line.startswith("old-api") for line in lines)


@pytest.mark.usefixtures("outside_any_project")
def test_cli_connections_works_without_a_project() -> None:
"""The catalog is worth reading before there is a gete.yaml."""
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(main, ["connections"])
result = CliRunner().invoke(main, ["connections"])
assert result.exit_code == 0, result.output
assert "freee" in result.output

Expand Down Expand Up @@ -126,12 +125,12 @@ def write_connections(project: ProjectBuilder) -> None:


def describe(project: ProjectBuilder, connection_id: str) -> Any:
"""Callers take below_project: connections reads the overrides from there."""
write_connections(project)
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
return runner.invoke(main, ["connections", connection_id])
return CliRunner().invoke(main, ["connections", connection_id])


@pytest.mark.usefixtures("below_project")
def test_describing_a_connection_prints_what_a_person_has_to_do_first(
project: ProjectBuilder,
) -> None:
Expand All @@ -141,6 +140,7 @@ def test_describing_a_connection_prints_what_a_person_has_to_do_first(
assert line in result.output


@pytest.mark.usefixtures("below_project")
def test_the_description_names_the_secrets_and_the_redirect_uri(
project: ProjectBuilder,
) -> None:
Expand All @@ -151,6 +151,7 @@ def test_the_description_names_the_secrets_and_the_redirect_uri(
assert "https://vertexaisearch.cloud.google.com/oauth-redirect" in output


@pytest.mark.usefixtures("below_project")
def test_a_connection_without_setup_notes_is_still_described(
project: ProjectBuilder,
) -> None:
Expand All @@ -160,6 +161,7 @@ def test_a_connection_without_setup_notes_is_still_described(
assert "https://accounts.secure.freee.co.jp/public_api/token" in result.output


@pytest.mark.usefixtures("below_project")
def test_describing_an_unknown_connection_names_the_known_ones(
project: ProjectBuilder,
) -> None:
Expand All @@ -168,14 +170,14 @@ def test_describing_an_unknown_connection_names_the_known_ones(
assert "freee" in result.output


@pytest.mark.usefixtures("outside_any_project")
def test_a_catalog_connection_can_be_described_without_a_project() -> None:
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(main, ["connections", "github"])
result = CliRunner().invoke(main, ["connections", "github"])
assert result.exit_code == 0, result.output
assert "api.github.com" in result.output


@pytest.mark.usefixtures("below_project")
def test_a_retired_connection_reads_retired_with_the_reason_alongside(
project: ProjectBuilder,
) -> None:
Expand All @@ -186,11 +188,10 @@ def test_a_retired_connection_reads_retired_with_the_reason_alongside(
assert "native connector" in result.output


@pytest.mark.usefixtures("outside_any_project")
def test_describing_slack_mcp_prints_the_app_setup_without_a_project() -> None:
"""The Slack app is prepared by a person; the description has to carry it."""
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(main, ["connections", "slack-mcp"])
result = CliRunner().invoke(main, ["connections", "slack-mcp"])
assert result.exit_code == 0, result.output
assert "mcp.slack.com" in result.output
assert "Before anyone can authorize:" in result.output
Expand Down
6 changes: 3 additions & 3 deletions tests/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import Any

import pytest
from click.testing import CliRunner
from conftest import ProjectBuilder

Expand Down Expand Up @@ -70,12 +71,11 @@ def test_node_ids_are_safe_mermaid_identifiers(project: ProjectBuilder) -> None:
assert head.replace("_", "").isalnum(), line


@pytest.mark.usefixtures("below_project")
def test_cli_prints_mermaid(project: ProjectBuilder) -> None:
project.write_agent("finance", FINANCE)
(project.agents_dir / "finance" / "src").mkdir()
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=project.root):
result = runner.invoke(main, ["graph", "finance"])
result = CliRunner().invoke(main, ["graph", "finance"])
assert result.exit_code == 0, result.output
assert result.output.startswith("flowchart LR")

Expand Down
Loading