mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-22 12:52:29 +00:00
* Update GitHub repo references from feedback* to feedBack* * rename: slopsmith -> feedBack, byron -> got-feedBack Renames across the entire codebase: - slopsmith/Slopsmith/SLOPSMITH/SlopSmith -> feedBack/FeedBack/FEEDBACK/FeedBack - byron/Byron/Byrongamatos -> got-feedBack/got-feedBack/got-feedBack - /home/byron/ -> /opt/got-feedBack/ - byron@ougsoft.com -> hi@got-feedBack.org - github.com/byrongamatos/ -> github.com/got-feedback/ - com.byron. -> com.got-feedback. - SLOPSMITH_ env vars -> FEEDBACK_ with backward-compat fallback - Protocol/storage strings migrated with read-old/write-new pattern - window.slopsmith JS API -> window.feedBack (canonical) + backward-compat alias Refs: #rename-slopsmith * rename: complete regen against current main + fix backward-compat alias Regenerated the slopsmith->feedBack / byron->got-feedBack rename on top of current main (3 commits had landed since the branch: #572/#554/#574), resolving the four content conflicts in favour of main's newer content (autoplay/auto-exit, accuracy-badge, Virtuoso re-home, feedpak badge). Completion fixes on top of the mechanical rename: - Re-apply rename to post-branch content the original rename never saw: window.slopsmith(.Tour) consumers in lessons.js / notifications.js / onboarding-tour.js, and the matching JS + python tests (autoplay_exit, progression_*, test_feedpak_extension FEEDBACK_* env vars). The test env vars now match server.py (which reads FEEDBACK_SYNC_STARTUP / FEEDBACK_SKIP_STARTUP_TASKS), so the sync-startup test exercises the real path again. - Restore the window.slopsmith backward-compat alias dropped during conflict resolution, and move the bus aliases to AFTER the _feedBackExisting merge block so they reference the fully-assembled object (also fixes the loop_api.test.js API-surface regex, which the original PR latently broke). - Drop the stray empty data/web_library.db (runtime DB lives in CONFIG_DIR) and gitignore it. - Fix stale tone-source test: feed[dB]ack -> fee[dB]ack to match shipped source labels. Verified locally (org CI billing-blocked): JS 819/819 pass; pytest 1669 passed / 1683 collected with 0 import errors; zero residual slopsmith/byron except the two intentional window.slopsmith aliases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * rename: implement advertised backward-compat + prune dead community plugins Address gaps where PR #537's "Backward compatibility" section was advertised but not implemented, and clean up the community plugin list. Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured): - New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py (_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI / SLOPSMITH_PLUGINS_DIR / etc. deployments keep working. - Fix the rename collapsing plugins/__init__.py and minigames/routes.py from `FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant `FEEDBACK_ or FEEDBACK_` (the fallback was silently lost). Storage (app.js update-channel): - Read feedBack-update-channel, fall back to legacy slopsmith-update-channel, and clear the legacy key on write — so a user's update-channel preference survives the rename instead of resetting to "stable". Community plugin list (README): the rename rewrote third-party repo URLs we don't own. Probed every one; their owners never renamed, so: - Restore the 13 live community plugins to their real slopsmith-* names. - Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle). - Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory). Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors, rename-sensitive + startup suites green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: byrongamatos <xasiklas@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
180 lines
7.0 KiB
Python
180 lines
7.0 KiB
Python
"""Integration test for CorrelationIdMiddleware wiring in server.py.
|
|
|
|
Verifies that every HTTP response carries the X-Request-ID header that was
|
|
introduced alongside the structured logging bootstrap (feedBack#155).
|
|
Requests that include an X-Request-ID should echo it; requests without it
|
|
should receive a server-generated ID. A cross-cutting end-to-end test
|
|
additionally asserts that the ID propagates into log output.
|
|
"""
|
|
|
|
import importlib
|
|
import io
|
|
import json
|
|
import logging
|
|
import sys
|
|
import uuid
|
|
|
|
import pytest
|
|
import structlog
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
from asgi_correlation_id import CorrelationIdMiddleware
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(tmp_path, monkeypatch):
|
|
"""Minimal server client with background I/O suppressed."""
|
|
monkeypatch.setenv("CONFIG_DIR", str(tmp_path))
|
|
monkeypatch.setenv("FEEDBACK_SYNC_STARTUP", "1")
|
|
sys.modules.pop("server", None)
|
|
server = importlib.import_module("server")
|
|
monkeypatch.setattr(server, "load_plugins", lambda *a, **kw: None)
|
|
monkeypatch.setattr(server, "startup_scan", lambda: None)
|
|
with TestClient(server.app) as tc:
|
|
try:
|
|
yield tc
|
|
finally:
|
|
conn = getattr(getattr(server, "meta_db", None), "conn", None)
|
|
if conn is not None:
|
|
conn.close()
|
|
|
|
|
|
def test_response_includes_x_request_id_header(client):
|
|
"""Every response must carry X-Request-ID regardless of the endpoint."""
|
|
r = client.get("/api/startup-status")
|
|
assert "x-request-id" in {k.lower() for k in r.headers}, (
|
|
f"X-Request-ID missing from response headers: {dict(r.headers)}"
|
|
)
|
|
|
|
|
|
def test_provided_x_request_id_is_echoed(client):
|
|
"""When the client sends a valid UUID X-Request-ID, the server echoes it."""
|
|
custom_id = str(uuid.uuid4())
|
|
r = client.get("/api/startup-status", headers={"X-Request-ID": custom_id})
|
|
assert r.headers.get("x-request-id") == custom_id
|
|
|
|
|
|
def test_opaque_proxy_id_is_echoed(client):
|
|
"""Non-UUID proxy-style request IDs must be propagated unchanged (validator=None)."""
|
|
opaque_id = "abc123def456"
|
|
r = client.get("/api/startup-status", headers={"X-Request-ID": opaque_id})
|
|
assert r.headers.get("x-request-id") == opaque_id, (
|
|
"Opaque proxy ID was replaced instead of propagated — "
|
|
"ensure CorrelationIdMiddleware is configured with validator=None"
|
|
)
|
|
|
|
|
|
def test_generated_x_request_id_is_nonempty(client):
|
|
"""When no X-Request-ID is sent, the server generates a non-empty one."""
|
|
r = client.get("/api/startup-status")
|
|
request_id = r.headers.get("x-request-id", "")
|
|
assert request_id, "Server-generated X-Request-ID must not be empty"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# End-to-end: middleware + logging_setup integration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _isolate_logging(isolate_logging):
|
|
"""Auto-use wrapper that pulls in the shared isolate_logging fixture."""
|
|
|
|
|
|
def test_request_id_appears_in_log_line(monkeypatch):
|
|
"""An HTTP request's X-Request-ID must appear as request_id in log output.
|
|
|
|
This is the end-to-end integration check: CorrelationIdMiddleware sets the
|
|
context var, and logging_setup._add_correlation_id reads it into the event
|
|
dict, so both pieces must be wired together for this test to pass.
|
|
"""
|
|
import logging_setup
|
|
|
|
monkeypatch.setenv("LOG_FORMAT", "json")
|
|
monkeypatch.setenv("LOG_LEVEL", "DEBUG")
|
|
monkeypatch.delenv("LOG_FILE", raising=False)
|
|
logging_setup.configure_logging()
|
|
|
|
buf = io.StringIO()
|
|
for h in logging.getLogger("feedBack").handlers:
|
|
if isinstance(h, logging.StreamHandler) and not isinstance(
|
|
h, logging.FileHandler
|
|
):
|
|
h.stream = buf
|
|
|
|
# Minimal app that replicates the same middleware wiring as server.py.
|
|
mini_app = FastAPI()
|
|
mini_app.add_middleware(CorrelationIdMiddleware, validator=None)
|
|
|
|
@mini_app.get("/probe")
|
|
def probe():
|
|
logging.getLogger("feedBack.probe").info("probe_event")
|
|
return {"ok": True}
|
|
|
|
known_id = str(uuid.uuid4())
|
|
with TestClient(mini_app) as tc:
|
|
tc.get("/probe", headers={"X-Request-ID": known_id})
|
|
|
|
lines = [ln for ln in buf.getvalue().splitlines() if "probe_event" in ln]
|
|
assert lines, "No log line captured during HTTP request"
|
|
parsed = json.loads(lines[0])
|
|
assert parsed.get("request_id") == known_id, (
|
|
f"Log line request_id {parsed.get('request_id')!r} != {known_id!r}"
|
|
)
|
|
|
|
|
|
def test_server_app_request_id_propagated_to_logs(monkeypatch, tmp_path):
|
|
"""request_id must appear in log output from the real server.app.
|
|
|
|
Uses the actual server.app instance (with its CorrelationIdMiddleware) to
|
|
confirm no middleware-ordering regression can silence the correlation field.
|
|
A temporary route is added for the test and removed afterwards so that
|
|
server.app is not permanently modified.
|
|
"""
|
|
monkeypatch.setenv("LOG_FORMAT", "json")
|
|
monkeypatch.setenv("LOG_LEVEL", "DEBUG")
|
|
monkeypatch.delenv("LOG_FILE", raising=False)
|
|
monkeypatch.setenv("CONFIG_DIR", str(tmp_path))
|
|
monkeypatch.setenv("FEEDBACK_SYNC_STARTUP", "1")
|
|
|
|
sys.modules.pop("server", None)
|
|
server_mod = importlib.import_module("server")
|
|
monkeypatch.setattr(server_mod, "load_plugins", lambda *a, **kw: None)
|
|
monkeypatch.setattr(server_mod, "startup_scan", lambda: None)
|
|
|
|
# Add a probe route to server.app so the test can trigger a log write.
|
|
@server_mod.app.get("/_test_log_probe")
|
|
def _probe():
|
|
logging.getLogger("feedBack.server").info("server_probe_event")
|
|
return {"ok": True}
|
|
|
|
known_id = str(uuid.uuid4())
|
|
buf = io.StringIO()
|
|
try:
|
|
with TestClient(server_mod.app) as tc:
|
|
# startup_events() has now run (including its configure_logging() call),
|
|
# so the feedBack handlers are freshly created. Redirect their stream
|
|
# to buf NOW, after startup, so we capture the probe request's output.
|
|
for h in logging.getLogger("feedBack").handlers:
|
|
if isinstance(h, logging.StreamHandler) and not isinstance(
|
|
h, logging.FileHandler
|
|
):
|
|
h.stream = buf
|
|
tc.get("/_test_log_probe", headers={"X-Request-ID": known_id})
|
|
finally:
|
|
# Remove the test route to avoid polluting server.app for other tests.
|
|
server_mod.app.router.routes = [
|
|
r for r in server_mod.app.router.routes
|
|
if getattr(r, "path", None) != "/_test_log_probe"
|
|
]
|
|
conn = getattr(getattr(server_mod, "meta_db", None), "conn", None)
|
|
if conn is not None:
|
|
conn.close()
|
|
|
|
lines = [ln for ln in buf.getvalue().splitlines() if "server_probe_event" in ln]
|
|
assert lines, "No log line captured from server.app probe route"
|
|
parsed = json.loads(lines[0])
|
|
assert parsed.get("request_id") == known_id, (
|
|
f"server.app log line request_id {parsed.get('request_id')!r} != {known_id!r}"
|
|
)
|