Files
dtf-system/local/smoke_test.py
Cauê Faleiros 98c951d374
Some checks failed
Validate, publish and deploy / validate (push) Successful in 2m2s
Validate, publish and deploy / publish-and-deploy (push) Failing after 8s
first commit
2026-09-15 16:42:34 -03:00

148 lines
8.6 KiB
Python

"""Local stack integration checks; creates and retains clearly named test orders.
Run: python3 -m local.smoke_test. Standard library only. Honors .env/environment.
"""
from concurrent.futures import ThreadPoolExecutor
import hashlib
import http.cookiejar
import json
import os
from pathlib import Path
import time
from urllib.error import HTTPError
from urllib.request import build_opener, HTTPCookieProcessor, Request, urlopen
from uuid import uuid4
if Path('.env').exists():
for line in Path('.env').read_text().splitlines():
if line.strip() and not line.startswith('#') and '=' in line:
key,value=line.split('=',1)
os.environ.setdefault(key,value)
BASE='http://localhost:'+os.environ.get('SITE_PORT','8080')
class Client:
def __init__(self):
self.jar=http.cookiejar.CookieJar()
self.opener=build_opener(HTTPCookieProcessor(self.jar))
self.operator_client=None
def call(self,path,body=None,operator=False,expected=200):
headers={'Content-Type':'application/json'}
if operator:
if self.operator_client is None:
self.operator_client=Client()
self.operator_client.call('/operator/login',{'username':os.environ.get('OPERATOR_USER','operator'),'password':os.environ.get('OPERATOR_PASSWORD','local-operator-only')})
return self.operator_client.call(path,body,expected=expected)
request=Request(BASE+'/api'+path,data=None if body is None else json.dumps(body).encode(),headers=headers)
try:
with self.opener.open(request,timeout=30) as response:
assert response.status==expected,(path,response.status,expected)
return json.load(response)
except HTTPError as exc:
if exc.code!=expected:raise AssertionError((path,exc.code,exc.read().decode())) from exc
return json.load(exc)
def wait_scan(client, uid, operator=False, expected='clean'):
prefix='/operator/uploads' if operator else '/uploads'
deadline=time.monotonic()+150
while time.monotonic()<deadline:
state=client.call(prefix+'/'+uid,operator=operator)
if state['scan_state'] in ('clean','rejected','error'):
assert state['scan_state']==expected,state
return state
time.sleep(0.5)
raise AssertionError('Malware scan did not finish')
def upload_bytes(client, content, name='LOCAL-TEST.cdr', order_id=None, expected_scan='clean'):
operator=order_id is not None
prefix='/operator/uploads' if operator else '/uploads'
start='/operator/orders/'+order_id+'/uploads' if operator else prefix
data=client.call(start,{'name':name,'size':len(content)},operator=operator)
uid=data['id'];size=data['part_bytes']
for offset in range(0,len(content),size):
url=client.call(prefix+'/'+uid+'/parts/'+str(offset//size+1),{},operator=operator)['url']
with urlopen(Request(url,data=content[offset:offset+size],method='PUT'),timeout=30) as response:
assert response.status==200
client.call(prefix+'/'+uid+'/complete',{},operator=operator)
wait_scan(client,uid,operator,expected_scan)
return uid
def run():
client=Client();other=Client()
config=client.call('/session');other.call('/session')
assert client.call('/health')['integrations']=='fake'
client.call('/operator/board',expected=401)
block=config['part_bytes'];content=b'DTF local multipart test\n'+b'x'*block
uid=client.call('/uploads',{'name':'LOCAL-SMOKE-ONLY.cdr','size':len(content)})['id']
other.call('/uploads/'+uid,expected=404)
other.call('/uploads/'+uid+'/parts/1',{},expected=404)
signed=client.call('/uploads/'+uid+'/parts/1',{})['url']
with urlopen(Request(signed,data=content[:block],method='PUT'),timeout=30) as response:assert response.status==200
assert client.call('/uploads/'+uid)['parts']==[1]
client.call('/uploads/'+uid+'/complete',{},expected=409)
signed=client.call('/uploads/'+uid+'/parts/2',{})['url']
with urlopen(Request(signed,data=content[block:],method='PUT'),timeout=30) as response:assert response.status==200
client.call('/uploads/'+uid+'/complete',{})
client.call('/uploads/'+uid+'/complete',{})
wait_scan(client,uid)
client.call('/uploads/'+uid+'/parts/1',{},expected=409)
download=client.call('/operator/uploads/'+uid+'/download',operator=True)
with urlopen(download['url'],timeout=30) as response:assert hashlib.sha256(response.read()).digest()==hashlib.sha256(content).digest()
unsigned=download['url'].split('?')[0]
try:urlopen(unsigned,timeout=10);raise AssertionError('Bucket must be private')
except HTTPError as exc:assert exc.code==403
print('PASS: multipart resume, incomplete rejection, immutable completion, ownership, private/downloaded bytes')
items=[{'mode':m,'metres':'2.75','grade':90,'uploads':[uid]} for m in ('file','avulsa','uvfile','uv')]
draft={'request_key':str(uuid4()),'customer':{'cnpj':'11222333000181','zap':'11999999999','mail':'local-smoke@example.test'},
'items':items,'freight':{'service':'mock-standard','postal_code':'14400000'}}
client.call('/quotes',{**draft,'total_cents':1},expected=422)
client.call('/quotes',{**draft,'customer':{**draft['customer'],'cnpj':'11111111111111'}},expected=422)
quote=client.call('/quotes',draft)
assert client.call('/quotes',draft)['id']==quote['id']
client.call('/quotes',{**draft,'freight':{'service':'pickup'}},expected=409)
qid=quote['id']
other.call('/quotes/'+qid,expected=404)
client.call('/orders/dev-paid',{'quote_id':qid},expected=409)
client.call('/operator/quotes/'+qid+'/approve',{'items':items},expected=401)
# Reviewer corrects a browser-supplied grade and length. Browser values are proposals.
corrected=[{**items[0],'metres':'1.01','grade':0},*items[1:]]
approved=client.call('/operator/quotes/'+qid+'/approve',{'items':corrected},operator=True)
assert approved['items'][0]['total_cents']==2189
assert approved['total_cents']==2189+6972+19572+23492+int(os.environ.get('MOCK_FREIGHT_CENTS','1500'))
client.call('/operator/quotes/'+qid+'/approve',{'items':items},operator=True,expected=409)
client.call('/orders/dev-paid',{'quote_id':qid,'total_cents':1},expected=422)
other.call('/orders/dev-paid',{'quote_id':qid},expected=404)
# Concurrent retries must produce precisely one payment/order/outbox pair.
with ThreadPoolExecutor(max_workers=4) as executor:
paid=list(executor.map(lambda _:client.call('/orders/dev-paid',{'quote_id':qid}),range(4)))
assert len({p['id'] for p in paid})==1
order=paid[0];oid=order['id']
assert order['payment']['status']=='paid' and order['snapshot']==approved
board=client.call('/operator/board',operator=True)
assert len([o for o in board['orders'] if o['quote_id']==qid])==1
client.call('/operator/orders/'+oid+'/move',{'state':'fin','version':0},operator=True,expected=409)
client.call('/operator/orders/'+oid+'/move',{'state':'cor','version':0},operator=True,expected=422)
version=0
for state in ('cor','rec','tra','fil','imp','fin'):
if state=='fil':
client.call('/operator/orders/'+oid+'/move',{'state':'fil','version':version},operator=True,expected=409)
files=[{'item_index':i,'upload_id':upload_bytes(client,b'LOCAL FINAL FIXTURE '+str(i).encode(),order_id=oid)} for i in range(4)]
result=client.call('/operator/orders/'+oid+'/final-files',{'version':version,'files':files,'note':'Local test manual final-file approval'},operator=True)
version=result['version']
moved=client.call('/operator/orders/'+oid+'/move',{'state':state,'version':version,'reason':'Local test correction' if state=='cor' else ''},operator=True)
version+=1;assert moved['version']==version
client.call('/operator/orders/'+oid+'/move',{'state':'rec','version':0},operator=True,expected=409)
assert len(client.call('/operator/orders/'+oid+'/history',operator=True))==6
print('PASS: all modes, authoritative review/prices/freight, tamper rejection, concurrent payment idempotency, transitions and history')
deadline=time.monotonic()+30
while time.monotonic()<deadline:
events=[e for e in client.call('/operator/board',operator=True)['events'] if e['payload']['order_id']==oid]
if len(events)==8 and all(e['delivered_at'] and e['receipt'] for e in events):break
time.sleep(1)
else:raise AssertionError('Mock outbox did not drain')
assert len({e['event_key'] for e in events})==8
print(f"PASS: 8 durable fake receipts. Local test order #{order['number']} retained in Finalizado.")
return oid
if __name__=='__main__':run()