-
Notifications
You must be signed in to change notification settings - Fork 0
test: ✅ mocking of basic app functionality #155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
caf43bc
fix: 🐛 Correctly add root keys
joelostblom 27119aa
test: ✅ Test fundamental app logic
joelostblom de44805
test: ✅ Test help output format
joelostblom 9c84926
test: ✅ Reformat comments
joelostblom daf506d
Apply suggestions from code review
joelostblom 5235dd2
docs: 📝 Remove leading = for section comments as per Lukes request
joelostblom 3ef568a
feat: ✨ Add function to generate help test strings
joelostblom eee5717
chore(pre-commit): :pencil2: automatic fixes
pre-commit-ci[bot] bbd38f5
fix: 🐛 Simplify docstring and printing
joelostblom 1d11e38
fix: 🐛 Restore test cases
joelostblom 1a46c90
chore(pre-commit): :pencil2: automatic fixes
pre-commit-ci[bot] 1e56fc4
refactor: ♻️ Functionalize outdated check
joelostblom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,50 +1,180 @@ | ||
| """These are integration tests for the CLI commands.""" | ||
| """Tests for the CLI commands.""" | ||
|
|
||
| import json | ||
| from pathlib import Path | ||
| from textwrap import dedent | ||
|
|
||
| from pytest import fixture, mark | ||
| import pytest | ||
|
|
||
| from seedcase_flower.cli import build, view | ||
| from seedcase_flower.cli import app, view | ||
| from seedcase_flower.internals import BuildStyle | ||
|
|
||
| _DATAPACKAGE_DATA = { | ||
| "name": "placeholder", | ||
| "created": "2026-02-12T11:25:49+01:00", | ||
| "description": "Placeholder", | ||
| "id": "Placeholder", | ||
| "licenses": [{"name": "Placeholder"}], | ||
| "title": "Placeholder", | ||
| "version": "0.0.0", | ||
| } | ||
|
|
||
| # Create a file at tmp_path that is automatically cleaned up after tests finish | ||
| @fixture | ||
| def datapackage_path(tmp_path): | ||
| data = { | ||
| "name": "placeholder", | ||
| "created": "2026-02-12T11:25:49+01:00", | ||
| "description": "Placeholder", | ||
| "id": "Placeholder", | ||
| "licenses": [{"name": "Placeholder"}], | ||
| "title": "Placeholder", | ||
| "version": "0.0.0", | ||
| } | ||
|
|
||
| @pytest.fixture | ||
| def datapackage_path(tmp_path): | ||
| """Create a temporary datapackage.json and return its path as a string.""" | ||
| file_path = tmp_path / "datapackage.json" | ||
| file_path.write_text(json.dumps(data)) | ||
|
|
||
| # Since `build` expects a str as the URI | ||
| file_path.write_text(json.dumps(_DATAPACKAGE_DATA)) | ||
| return str(file_path) | ||
|
|
||
|
|
||
| @mark.parametrize( | ||
| "style, expected", | ||
| [ | ||
| (BuildStyle.quarto_one_page, None), | ||
| ], | ||
| @pytest.fixture | ||
| def mock_resolve_uri(mocker): | ||
| """Mock _resolve_uri to isolate CLI tests from filesystem resolution.""" | ||
| return mocker.patch("seedcase_flower.cli._resolve_uri") | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def mock_read_properties(mocker): | ||
joelostblom marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """Mock _read_properties to isolate CLI tests from file I/O.""" | ||
| return mocker.patch("seedcase_flower.cli._read_properties") | ||
|
|
||
|
|
||
| # Testing CLI invocation ==== | ||
|
|
||
|
|
||
| def test_build_with_mocked_internals(mock_resolve_uri, mock_read_properties): | ||
| """Isolate CLI behaviour by mocking internal helpers.""" | ||
| fake_path = Path("datapackage.json") | ||
| mock_resolve_uri.return_value = fake_path | ||
| # Simulate running the app from the command line (but without calling sys.exit()) | ||
| app(["build", "datapackage.json"], result_action="return_value") | ||
|
|
||
| # Checking that the correct values were passed to the internal functions | ||
| mock_resolve_uri.assert_called_once_with("datapackage.json") | ||
| mock_read_properties.assert_called_once_with(fake_path) | ||
|
|
||
|
|
||
| # Checking stdout ==== | ||
|
|
||
|
|
||
| # TODO: Update this when verbose is added. | ||
| def test_build_verbose_prints_output(capsys, datapackage_path): | ||
joelostblom marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """--verbose should print output_dir, properties, template_dir, and style.""" | ||
| app( | ||
| ["build", datapackage_path, "--verbose"], | ||
| result_action="return_value", | ||
| ) | ||
| expected = f"docs {_DATAPACKAGE_DATA} None BuildStyle.quarto_one_page\n" | ||
| assert capsys.readouterr().out == expected | ||
|
|
||
|
|
||
| def test_build_no_verbose_produces_no_output(capsys, datapackage_path): | ||
| """Without --verbose, build should produce no stdout.""" | ||
| app(["build", datapackage_path], result_action="return_value") | ||
| assert capsys.readouterr().out == "" | ||
|
|
||
|
|
||
| # File-based config ==== | ||
|
|
||
|
|
||
| def test_build_reads_uri_from_flower_toml(tmp_path, monkeypatch): | ||
| """Build args specified in .flower.toml should overwrite the default values.""" | ||
| toml_path = tmp_path / ".flower.toml" | ||
| toml_path.write_text( | ||
| 'uri = "custom.json"\n' | ||
| 'style = "quarto_resource_listing"\n' | ||
| 'template_dir = "my-templates/"\n' | ||
| 'output_dir = "my-docs/"\n' | ||
| "verbose = true\n" | ||
| ) | ||
|
|
||
| monkeypatch.chdir(tmp_path) | ||
|
|
||
| _, bound, _ = app.parse_args(["build"]) | ||
| assert bound.arguments["uri"] == "custom.json" | ||
| assert bound.arguments["style"] == BuildStyle.quarto_resource_listing | ||
| assert bound.arguments["template_dir"] == Path("my-templates/") | ||
| assert bound.arguments["output_dir"] == Path("my-docs/") | ||
| assert bound.arguments["verbose"] is True | ||
|
|
||
|
|
||
| # Help output ==== | ||
|
|
||
| _HELP_PAGE = dedent( | ||
| """\ | ||
| Usage: seedcase-flower COMMAND | ||
|
|
||
| Flower generates human-readable documentation from Data Packages. | ||
|
|
||
| ╭─ Commands ─────────────────────────────────────────────────────────────────────────────╮ | ||
| │ build Build human-readable documentation from a datapackage.json file. │ | ||
| │ --help (-h) Display this message and exit. │ | ||
| │ --version Display application version. │ | ||
| ╰────────────────────────────────────────────────────────────────────────────────────────╯ | ||
| """ # noqa | ||
| ) | ||
| def test_build( | ||
| datapackage_path: str, | ||
| style: BuildStyle, | ||
| expected: None, | ||
| ) -> None: | ||
| """Test the build CLI function.""" | ||
| result = build(datapackage_path, style=style) | ||
| assert result == expected | ||
|
|
||
| _BUILD_HELP_PAGE = dedent( | ||
| """\ | ||
| Usage: seedcase-flower build [ARGS] | ||
|
|
||
| Build human-readable documentation from a datapackage.json file. | ||
|
|
||
| ╭─ Parameters ───────────────────────────────────────────────────────────────────────────╮ | ||
| │ URI --uri The URI to a datapackage.json file. [default: │ | ||
| │ datapackage.json] │ | ||
| │ STYLE --style The style used to structure the output. If a template │ | ||
| │ directory is given, this parameter will be ignored. │ | ||
| │ [choices: quarto-one-page, quarto-resource-listing, │ | ||
| │ quarto-resource-tables] [default: quarto-one-page] │ | ||
| │ TEMPLATE-DIR --template-dir The directory that contains the Jinja template files and │ | ||
| │ sections.toml. When set, it will override any built-in │ | ||
| │ style given by the style parameter. │ | ||
| │ OUTPUT-DIR --output-dir The directory to save the generated files in. [default: │ | ||
| │ docs] │ | ||
| │ VERBOSE --verbose If True, prints additional information to the console. │ | ||
| │ [default: False] │ | ||
| ╰────────────────────────────────────────────────────────────────────────────────────────╯ | ||
| """ # noqa | ||
| ) | ||
|
|
||
| _CHANGED_MSG = ( | ||
| "The `{cmd}` help output changed. Run `just generate-help-strings` " | ||
| "and paste the updated string into the relevant test." | ||
| ) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def console(): | ||
| from rich.console import Console | ||
|
|
||
| return Console( | ||
| width=90, | ||
| force_terminal=True, | ||
| highlight=False, | ||
| color_system=None, | ||
| legacy_windows=False, | ||
| ) | ||
|
|
||
|
|
||
| def test_help_page(capsys, console): | ||
| """Top-level --help should match expected output.""" | ||
| with pytest.raises(SystemExit): | ||
| app(["--help"], console=console) | ||
| assert capsys.readouterr().out == _HELP_PAGE, _CHANGED_MSG.format(cmd="general") | ||
|
|
||
|
|
||
| def test_build_help_page(capsys, console): | ||
| """build --help should document all parameters with defaults and choices.""" | ||
| with pytest.raises(SystemExit): | ||
| app(["build", "--help"], console=console) | ||
| assert capsys.readouterr().out == _BUILD_HELP_PAGE, _CHANGED_MSG.format(cmd="build") | ||
|
|
||
|
|
||
| # view (placeholder) ==== | ||
|
|
||
|
|
||
| def test_view() -> None: | ||
| """Test the view CLI function.""" | ||
| result = view() | ||
| assert result == "" | ||
| """view returns an empty string.""" | ||
| assert view() == "" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| """Generate the expected help-output strings used in test_cli.py. | ||
lwjohnst86 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Run this script after changing a docstring or CLI parameter. Only snippets | ||
| whose output differs from the current constants in test_cli.py are printed. | ||
| """ | ||
|
|
||
| import sys | ||
| from io import StringIO | ||
| from operator import itemgetter | ||
|
|
||
| from rich.console import Console | ||
|
|
||
| from seedcase_flower.cli import app | ||
| from tests.test_cli import _BUILD_HELP_PAGE, _HELP_PAGE | ||
|
|
||
|
|
||
| def _capture_help(args: list[str]) -> str: | ||
| """Return the help text produced by *args* as a plain string.""" | ||
| console = Console( | ||
| width=90, | ||
| force_terminal=True, | ||
| highlight=False, | ||
| color_system=None, | ||
| legacy_windows=False, | ||
| ) | ||
|
|
||
| old_stdout = sys.stdout | ||
| sys.stdout = StringIO() | ||
| try: | ||
| try: | ||
| app(args, console=console) | ||
| except SystemExit: | ||
| pass | ||
| return sys.stdout.getvalue() | ||
| finally: | ||
| sys.stdout = old_stdout | ||
|
|
||
|
|
||
| def _is_outdated(check: tuple[str, list[str], str]) -> bool: | ||
| """Return True if the current help output differs from the stored constant.""" | ||
| _, args, current = check | ||
| return _capture_help(args) != current | ||
|
|
||
|
|
||
| def _find_outdated_checks( | ||
| checks: list[tuple[str, list[str], str]], | ||
| ) -> list[tuple[str, list[str]]]: | ||
| """Return checks whose current help output differs from the stored constant.""" | ||
| return list(map(itemgetter(0, 1), filter(_is_outdated, checks))) | ||
|
|
||
|
|
||
| def _as_constant_snippet(name: str, text: str) -> str: | ||
| """Return a copy-pasteable constant assignment for *text*.""" | ||
| lines = text.splitlines() | ||
| indented_body = "\n".join(f" {line}" if line else "" for line in lines) | ||
| return f'{name} = dedent(\n """\\\n{indented_body}\n """ # noqa\n)' | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| checks = [ | ||
| ("_HELP_PAGE", ["--help"], _HELP_PAGE), | ||
| ("_BUILD_HELP_PAGE", ["build", "--help"], _BUILD_HELP_PAGE), | ||
| ] | ||
| changed = _find_outdated_checks(checks) | ||
|
|
||
| if not changed: | ||
| print("No changes detected. All help-output constants are up to date.") | ||
| else: | ||
| print("\nReview that the output below looks as expected.") | ||
| print("Then, copy and paste it into tests/test_cli.py,") | ||
| print("replacing the variable(s) with the same name.") | ||
| for name, args in changed: | ||
| print("\n\n") | ||
| print(_as_constant_snippet(name, _capture_help(args))) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.