idk
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
import argparse
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from getpass import getpass
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthResult:
|
||||
token: str
|
||||
|
||||
|
||||
def signup(session: requests.Session, email: str, username: str, password: str, access_token: str) -> None:
|
||||
url = f"{BASE_URL}/api/signup"
|
||||
payload = {
|
||||
"email": email,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"access_token": access_token,
|
||||
}
|
||||
resp = session.post(url, json=payload, timeout=10)
|
||||
resp.raise_for_status()
|
||||
|
||||
|
||||
def login(session: requests.Session, username: str, password: str) -> AuthResult:
|
||||
url = f"{BASE_URL}/api/login"
|
||||
payload = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
resp = session.post(url, json=payload, timeout=10)
|
||||
resp.raise_for_status()
|
||||
|
||||
data = resp.json()
|
||||
token = data.get("token")
|
||||
if not token:
|
||||
raise RuntimeError(f"Login response did not contain token: {data}")
|
||||
return AuthResult(token=token)
|
||||
|
||||
|
||||
def post_message(session: requests.Session, channel_id: int, token: str, text: str, display_name: str, user_id: int) -> None:
|
||||
url = f"{BASE_URL}/api/chat/{channel_id}"
|
||||
payload = {
|
||||
"display_name": display_name,
|
||||
"user_id": user_id,
|
||||
"text": text,
|
||||
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
}
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
resp = session.post(url, json=payload, headers=headers, timeout=10)
|
||||
resp.raise_for_status()
|
||||
|
||||
|
||||
def read_sse_messages(
|
||||
session: requests.Session,
|
||||
channel_id: int,
|
||||
token: str,
|
||||
expected_count: int,
|
||||
timeout_s: int,
|
||||
capture_live_messages: threading.Event,
|
||||
) -> List[dict]:
|
||||
url = f"{BASE_URL}/api/events/{channel_id}"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Accept": "text/event-stream",
|
||||
}
|
||||
|
||||
received: List[dict] = []
|
||||
deadline = time.monotonic() + timeout_s
|
||||
|
||||
try:
|
||||
with session.get(url, headers=headers, stream=True, timeout=(5, timeout_s)) as resp:
|
||||
resp.raise_for_status()
|
||||
|
||||
event_data_lines: List[str] = []
|
||||
|
||||
for raw_line in resp.iter_lines(decode_unicode=True):
|
||||
if time.monotonic() > deadline:
|
||||
break
|
||||
|
||||
if raw_line is None:
|
||||
continue
|
||||
|
||||
line = raw_line.strip()
|
||||
|
||||
if not line:
|
||||
if event_data_lines:
|
||||
joined = "\n".join(event_data_lines)
|
||||
event_data_lines.clear()
|
||||
try:
|
||||
obj = json.loads(joined)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
if capture_live_messages.is_set():
|
||||
received.append(obj)
|
||||
if expected_count > 0 and len(received) >= expected_count:
|
||||
break
|
||||
else:
|
||||
print(f"Discarding message: {obj}")
|
||||
continue
|
||||
|
||||
if line.startswith("data:"):
|
||||
event_data_lines.append(line[len("data:"):].strip())
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
print("Timeout while reading SSE.")
|
||||
except requests.exceptions.RequestException as exc:
|
||||
print(f"Error reading SSE: {exc}")
|
||||
|
||||
return received
|
||||
|
||||
|
||||
def prompt_nonempty(label: str, secret: bool = False) -> str:
|
||||
while True:
|
||||
value = getpass(label) if secret else input(label)
|
||||
value = value.strip()
|
||||
if value:
|
||||
return value
|
||||
print("Please enter a value.")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Chat integration test against localhost:8000")
|
||||
parser.add_argument("--existing-account", action="store_true",
|
||||
help="Skip signup and only log in with an existing account")
|
||||
parser.add_argument("--email", default=None)
|
||||
parser.add_argument("--username", default=None)
|
||||
parser.add_argument("--password", default=None)
|
||||
parser.add_argument("--access-token", default=None,
|
||||
help="Required only for signup mode")
|
||||
parser.add_argument("--channel-id", type=int, default=1)
|
||||
parser.add_argument("--message-count", type=int, default=5)
|
||||
parser.add_argument("--timeout", type=int, default=15)
|
||||
args = parser.parse_args()
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
if args.existing_account:
|
||||
username = args.username or prompt_nonempty("Username: ")
|
||||
password = args.password or prompt_nonempty("Password: ", secret=True)
|
||||
else:
|
||||
email = args.email or prompt_nonempty("Email: ")
|
||||
username = args.username or prompt_nonempty("Username: ")
|
||||
password = args.password or prompt_nonempty("Password: ", secret=True)
|
||||
access_token = args.access_token or prompt_nonempty("Access token: ")
|
||||
|
||||
print("[1/5] Signing up...")
|
||||
try:
|
||||
signup(session, email, username, password, access_token)
|
||||
print(" signup ok")
|
||||
except requests.HTTPError as e:
|
||||
print(f" signup returned HTTP error: {e}")
|
||||
print(" continuing to login...")
|
||||
|
||||
print("[2/5] Logging in...")
|
||||
auth = login(session, username, password)
|
||||
print(" login ok")
|
||||
print(f" token: {auth.token[:12]}...")
|
||||
|
||||
print("[3/5] Opening event stream...")
|
||||
received_messages: List[dict] = []
|
||||
capture_live_messages = threading.Event()
|
||||
stream_done = threading.Event()
|
||||
|
||||
def stream_reader() -> None:
|
||||
nonlocal received_messages
|
||||
try:
|
||||
received_messages = read_sse_messages(
|
||||
session=session,
|
||||
channel_id=args.channel_id,
|
||||
token=auth.token,
|
||||
expected_count=args.message_count,
|
||||
timeout_s=args.timeout,
|
||||
capture_live_messages=capture_live_messages,
|
||||
)
|
||||
finally:
|
||||
stream_done.set()
|
||||
|
||||
t = threading.Thread(target=stream_reader, daemon=True)
|
||||
t.start()
|
||||
|
||||
# Give the server time to flush backlog on this same stream connection.
|
||||
time.sleep(1.0)
|
||||
|
||||
print("[4/5] Starting to capture live messages and sending messages...")
|
||||
capture_live_messages.set()
|
||||
|
||||
sent_texts = [f"Message {i}" for i in range(args.message_count)]
|
||||
for i, text in enumerate(sent_texts):
|
||||
post_message(
|
||||
session=session,
|
||||
channel_id=args.channel_id,
|
||||
token=auth.token,
|
||||
text=text,
|
||||
display_name=username,
|
||||
user_id=1,
|
||||
)
|
||||
print(f" sent {i + 1}/{args.message_count}: {text}")
|
||||
time.sleep(0.1)
|
||||
|
||||
stream_done.wait(timeout=args.timeout)
|
||||
t.join(timeout=1)
|
||||
|
||||
print("\nReceived messages:")
|
||||
for i, msg in enumerate(received_messages, start=1):
|
||||
print(f" {i}. {msg}")
|
||||
|
||||
received_texts = [m.get("text") for m in received_messages if isinstance(m, dict)]
|
||||
|
||||
for text in sent_texts:
|
||||
if text not in received_texts:
|
||||
print(f"\nFAIL: missing message: {text}")
|
||||
return 1
|
||||
|
||||
print("\nPASS: login and message delivery test succeeded.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user