Files
recipe-maintainer/recipe-info/lasuite-meet/tests/meeting_flow.py
T
autonomic-bot f283a371bb recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)
Sanitized single-commit public mirror of recipe-maintainer.
- Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders.
- Removed plans/ and planned-updates/ (deployment-planning docs) so no client/
  deployment domains appear in the public repo.
- All other secret stores were already gitignored.
- docs.coopcloud.tech retained as a submodule (public upstream).
2026-06-16 20:18:24 +00:00

191 lines
7.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Meeting flow test for La Suite Meet — create room, join as two users, clean up."""
import argparse
import base64
import json
import os
import sys
import time
import urllib.error
import urllib.request
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from utils.tests.helpers import (
http_get, http_post, load_toml_credentials, resolve_domain,
)
def get_meet_token(kc_url, creds, username, password):
"""Obtain a Keycloak token for the given user."""
status, data = http_post(
f"{kc_url}/realms/{creds['kc_realm']}/protocol/openid-connect/token",
data={
"grant_type": "password",
"client_id": creds["kc_client_id"],
"client_secret": creds["kc_client_secret"],
"username": username,
"password": password,
"scope": "openid email",
},
content_type="application/x-www-form-urlencoded",
)
return (data or {}).get("access_token", "")
def decode_jwt_payload(token):
"""Decode the payload segment of a JWT token."""
try:
payload_b64 = token.split('.')[1]
# Add padding
payload_b64 += '=' * (4 - len(payload_b64) % 4)
# Replace URL-safe chars
payload_b64 = payload_b64.replace('-', '+').replace('_', '/')
decoded = base64.b64decode(payload_b64)
return json.loads(decoded)
except Exception:
return {}
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--domain', default=os.environ.get('TEST_DOMAIN'))
args = parser.parse_args()
recipe_dir = os.path.join(os.path.dirname(__file__), '..')
creds = load_toml_credentials(recipe_dir, 'keycloak')
if creds is None:
print("FAIL: Credentials file not found: keycloak-test-credentials.<domain_suffix>.toml")
print("Run setup_keycloak_integration.py first.")
sys.exit(1)
meet_domain = args.domain or resolve_domain('lasuite-meet')
kc_domain = resolve_domain('keycloak')
meet_url = f"https://{meet_domain}"
kc_url = f"https://{kc_domain}"
room_name = f"autotest-{int(time.time())}"
print("Testing meeting flow: create room, join as two users, clean up")
print()
# Step 1: Authenticate both users via Keycloak
print("Step 1: Authenticating both users via Keycloak ...")
token_user1 = get_meet_token(kc_url, creds, creds["kc_test_user"], creds["kc_test_pass"])
if not token_user1:
print(f" FAIL: Could not authenticate {creds['kc_test_user']}")
sys.exit(1)
print(f" PASS: {creds['kc_test_user']} authenticated ({len(token_user1)} chars)")
token_user2 = get_meet_token(kc_url, creds, creds["kc_test_user2"], creds["kc_test_pass2"])
if not token_user2:
print(f" FAIL: Could not authenticate {creds['kc_test_user2']}")
sys.exit(1)
print(f" PASS: {creds['kc_test_user2']} authenticated ({len(token_user2)} chars)")
# Step 2: User 1 creates a room
print(f"Step 2: User 1 creates room '{room_name}' ...")
status, create_body = http_post(
f"{meet_url}/api/v1.0/rooms/",
data={"name": room_name, "access_level": "public"},
headers={"Authorization": f"Bearer {token_user1}"},
)
if status != 201:
print(f" FAIL: Room creation returned HTTP {status} (expected 201)")
sys.exit(1)
room_id = create_body["id"]
room_slug = create_body["slug"]
user1_lk_room = create_body["livekit"]["room"]
user1_lk_token = create_body["livekit"]["token"]
if not room_id or not user1_lk_token:
print(" FAIL: Room created but missing id or LiveKit token")
sys.exit(1)
print(f" PASS: Room created (id={room_id}, slug={room_slug})")
print(f" PASS: User 1 received LiveKit token (room={user1_lk_room})")
# Step 3: User 2 joins the room
print(f"Step 3: User 2 joins room '{room_slug}' ...")
status, join_body = http_get(
f"{meet_url}/api/v1.0/rooms/{room_id}/",
headers={"Authorization": f"Bearer {token_user2}"},
)
if status != 200:
print(f" FAIL: Room retrieval returned HTTP {status} (expected 200)")
# Clean up
req = urllib.request.Request(f"{meet_url}/api/v1.0/rooms/{room_id}/", method="DELETE")
req.add_header("Authorization", f"Bearer {token_user1}")
try:
urllib.request.urlopen(req, timeout=10)
except Exception:
pass
sys.exit(1)
user2_lk_room = (join_body or {}).get("livekit", {}).get("room", "")
user2_lk_token = (join_body or {}).get("livekit", {}).get("token", "")
if not user2_lk_token:
print(" FAIL: User 2 did not receive a LiveKit token")
# Clean up
req = urllib.request.Request(f"{meet_url}/api/v1.0/rooms/{room_id}/", method="DELETE")
req.add_header("Authorization", f"Bearer {token_user1}")
try:
urllib.request.urlopen(req, timeout=10)
except Exception:
pass
sys.exit(1)
print(f" PASS: User 2 received LiveKit token (room={user2_lk_room})")
# Step 4: Verify both tokens reference the same room
print("Step 4: Verifying LiveKit tokens ...")
if user1_lk_room != user2_lk_room:
print(f" FAIL: LiveKit room mismatch: user1={user1_lk_room} user2={user2_lk_room}")
sys.exit(1)
print(f" PASS: Both users have tokens for the same LiveKit room ({user1_lk_room})")
# Decode JWT payloads and verify distinct identities
user1_payload = decode_jwt_payload(user1_lk_token)
user2_payload = decode_jwt_payload(user2_lk_token)
user1_identity = user1_payload.get("sub", "")
user2_identity = user2_payload.get("sub", "")
if user1_identity and user2_identity and user1_identity != user2_identity:
print(f" PASS: Tokens have distinct identities (user1={user1_identity[:12]}..., user2={user2_identity[:12]}...)")
else:
print(" WARN: Could not verify distinct JWT identities (non-fatal)")
# Step 5: User 1 deletes the room
print("Step 5: User 1 deletes the room ...")
req = urllib.request.Request(f"{meet_url}/api/v1.0/rooms/{room_id}/", method="DELETE")
req.add_header("Authorization", f"Bearer {token_user1}")
try:
with urllib.request.urlopen(req, timeout=10) as resp:
delete_status = resp.getcode()
except urllib.error.HTTPError as e:
delete_status = e.code
if delete_status != 204:
print(f" FAIL: Room deletion returned HTTP {delete_status} (expected 204)")
sys.exit(1)
print(" PASS: Room deleted (HTTP 204)")
# Step 6: Verify room is gone
print("Step 6: Verifying room no longer exists ...")
status, _ = http_get(
f"{meet_url}/api/v1.0/rooms/{room_id}/",
headers={"Authorization": f"Bearer {token_user1}"},
)
if status == 404:
print(" PASS: Room returns 404 (deleted)")
elif status == 200:
print(" PASS: Room ID no longer resolves to original room")
else:
print(f" WARN: Unexpected HTTP {status} when checking deleted room (non-fatal)")
print()
print("PASS: Meeting flow test passed — two users can create, join, and clean up rooms")
if __name__ == '__main__':
main()