superbrain-server 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/api.py +1 -0
- package/payload/config/model_rankings.json +250 -20
- package/payload/config/openrouter_free_models.json +519 -482
- package/payload/core/model_router.py +7 -10
- package/payload/start.py +188 -189
- package/payload/test_backend.py +241 -0
- package/payload/tests/__init__.py +0 -0
- package/payload/tests/test_api.py +17 -0
- package/payload/tests/test_db.py +22 -0
- package/payload/tests/test_sync_code.py +65 -0
- package/payload/__pycache__/api.cpython-311.pyc +0 -0
- package/payload/__pycache__/main.cpython-311.pyc +0 -0
- package/payload/__pycache__/start.cpython-311.pyc +0 -0
- package/payload/config/.api_keys +0 -3
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperBrain - Comprehensive Backend Test Suite
|
|
3
|
+
Tests all API endpoints against the running backend.
|
|
4
|
+
"""
|
|
5
|
+
import requests
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
# Config
|
|
11
|
+
BASE_URL = "http://localhost:5000"
|
|
12
|
+
TOKEN = "4XVTLWWV"
|
|
13
|
+
HEADERS = {"X-API-Key": TOKEN, "Content-Type": "application/json"}
|
|
14
|
+
|
|
15
|
+
TEST_YT_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
|
16
|
+
|
|
17
|
+
passed = 0
|
|
18
|
+
failed = 0
|
|
19
|
+
skipped = 0
|
|
20
|
+
|
|
21
|
+
def test(name, condition, detail=""):
|
|
22
|
+
global passed, failed
|
|
23
|
+
if condition:
|
|
24
|
+
passed += 1
|
|
25
|
+
print(f" [PASS] {name}")
|
|
26
|
+
else:
|
|
27
|
+
failed += 1
|
|
28
|
+
print(f" [FAIL] {name} -- {detail}")
|
|
29
|
+
|
|
30
|
+
def skip(name, reason):
|
|
31
|
+
global skipped
|
|
32
|
+
skipped += 1
|
|
33
|
+
print(f" [SKIP] {name} -- {reason}")
|
|
34
|
+
|
|
35
|
+
# Phase 1: Health & Auth
|
|
36
|
+
print("\n--- Phase 1: Health & Auth ---")
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
r = requests.get(f"{BASE_URL}/status", timeout=5)
|
|
40
|
+
test("1.1 GET /status", r.status_code == 200 and r.json().get("status") == "online", f"status={r.status_code}")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
test("1.1 GET /status", False, str(e))
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
r = requests.get(f"{BASE_URL}/health", headers=HEADERS, timeout=5)
|
|
46
|
+
data = r.json()
|
|
47
|
+
test("1.2 GET /health (auth)", r.status_code == 200 and "database" in data, f"status={r.status_code}")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
test("1.2 GET /health", False, str(e))
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
r = requests.get(f"{BASE_URL}/health", headers={"X-API-Key": "WRONGTKN"}, timeout=5)
|
|
53
|
+
test("1.3 Auth rejection (wrong)", r.status_code == 401, f"status={r.status_code}")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
test("1.3 Auth rejection", False, str(e))
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
r = requests.get(f"{BASE_URL}/health", timeout=5)
|
|
59
|
+
test("1.4 Auth rejection (none)", r.status_code in [401, 403, 422], f"status={r.status_code}")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
test("1.4 Auth rejection (none)", False, str(e))
|
|
62
|
+
|
|
63
|
+
# Phase 2: Read Endpoints
|
|
64
|
+
print("\n--- Phase 2: Read Endpoints ---")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
r = requests.get(f"{BASE_URL}/recent?limit=10", headers=HEADERS, timeout=10)
|
|
68
|
+
data = r.json()
|
|
69
|
+
test("2.1 GET /recent", r.status_code == 200 and "data" in data, f"status={r.status_code}")
|
|
70
|
+
print(f" found {len(data.get('data', []))} posts")
|
|
71
|
+
except Exception as e:
|
|
72
|
+
test("2.1 GET /recent", False, str(e))
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
r = requests.get(f"{BASE_URL}/categories", headers=HEADERS, timeout=10)
|
|
76
|
+
data = r.json()
|
|
77
|
+
test("2.2 GET /categories", r.status_code == 200 and "categories" in data, f"status={r.status_code}")
|
|
78
|
+
print(f" found {len(data.get('categories', []))} categories")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
test("2.2 GET /categories", False, str(e))
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
r = requests.get(f"{BASE_URL}/queue-status", headers=HEADERS, timeout=10)
|
|
84
|
+
data = r.json()
|
|
85
|
+
test("2.3 GET /queue-status", r.status_code == 200 and "processing_count" in data, f"keys={list(data.keys())}")
|
|
86
|
+
print(f" processing={data.get('processing_count',0)}, queue={data.get('queue_count',0)}, retry={data.get('retry_count',0)}")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
test("2.3 GET /queue-status", False, str(e))
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
r = requests.get(f"{BASE_URL}/stats", headers=HEADERS, timeout=10)
|
|
92
|
+
test("2.4 GET /stats", r.status_code == 200, f"status={r.status_code}")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
test("2.4 GET /stats", False, str(e))
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
r = requests.get(f"{BASE_URL}/search?tags=travel&limit=5", headers=HEADERS, timeout=10)
|
|
98
|
+
test("2.5 GET /search?tags=travel", r.status_code == 200, f"status={r.status_code}")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
test("2.5 GET /search", False, str(e))
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
r = requests.get(f"{BASE_URL}/queue/retry", headers=HEADERS, timeout=10)
|
|
104
|
+
test("2.6 GET /queue/retry", r.status_code == 200, f"status={r.status_code}")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
test("2.6 GET /queue/retry", False, str(e))
|
|
107
|
+
|
|
108
|
+
# Phase 3: Collections CRUD
|
|
109
|
+
print("\n--- Phase 3: Collections CRUD ---")
|
|
110
|
+
|
|
111
|
+
test_collection_id = None
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
r = requests.get(f"{BASE_URL}/collections", headers=HEADERS, timeout=10)
|
|
115
|
+
data = r.json()
|
|
116
|
+
test("3.1 GET /collections", r.status_code == 200, f"status={r.status_code}")
|
|
117
|
+
print(f" found {len(data)} collections")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
test("3.1 GET /collections", False, str(e))
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
r = requests.post(f"{BASE_URL}/collections", headers=HEADERS, json={"id": "test-id", "name": "Test Collection", "icon": "test"}, timeout=10)
|
|
123
|
+
data = r.json()
|
|
124
|
+
test("3.2 POST /collections (create)", r.status_code == 200 and data.get("id"), f"status={r.status_code}")
|
|
125
|
+
test_collection_id = data.get("id")
|
|
126
|
+
print(f" created ID: {test_collection_id}")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
test("3.2 POST /collections", False, str(e))
|
|
129
|
+
|
|
130
|
+
if test_collection_id:
|
|
131
|
+
try:
|
|
132
|
+
r = requests.post(f"{BASE_URL}/collections", headers=HEADERS, json={"id": test_collection_id, "name": "Updated Test", "icon": "ok"}, timeout=10)
|
|
133
|
+
test("3.3 POST /collections (update)", r.status_code == 200, f"status={r.status_code}")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
test("3.3 POST /collections (update)", False, str(e))
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
r = requests.delete(f"{BASE_URL}/collections/{test_collection_id}", headers=HEADERS, timeout=10)
|
|
139
|
+
test("3.4 DELETE /collections", r.status_code == 200, f"status={r.status_code}")
|
|
140
|
+
except Exception as e:
|
|
141
|
+
test("3.4 DELETE /collections", False, str(e))
|
|
142
|
+
else:
|
|
143
|
+
skip("3.3 PUT /collections", "no collection created")
|
|
144
|
+
skip("3.4 DELETE /collections", "no collection created")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
r = requests.get(f"{BASE_URL}/collections", headers=HEADERS, timeout=10)
|
|
148
|
+
data = r.json()
|
|
149
|
+
collections = data.get("data", [])
|
|
150
|
+
wl = next((c for c in collections if c.get("name") == "Watch Later"), None)
|
|
151
|
+
if wl:
|
|
152
|
+
r = requests.delete(f"{BASE_URL}/collections/{wl['id']}", headers=HEADERS, timeout=10)
|
|
153
|
+
test("3.5 Watch Later protection", r.status_code in [400, 403], f"status={r.status_code} (should be blocked)")
|
|
154
|
+
else:
|
|
155
|
+
skip("3.5 Watch Later protection", "no Watch Later found")
|
|
156
|
+
except Exception as e:
|
|
157
|
+
test("3.5 Watch Later protection", False, str(e))
|
|
158
|
+
|
|
159
|
+
# Phase 4: Settings
|
|
160
|
+
print("\n--- Phase 4: Settings ---")
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
r = requests.get(f"{BASE_URL}/settings/ai-providers", headers=HEADERS, timeout=10)
|
|
164
|
+
test("4.1 GET /settings/ai-providers", r.status_code == 200, f"status={r.status_code}")
|
|
165
|
+
except Exception as e:
|
|
166
|
+
test("4.1 GET /settings/ai-providers", False, str(e))
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
r = requests.post(f"{BASE_URL}/settings/ai-providers", headers=HEADERS,
|
|
170
|
+
json={"provider": "groq", "api_key": "gsk_test"}, timeout=10)
|
|
171
|
+
test("4.2 POST ai-providers (Groq)", r.status_code == 200, f"status={r.status_code}")
|
|
172
|
+
except Exception as e:
|
|
173
|
+
test("4.2 POST ai-providers", False, str(e))
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
r = requests.get(f"{BASE_URL}/settings/instagram", headers=HEADERS, timeout=10)
|
|
177
|
+
test("4.3 GET /settings/instagram", r.status_code == 200, f"status={r.status_code}")
|
|
178
|
+
except Exception as e:
|
|
179
|
+
test("4.3 GET /settings/instagram", False, str(e))
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
r = requests.post(f"{BASE_URL}/settings/instagram", headers=HEADERS,
|
|
183
|
+
json={"username": "testuser", "password": "testpass"}, timeout=10)
|
|
184
|
+
test("4.4 POST /settings/instagram", r.status_code == 200, f"status={r.status_code}")
|
|
185
|
+
except Exception as e:
|
|
186
|
+
test("4.4 POST /settings/instagram", False, str(e))
|
|
187
|
+
|
|
188
|
+
# Phase 5: Export/Import
|
|
189
|
+
print("\n--- Phase 5: Export/Import ---")
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
r = requests.get(f"{BASE_URL}/export", headers=HEADERS, timeout=15)
|
|
193
|
+
test("5.1 GET /export", r.status_code == 200, f"status={r.status_code}")
|
|
194
|
+
ed = r.json()
|
|
195
|
+
print(f" exported {len(ed.get('posts', []))} posts, {len(ed.get('collections', []))} collections")
|
|
196
|
+
except Exception as e:
|
|
197
|
+
test("5.1 GET /export", False, str(e))
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
r = requests.post(f"{BASE_URL}/import", headers=HEADERS,
|
|
201
|
+
json={"posts": [], "collections": [], "mode": "merge"}, timeout=15)
|
|
202
|
+
test("5.2 POST /import (merge, empty)", r.status_code == 200, f"status={r.status_code}")
|
|
203
|
+
except Exception as e:
|
|
204
|
+
test("5.2 POST /import", False, str(e))
|
|
205
|
+
|
|
206
|
+
# Phase 6: Analysis
|
|
207
|
+
print("\n--- Phase 6: Analysis (YouTube) ---")
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
print(" submitting YouTube URL... (timeout 90s)")
|
|
211
|
+
r = requests.post(f"{BASE_URL}/analyze", headers=HEADERS,
|
|
212
|
+
json={"url": TEST_YT_URL}, timeout=90)
|
|
213
|
+
data = r.json()
|
|
214
|
+
if r.status_code == 200:
|
|
215
|
+
test("6.1 POST /analyze (YouTube)", data.get("success") == True, f"success={data.get('success')}")
|
|
216
|
+
if data.get("data"):
|
|
217
|
+
d = data["data"]
|
|
218
|
+
print(f" title: {d.get('title', 'N/A')[:60]}")
|
|
219
|
+
print(f" category: {d.get('category', 'N/A')}")
|
|
220
|
+
print(f" tags: {str(d.get('tags', [])[:5])}")
|
|
221
|
+
print(f" cached: {data.get('cached', False)}")
|
|
222
|
+
elif r.status_code == 202:
|
|
223
|
+
skip("6.1 POST /analyze (YouTube)", "queued for retry (quota)")
|
|
224
|
+
elif r.status_code == 503:
|
|
225
|
+
skip("6.1 POST /analyze (YouTube)", "503 server busy")
|
|
226
|
+
else:
|
|
227
|
+
test("6.1 POST /analyze (YouTube)", False, f"status={r.status_code}")
|
|
228
|
+
except requests.exceptions.Timeout:
|
|
229
|
+
skip("6.1 POST /analyze (YouTube)", "timed out (90s)")
|
|
230
|
+
except Exception as e:
|
|
231
|
+
test("6.1 POST /analyze (YouTube)", False, str(e))
|
|
232
|
+
|
|
233
|
+
# Summary
|
|
234
|
+
print("\n" + "=" * 50)
|
|
235
|
+
print(f" PASSED: {passed}")
|
|
236
|
+
print(f" FAILED: {failed}")
|
|
237
|
+
print(f" SKIPPED: {skipped}")
|
|
238
|
+
print(f" TOTAL: {passed + failed + skipped}")
|
|
239
|
+
print("=" * 50)
|
|
240
|
+
|
|
241
|
+
sys.exit(1 if failed > 0 else 0)
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import requests
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
# Read token
|
|
6
|
+
with open('token.txt', 'r') as f:
|
|
7
|
+
token = f.read().strip()
|
|
8
|
+
|
|
9
|
+
# Test the /recent endpoint
|
|
10
|
+
response = requests.get(
|
|
11
|
+
'http://localhost:5000/recent?limit=10',
|
|
12
|
+
headers={'X-API-Key': token}
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
print(f"Status Code: {response.status_code}")
|
|
16
|
+
print(f"\nResponse:")
|
|
17
|
+
print(json.dumps(response.json(), indent=2))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Ensure backend root is in sys.path
|
|
6
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
7
|
+
|
|
8
|
+
from core.database import get_db
|
|
9
|
+
|
|
10
|
+
db = get_db()
|
|
11
|
+
posts = db.get_recent(5)
|
|
12
|
+
print(f'Found {len(posts)} posts in database')
|
|
13
|
+
print()
|
|
14
|
+
|
|
15
|
+
for p in posts:
|
|
16
|
+
print(f"Shortcode: {p.get('shortcode', 'N/A')}")
|
|
17
|
+
print(f"Title: {p.get('title', 'N/A')}")
|
|
18
|
+
print(f"Username: {p.get('username', 'N/A')}")
|
|
19
|
+
print(f"URL: {p.get('url', 'N/A')}")
|
|
20
|
+
print(f"Category: {p.get('category', 'N/A')}")
|
|
21
|
+
print(f"Analyzed: {p.get('analyzed_at', 'N/A')}")
|
|
22
|
+
print("-" * 60)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Legacy filename retained for compatibility.
|
|
4
|
+
This script now validates API key authentication behavior.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from fastapi.testclient import TestClient
|
|
11
|
+
|
|
12
|
+
# Ensure backend root is in sys.path
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
14
|
+
|
|
15
|
+
from api import app, API_TOKEN, generate_api_token # noqa: E402
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
client = TestClient(app)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_generate_api_token_shape():
|
|
22
|
+
token = generate_api_token()
|
|
23
|
+
assert isinstance(token, str)
|
|
24
|
+
assert len(token) == 8
|
|
25
|
+
assert token.isalnum()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_ping_works_without_auth():
|
|
29
|
+
response = client.get('/ping')
|
|
30
|
+
assert response.status_code == 200
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_queue_status_rejects_invalid_api_key():
|
|
34
|
+
response = client.get('/queue-status', headers={'X-API-Key': 'INVALID_TOKEN'})
|
|
35
|
+
assert response.status_code == 401
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_queue_status_accepts_valid_api_key():
|
|
39
|
+
response = client.get('/queue-status', headers={'X-API-Key': API_TOKEN})
|
|
40
|
+
assert response.status_code == 200
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_connect_endpoint_is_deprecated():
|
|
44
|
+
response = client.post('/connect', json={'api_key': API_TOKEN})
|
|
45
|
+
assert response.status_code == 410
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == '__main__':
|
|
49
|
+
print('Running API key auth tests...')
|
|
50
|
+
test_generate_api_token_shape()
|
|
51
|
+
print('PASS: API token generation shape')
|
|
52
|
+
|
|
53
|
+
test_ping_works_without_auth()
|
|
54
|
+
print('PASS: /ping unauthenticated access')
|
|
55
|
+
|
|
56
|
+
test_queue_status_rejects_invalid_api_key()
|
|
57
|
+
print('PASS: /queue-status rejects invalid API key')
|
|
58
|
+
|
|
59
|
+
test_queue_status_accepts_valid_api_key()
|
|
60
|
+
print('PASS: /queue-status accepts valid API key')
|
|
61
|
+
|
|
62
|
+
test_connect_endpoint_is_deprecated()
|
|
63
|
+
print('PASS: /connect deprecated behavior')
|
|
64
|
+
|
|
65
|
+
print('\nAll API key auth tests passed!')
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/payload/config/.api_keys
DELETED