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).
This commit is contained in:
167
recipe-info/lasuite-docs/tests/upload_conversion.py
Normal file
167
recipe-info/lasuite-docs/tests/upload_conversion.py
Normal file
@ -0,0 +1,167 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user