Files
altium-render-engine/backend/tests/test_expiry.py
T
2026-11-09 14:55:36 +03:30

74 خطوط
3.6 KiB
Python

import time
import pytest
from app.config import settings
from app.db import Job,claim,sessions
from app.storage import storage
from app.worker import process
from app.cleanup import cleanup
from test_platform import client,KEY
from pcb_fixture import pcb
ROUTES=['/api/v1/schematics/render','/api/v1/comparisons','/api/v1/pcbs/render','/api/v1/pcbs/comparisons']
def create(client,route,raw,expires=None):
suffix='PcbDoc' if '/pcbs/' in route else 'SchDoc'
files={f'{side}_file':(side+'.'+suffix,raw) for side in ('old','new')} if route.endswith('comparisons') else {'file':('input.'+suffix,raw)}
return client.post(route,headers={'X-API-Key':KEY},files=files,data={} if expires is None else {'expires_in':str(expires)})
@pytest.mark.parametrize('route',ROUTES)
@pytest.mark.parametrize('backend',['local','s3'])
def test_unlimited_retention_survives_claim_read_and_cleanup(client,ascii_schematic,monkeypatch,route,backend):
fake=None
if backend=='s3':
import boto3
from test_storage_s3 import MemoryS3
fake=MemoryS3();monkeypatch.setattr(boto3,'client',lambda *a,**kw:fake)
monkeypatch.setenv('STORAGE_BACKEND','s3');monkeypatch.setenv('S3_BUCKET','test-private');monkeypatch.setenv('S3_PREFIX','unlimited')
settings.cache_clear();storage.cache_clear()
raw=pcb() if '/pcbs/' in route else ascii_schematic
created=create(client,route,raw,-1);assert created.status_code==202,created.text
body=created.json();assert body['expires_at']==-1
with sessions()() as db:assert db.get(Job,body['id']).expires_at==-1
# Advance the application clock far beyond every configured finite TTL.
future=time.time()+100*365*86400;monkeypatch.setattr(time,'time',lambda:future)
claimed=claim();assert claimed is not None and claimed['id']==body['id']
process(claimed)
headers={'X-Viewer-Token':body['viewer_token']}
response=client.get(body['status_url'],headers=headers);assert response.status_code==200
status=response.json();assert status['status']=='completed',status
assert status['expires_at']==-1
base=body['status_url'].removesuffix('/status')
assert client.get(base+('/svg/old' if route.endswith('comparisons') else '/svg'),headers=headers).status_code==200
assert client.get(body['status_url']).status_code==404
assert cleanup()==0
with sessions()() as db:assert db.get(Job,body['id']) is not None
if fake:assert fake.objects and not (settings().storage_dir/'objects').exists()
else:assert list((settings().storage_dir/'objects'/body['id']).rglob('*'))
@pytest.mark.parametrize('route',ROUTES)
@pytest.mark.parametrize('expires',[0,-2,999999999])
def test_invalid_expiry_rejected_for_every_operation(client,ascii_schematic,route,expires):
response=create(client,route,pcb() if '/pcbs/' in route else ascii_schematic,expires)
assert response.status_code==422,response.text
assert claim() is None
@pytest.mark.parametrize('route',ROUTES)
def test_unlimited_jobs_still_count_toward_queue_limit(client,ascii_schematic,monkeypatch,route):
monkeypatch.setenv('MAX_PENDING_JOBS','1');settings.cache_clear()
raw=pcb() if '/pcbs/' in route else ascii_schematic
assert create(client,route,raw,-1).status_code==202
assert create(client,route,raw,-1).status_code==503
@pytest.mark.parametrize('route',ROUTES)
def test_default_and_positive_expiry_remain_finite(client,ascii_schematic,route):
raw=pcb() if '/pcbs/' in route else ascii_schematic
now=time.time()
for expires,ttl in [(None,settings().link_ttl),(60,60)]:
response=create(client,route,raw,expires);assert response.status_code==202
assert now+ttl<=response.json()['expires_at']<=time.time()+ttl