Files
PraisonAIUI/tests/test_feature_sessions_ext.py
a433c483c1 fix: document bootstrap entrypoints in examples (fixes #71) (#96)
* feat: standardize bootstrap entrypoint to 'aiui run app.py' across examples

Standardizes platform bootstrap patterns across examples per issue #71:

- Update examples README table to use 'aiui run app.py' consistently
- Update docstrings in examples 10, 12, 19-21 to show 'aiui run' instead of 'python app.py'
- Remove fragmented bootstrap approaches (uvicorn.run, asyncio patterns)
- Enforce single bootstrap SSOT as designed in CLI

Co-authored-by: Mervin Praison <[email protected]>

* feat: consolidate bootstrap entrypoints - auto-detect gateway apps (fixes #71)

- Add bootstrap detection in CLI run command
- Auto-detect gateway pattern (async main + AIUIGateway imports)
- Auto-switch to praisonai backend for gateway apps
- Delegate to user-defined main() when present
- Remove sys.path hacks from example 14
- Update examples README to show unified aiui run entrypoint
- Add BOOTSTRAP.md documentation for migration patterns

Co-authored-by: Mervin Praison <[email protected]>

* fix: resolve critical bootstrap entrypoint issues

- Fix AST parsing bug in gateway auto-detection (cli.py:1419)
- Revert background bots (email-bot, agentmail-bot) to python app.py
- Revert custom UI apps (agent-dashboard) to python app.py
- Update README.md to reflect correct run commands
- Fix import formatting and remove unused imports

These changes ensure that:
1. Gateway auto-detection works correctly by properly checking ast.alias.name
2. Background bots execute their __main__ blocks and don't start unnecessary web servers
3. Custom UI apps register their custom routes in __main__ blocks
4. Documentation matches actual working run commands

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

Co-authored-by: Mervin Praison <[email protected]>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Mervin Praison <[email protected]>
Co-authored-by: Mervin Praison <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-06-15 16:36:35 +01:00

106 lines
3.5 KiB
Python

"""Per-feature test: Extended Sessions — API tests."""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from starlette.testclient import TestClient
from praisonaiui.server import create_app
client = TestClient(create_app())
passed = failed = 0
errors = []
def check(name, r, status=200, keys=None):
global passed, failed
try:
assert r.status_code == status, f"Expected {status}, got {r.status_code}: {r.text[:200]}"
if keys:
data = r.json()
for k in keys:
assert k in data, f"Missing '{k}' in {list(data.keys())}"
passed += 1
print(f" ✓ {name}")
except AssertionError as e:
failed += 1; errors.append(f" ✗ {name}: {e}"); print(f" ✗ {name}: {e}")
print("\n── Extended Sessions: API Tests ──")
sid = "test-session-ext"
# State — empty initially
r = client.get(f"/api/sessions/{sid}/state")
check("GET state (empty)", r, 200, ["session_id", "state"])
assert isinstance(r.json()["state"], dict)
# State — save
r = client.post(f"/api/sessions/{sid}/state", json={"state": {"mood": "focused", "topic": "ML"}})
check("POST state (save)", r, 200, ["session_id", "state"])
assert r.json()["state"]["mood"] == "focused"
# State — restore
r = client.get(f"/api/sessions/{sid}/state")
check("GET state (restored)", r, 200)
assert r.json()["state"]["topic"] == "ML"
# State — update (merge update — topic should still exist)
r = client.post(f"/api/sessions/{sid}/state", json={"state": {"mood": "relaxed"}})
check("POST state (update)", r, 200)
assert r.json()["state"]["mood"] == "relaxed"
assert r.json()["state"]["topic"] == "ML" # merge preserves previous keys
# Context
r = client.post(f"/api/sessions/{sid}/context", json={"query": "What is the user working on?"})
check("POST context", r, 200, ["session_id", "context"])
# Labels — set
r = client.post(f"/api/sessions/{sid}/labels", json={"labels": ["priority", "customer-demo"]})
check("POST labels (set)", r, 200, ["labels"])
assert r.json()["labels"] == ["priority", "customer-demo"]
# Labels — get
r = client.get(f"/api/sessions/{sid}/labels")
check("GET labels", r, 200, ["labels"])
assert len(r.json()["labels"]) == 2
# Labels — update
r = client.post(f"/api/sessions/{sid}/labels", json={"labels": ["vip"]})
check("POST labels (update)", r, 200)
assert r.json()["labels"] == ["vip"]
# Usage
r = client.get(f"/api/sessions/{sid}/usage")
check("GET usage", r, 200, ["session_id", "usage"])
# Compact
r = client.post(f"/api/sessions/{sid}/compact")
check("POST compact", r, 200, ["compacted"])
# Reset
r = client.post(f"/api/sessions/{sid}/reset", json={"mode": "clear"})
check("POST reset (clear)", r, 200, ["reset_mode"])
# Verify state is cleared
r = client.get(f"/api/sessions/{sid}/state")
check("GET state (after reset)", r, 200)
# Test different session
sid2 = "test-session-ext-2"
r = client.post(f"/api/sessions/{sid2}/state", json={"state": {"x": 1}})
check("POST state (different session)", r, 200)
assert r.json()["session_id"] == sid2
# Search sessions
r = client.post("/sessions", json={"session_id": sid2, "title": "session-ext-2"})
check("POST /sessions (seed search target)", r, 200)
r = client.get("/api/sessions/search?q=session-ext-2")
check("GET sessions search", r, 200, ["sessions", "count", "query"])
assert r.json()["query"] == "session-ext-2"
print(f"\n{'='*50}")
print(f" Sessions Ext: {passed} passed, {failed} failed")
print(f"{'='*50}")
if errors:
for e in errors: print(e)
sys.exit(1)