Files
recipe-maintainer/recipe-info/lasuite-docs/tests/upload_conversion.py
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

168 lines
6.1 KiB
Python

#!/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',
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
'<Default Extension="xml" ContentType="application/xml"/>'
'<Override PartName="/word/document.xml" '
'ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
'</Types>')
z.writestr('_rels/.rels',
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
'<Relationship Id="rId1" '
'Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" '
'Target="word/document.xml"/>'
'</Relationships>')
z.writestr('word/document.xml',
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
'<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
'<w:body><w:p><w:r><w:t>Upload conversion test</w:t></w:r></w:p></w:body>'
'</w:document>')
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.<domain_suffix>.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()