superbrain-server 1.0.10 → 1.0.12
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/bin/superbrain.js +4 -0
- package/package.json +1 -1
- 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 +3 -0
- package/payload/start.py +82 -149
- package/payload/analyzers/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/audio_transcribe.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/caption.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/music_identifier.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/text_analyzer.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/visual_analyze.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/webpage_analyzer.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/youtube_analyzer.cpython-311.pyc +0 -0
- package/payload/config/backend_id.txt +0 -1
- package/payload/config/localtunnel.log +0 -86
- package/payload/core/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/core/__pycache__/category_manager.cpython-311.pyc +0 -0
- package/payload/core/__pycache__/database.cpython-311.pyc +0 -0
- package/payload/core/__pycache__/link_checker.cpython-311.pyc +0 -0
- package/payload/core/__pycache__/model_router.cpython-311.pyc +0 -0
- package/payload/instagram/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/instagram/__pycache__/instagram_downloader.cpython-311.pyc +0 -0
- package/payload/instagram/__pycache__/instagram_login.cpython-311.pyc +0 -0
- package/payload/test_backend.py +0 -241
- package/payload/tests/__init__.py +0 -0
- package/payload/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/tests/__pycache__/test_api.cpython-311.pyc +0 -0
- package/payload/tests/__pycache__/test_db.cpython-311.pyc +0 -0
- package/payload/tests/__pycache__/test_sync_code.cpython-311.pyc +0 -0
- package/payload/tests/test_api.py +0 -17
- package/payload/tests/test_db.py +0 -22
- package/payload/tests/test_sync_code.py +0 -65
- package/payload/utils/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/utils/__pycache__/db_stats.cpython-311.pyc +0 -0
- package/payload/utils/__pycache__/manage_token.cpython-311.pyc +0 -0
package/bin/superbrain.js
CHANGED
|
@@ -171,11 +171,15 @@ if (userArgs.length > 0) {
|
|
|
171
171
|
superbrain-server reset -> Open Reset Menu
|
|
172
172
|
superbrain-server reset --all -> Force complete wipe
|
|
173
173
|
superbrain-server status -> Show QR Code and Server Info
|
|
174
|
+
superbrain-server ngrok -> Configure Ngrok tunnel
|
|
174
175
|
`);
|
|
175
176
|
process.exit(0);
|
|
176
177
|
} else if (cmd === 'status' || cmd === 'update') {
|
|
177
178
|
targetScript = 'start.py';
|
|
178
179
|
finalArgs = ['--status'];
|
|
180
|
+
} else if (cmd === 'ngrok') {
|
|
181
|
+
targetScript = 'start.py';
|
|
182
|
+
finalArgs = ['--ngrok'];
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/payload/start.py
CHANGED
|
@@ -22,6 +22,7 @@ import string
|
|
|
22
22
|
import textwrap
|
|
23
23
|
import time
|
|
24
24
|
import importlib
|
|
25
|
+
import re
|
|
25
26
|
from pathlib import Path
|
|
26
27
|
|
|
27
28
|
# ── Paths ─────────────────────────────────────────────────────────────────────
|
|
@@ -157,6 +158,7 @@ def ensure_runtime_dependencies():
|
|
|
157
158
|
("fastapi", "fastapi"),
|
|
158
159
|
("uvicorn", "uvicorn"),
|
|
159
160
|
("multipart", "python-multipart"),
|
|
161
|
+
("pyngrok", "pyngrok"),
|
|
160
162
|
("instaloader", "instaloader"),
|
|
161
163
|
("segno", "segno"),
|
|
162
164
|
]
|
|
@@ -662,64 +664,41 @@ def setup_whisper():
|
|
|
662
664
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
663
665
|
# Step 6 — Remote Access / Port Forwarding
|
|
664
666
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
665
|
-
|
|
666
|
-
|
|
667
|
+
NGROK_ENABLED = BASE_DIR / "config" / "ngrok_enabled.txt"
|
|
668
|
+
NGROK_TOKEN = BASE_DIR / "config" / "ngrok_token.txt"
|
|
667
669
|
|
|
668
670
|
def setup_remote_access():
|
|
669
|
-
h1("Step 6 of 7 — Remote Access (
|
|
671
|
+
h1("Step 6 of 7 — Remote Access (ngrok)")
|
|
670
672
|
|
|
671
673
|
print(f"""
|
|
672
|
-
The SuperBrain backend runs
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
{BOLD}Option A — localtunnel (easiest + free){RESET}
|
|
678
|
-
localtunnel creates a public HTTPS URL that tunnels to your local port 5000.
|
|
679
|
-
No account required.
|
|
680
|
-
Official site: {CYAN}https://theboroer.github.io/localtunnel-www/{RESET}
|
|
681
|
-
|
|
682
|
-
{BOLD}Option B — Your own port forwarding (advanced){RESET}
|
|
683
|
-
Forward {BOLD}TCP port 5000{RESET} on your router to your machine's local IP.
|
|
684
|
-
Then use {BOLD}http://<your-public-ip>:5000{RESET} in the mobile app.
|
|
685
|
-
Steps:
|
|
686
|
-
1. Find your machine's local IP → ip addr (Linux) / ipconfig (Windows)
|
|
687
|
-
2. Log into your router admin panel (usually http://192.168.1.1)
|
|
688
|
-
3. Add a port forwarding rule: External 5000 → Internal <your-local-IP>:5000
|
|
689
|
-
4. Use your public IP (check https://ipify.org) in the mobile app.
|
|
690
|
-
{YELLOW}Note: dynamic public IPs change on router restart — consider a DDNS service.{RESET}
|
|
691
|
-
|
|
692
|
-
{DIM}You can also run only on your local WiFi — both phone and PC must be on
|
|
693
|
-
the same network. Use your PC's local IP (e.g. 192.168.x.x) in the app.{RESET}
|
|
674
|
+
The SuperBrain backend runs locally. Your phone needs to reach it over the internet.
|
|
675
|
+
We recommend {BOLD}ngrok{RESET} for a secure tunnel.
|
|
676
|
+
|
|
677
|
+
Requires a free account from: {CYAN}https://dashboard.ngrok.com/signup{RESET}
|
|
678
|
+
Get your Authtoken at: {CYAN}https://dashboard.ngrok.com/get-started/your-authtoken{RESET}
|
|
694
679
|
""")
|
|
695
680
|
|
|
696
|
-
choice = ask_yn("Enable
|
|
681
|
+
choice = ask_yn("Enable ngrok on startup?", default=True)
|
|
697
682
|
if not choice:
|
|
698
|
-
|
|
699
|
-
warn("Skipping
|
|
700
|
-
info("Remember: set the correct server URL in the mobile app Settings.")
|
|
701
|
-
return
|
|
702
|
-
|
|
703
|
-
if not shutil.which("npx"):
|
|
704
|
-
print(f"""
|
|
705
|
-
{YELLOW}npx is not installed / not on PATH.{RESET}
|
|
706
|
-
|
|
707
|
-
Install it:
|
|
708
|
-
Linux → {CYAN}Install Node.js (includes npm + npx){RESET}
|
|
709
|
-
macOS → {CYAN}brew install node{RESET}
|
|
710
|
-
Windows → Install Node.js LTS from {CYAN}https://nodejs.org/{RESET}
|
|
711
|
-
|
|
712
|
-
After installing, re-run {BOLD}python start.py{RESET}.
|
|
713
|
-
""")
|
|
714
|
-
warn("Skipping localtunnel setup.")
|
|
683
|
+
NGROK_ENABLED.unlink(missing_ok=True)
|
|
684
|
+
warn("Skipping ngrok. Local WiFi only.")
|
|
715
685
|
return
|
|
716
686
|
|
|
717
|
-
ok("
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
687
|
+
ok("ngrok auto-start enabled")
|
|
688
|
+
NGROK_ENABLED.parent.mkdir(parents=True, exist_ok=True)
|
|
689
|
+
NGROK_ENABLED.write_text("enabled")
|
|
690
|
+
|
|
691
|
+
existing_token = NGROK_TOKEN.read_text().strip() if NGROK_TOKEN.exists() else ""
|
|
692
|
+
print(f"\n {YELLOW}Please paste your ngrok Authtoken.{RESET}")
|
|
693
|
+
if existing_token:
|
|
694
|
+
print(f" {DIM}(Leave blank to keep existing token){RESET}")
|
|
695
|
+
|
|
696
|
+
auth_token = ask("Authtoken", default=existing_token, paste=True)
|
|
697
|
+
if auth_token.strip():
|
|
698
|
+
NGROK_TOKEN.write_text(auth_token.strip())
|
|
699
|
+
ok("ngrok token saved.")
|
|
700
|
+
else:
|
|
701
|
+
warn("No ngrok token provided. ngrok may disconnect. To fix, re-run setup.")
|
|
723
702
|
|
|
724
703
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
725
704
|
# Step 6 — Access Token & Database
|
|
@@ -754,87 +733,21 @@ def setup_token_and_db():
|
|
|
754
733
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
755
734
|
# Launch Backend
|
|
756
735
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
757
|
-
def
|
|
758
|
-
"""Extract first localtunnel public URL from text."""
|
|
759
|
-
import re
|
|
760
|
-
m = re.search(r"https://[\w.-]+\.loca\.lt\b", text)
|
|
761
|
-
return m.group(0) if m else None
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
def _find_localtunnel_url_from_log() -> str | None:
|
|
765
|
-
"""Read local tunnel log and return detected public URL if available."""
|
|
766
|
-
try:
|
|
767
|
-
if not LOCALTUNNEL_LOG.exists():
|
|
768
|
-
return None
|
|
769
|
-
text = LOCALTUNNEL_LOG.read_text(encoding="utf-8", errors="ignore")
|
|
770
|
-
return _extract_localtunnel_url(text)
|
|
771
|
-
except Exception:
|
|
772
|
-
return None
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
def _stop_localtunnel_processes():
|
|
776
|
-
"""Stop existing localtunnel processes so only one tunnel remains active."""
|
|
777
|
-
try:
|
|
778
|
-
if IS_WINDOWS:
|
|
779
|
-
script = (
|
|
780
|
-
"Get-CimInstance Win32_Process "
|
|
781
|
-
"| Where-Object { $_.CommandLine -match 'localtunnel|\\.loca\\.lt' } "
|
|
782
|
-
"| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
|
|
783
|
-
)
|
|
784
|
-
subprocess.run(["powershell", "-NoProfile", "-Command", script], check=False)
|
|
785
|
-
else:
|
|
786
|
-
subprocess.run(["pkill", "-f", "localtunnel"], check=False)
|
|
787
|
-
except Exception:
|
|
788
|
-
pass
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
def _start_localtunnel(port: int, timeout: int = 25) -> str | None:
|
|
792
|
-
"""Start localtunnel in the background and wait for the public URL."""
|
|
793
|
-
import time as _time
|
|
794
|
-
|
|
795
|
-
npx_exec = shutil.which("npx") or shutil.which("npx.cmd")
|
|
796
|
-
if not npx_exec:
|
|
797
|
-
return None
|
|
798
|
-
|
|
799
|
-
# Clean stale localtunnel processes.
|
|
800
|
-
_stop_localtunnel_processes()
|
|
801
|
-
_time.sleep(0.8)
|
|
802
|
-
|
|
803
|
-
info("Starting localtunnel in background …")
|
|
736
|
+
def _start_ngrok(port: int) -> str | None:
|
|
804
737
|
try:
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
815
|
-
if IS_WINDOWS and npx_exec.lower().endswith(".cmd"):
|
|
816
|
-
cmd = ["cmd", "/c", npx_exec, "-y", "localtunnel", "--port", str(port)]
|
|
817
|
-
else:
|
|
818
|
-
cmd = [npx_exec, "-y", "localtunnel", "--port", str(port)]
|
|
819
|
-
subprocess.Popen(cmd, **kwargs)
|
|
738
|
+
import pyngrok
|
|
739
|
+
from pyngrok import ngrok
|
|
740
|
+
|
|
741
|
+
token = NGROK_TOKEN.read_text().strip() if NGROK_TOKEN.exists() else None
|
|
742
|
+
if token:
|
|
743
|
+
ngrok.set_auth_token(token)
|
|
744
|
+
|
|
745
|
+
tunnel = ngrok.connect(port, bind_tls=True)
|
|
746
|
+
return tunnel.public_url
|
|
820
747
|
except Exception as e:
|
|
821
|
-
warn(f"
|
|
748
|
+
warn(f"Failed to start ngrok: {e}")
|
|
822
749
|
return None
|
|
823
750
|
|
|
824
|
-
# Poll log output until URL is emitted.
|
|
825
|
-
deadline = _time.time() + timeout
|
|
826
|
-
while _time.time() < deadline:
|
|
827
|
-
_time.sleep(1)
|
|
828
|
-
url = _find_localtunnel_url_from_log()
|
|
829
|
-
if url:
|
|
830
|
-
ok(f"localtunnel active → {GREEN}{BOLD}{url}{RESET}")
|
|
831
|
-
return url
|
|
832
|
-
|
|
833
|
-
warn("localtunnel started but URL is not available yet.")
|
|
834
|
-
info(f"Check tunnel logs in: {LOCALTUNNEL_LOG}")
|
|
835
|
-
return None
|
|
836
|
-
|
|
837
|
-
|
|
838
751
|
def _get_windows_pids_on_port(port: int) -> list[int]:
|
|
839
752
|
"""Return listener PIDs on Windows using Get-NetTCPConnection when available."""
|
|
840
753
|
pids: set[int] = set()
|
|
@@ -1202,26 +1115,32 @@ def launch_backend():
|
|
|
1202
1115
|
token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "—"
|
|
1203
1116
|
local_ip = _detect_local_ip()
|
|
1204
1117
|
|
|
1205
|
-
|
|
1118
|
+
# ngrok startup
|
|
1119
|
+
public_url: str | None = None
|
|
1120
|
+
if NGROK_ENABLED.exists():
|
|
1121
|
+
token_txt = NGROK_TOKEN.read_text().strip() if NGROK_TOKEN.exists() else ""
|
|
1122
|
+
if not token_txt:
|
|
1123
|
+
warn("Ngrok is enabled but no Authtoken was found.")
|
|
1124
|
+
setup_remote_access()
|
|
1206
1125
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
if
|
|
1214
|
-
tunnel_line = f" Public URL → {GREEN}{BOLD}{
|
|
1215
|
-
tunnel_hint = f" · public → {GREEN}{
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1126
|
+
info("Starting ngrok in background...")
|
|
1127
|
+
public_url = _start_ngrok(PORT)
|
|
1128
|
+
|
|
1129
|
+
tunnel_line = ""
|
|
1130
|
+
tunnel_hint = ""
|
|
1131
|
+
|
|
1132
|
+
if public_url:
|
|
1133
|
+
tunnel_line = f" Public URL → {GREEN}{BOLD}{public_url}{RESET} {DIM}(ngrok){RESET}"
|
|
1134
|
+
tunnel_hint = f" · public → {GREEN}{public_url}{RESET}"
|
|
1135
|
+
ok(f"ngrok active → {GREEN}{BOLD}{public_url}{RESET}")
|
|
1136
|
+
elif NGROK_ENABLED.exists():
|
|
1137
|
+
tunnel_line = f" Public URL → {YELLOW}(failed to start ngrok){RESET}"
|
|
1138
|
+
tunnel_hint = f" · public → run manually: {DIM}ngrok http {PORT}{RESET}"
|
|
1219
1139
|
else:
|
|
1220
|
-
|
|
1221
|
-
tunnel_hint = f" · public → install Node.js first, then run: {DIM}npx localtunnel --port {PORT}{RESET}"
|
|
1140
|
+
tunnel_hint = f" · public → enable ngrok via {DIM}python start.py --reset{RESET}"
|
|
1222
1141
|
|
|
1223
1142
|
# ── Generate and display QR code ──────────────────────────────────────────
|
|
1224
|
-
qr_url =
|
|
1143
|
+
qr_url = public_url if public_url else f"http://{local_ip}:{PORT}"
|
|
1225
1144
|
_display_connect_qr(qr_url, token)
|
|
1226
1145
|
|
|
1227
1146
|
print(f"""
|
|
@@ -1274,15 +1193,21 @@ def launch_backend_status():
|
|
|
1274
1193
|
if TOKEN_FILE.exists():
|
|
1275
1194
|
token = TOKEN_FILE.read_text(encoding="utf-8").strip()
|
|
1276
1195
|
|
|
1196
|
+
# Fetch ngrok status via API
|
|
1277
1197
|
url = "NOT_FOUND"
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1198
|
+
try:
|
|
1199
|
+
import urllib.request, json
|
|
1200
|
+
req = urllib.request.urlopen("http://127.0.0.1:4040/api/tunnels", timeout=2)
|
|
1201
|
+
data = json.loads(req.read())
|
|
1202
|
+
for tunnel in data.get("tunnels", []):
|
|
1203
|
+
if tunnel.get("proto") == "https":
|
|
1204
|
+
url = tunnel.get("public_url")
|
|
1205
|
+
break
|
|
1206
|
+
except Exception:
|
|
1207
|
+
pass
|
|
1208
|
+
|
|
1284
1209
|
if url == "NOT_FOUND":
|
|
1285
|
-
warn("Could not find a running
|
|
1210
|
+
warn("Could not find a running ngrok URL. Is the server running?")
|
|
1286
1211
|
nl()
|
|
1287
1212
|
print(" Wait 5 seconds, or run 'superbrain-server' to start the server.")
|
|
1288
1213
|
return
|
|
@@ -1310,6 +1235,14 @@ def main():
|
|
|
1310
1235
|
launch_backend_status()
|
|
1311
1236
|
return
|
|
1312
1237
|
|
|
1238
|
+
ngrok_mode = "--ngrok" in sys.argv
|
|
1239
|
+
if ngrok_mode:
|
|
1240
|
+
h1("SuperBrain Ngrok Configuration")
|
|
1241
|
+
setup_remote_access()
|
|
1242
|
+
nl()
|
|
1243
|
+
ok("Ngrok configuration finished. Run 'superbrain-server' to start the backend.")
|
|
1244
|
+
return
|
|
1245
|
+
|
|
1313
1246
|
reset_mode = "--reset" in sys.argv
|
|
1314
1247
|
|
|
1315
1248
|
if SETUP_DONE.exists() and not reset_mode:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3f491f84-184a-40ba-9660-b882b881c4d5
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
===============================================================================
|
|
3
|
-
Welcome to localhost.run!
|
|
4
|
-
|
|
5
|
-
Follow your favourite reverse tunnel at [https://twitter.com/localhost_run].
|
|
6
|
-
|
|
7
|
-
To set up and manage custom domains go to https://admin.localhost.run/
|
|
8
|
-
|
|
9
|
-
More details on custom domains (and how to enable subdomains of your custom
|
|
10
|
-
domain) at https://localhost.run/docs/custom-domains
|
|
11
|
-
|
|
12
|
-
If you get a permission denied error check the faq for how to connect with a key or
|
|
13
|
-
create a free tunnel without a key at [http://localhost:3000/docs/faq#generating-an-ssh-key].
|
|
14
|
-
|
|
15
|
-
To explore using localhost.run visit the documentation site:
|
|
16
|
-
https://localhost.run/docs/
|
|
17
|
-
|
|
18
|
-
===============================================================================
|
|
19
|
-
|
|
20
|
-
** your connection id is 78ac0b8b-a8f4-4185-9da5-a446d15e032f, please mention it if you send me a message about an issue. **
|
|
21
|
-
|
|
22
|
-
authenticated as anonymous user
|
|
23
|
-
14ebb2ac2ccab3.lhr.life tunneled with tls termination, https://14ebb2ac2ccab3.lhr.life
|
|
24
|
-
create an account and add your key for a longer lasting domain name. see https://localhost.run/docs/forever-free/ for more information.
|
|
25
|
-
Open your tunnel address on your mobile with this QR:
|
|
26
|
-
|
|
27
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
28
|
-
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
29
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
30
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
31
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
32
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
33
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
34
|
-
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
35
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
36
|
-
[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
37
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
38
|
-
[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
39
|
-
[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
40
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
41
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
42
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
43
|
-
[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
44
|
-
[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
45
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m
|
|
46
|
-
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
47
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
48
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m
|
|
49
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
50
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m
|
|
51
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
52
|
-
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
53
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
54
|
-
|
|
55
|
-
c3f3097e2d129a.lhr.life tunneled with tls termination, https://c3f3097e2d129a.lhr.life
|
|
56
|
-
create an account and add your key for a longer lasting domain name. see https://localhost.run/docs/forever-free/ for more information.
|
|
57
|
-
Open your tunnel address on your mobile with this QR:
|
|
58
|
-
|
|
59
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
60
|
-
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
61
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
62
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
63
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
64
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
65
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
66
|
-
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
67
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
68
|
-
[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
69
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
70
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
71
|
-
[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
72
|
-
[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
73
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
74
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
75
|
-
[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
76
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
77
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
78
|
-
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
79
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
80
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m
|
|
81
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m
|
|
82
|
-
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
83
|
-
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
84
|
-
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
85
|
-
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
86
|
-
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/payload/test_backend.py
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,17 +0,0 @@
|
|
|
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))
|
package/payload/tests/test_db.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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)
|
|
@@ -1,65 +0,0 @@
|
|
|
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
|