7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
157 lines
5.1 KiB
Python
157 lines
5.1 KiB
Python
"""
|
|
Tests for HTTP API example scripts.
|
|
|
|
Validates that the example scripts under examples/api_usage/http/ exist,
|
|
are syntactically valid Python, and define the expected entry points.
|
|
|
|
The actual API functionality exercised by these examples is already covered
|
|
by test_rest_api.py and test_endpoints_health.py via the Flask test client.
|
|
"""
|
|
|
|
import ast
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
# Root of the repository
|
|
REPO_ROOT = Path(__file__).parent.parent.parent
|
|
EXAMPLES_DIR = REPO_ROOT / "examples" / "api_usage" / "http"
|
|
|
|
# All example scripts that should be tested — single source of truth
|
|
EXAMPLE_SCRIPTS = [
|
|
"simple_working_example.py",
|
|
"advanced/http_api_examples.py",
|
|
"advanced/simple_http_example.py",
|
|
]
|
|
|
|
|
|
def _parse_example(relative_path: str) -> tuple[Path, str, ast.Module]:
|
|
"""Read and parse an example file, failing if it doesn't exist.
|
|
|
|
Returns (path, source_text, ast_tree).
|
|
"""
|
|
path = EXAMPLES_DIR / relative_path
|
|
if not path.is_file():
|
|
pytest.fail(f"Example not found: {path}")
|
|
|
|
source = path.read_text(encoding="utf-8")
|
|
tree = ast.parse(source, filename=str(path))
|
|
return path, source, tree
|
|
|
|
|
|
def _has_main_guard(tree: ast.Module) -> bool:
|
|
"""Check if an AST contains an ``if __name__ == '__main__':`` guard."""
|
|
for node in ast.walk(tree):
|
|
if not isinstance(node, ast.If):
|
|
continue
|
|
test = node.test
|
|
if not isinstance(test, ast.Compare):
|
|
continue
|
|
if len(test.ops) != 1 or not isinstance(test.ops[0], ast.Eq):
|
|
continue
|
|
# Match both orderings:
|
|
# __name__ == "__main__"
|
|
# "__main__" == __name__
|
|
left = test.left
|
|
comparators = test.comparators
|
|
if len(comparators) != 1:
|
|
continue
|
|
right = comparators[0]
|
|
if (
|
|
isinstance(left, ast.Name)
|
|
and left.id == "__name__"
|
|
and isinstance(right, ast.Constant)
|
|
and right.value == "__main__"
|
|
) or (
|
|
isinstance(right, ast.Name)
|
|
and right.id == "__name__"
|
|
and isinstance(left, ast.Constant)
|
|
and left.value == "__main__"
|
|
):
|
|
return True
|
|
return False
|
|
|
|
|
|
class TestHttpExamplesExist:
|
|
"""Verify that expected HTTP example files are present."""
|
|
|
|
def test_examples_directory_exists(self):
|
|
assert EXAMPLES_DIR.is_dir(), (
|
|
f"Examples directory not found: {EXAMPLES_DIR}"
|
|
)
|
|
|
|
@pytest.mark.parametrize("relative_path", EXAMPLE_SCRIPTS)
|
|
def test_example_file_exists(self, relative_path):
|
|
path = EXAMPLES_DIR / relative_path
|
|
assert path.is_file(), f"Missing example: {path}"
|
|
|
|
|
|
class TestHttpExamplesSyntax:
|
|
"""Verify that example scripts are syntactically valid Python."""
|
|
|
|
@pytest.mark.parametrize("relative_path", EXAMPLE_SCRIPTS)
|
|
def test_example_parses(self, relative_path):
|
|
"""Each example file must be valid Python (no syntax errors)."""
|
|
_path, _source, tree = _parse_example(relative_path)
|
|
assert tree is not None
|
|
|
|
@pytest.mark.parametrize("relative_path", EXAMPLE_SCRIPTS)
|
|
def test_example_has_main_function(self, relative_path):
|
|
"""Each example script should define a main() function."""
|
|
_path, _source, tree = _parse_example(relative_path)
|
|
|
|
function_names = [
|
|
node.name
|
|
for node in ast.walk(tree)
|
|
if isinstance(node, ast.FunctionDef)
|
|
]
|
|
assert "main" in function_names, (
|
|
f"{relative_path} should define a main() function"
|
|
)
|
|
|
|
@pytest.mark.parametrize("relative_path", EXAMPLE_SCRIPTS)
|
|
def test_example_has_main_guard(self, relative_path):
|
|
"""Each example script should have an if __name__ == '__main__' guard."""
|
|
_path, _source, tree = _parse_example(relative_path)
|
|
|
|
assert _has_main_guard(tree), (
|
|
f"{relative_path} should have an if __name__ == '__main__' guard"
|
|
)
|
|
|
|
|
|
class TestAdvancedExampleStructure:
|
|
"""Verify the advanced example defines expected helper classes/functions."""
|
|
|
|
def test_ldr_client_class_defined(self):
|
|
"""The advanced example should define an LDRClient class."""
|
|
_path, _source, tree = _parse_example("advanced/http_api_examples.py")
|
|
|
|
class_names = [
|
|
node.name
|
|
for node in ast.walk(tree)
|
|
if isinstance(node, ast.ClassDef)
|
|
]
|
|
assert "LDRClient" in class_names, (
|
|
"Advanced example should define LDRClient class"
|
|
)
|
|
|
|
def test_example_functions_defined(self):
|
|
"""The advanced example should define the documented example functions."""
|
|
_path, _source, tree = _parse_example("advanced/http_api_examples.py")
|
|
|
|
function_names = {
|
|
node.name
|
|
for node in ast.walk(tree)
|
|
if isinstance(node, ast.FunctionDef)
|
|
}
|
|
|
|
expected_examples = [
|
|
"example_quick_research",
|
|
"example_settings_management",
|
|
"example_research_history",
|
|
]
|
|
for fn in expected_examples:
|
|
assert fn in function_names, (
|
|
f"Advanced example should define {fn}()"
|
|
)
|