Add frontend integration checklist and docs-drift guard
PLUGIN-QUICKSTART Step 7 is now a concrete 7-item checklist (view conventions, route auto-discovery + ADR-009 gating meta, api client shape, nav/report hooks, settings auto-nesting, verification). New tests/test_docs_contract.py introspects BasePlugin and fails CI when a public hook is missing from PLUGIN-HOOKS.md or the documented contract version drifts - it immediately caught two undocumented hooks (get_provisioning_note, get_config_schema), now documented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
61
tests/test_docs_contract.py
Normal file
61
tests/test_docs_contract.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Docs-drift guards: docs/PLUGIN-HOOKS.md must track the live contract.
|
||||
|
||||
PLUGIN-HOOKS.md is the canonical plugin-author reference. These tests fail
|
||||
when the contract surface moves without the doc: a version bump that skips
|
||||
the doc's version example, or a new/renamed BasePlugin hook with no doc
|
||||
mention. Keeping this structural (not manual review) is what lets sister
|
||||
sites trust the doc.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
|
||||
from shopdb import __contract_version__
|
||||
from shopdb.plugins.base import BasePlugin
|
||||
|
||||
HOOKS_DOC = Path(__file__).resolve().parent.parent / 'docs' / 'PLUGIN-HOOKS.md'
|
||||
|
||||
|
||||
def test_hooks_doc_exists():
|
||||
assert HOOKS_DOC.exists(), 'docs/PLUGIN-HOOKS.md is missing'
|
||||
|
||||
|
||||
def test_hooks_doc_declares_current_contract_version():
|
||||
"""The doc's version example must match the live __contract_version__."""
|
||||
text = HOOKS_DOC.read_text()
|
||||
expected = f"__contract_version__ = '{__contract_version__}'"
|
||||
assert expected in text, (
|
||||
f'docs/PLUGIN-HOOKS.md version example is stale: expected {expected}. '
|
||||
f'Update the "Contract version" section when bumping the contract.'
|
||||
)
|
||||
|
||||
|
||||
def test_every_public_hook_is_documented():
|
||||
"""Every public BasePlugin method must be mentioned in the doc."""
|
||||
text = HOOKS_DOC.read_text()
|
||||
hooks = [
|
||||
name for name, member in inspect.getmembers(
|
||||
BasePlugin, predicate=inspect.isfunction)
|
||||
if not name.startswith('_')
|
||||
]
|
||||
assert hooks, 'No public hooks found on BasePlugin (introspection broke?)'
|
||||
missing = [hook for hook in hooks if hook not in text]
|
||||
assert not missing, (
|
||||
'BasePlugin hooks missing from docs/PLUGIN-HOOKS.md: '
|
||||
+ ', '.join(missing)
|
||||
+ '. Add a section (or mention) for each before shipping the hook.'
|
||||
)
|
||||
|
||||
|
||||
def test_doc_does_not_reference_removed_hooks():
|
||||
"""Hooks removed from the contract must not be documented as current.
|
||||
|
||||
They may appear in "Removed" notes; this only guards section headings.
|
||||
"""
|
||||
text = HOOKS_DOC.read_text()
|
||||
for removed in ('get_searchable_fields', 'get_event_handlers'):
|
||||
assert not hasattr(BasePlugin, removed)
|
||||
assert f'### `{removed}' not in text, (
|
||||
f'{removed} was removed from the contract but still has a '
|
||||
f'section heading in docs/PLUGIN-HOOKS.md'
|
||||
)
|
||||
Reference in New Issue
Block a user