Files
2026-11-09 12:34:50 +03:30

59 خطوط
3.6 KiB
Python

#!/usr/bin/env python3
"""Real HTTP smoke: private PCB render + comparison, without printing read capabilities."""
import argparse
import json
import os
from pathlib import Path
import secrets
import time
import urllib.error
import urllib.request
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('old',type=Path);parser.add_argument('new',type=Path,nargs='?')
parser.add_argument('--url',default='http://127.0.0.1:8000')
parser.add_argument('--timeout',type=int,default=180)
args=parser.parse_args();base=args.url.rstrip('/');key=os.environ.get('API_KEY','')
def request(path,headers=None,data=None):
req=urllib.request.Request(base+path,data=data,headers=headers or {})
with urllib.request.urlopen(req,timeout=30) as response:return response.status,response.headers,response.read()
def upload(path,files):
boundary='pcb-'+secrets.token_hex(16);parts=[]
for field,file in files.items():
parts.extend([f'--{boundary}\r\nContent-Disposition: form-data; name="{field}"; filename="board.PcbDoc"\r\nContent-Type: application/octet-stream\r\n\r\n'.encode(),file.read_bytes(),b'\r\n'])
parts.extend([f'--{boundary}\r\nContent-Disposition: form-data; name="access"\r\n\r\nprivate\r\n--{boundary}--\r\n'.encode()])
status,headers,body=request(path,{'X-API-Key':key,'Content-Type':f'multipart/form-data; boundary={boundary}'},b''.join(parts))
assert status==202;created=json.loads(body);assert '#token=' in created['viewer_url']
return created
for comparison in (False,True):
path='/api/v1/pcbs/comparisons' if comparison else '/api/v1/pcbs/render'
files={'old_file':args.old,'new_file':args.new or args.old} if comparison else {'file':args.old}
created=upload(path,files);identifier=created['id'];headers={'X-Viewer-Token':created['viewer_token']}
resource=f'/api/v1/pcbs/comparisons/{identifier}' if comparison else f'/api/v1/pcbs/{identifier}'
status_path=resource if comparison else resource+'/status'
try:request(status_path)
except urllib.error.HTTPError as exc:assert exc.code==404
else:raise AssertionError('Private PCB accessible without a capability')
deadline=time.monotonic()+args.timeout
while True:
_,_,body=request(status_path,headers);result=json.loads(body)
if result['status']=='failed':raise RuntimeError(result['error'])
if result['status']=='completed':break
if time.monotonic()>deadline:raise TimeoutError('PCB worker did not finish')
time.sleep(.5)
for side in (('old','new') if comparison else ('',)):
suffix='/svg/'+side if comparison else '/svg'
status,_,svg=request(resource+suffix,headers);assert status==200 and b'<svg' in svg and b'data-layer-id' in svg
view=f'/compare/pcb/{identifier}' if comparison else f'/view/pcb/{identifier}'
_,response_headers,html=request(view)
csp=response_headers.get_all('Content-Security-Policy')
assert len(csp)==1 and "frame-ancestors 'self' https://git.sadentel.net" in csp[0]
assert not response_headers.get_all('X-Frame-Options') and b'/assets/pcb.js' in html
if comparison and (args.new is None or args.old.read_bytes()==args.new.read_bytes()):assert result['total_changed']==0
print(json.dumps(dict(kind='pcb_comparison' if comparison else 'pcb_render',id=identifier,status=result['status'],
summary=result.get('summary'),primitive_count=result.get('primitive_count'))))
if __name__=='__main__':main()