mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
* 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]>
175 lines
6.6 KiB
Python
175 lines
6.6 KiB
Python
"""Per-feature test: Channels — API + gateway integration + CLI parity."""
|
|
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}")
|
|
|
|
|
|
# ── CRUD Tests ────────────────────────────────────────────────────────
|
|
|
|
print("\n── Channels: API CRUD Tests ──")
|
|
|
|
# Empty list
|
|
check("GET /api/channels (empty)", client.get("/api/channels"), 200, ["channels", "count"])
|
|
r = client.get("/api/channels")
|
|
assert r.json()["count"] == 0, f"Expected 0 channels, got {r.json()['count']}"
|
|
|
|
# Create
|
|
r = client.post("/api/channels", json={
|
|
"name": "Test Discord", "platform": "discord",
|
|
"config": {"guild_id": "123", "channel_id": "456"},
|
|
})
|
|
check("POST /api/channels (create discord)", r, 201, ["id", "name", "platform", "enabled"])
|
|
ch_id = r.json()["id"]
|
|
assert r.json()["platform"] == "discord"
|
|
assert r.json()["enabled"] == True
|
|
|
|
# Create second
|
|
r2 = client.post("/api/channels", json={
|
|
"name": "Test Telegram", "platform": "telegram",
|
|
"config": {"bot_token": "test-token"},
|
|
})
|
|
check("POST /api/channels (create telegram)", r2, 201)
|
|
ch_id2 = r2.json()["id"]
|
|
|
|
# List (should have 2)
|
|
r = client.get("/api/channels")
|
|
check("GET /api/channels (list 2)", r, 200)
|
|
assert r.json()["count"] == 2, f"Expected 2, got {r.json()['count']}"
|
|
|
|
# Get by ID
|
|
check("GET /api/channels/:id", client.get(f"/api/channels/{ch_id}"), 200, ["id", "platform"])
|
|
|
|
# Update
|
|
r = client.put(f"/api/channels/{ch_id}", json={"name": "Updated Discord"})
|
|
check("PUT /api/channels/:id (update)", r, 200)
|
|
assert r.json()["name"] == "Updated Discord"
|
|
|
|
# Delete
|
|
check("DELETE /api/channels/:id", client.delete(f"/api/channels/{ch_id}"), 200, ["deleted"])
|
|
|
|
# 404s
|
|
check("GET missing → 404", client.get("/api/channels/nonexistent"), 404)
|
|
check("PUT missing → 404", client.put("/api/channels/nonexistent", json={"name": "x"}), 404)
|
|
check("DELETE missing → 404", client.delete("/api/channels/nonexistent"), 404)
|
|
|
|
# ── Toggle Test ───────────────────────────────────────────────────────
|
|
|
|
print("\n── Channels: Toggle Tests ──")
|
|
|
|
r = client.post(f"/api/channels/{ch_id2}/toggle")
|
|
check("POST toggle (disable)", r, 200, ["id", "enabled"])
|
|
assert r.json()["enabled"] == False
|
|
|
|
r = client.post(f"/api/channels/{ch_id2}/toggle")
|
|
check("POST toggle (enable)", r, 200)
|
|
assert r.json()["enabled"] == True
|
|
|
|
check("POST toggle missing → 404", client.post("/api/channels/xxx/toggle"), 404)
|
|
|
|
# ── Status Test ───────────────────────────────────────────────────────
|
|
|
|
print("\n── Channels: Status Tests ──")
|
|
|
|
r = client.get(f"/api/channels/{ch_id2}/status")
|
|
check("GET /api/channels/:id/status", r, 200, ["id", "platform", "enabled", "running"])
|
|
|
|
check("GET status missing → 404", client.get("/api/channels/nonexistent/status"), 404)
|
|
|
|
# ── Platforms Test ────────────────────────────────────────────────────
|
|
|
|
print("\n── Channels: Platforms ──")
|
|
|
|
r = client.get("/api/channels/platforms")
|
|
check("GET /api/channels/platforms", r, 200, ["platforms"])
|
|
platforms = r.json()["platforms"]
|
|
assert "discord" in platforms
|
|
assert "telegram" in platforms
|
|
assert "slack" in platforms
|
|
assert "whatsapp" in platforms
|
|
|
|
# ── Platform validation ──────────────────────────────────────────────
|
|
|
|
print("\n── Channels: Platform Validation ──")
|
|
|
|
r = client.post("/api/channels", json={"name": "Bad", "platform": "fakechat"})
|
|
check("POST invalid platform → 400", r, 400)
|
|
|
|
# ── Gateway Integration Tests ─────────────────────────────────────────
|
|
|
|
print("\n── Channels: Gateway Integration ──")
|
|
|
|
# Test _get_gateway_health returns None when no gateway is set
|
|
from praisonaiui.features.channels import PraisonAIChannels
|
|
|
|
ch_feature = PraisonAIChannels()
|
|
gw_health = ch_feature._get_gateway_health()
|
|
try:
|
|
assert gw_health is None, f"Expected None when no gateway, got {gw_health}"
|
|
passed += 1
|
|
print(" ✓ _get_gateway_health() returns None without gateway")
|
|
except AssertionError as e:
|
|
failed += 1; errors.append(f" ✗ _get_gateway_health: {e}"); print(f" ✗ _get_gateway_health: {e}")
|
|
|
|
# Test with mock gateway set via _gateway_ref
|
|
from praisonaiui.features import _gateway_ref
|
|
|
|
|
|
class MockGateway:
|
|
def health(self):
|
|
return {
|
|
"status": "healthy",
|
|
"channels": {
|
|
"mock-ch": {"platform": "discord", "running": True},
|
|
},
|
|
}
|
|
|
|
_gateway_ref.set_gateway(MockGateway())
|
|
gw_health = ch_feature._get_gateway_health()
|
|
try:
|
|
assert gw_health is not None, "Expected gateway health data"
|
|
assert "mock-ch" in gw_health, f"Expected 'mock-ch' in {gw_health}"
|
|
assert gw_health["mock-ch"]["running"] == True
|
|
passed += 1
|
|
print(" ✓ _get_gateway_health() returns data with mock gateway")
|
|
except AssertionError as e:
|
|
failed += 1; errors.append(f" ✗ _get_gateway_health mock: {e}"); print(f" ✗ _get_gateway_health mock: {e}")
|
|
|
|
# Reset
|
|
_gateway_ref.set_gateway(None)
|
|
|
|
# ── Cleanup ───────────────────────────────────────────────────────────
|
|
|
|
# Clean up remaining channel
|
|
client.delete(f"/api/channels/{ch_id2}")
|
|
|
|
# ── Summary ───────────────────────────────────────────────────────────
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f" Channels: {passed} passed, {failed} failed")
|
|
print(f"{'='*50}")
|
|
if errors:
|
|
for e in errors: print(e)
|
|
sys.exit(1)
|