feat: Complete v1.1.1 Phases 2 & 3 - Enhancements and Polish

Phase 2 - Enhancements:
- Add performance monitoring infrastructure with MetricsBuffer
- Implement three-tier health checks (/health, /health?detailed, /admin/health)
- Enhance search with FTS5 fallback and XSS-safe highlighting
- Add Unicode slug generation with timestamp fallback
- Expose database pool statistics via /admin/metrics
- Create missing error templates (400, 401, 403, 405, 503)

Phase 3 - Polish:
- Implement RSS streaming optimization (memory O(n) → O(1))
- Add admin metrics dashboard with htmx and Chart.js
- Fix flaky migration race condition tests
- Create comprehensive operational documentation
- Add upgrade guide and troubleshooting guide

Testing: 632 tests passing, zero flaky tests
Documentation: Complete operational guides
Security: All security reviews passed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-25 20:10:41 -07:00
parent 93d2398c1d
commit 07fff01fab
25 changed files with 4371 additions and 142 deletions

View File

@@ -53,14 +53,12 @@ def client(app):
def clear_feed_cache():
"""Clear feed cache before each test"""
from starpunk.routes import public
public._feed_cache["xml"] = None
public._feed_cache["notes"] = None
public._feed_cache["timestamp"] = None
public._feed_cache["etag"] = None
yield
# Clear again after test
public._feed_cache["xml"] = None
public._feed_cache["notes"] = None
public._feed_cache["timestamp"] = None
public._feed_cache["etag"] = None
@pytest.fixture
@@ -116,14 +114,17 @@ class TestFeedRoute:
cache_seconds = app.config.get("FEED_CACHE_SECONDS", 300)
assert f"max-age={cache_seconds}" in response.headers["Cache-Control"]
def test_feed_route_etag_header(self, client):
"""Test /feed.xml has ETag header"""
def test_feed_route_streaming(self, client):
"""Test /feed.xml uses streaming response (no ETag)"""
response = client.get("/feed.xml")
assert response.status_code == 200
# Should have ETag header
assert "ETag" in response.headers
assert len(response.headers["ETag"]) > 0
# Streaming responses don't have ETags (can't calculate hash before streaming)
# This is intentional - memory optimization for large feeds
assert "ETag" not in response.headers
# But should still have cache control
assert "Cache-Control" in response.headers
class TestFeedContent:
@@ -236,27 +237,26 @@ class TestFeedContent:
class TestFeedCaching:
"""Test feed caching behavior"""
def test_feed_caches_response(self, client, sample_notes):
"""Test feed caches response on server side"""
# First request
def test_feed_caches_note_list(self, client, sample_notes):
"""Test feed caches note list on server side (not full XML)"""
# First request - generates and caches note list
response1 = client.get("/feed.xml")
etag1 = response1.headers.get("ETag")
# Second request (should be cached)
# Second request - should use cached note list (but still stream XML)
response2 = client.get("/feed.xml")
etag2 = response2.headers.get("ETag")
# ETags should match (same cached content)
assert etag1 == etag2
# Content should be identical
# Content should be identical (same notes)
assert response1.data == response2.data
# Note: We don't use ETags anymore due to streaming optimization
# The note list is cached to avoid repeated DB queries,
# but XML is still streamed for memory efficiency
def test_feed_cache_expires(self, client, sample_notes, app):
"""Test feed cache expires after configured duration"""
"""Test feed note list cache expires after configured duration"""
# First request
response1 = client.get("/feed.xml")
etag1 = response1.headers.get("ETag")
content1 = response1.data
# Wait for cache to expire (cache is 2 seconds in test config)
time.sleep(3)
@@ -265,32 +265,34 @@ class TestFeedCaching:
with app.app_context():
create_note(content="New note after cache expiry", published=True)
# Second request (cache should be expired and regenerated)
# Second request (cache should be expired and regenerated with new note)
response2 = client.get("/feed.xml")
etag2 = response2.headers.get("ETag")
content2 = response2.data
# ETags should be different (content changed)
assert etag1 != etag2
# Content should be different (new note added)
assert content1 != content2
assert b"New note after cache expiry" in content2
def test_feed_etag_changes_with_content(self, client, app):
"""Test ETag changes when content changes"""
def test_feed_content_changes_with_new_notes(self, client, app):
"""Test feed content changes when notes are added"""
# First request
response1 = client.get("/feed.xml")
etag1 = response1.headers.get("ETag")
content1 = response1.data
# Wait for cache expiry
time.sleep(3)
# Add new note
with app.app_context():
create_note(content="New note changes ETag", published=True)
create_note(content="New note changes content", published=True)
# Second request
response2 = client.get("/feed.xml")
etag2 = response2.headers.get("ETag")
content2 = response2.data
# ETags should be different
assert etag1 != etag2
# Content should be different (new note added)
assert content1 != content2
assert b"New note changes content" in content2
def test_feed_cache_consistent_within_window(self, client, sample_notes):
"""Test cache returns consistent content within cache window"""
@@ -300,13 +302,11 @@ class TestFeedCaching:
response = client.get("/feed.xml")
responses.append(response)
# All responses should be identical
# All responses should be identical (same cached note list)
first_content = responses[0].data
first_etag = responses[0].headers.get("ETag")
for response in responses[1:]:
assert response.data == first_content
assert response.headers.get("ETag") == first_etag
class TestFeedEdgeCases: