feat: v3.1.0 — Live Platform Foundation (Track E.1-E.3)

Major release introducing the Live Platform architecture:

- SQLite database backend with async operations and migrations (E.1)
- AES-256-GCM credential encryption with HKDF key derivation (E.1)
- OAuth Social Login with GitHub and Google (E.2)
- Site management API with encrypted credential storage (E.3)
- Per-user MCP endpoints at /u/{user_id}/{alias}/mcp (E.3)
- User API keys with bcrypt hashing (E.3)
- Auto-generated config snippets for 5 MCP clients (E.3)
- Dashboard: dark/light mode, RBAC, My Sites, Connect page
- Active background health checks
- 452 tests (up from 303), all passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
airano
2026-02-23 23:20:03 +03:30
parent 42ea75bcb3
commit 9e6f06d933
53 changed files with 8654 additions and 1820 deletions

View File

@@ -0,0 +1,42 @@
"""Tests for tenant isolation in Dashboard routes."""
import pytest
from core.dashboard.routes import get_all_projects
from core.site_manager import SiteManager, SiteConfig
@pytest.mark.asyncio
async def test_get_all_projects_tenant_isolation(monkeypatch):
"""Normal user should only see their own sites, ignoring global ones."""
# Mock SiteManager with some global and user sites
mgr = SiteManager()
# Global site (no user_id)
mgr.register_site(SiteConfig(site_id="global1", plugin_type="wordpress"))
# User 123's site
mgr.register_site(SiteConfig(
site_id="user1site",
plugin_type="wordpress",
user_id="user-123"
))
monkeypatch.setattr("core.site_manager.get_site_manager", lambda: mgr)
# Normal user 456 (has no sites)
session_456 = {"user_id": "user-456", "type": "user"}
res = await get_all_projects(user_session=session_456)
assert len(res["projects"]) == 0
# Normal user 123 (has 1 site)
session_123 = {"user_id": "user-123", "type": "user"}
res = await get_all_projects(user_session=session_123)
assert len(res["projects"]) == 1
assert res["projects"][0]["site_id"] == "user1site"
# Master user (sees all)
class DummyMaster:
user_type = "master"
res = await get_all_projects(user_session=DummyMaster())
assert len(res["projects"]) == 2