Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dev = [
"pre-commit>=4.5.0",
"pytest>=9.0.1",
"pytest-cov>=7.0.0",
"pytest-mock>=3.14.0",
"quartodoc>=0.11.1",
"ruff>=0.14.7",
"types-tabulate>=0.9.0.20241207",
Expand Down
3 changes: 2 additions & 1 deletion src/seedcase_flower/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from seedcase_flower.internals import BuildStyle, _read_properties, _resolve_uri

app = App(
name="seedcase-flower",
help="Flower generates human-readable documentation from Data Packages.",
default_parameter=Parameter(negative=()),
config=[
Expand All @@ -19,7 +20,7 @@
),
config.Toml(
"pyproject.toml",
root_keys="tool.seedcase-flower",
root_keys=["tool", "seedcase-flower"],
search_parents=True,
use_commands_as_keys=False,
),
Expand Down
190 changes: 155 additions & 35 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,170 @@
"""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),
],
)
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
@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):
"""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 ===


def test_build_verbose_prints_output(capsys, datapackage_path):
"""--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 ===


@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 == 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_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 == 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
)


# === view (placeholder) ===


def test_view() -> None:
"""Test the view CLI function."""
result = view()
assert result == ""
"""view returns an empty string."""
assert view() == ""
Loading