Files
orbit/scripts/test-console-applications.py

92 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""通过真实 HTTP 验证应用接入,不打印或记录密码、Cookie 与 Token。"""
import copy
import http.cookiejar
import json
import os
from pathlib import Path
import urllib.error
import urllib.request
import uuid
base = os.environ['ORBIT_E2E_URL'].rstrip('/')
user = os.environ['ORBIT_E2E_USER']
password = Path(os.environ['ORBIT_E2E_PASSWORD_FILE']).read_text().strip()
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
def call(path, body=None, token=None, headers=None, expected=200, session=True):
hdr = {'Accept': 'application/json', **(headers or {})}
if body is not None:
hdr['Content-Type'] = 'application/json'
if path.startswith('/console/applications'):
hdr.setdefault('X-Orbit-Console', '1')
hdr.setdefault('Origin', base)
if token:
hdr['Authorization'] = 'Bearer ' + token
req = urllib.request.Request(base + path, data=None if body is None else json.dumps(body).encode(), headers=hdr)
try:
response = (opener.open if session else urllib.request.urlopen)(req, timeout=15)
except urllib.error.HTTPError as error:
response = error
with response:
status = response.status
value = json.load(response)
assert status == expected, f'{path}: HTTP {status}, expected {expected}; code={value.get("error", {}).get("code")}'
return value.get('data', value)
call('/console/applications', expected=401, session=False)
call('/console/auth/login', {'username': user, 'password': password})
assert call('/console/auth/session')['authenticated']
call('/console/applications/prepare', {}, headers={'Origin': 'https://other.invalid'}, expected=403)
name = 'access-demo-' + uuid.uuid4().hex[:8]
request = {'kind': 'CREATE', 'application': name, 'namespace': 'default', 'environment': 'test', 'queue': 'integration', 'capabilities': ['worker', 'events', 'schedules', 'workflows'], 'valid_days': 30, 'expected_revision': '0', 'reason': 'application onboarding acceptance'}
op = call('/console/applications/prepare', request)
# 固定操作在提交前写入测试私密目录,以便提交结果不明时核对。
private = os.environ.get('ORBIT_E2E_APPLICATION_DIR')
if private:
os.umask(0o077)
Path(private).mkdir(parents=True, exist_ok=True)
Path(private, 'create.operation.json').write_text(json.dumps(op))
first = call('/console/applications/apply', op)
assert not first['deduplicated']
assert call('/console/applications/apply', op)['deduplicated']
changed = copy.deepcopy(op)
changed['input']['reason'] = 'changed'
call('/console/applications/apply', changed, expected=409)
page = call('/console/applications')
assert any(x['namespace_id'] == op['namespace_id'] for x in page['items'])
detail = call('/console/applications/' + op['namespace_id'])
assert op['token'] not in json.dumps(detail)
queue = detail['queues'][0]['id']
assert queue == op['queue_id']
call('/console/applications', token=op['token'], expected=401, session=False)
call('/v1/queues/' + queue, token=op['token'], session=False)
sub = call('/v1/subscriptions', {'name': 'result-example', 'queue_id': queue}, token=op['token'], headers={'Idempotency-Key': 'onboarding-subscription'}, expected=201, session=False)
subscription_id = sub['subscription']['id']
task = call('/v1/tasks', {'queue_id': queue, 'handler': 'onboarding.noop', 'handler_version': 'v1', 'payload_version': 'v1', 'payload': {}}, token=op['token'], headers={'Idempotency-Key': 'onboarding-task'}, expected=201, session=False)
task_id = task['task']['id']
call('/v1/tasks/' + task_id + ':cancel', {'expected_revision': task['task']['revision'], 'reason': 'acceptance complete'}, token=op['token'], headers={'Idempotency-Key': 'onboarding-cancel'}, session=False)
rotate = call('/console/applications/prepare', {'kind': 'ROTATE', 'namespace_id': op['namespace_id'], 'credential_id': op['credential_id'], 'expected_revision': '1', 'valid_days': 30, 'reason': 'rotation acceptance'})
if private:
Path(private, 'rotate.operation.json').write_text(json.dumps(rotate))
assert call('/console/applications/apply', rotate)['revision'] == '2'
assert call('/console/applications/apply', rotate)['deduplicated']
call('/v1/queues/' + queue, token=op['token'], expected=401, session=False)
call('/v1/queues/' + queue, token=rotate['token'], session=False)
current_task = call('/v1/tasks/' + task_id, token=rotate['token'], session=False)
assert current_task['state'] == 'CANCELLED'
current_sub = call('/v1/subscriptions/' + subscription_id, token=rotate['token'], session=False)
assert current_sub['id'] == subscription_id
# 不让联调示例遗留启用的订阅,历史和稳定凭据身份保持不变。
call('/v1/subscriptions/' + subscription_id + ':configure', {'expected_revision': current_sub['revision'], 'enabled': False, 'reason': 'acceptance complete'}, token=rotate['token'], headers={'Idempotency-Key': 'onboarding-pause-subscription'}, session=False)
if private:
Path(private, 'host.env').write_text(f"ORBIT_URL={base}\nORBIT_TOKEN={rotate['token']}\nORBIT_NAMESPACE_ID={op['namespace_id']}\nORBIT_QUEUE_ID={queue}\n")
Path(private, 'result.json').write_text(json.dumps({'application': name, 'namespace_id': op['namespace_id'], 'queue_id': queue, 'credential_id': op['credential_id'], 'task_id': task_id, 'subscription_id': subscription_id}))
call('/console/auth/logout', {})
call('/console/applications', expected=401)
print('PASS: application create/replay, CSRF/session isolation, token rotation, original task/subscription preserved, logout')
print('Test application: ' + name)