"""Deterministic P0/P2 regression fixtures; no outside network or Docker dependency.""" import io import json import sqlite3 import zipfile from pathlib import Path import pytest from app import db, backup, wp_migration_v82 as media from scripts.restore_backup import restore def test_responsive_media_preserves_sizes_and_catches_cdn(): original={ 'cover':'https://example.org/cover.jpg', 'body':''' ''' } targets=media._media_urls(original) assert len(targets)==5 and 'https://example.org/big.jpg' in targets assert 'https://cdn.example.net/cdn.jpg' in targets converted={x:'/media/'+x.rsplit('/',1)[-1] for x in targets if 'example.org/' in x} out,changed=media._rewrite_media_html(original['body'],converted) assert changed assert '/media/big.jpg 1440w' in out assert '/media/small.jpg 360w' in out assert 'data-src="/media/full.jpg"' in out assert 'https://cdn.example.net/cdn.jpg 2x' in out assert media._media_urls({'body':out})==['https://cdn.example.net/cdn.jpg'] def test_restore_tampered_archive_cannot_change_existing_data(tmp_path): target=tmp_path/'restore';target.mkdir() (target/'old.txt').write_text('must survive') for filename in ('../outside.txt','evil.txt'): archive=tmp_path/(filename.replace('/','_')+'.zip') with zipfile.ZipFile(archive,'w') as z: z.writestr('siteforge.sqlite3',b'not a database') z.writestr(filename,'evil') with pytest.raises(ValueError):restore(archive,target,confirmed=True) assert (target/'old.txt').read_text()=='must survive' assert not (tmp_path/'outside.txt').exists() def test_restore_replaces_directory_atomically_and_preserves_previous(tmp_path,monkeypatch): source=tmp_path/'original';source.mkdir() monkeypatch.setattr(db,'DATA',source) monkeypatch.setattr(db,'DB_FILE',source/'siteforge.sqlite3') db.init() db.insert('INSERT INTO sites(id,name,created_at,updated_at) VALUES(?,?,?,?)', ('testsite','Restore Success',db.now(),db.now())) (source/'media').mkdir();(source/'media'/'image.png').write_bytes(b'img') archive=backup.backup() target=tmp_path/'production';target.mkdir() (target/'old-sentinel').write_text('old installation') out=restore(archive,target,confirmed=True) assert (target/'media'/'image.png').read_bytes()==b'img' assert not (target/'old-sentinel').exists() assert out['previous'] and (Path(out['previous'])/'old-sentinel').read_text()=='old installation' with sqlite3.connect(target/'siteforge.sqlite3') as conn: assert conn.execute('SELECT name FROM sites WHERE id=?',('testsite',)).fetchone()[0]=='Restore Success' def test_remote_only_media_candidate_reported(): assert media._media_urls({'body':''})==['https://cdn.example.net/a.png'] def test_build_quality_reports_missing_local_assets_and_blocks_traversal(tmp_path,monkeypatch): from app import quality monkeypatch.setattr(db,'DATA',tmp_path) monkeypatch.setattr(db,'DB_FILE',tmp_path/'db.sqlite3') db.init() sid='p0p4qa';now=db.now() db.insert('INSERT INTO sites(id,name,domain,created_at,updated_at) VALUES(?,?,?,?,?)', (sid,'QA','https://example.org',now,now)) dist=tmp_path/'builds'/sid/'sample'/'dist' dist.mkdir(parents=True) (dist/'index.html').write_text('''Home

Welcome

Missing OKBad''') audit=quality.report(sid,'sample') assert audit['files']==1 and audit['errors']==3 assert {x['type'] for x in audit['issues']}=={'broken_internal_reference'} (dist/'assets').mkdir();(dist/'assets'/'not-there.png').write_bytes(b'demo') (dist/'missing').mkdir();(dist/'missing'/'index.html').write_text('Missing

There

') audit=quality.report(sid,'sample') assert audit['errors']==1 # the traversal never resolves to an outside file def test_catalogue_prepared_context_reuses_groups_without_mutating_records(): from app import theme_pro rows=[{'type':'vehicle','title':f'Model {i}','route':f'/vehicle/{i}/', 'meta_json':json.dumps({'fields':{'brand':'Acme','category':'SUV'}})} for i in range(30)] prepared=theme_pro.prepare(rows) assert prepared['brands']==[{'name':'Acme','url':'/brands/acme/'}] assert prepared['categories']==[{'name':'SUV','url':'/categories/suv/'}] site={'name':'Demo','theme':'automotive','domain':'','settings_json':'{}','tagline':''} ctx=theme_pro.make_context(site,rows[0],rows,prepared=prepared) assert ctx['kind']=='product' and ctx['listing']==[] assert ctx['groups'] is prepared['groups'] assert rows[0]['title']=='Model 0' def test_static_pagination_keeps_every_product_reachable(tmp_path,monkeypatch): from app import builder,quality,v8_workflows as wf from app.main import app from app.core import uid from fastapi.testclient import TestClient monkeypatch.setattr(db,'DATA',tmp_path) monkeypatch.setattr(db,'DB_FILE',tmp_path/'pagination.sqlite3') monkeypatch.setenv('SITEFORGE_ADMIN_PASSWORD','pagination-demo-only-password-long') monkeypatch.setenv('SITEFORGE_SESSION_SECRET','pagination-demo-only-secret-long-enough-1234') with TestClient(app): sid=uid();now=db.now() db.insert('INSERT INTO sites(id,name,theme,created_at,updated_at) VALUES(?,?,?,?,?)', (sid,'Pagination Pilot','automotive',now,now)) entries=[{'车型名称':f'Product {i:04d}','品牌':'Maker','分类':'SUV'} for i in range(235)] b=wf.stage(sid,entries,kind='vehicle') assert wf.commit(sid,b['batch_id'])['created']==235 result=builder.build(sid,False) dist=tmp_path/'builds'/sid/result['version']/'dist' first=(dist/'categories/suv/index.html').read_text() second=(dist/'categories/suv/page/2/index.html').read_text() third=(dist/'categories/suv/page/3/index.html').read_text() assert '/categories/suv/page/2/' in first assert '/categories/suv/page/3/' in second assert '/categories/suv/page/2/' in third assert first.count('pro-card pro-card-catalog')==100 assert second.count('pro-card pro-card-catalog')==100 assert third.count('pro-card pro-card-catalog')==35 assert (dist/'vehicle/page/2/index.html').is_file() assert (dist/'products/page/2/index.html').is_file() assert quality.report(sid,result['version'])['errors']==0