diff --git a/src/dualentry_cli/commands/__init__.py b/src/dualentry_cli/commands/__init__.py index e6e43f7..69c3ad5 100644 --- a/src/dualentry_cli/commands/__init__.py +++ b/src/dualentry_cli/commands/__init__.py @@ -183,6 +183,7 @@ def make_resource_app( resource: str, path: str, *, + has_get: bool = True, has_create: bool = True, has_update: bool = True, has_delete: bool = False, @@ -254,7 +255,7 @@ def list_cmd( sig = inspect.signature(list_cmd) list_cmd.__signature__ = sig.replace(parameters=[p for p in sig.parameters.values() if p.name not in remove]) - if has_number: + if has_get and has_number: @app.command("get") def get_cmd_auto( @@ -308,7 +309,8 @@ def get_cmd_by_id( format_output(data, resource=resource, fmt=output) get_cmd_by_id.__doc__ = f"Get a {resource} by ID." - else: + + elif has_get and not has_number: @app.command("get") def get_cmd( diff --git a/src/dualentry_cli/main.py b/src/dualentry_cli/main.py index 912bdb1..5576943 100644 --- a/src/dualentry_cli/main.py +++ b/src/dualentry_cli/main.py @@ -61,13 +61,13 @@ make_resource_app("fixed assets", "fixed-asset", "fixed-assets", has_number=True, filters={"search", "status", "company", "customer", "vendor"}), name="fixed-assets", ) -app.add_typer(make_resource_app("depreciation books", "depreciation-book", "depreciation-books", filters=set()), name="depreciation-books") +app.add_typer(make_resource_app("depreciation books", "depreciation-book", "depreciation-books", has_create=False, has_update=False, filters=set()), name="depreciation-books") # Entities app.add_typer(make_resource_app("customers", "customer", "customers", filters={"search", "status", "company"}), name="customers") app.add_typer(make_resource_app("vendors", "vendor", "vendors", filters=TXN), name="vendors") app.add_typer(make_resource_app("items", "item", "items", filters={"search", "status"}), name="items") -app.add_typer(make_resource_app("companies", "company", "companies", filters={"search"}), name="companies") +app.add_typer(make_resource_app("companies", "company", "companies", has_create=False, has_update=False, filters={"search"}), name="companies") app.add_typer(make_resource_app("classifications", "classification", "classifications", filters={"search"}), name="classifications") # Recurring @@ -84,7 +84,7 @@ # Other # Contracts name their status filter `status`, not `record_status`. app.add_typer(make_resource_app("contracts", "contract", "contracts", filters=TXN_CUSTOMER, status_param="status"), name="contracts") -app.add_typer(make_resource_app("budgets", "budget", "budgets", filters={"search", "status", "company"}), name="budgets") +app.add_typer(make_resource_app("budgets", "budget", "budgets", has_create=False, has_update=False, filters={"search", "status", "company"}), name="budgets") app.add_typer( make_resource_app("workflows", "workflow", "workflows", has_create=False, has_update=False, filters={"search", "company"}), name="workflows", @@ -103,8 +103,8 @@ ), name="intercompany-journal-entries", ) -app.add_typer(make_resource_app("paper checks", "paper-check", "paper-checks", has_number=True, filters=TXN_ALL_PARTIES), name="paper-checks") -app.add_typer(make_resource_app("inbox items", "inbox-item", "inbox", has_create=False, has_update=False, filters={"search"}), name="inbox") +app.add_typer(make_resource_app("paper checks", "paper-check", "paper-checks", has_create=False, has_update=False, filters=TXN_ALL_PARTIES), name="paper-checks") +app.add_typer(make_resource_app("inbox items", "inbox-item", "inbox", has_get=False, has_create=False, has_update=False, filters={"search"}), name="inbox") def version_callback(value: bool): diff --git a/tests/test_stale_commands.py b/tests/test_stale_commands.py new file mode 100644 index 0000000..f3fbf39 --- /dev/null +++ b/tests/test_stale_commands.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +import pytest + +from dualentry_cli.main import app + +STALE_COMMANDS = [ + ("companies", "create"), + ("companies", "update"), + ("budgets", "create"), + ("budgets", "update"), + ("depreciation-books", "create"), + ("depreciation-books", "update"), + ("paper-checks", "create"), + ("paper-checks", "update"), + ("inbox", "get"), +] + + +def _commands(resource: str) -> set[str]: + group = next(g for g in app.registered_groups if g.name == resource) + return {c.name for c in group.typer_instance.registered_commands} + + +@pytest.mark.parametrize(("resource", "command"), STALE_COMMANDS) +def test_stale_command_is_not_registered(resource: str, command: str): + assert command not in _commands(resource), f"'dualentry {resource} {command}' has no v2 route and must not be registered" + + +@pytest.mark.parametrize("resource", ["companies", "budgets", "depreciation-books", "paper-checks"]) +def test_read_only_resource_keeps_its_read_commands(resource: str): + assert _commands(resource) == {"list", "get"} + + +def test_inbox_keeps_only_list(): + assert _commands("inbox") == {"list"} + + +def test_paper_checks_has_no_number_lookups(): + assert not _commands("paper-checks") & {"get-number", "get-id"} + + +def test_writable_resource_is_untouched(): + assert {"create", "update"} <= _commands("invoices")