feat: configurable branding, private preferences, city photo cards and database backups
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import base64
|
||||
import copy
|
||||
import http.client
|
||||
import struct
|
||||
import unittest
|
||||
import zlib
|
||||
from test_app import app
|
||||
import test_households as fixture
|
||||
from appearance import BRAND, png_bytes
|
||||
|
||||
|
||||
def image_data():
|
||||
def chunk(kind, data):
|
||||
return struct.pack('>I',len(data))+kind+data+struct.pack('>I',zlib.crc32(kind+data))
|
||||
raw=b'\x89PNG\r\n\x1a\n'+chunk(b'IHDR',struct.pack('>IIBBBBB',2,2,8,6,0,0,0))+chunk(b'IDAT',zlib.compress((b'\x00'+b'\x33\x66\x55\xff'*2)*2))+chunk(b'IEND',b'')
|
||||
return 'data:image/png;base64,'+base64.b64encode(raw).decode()
|
||||
|
||||
|
||||
class AppearanceTests(unittest.TestCase):
|
||||
setUp=fixture.HouseholdTests.setUp
|
||||
stop_server=fixture.HouseholdTests.stop_server
|
||||
request=fixture.HouseholdTests.request
|
||||
|
||||
def test_platform_branding_is_admin_only_and_public_values_are_allowlisted(self):
|
||||
data=copy.deepcopy(BRAND);data['name']='自有品牌';data['logoData']=image_data();data['iconData']=image_data()
|
||||
self.assertEqual(self.request('/api/appearance',self.owner,data)[0],403)
|
||||
self.assertEqual(self.request('/api/appearance',self.members[self.aid])[0],403)
|
||||
self.assertEqual(self.request('/api/appearance',self.root,data)[0],200)
|
||||
c=http.client.HTTPConnection(*self.server.server_address)
|
||||
c.request('GET','/api/branding');response=c.getresponse();raw=response.read();c.close()
|
||||
self.assertEqual(response.status,200)
|
||||
self.assertNotIn(b'logoData',raw);self.assertNotIn(b'password',raw)
|
||||
self.assertEqual(app.APPEARANCE.public()['name'],'自有品牌')
|
||||
self.assertTrue(app.APPEARANCE.public()['iconUrl'].startswith('/brand-icon.png'))
|
||||
|
||||
def test_brand_config_conflict_and_invalid_values(self):
|
||||
original=copy.deepcopy(BRAND)
|
||||
self.assertEqual(self.request('/api/appearance',self.root,original)[0],200)
|
||||
self.assertEqual(self.request('/api/appearance',self.root,original)[0],409)
|
||||
data=app.APPEARANCE.config();data['defaults']['layout']='arbitrary-css'
|
||||
self.assertEqual(self.request('/api/appearance',self.root,data)[0],400)
|
||||
data=app.APPEARANCE.config();data['logoData']='https://example.org/private.png'
|
||||
self.assertEqual(self.request('/api/appearance',self.root,data)[0],400)
|
||||
|
||||
def test_preferences_are_account_scoped_and_survive_account_edits(self):
|
||||
one,two=self.members[self.aid],self.members[self.bid]
|
||||
config=self.request('/api/preferences',one)[1]
|
||||
values=config['values'];values['theme']='ink'
|
||||
self.assertEqual(self.request('/api/preferences',one,dict(revision=0,values=values))[0],200)
|
||||
self.assertEqual(self.request('/api/preferences',two)[1]['effective']['theme'],'paper')
|
||||
self.assertEqual(self.request('/api/preferences',one,dict(revision=1,values=values,userId=two['id']))[0],400)
|
||||
app.ACCOUNTS.save(dict(app.get_object('users',one['id']),name='重命名'),self.root)
|
||||
self.assertEqual(app.APPEARANCE.preferences(one)['effective']['theme'],'ink')
|
||||
|
||||
def test_defaults_inheritance_and_personal_conflicts(self):
|
||||
user=self.members[self.aid];values=app.APPEARANCE.preferences(user)['values'];values['layout']='top'
|
||||
app.APPEARANCE.save_preferences(dict(revision=0,values=values),user)
|
||||
config=copy.deepcopy(BRAND);config['defaults']['theme']='clean'
|
||||
app.APPEARANCE.save_config(config,self.root)
|
||||
effective=app.APPEARANCE.preferences(user)['effective']
|
||||
self.assertEqual(effective['theme'],'clean');self.assertEqual(effective['layout'],'top')
|
||||
self.assertEqual(self.request('/api/preferences',user,dict(revision=0,values=values))[0],409)
|
||||
|
||||
def test_png_validation_rejects_svg_corruption_and_trailing_data(self):
|
||||
good=image_data();self.assertGreater(len(png_bytes(good)),10)
|
||||
for bad in ['data:image/svg+xml;base64,AAA','data:image/png;base64,!!',good[:-2],
|
||||
'data:image/png;base64,'+base64.b64encode(png_bytes(good)+b'extra').decode()]:
|
||||
with self.assertRaises(ValueError):png_bytes(bad)
|
||||
|
||||
def test_city_and_cover_are_preserved_without_exposing_inline_image_in_state(self):
|
||||
home=app.get_object('households',self.aid)
|
||||
self.assertEqual(self.request('/api/households',self.root,dict(home,province='山东省',city='烟台市',district='莱阳市',coverData=image_data()))[0],200)
|
||||
listed=self.request('/api/households',self.owner)[1]['households'][0]
|
||||
self.assertEqual(listed['city'],'烟台市');self.assertNotIn('coverData',listed)
|
||||
self.assertIn('/api/households/cover?',listed['coverUrl'])
|
||||
saved=self.request('/api/households',self.owner,dict(id=self.aid,name='新名称'))[1]
|
||||
self.assertEqual(saved['city'],'烟台市');self.assertEqual(saved['coverData'],image_data())
|
||||
for user,expected in [(self.members[self.aid],200),(self.members[self.bid],404)]:
|
||||
c=http.client.HTTPConnection(*self.server.server_address)
|
||||
c.request('GET',listed['coverUrl'],headers={'Cookie':'vision='+self.tokens[user['id']]})
|
||||
r=c.getresponse();r.read();c.close();self.assertEqual(r.status,expected)
|
||||
|
||||
def test_untrusted_fields_do_not_become_executable_or_change_roles(self):
|
||||
user=self.members[self.aid];data=app.APPEARANCE.preferences(user)
|
||||
self.assertEqual(self.request('/api/preferences',user,dict(revision=0,values=dict(data['values'],css='url(x)')))[0],400)
|
||||
self.assertEqual(self.request('/api/appearance',self.root,dict(copy.deepcopy(BRAND),role='admin'))[0],400)
|
||||
Reference in New Issue
Block a user