"""Cross-account and compare-and-swap safeguards for shared demo inputs.""" import copy,json,time,uuid,hashlib from pathlib import Path from concurrent.futures import ThreadPoolExecutor from dataclasses import replace from backend.app import create_app from backend.duplicates import CATALOGUE,EXAMPLES,example_identity from backend.store import canonical from backend.repository import Conflict from backend.validation import normalize_input from test_api_publication import setup,client,fixture_generation,payload as base_payload,publish def payload(identifier): body=base_payload(identifier);body['rights']={'basis':'permission_for_cc_by','attribution':'Explicit fixture permission to share the source-derived result under CC BY.'};return body EXAMPLE_SHA=next(k for k,v in EXAMPLES.items() if v['image']=='examples/obj-backpack.webp') def demo_generation(app,owner,offset=0,seed=42,source=EXAMPLE_SHA,revision=None): identifier=fixture_generation(app,owner,offset) row=app.state.store.one('SELECT * FROM generations WHERE id=?',(identifier,)) result=json.loads(row['result']);params=json.loads(row['params']);params['seed']=seed result['provenance']['source_image']={'kind':'uploaded','sha256':source} if revision:result['provenance']['model']['revision']=revision with app.state.store.transaction() as db:db.execute('UPDATE generations SET result=?,params=? WHERE id=?',(canonical(result),canonical(params),identifier)) return identifier def test_pinned_catalogue_matches_actual_normalizer(tmp_path): assert len(CATALOGUE['examples'])==94==len(EXAMPLES) for path in Path('frontend/public/examples').glob('*.webp'): entry=next(p for p in CATALOGUE['examples'] if p['image']=='examples/'+path.name) assert hashlib.sha256(path.read_bytes()).hexdigest()==entry['raw_sha256'] assert normalize_input(path.read_bytes(),tmp_path/'normalized.png')['sha256']==entry['normalized_sha256'] def test_identical_file_between_accounts_links_to_original(setup): app,repo=setup;a=client(app,'alice');b=client(app,'bob') first=publish(a,payload(fixture_generation(app,'alice'))).json() response=publish(b,payload(fixture_generation(app,'bob'))) assert response.status_code==200 and response.json()['status']=='already_exists' assert response.json()['id']==first['id'] and response.json()['author']=='user-alice' assert repo.version==1 and len(repo.data['records'])==1 and len(repo.files)==5 def test_example_other_seed_settings_title_and_account_is_one_asset(setup): app,repo=setup;a=client(app,'alice');b=client(app,'bob') first=publish(a,payload(demo_generation(app,'alice'))).json() body=payload(demo_generation(app,'bob',.2,123));body['editorial']['title']='Another title' response=publish(b,body);assert response.status_code==200,response.text assert response.json()['status']=='already_exists' and response.json()['reason']=='demo_example' assert response.json()['id']==first['id'] assert publish(b,body).json()['id']==first['id'] body['editorial']['title']='Changed again';assert publish(b,body).json()['id']==first['id'] assert repo.version==1 and len(repo.files)==5 asset=a.get('/api/catalogue').json()['assets'][0] assert asset['demoExample']=='examples/obj-backpack.webp' and asset['author']=='user-alice' def test_new_image_or_model_revision_is_not_false_duplicate(setup): app,repo=setup;c=client(app,'alice') ids=[demo_generation(app,'alice'),demo_generation(app,'alice',.1,source='e'*64),demo_generation(app,'alice',.2,revision='f'*40)] for identifier in ids:assert publish(c,payload(identifier)).json()['status']=='published' assert len(repo.data['records'])==3 def test_simultaneous_example_publications_commit_once(setup): app,repo=setup entries=[(client(app,owner),payload(demo_generation(app,owner,i*.1,10+i)),str(uuid.uuid4())) for i,owner in enumerate(['alice','bob'])] def attempt(entry): c,body,key=entry for _ in range(50): response=publish(c,body,key) if response.status_code!=409:return response time.sleep(.02) return response with ThreadPoolExecutor(max_workers=2) as pool:responses=list(pool.map(attempt,entries)) assert all(r.status_code==200 for r in responses),[r.text for r in responses] assert {r.json()['status'] for r in responses}=={'published','already_exists'} assert len({r.json()['id'] for r in responses})==1 and repo.version==1 def test_other_server_wins_cas_then_example_is_reconciled(setup,tmp_path): app,repo=setup;other=create_app(replace(app.state.settings,state=tmp_path/'other'),repo) a=client(app,'alice');b=client(other,'bob') left=payload(demo_generation(app,'alice'));right=payload(demo_generation(other,'bob',.3,900)) original=repo.commit;winner=[] def race(*args,**kwargs): repo.commit=original winner.append(publish(b,right).json()) raise Conflict() repo.commit=race response=publish(a,left) assert response.status_code==200 and response.json()['status']=='already_exists' assert response.json()['id']==winner[0]['id'] and repo.version==1 and len(repo.files)==5 def test_withdrawn_example_cannot_be_reintroduced_with_new_seed(setup): app,repo=setup;c=client(app,'alice');first=publish(c,payload(demo_generation(app,'alice'))).json() with app.state.store.transaction() as db:db.execute("UPDATE publications SET status='withdrawn' WHERE id=?",(first['id'],)) repo.data['records']=[] assert publish(client(app,'bob'),payload(demo_generation(app,'bob',.1,777))).status_code==409