Phase 1-3 + 6: pluggable filter system, app factory, test harness
Implements the parallel.md workstreams (Agents A-E) toward REFACTOR_GOAL.md. Phase 1 — app factory + blueprints + /api/v1: - app.py -> create_app() factory (no module-level app); entrypoints updated - routes/ (auth, pages, settings, admin, assets) + blueprints/api.py at /api/v1 - config.py / extensions.py / security.py extracted; services/ layer added - endpoint names preserved so template url_for() calls keep resolving (static check: all 27 template url_for endpoints are defined routes) Phase 2 — one pluggable filter system: - filter_pipeline/registry.py: @register_stage / @register_plugin + discover_modules - engine._init_stages() instantiates registered stages (no hardcoded dict); process_batch is AI-aware: only short-circuits to the AI-disabled path when a filterset's stages declare requires_ai, so offline filtersets run with AI off - BaseFilterPlugin gets a consumer (stages/plugins.py); Keyword/Quality re-enabled via filter_config.json plugins config - comment tree modes ported to stages/comment_filter.py + shared rules.py; wired into /api/v1/posts/<uuid> and /api/v1/comments/<uuid> via FilterEngine.filter_comments() (fails open) - offline quality_filter filterset exercises plugins+ranker without AI - legacy filter_lib / comment_lib / html_generation_lib / generate_html / active_html path deleted Phase 3 prep — pluggable fetchers + Postgres models: - Post / Comment SQLAlchemy models added to models.py - migrate_content_to_db.py backfill (idempotent by uuid, batched, --dry-run) - platforms/ fetcher registry (extension point) - live reads/writes still go through PostService (disk JSON); cutover deferred Phase 6 — test harness: - pytest.ini + tests/ (conftest with in-memory SQLite fixture, no Postgres; stubbed polling/filter singletons) - test_app_factory.py (route registration, no module-level app), test_api_contracts.py (posts/post_detail/comments/filters shape with monkeypatched post_service + get_filter_engine), test_filter_pipeline.py + test_plugin_contract.py (Flask-free; validated locally 12/12 incl. drop-in stage/plugin discovered with zero core edits) Other: .gitignore added (__pycache__, data/, secrets); pytest in requirements. Verification: py_compile clean across the project; the Flask-free filter-pipeline and plugin-contract tests pass locally. App-factory / API-contract tests need deps+docker to run; runtime flask routes / Auth0-repeated-create_app also gated on docker. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
58
tests/test_app_factory.py
Normal file
58
tests/test_app_factory.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""App-factory tests: route registration and endpoint names are intact.
|
||||
|
||||
These guard the Phase 1 refactor (module-level ``app`` → ``create_app()`` with
|
||||
routes split under ``routes/`` and ``blueprints/``). They assert that the
|
||||
endpoints referenced by templates via ``url_for(...)`` still resolve in the
|
||||
Flask url_map. Runs against in-memory SQLite (no Postgres).
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
# Endpoints that templates reference via url_for(...) and that must survive
|
||||
# the factory refactor. Sourced from the Jinja templates.
|
||||
TEMPLATE_ENDPOINTS = [
|
||||
"login",
|
||||
"signup",
|
||||
"admin_setup",
|
||||
"logout",
|
||||
"serve_logo",
|
||||
"serve_theme",
|
||||
"static",
|
||||
# API blueprint endpoints (mounted at /api/v1)
|
||||
"api.posts",
|
||||
"api.post_detail",
|
||||
"api.comments",
|
||||
"api.filters",
|
||||
"api.bookmarks",
|
||||
"api.platforms",
|
||||
]
|
||||
|
||||
|
||||
def test_create_app_returns_flask_app(app):
|
||||
from flask import Flask
|
||||
|
||||
assert isinstance(app, Flask)
|
||||
|
||||
|
||||
def test_api_blueprint_mounted_under_v1(app):
|
||||
rules = [r.rule for r in app.url_map.iter_rules()]
|
||||
assert any(r.startswith("/api/v1/posts") for r in rules), rules
|
||||
assert any(r.startswith("/api/v1/comments") for r in rules), rules
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", TEMPLATE_ENDPOINTS)
|
||||
def test_template_endpoints_exist(app, endpoint):
|
||||
# Flask stores endpoints as "<blueprint>.<view>" for blueprint views and
|
||||
# bare names for app-level views. ``url_map`` has both.
|
||||
all_endpoints = {r.endpoint for r in app.url_map.iter_rules()}
|
||||
assert endpoint in all_endpoints, (
|
||||
f"endpoint '{endpoint}' missing from url_map; have: {sorted(all_endpoints)[:20]}..."
|
||||
)
|
||||
|
||||
|
||||
def test_no_module_level_app_object():
|
||||
"""Phase 1 removed the module-level ``app``; importing app.py must not
|
||||
start a server or expose a global Flask app."""
|
||||
import app as app_module
|
||||
|
||||
assert not hasattr(app_module, "app"), "app.py must not keep a module-level `app`"
|
||||
Reference in New Issue
Block a user