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).
168 lines
6.1 KiB
Python
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()
|