"""V8.25 opt-in shared component correctness, isolation and draft safety.""" import copy import json import pytest from tests.test_factory import client,create from app import db,blueprints def home(c,site): return next(row for row in c.get(f'/api/sites/{site}/entries').json() if row['route']=='/') def library(c,site,title='SHARED_OLD'): block=blueprints.new_block('cta',{'title':title}) r=c.post(f'/api/sites/{site}/components',json={'name':'公共行动区','block':block}) assert r.status_code==200,r.text return r.json() def link(c,site,entry,item): block=copy.deepcopy(item['block']) block['id']='link_'+entry['id'][:10] block['shared_ref']={'id':item['id'],'version':item['version'],'hash':item['snapshot_hash']} url=f'/api/sites/{site}/pages/{entry["id"]}/studio-draft' draft=c.get(url).json() r=c.put(url,json={'blocks':[block],'page_template':'standard','layout_mode':'replace','expected_revision':draft['revision']}) assert r.status_code==200,r.text return url,block def change(c,site,item,title='SHARED_NEW'): updated=blueprints.new_block('cta',{'title':title}) r=c.put(f'/api/sites/{site}/components/{item["id"]}',json={ 'name':'公共行动区','expected_version':item['version'],'block':updated}) assert r.status_code==200,r.text return r.json() def test_linked_usage_and_explicit_sync_never_modifies_publication(client): site=create(client);entry=home(client,site);original=db.one('SELECT meta_json FROM entries WHERE id=?',(entry['id'],))['meta_json'] item=library(client,site);url,block=link(client,site,entry,item) v2=change(client,site,item) base=f'/api/sites/{site}/components/{item["id"]}' preview=client.get(base+'/usage').json() assert preview['references'][0]['eligible'] assert preview['references'][0]['block_id']==block['id'] assert preview['references'][0]['current_version']==1 r=client.post(base+'/sync',json={'plan_token':preview['plan_token'],'entry_ids':[entry['id']]}) assert r.status_code==200,r.text assert r.json()['published_unchanged'] newer=client.get(url).json() assert newer['revision']==2 assert newer['blocks'][0]['title']=='SHARED_NEW' assert newer['blocks'][0]['id']==block['id'] assert newer['blocks'][0]['shared_ref']['version']==2 assert db.one('SELECT meta_json FROM entries WHERE id=?',(entry['id'],))['meta_json']==original assert client.get(base+'/usage').json()['references'][0]['up_to_date'] history=client.get(f'/api/sites/{site}/pages/{entry["id"]}/studio-history').json()['items'] assert len(history)==2 v1=client.get(base+'/versions/1').json();v2view=client.get(base+'/versions/2').json() assert v1['block']['title']=='SHARED_OLD' and v2view['block']['title']=='SHARED_NEW' assert v1['snapshot_hash']!=v2['snapshot_hash'] def test_edited_instances_are_not_overwritten(client): site=create(client);entry=home(client,site);item=library(client,site) url,block=link(client,site,entry,item) current=client.get(url).json() current['blocks'][0]['title']='LOCALLY_CUSTOMIZED' payload={'blocks':current['blocks'],'page_template':'standard','layout_mode':'replace','expected_revision':1} assert client.put(url,json=payload).status_code==200 change(client,site,item) root=f'/api/sites/{site}/components/{item["id"]}' preview=client.get(root+'/usage').json() assert preview['references'][0]['local_modified'] assert not preview['references'][0]['eligible'] response=client.post(root+'/sync',json={'plan_token':preview['plan_token'],'entry_ids':[entry['id']]}) assert response.status_code==409 assert client.get(url).json()['blocks'][0]['title']=='LOCALLY_CUSTOMIZED' def test_stale_preview_and_foreign_site_protected(client): a=create(client);b=create(client);entry=home(client,a) item=library(client,a);url,_=link(client,a,entry,item) change(client,a,item) src=f'/api/sites/{a}/components/{item["id"]}' preview=client.get(src+'/usage').json() assert client.get(f'/api/sites/{b}/components/{item["id"]}/usage').status_code==404 assert client.get(f'/api/sites/{b}/components/{item["id"]}/versions').status_code==404 assert client.post(f'/api/sites/{b}/components/{item["id"]}/sync',json={ 'plan_token':preview['plan_token'],'entry_ids':[entry['id']]}).status_code==404 draft=client.get(url).json() assert client.put(url,json={**{k:draft[k] for k in ('blocks','page_template','layout_mode')}, 'expected_revision':draft['revision']}).status_code==200 assert client.post(src+'/sync',json={'plan_token':preview['plan_token'],'entry_ids':[entry['id']]}).status_code==409 def test_unlinked_copies_not_counted_and_deletion_blocked(client): site=create(client);item=library(client,site);entry=home(client,site) block=copy.deepcopy(item['block']) r=client.put(f'/api/sites/{site}/pages/{entry["id"]}/studio-draft',json={ 'blocks':[block],'page_template':'standard','expected_revision':0}) assert r.status_code==200 root=f'/api/sites/{site}/components/{item["id"]}' assert client.get(root+'/usage').json()['references']==[] link(client,site,entry,item) assert client.delete(root).status_code==409 other=library(client,site,'OTHER') assert client.delete(f'/api/sites/{site}/components/{other["id"]}').status_code==200 def test_invalid_component_ref_rejected(client): site=create(client);entry=home(client,site) item=blueprints.new_block('cta') item['shared_ref']={'id':'