@@ -0,0 +1,58 @@
|
||||
#!/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()
|
||||
مرجع در شماره جدید
مسدود کردن کاربر