#!/usr/bin/env python3
"""Upload + conversion test for La Suite Docs.
Verifies the end-to-end conversion path:
- `.md` upload → backend → y-provider (yjs)
- `.docx` upload → backend → docspec (BlockNote JSON) → y-provider (yjs)
Exercises CONVERSION_UPLOAD_ENABLED on the backend, backend→y-provider auth
(Y_PROVIDER_API_KEY_FILE) and routing (Y_PROVIDER_API_BASE_URL), and
DOCSPEC_API_URL for the .docx import path.
"""
import argparse
import io
import json
import os
import sys
import urllib.error
import urllib.request
import uuid
import zipfile
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from utils.tests.helpers import (
http_post, load_toml_credentials, resolve_domain,
)
def build_minimal_docx() -> bytes:
"""Build a minimal valid .docx (OOXML) in memory."""
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as z:
z.writestr('[Content_Types].xml',
''
''
''
''
''
'')
z.writestr('_rels/.rels',
''
''
''
'')
z.writestr('word/document.xml',
''
''
'Upload conversion test'
'')
return buf.getvalue()
def get_oidc_token(kc_url, creds):
"""Direct grant flow against the lasuite-docs realm."""
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": creds["kc_test_user"],
"password": creds["kc_test_pass"],
"scope": "openid email",
},
content_type="application/x-www-form-urlencoded",
)
return (data or {}).get("access_token", "")
def multipart_upload(url, token, filename, content_type, body):
"""POST a file via multipart/form-data with a Bearer token."""
boundary = uuid.uuid4().hex
parts = [
f"--{boundary}\r\n".encode(),
b'Content-Disposition: form-data; name="title"\r\n\r\n',
b"upload-conversion-test\r\n",
f"--{boundary}\r\n".encode(),
f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'.encode(),
f"Content-Type: {content_type}\r\n\r\n".encode(),
body,
f"\r\n--{boundary}--\r\n".encode(),
]
req_body = b"".join(parts)
req = urllib.request.Request(
url,
data=req_body,
headers={
"Authorization": f"Bearer {token}",
"Content-Type": f"multipart/form-data; boundary={boundary}",
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.status, resp.read().decode()
except urllib.error.HTTPError as e:
return e.code, e.read().decode()
def upload_step(label, docs_url, token, filename, content_type, body):
print(f"Step: Upload {label} ...")
status, raw = multipart_upload(
f"{docs_url}/api/v1.0/documents/", token, filename, content_type, body,
)
if status != 201:
print(f" FAIL: {label} upload returned HTTP {status}: {raw[:200]}")
sys.exit(1)
try:
doc = json.loads(raw)
except json.JSONDecodeError:
print(f" FAIL: {label} response not JSON: {raw[:200]}")
sys.exit(1)
doc_id = doc.get('id')
if not doc_id:
print(f" FAIL: {label} response missing 'id': {raw[:200]}")
sys.exit(1)
print(f" PASS: {label} converted and persisted (document id {doc_id})")
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..toml")
print("Run setup_keycloak_integration.py first.")
sys.exit(1)
docs_domain = args.domain or resolve_domain('lasuite-docs')
kc_domain = resolve_domain('keycloak')
docs_url = f"https://{docs_domain}"
kc_url = f"https://{kc_domain}"
print("Testing file upload + conversion (CONVERSION_UPLOAD_ENABLED + Y_PROVIDER)")
print()
print("Step 1: Obtaining access token from Keycloak ...")
token = get_oidc_token(kc_url, creds)
if not token:
print(" FAIL: Could not obtain access token from Keycloak")
sys.exit(1)
print(f" PASS: Obtained access token ({len(token)} chars)")
upload_step(
".md file", docs_url, token,
"upload-test.md", "text/markdown",
b"# Upload Conversion Test\n\nValidating **CONVERSION_UPLOAD_ENABLED** end-to-end.\n",
)
upload_step(
".docx file", docs_url, token,
"upload-test.docx",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
build_minimal_docx(),
)
print()
print("PASS: Upload + conversion working end-to-end (.md and .docx)")
if __name__ == '__main__':
main()