Files

125 lines
6.1 KiB
Python
Raw Permalink Normal View History

"""V8.20 offline CMS compatibility tests, independent of the public network."""
import io
import json
import pytest
from app import wordpress_compat
from app.wp_complex_v820 import inspect_wxr, extract_acf
from app.wp_migration_v82 import _media_urls, _rewrite_media_html
from test_factory_v2 import factory,site
def wxr(records):
return ('''<?xml version="1.0"?><rss version="2.0" xmlns:wp="http://wordpress.org/export/1.2/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel>'''+records+'</channel></rss>').encode()
def entry(wp_id,link,custom='',body='',kind='page'):
return f'''<item><title>Page {wp_id}</title><link>{link.replace('&','&amp;')}</link>
<wp:post_type>{kind}</wp:post_type><wp:post_id>{wp_id}</wp:post_id>
<wp:status>publish</wp:status><content:encoded><![CDATA[{body}]]></content:encoded>{custom}</item>'''
def meta(key,value):
return f'<wp:postmeta><wp:meta_key>{key}</wp:meta_key><wp:meta_value><![CDATA[{value}]]></wp:meta_value></wp:postmeta>'
def test_wxr_duplicate_route_and_dynamic_query_blocked():
data=wxr(entry(1,'https://old.example/products/a/')+entry(2,'https://old.example/products/a/')+entry(3,'https://old.example/?p=5'))
out=inspect_wxr(data)
assert out['unique_published_routes']==1
assert out['blocking_issues']==2
assert {x['code'] for x in out['issues']}=={'duplicate_route','invalid_permalink'}
assert out['ready_for_automatic_publish'] is False
def test_wxr_counts_attachments_and_custom_types():
data=wxr(entry(1,'https://old.example/a/',kind='attachment')+entry(2,'https://old.example/b/',kind='portfolio'))
out=inspect_wxr(data)
assert out['counts']['attachments']==1
assert out['features']['custom_post_types']==1
assert out['sample_routes'][0]['route']=='/b/'
def test_wxr_dynamic_shortcode_not_claimed_as_static():
data=wxr(entry(1,'https://old.example/contact/',body='[contact-form-7 id="4"]'))
out=inspect_wxr(data)
assert out['blocking_issues']==1
assert out['issues'][0]['code']=='dynamic_shortcode'
def test_acf_simple_repeater_explicit_field_references():
values={'_features':'field_parent','features':'2','features_0_title':'First','_features_0_title':'field_child',
'features_1_title':'Second','_features_1_title':'field_child','headline':'About','_headline':'field_header'}
parsed=extract_acf(values)
assert parsed['repeaters']['features']==[{'title':'First'},{'title':'Second'}]
assert parsed['fields']['headline']=='About'
assert any('核对' in s for s in parsed['warnings'])
def test_acf_serialized_does_not_execute_or_decode():
parsed=extract_acf({'_links':'field_test','links':'a:1:{i:0;s:4:"evil";}'})
assert 'links' not in parsed['fields']
assert any('序列化' in s for s in parsed['warnings'])
def test_acf_incomplete_repeater_kept_raw_only():
meta_values={'_list':'field_list','list':'2','_list_0_title':'field_title','list_0_title':'First'}
parsed=extract_acf(meta_values)
assert 'list' not in parsed['repeaters']
assert any('不连续' in x for x in parsed['warnings'])
def test_elementor_unknown_widget_reported():
data=json.dumps([{'elType':'widget','widgetType':'unknown_gallery','settings':{}},
{'elType':'widget','widgetType':'heading','settings':{'title':'A & B'}}])
out=wordpress_compat.elementor_to_html(data)
assert out['widgets']==2
assert 'A &amp; B' in out['html']
assert any('unknown_gallery' in x for x in out['warnings'])
def test_css_remote_image_inventory_and_rewrite():
source={'body':'''<div style="background-image: url('https://old.example/bg.webp')"><img data-background="https://old.example/lazy.png"></div>'''}
assert _media_urls(source)==['https://old.example/bg.webp','https://old.example/lazy.png']
converted={'https://old.example/bg.webp':'/media/a.webp','https://old.example/lazy.png':'/media/b.png'}
result,changed=_rewrite_media_html(source['body'],converted)
assert changed and '/media/a.webp' in result and '/media/b.png' in result
assert not _media_urls({'body':result})
def test_xxe_and_invalid_input_are_rejected():
with pytest.raises(ValueError):inspect_wxr(b'')
with pytest.raises(Exception):inspect_wxr(b'<!DOCTYPE rss [<!ENTITY x SYSTEM "file:///etc/passwd">]><rss>&x;</rss>')
with pytest.raises(ValueError):inspect_wxr(b'<urlset/>')
def test_wxr_api_enforces_auth_and_csrf(factory):
sid=site(factory,mode='migration')
payload=wxr(entry(1,'https://old.example/about/'))
response=factory.post(f'/api/migrations/sites/{sid}/wordpress/wxr-preflight',files={'file':('site.xml',payload)})
assert response.status_code==200,response.text
assert response.json()['unique_published_routes']==1
assert response.json()['ready_for_automatic_publish'] is False
bad=factory.post(f'/api/migrations/sites/{sid}/wordpress/wxr-preflight',files={'file':('site.xml',b'<urlset/>')})
assert bad.status_code==400
def test_import_wxr_rejects_dynamic_path_before_writing(factory):
sid=site(factory,mode='migration')
payload=wxr(entry(1,'https://old.example/?p=13'))
response=factory.post(f'/api/sites/{sid}/import',files={'file':('site.xml',payload)})
assert response.status_code in (400,422)
assert not any(x['route']=='/' and x['title']=='Page 1' for x in factory.get(f'/api/sites/{sid}/entries').json())
def test_wxr_acf_repeater_survives_import(factory):
sid=site(factory,mode='migration')
custom=''.join(meta(k,v) for k,v in {'_features':'field_parent','features':'2',
'_features_0_name':'field_child','features_0_name':'SUV',
'_features_1_name':'field_child','features_1_name':'Pickup'}.items())
payload=wxr(entry(1,'https://old.example/products/a/',custom=custom))
response=factory.post(f'/api/sites/{sid}/import',files={'file':('site.xml',payload)})
assert response.status_code==200,response.text
item=next(x for x in factory.get(f'/api/sites/{sid}/entries').json() if x['route']=='/products/a/')
assert item['meta']['fields']['features']==[{'name':'SUV'},{'name':'Pickup'}]
assert item['meta']['acf_repeaters']=={'features':[{'name':'SUV'},{'name':'Pickup'}]}