test(http): exercise OSE through a running server
Test web and DSi responses, permissions, moderation, settings and multipart logo uploads over HTTP.
This commit is contained in:
1 parent
95168fb1a5
commit
425da88711
1 file changed
+159
@@ -0,0 +1,159 @@
|
||||
"""Run the real proxy and browser routes against a disposable installation."""
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from http.client import HTTPConnection
|
||||
from urllib.parse import urlencode
|
||||
from contextlib import closing
|
||||
import json
|
||||
import gc
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class HttpIntegrationTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.temporary = TemporaryDirectory()
|
||||
cls.directory = Path(cls.temporary.name) / 'ose'
|
||||
shutil.copytree(ROOT, cls.directory, ignore=shutil.ignore_patterns('__pycache__', '.venv', 'logs', 'backups', 'tests', '*.sqlite3*', 'server-settings.json'))
|
||||
source = sqlite3.connect((ROOT/'database/reximemo.sqlite3').as_uri()+'?mode=ro', uri=True)
|
||||
destination = sqlite3.connect(cls.directory/'database/reximemo.sqlite3')
|
||||
source.backup(destination)
|
||||
destination.execute('DELETE FROM ip_logins')
|
||||
destination.commit()
|
||||
destination.close()
|
||||
source.close()
|
||||
with socket.socket() as probe:
|
||||
probe.bind(('127.0.0.1',0))
|
||||
cls.port = probe.getsockname()[1]
|
||||
config = {'site_name':'OSE HTTP Test','host':'127.0.0.1','port':cls.port}
|
||||
(cls.directory/'server-settings.json').write_text(json.dumps(config), encoding='utf-8')
|
||||
env = dict(os.environ, REXIMEMO_PORT=str(cls.port), REXIMEMO_HOST='127.0.0.1',
|
||||
REXIMEMO_DB=str(cls.directory/'database/reximemo.sqlite3'),
|
||||
REXIMEMO_CONFIG=str(cls.directory/'server-settings.json'), PYTHONUTF8='1')
|
||||
cls.log = (cls.directory/'http-test.log').open('w+',encoding='utf-8')
|
||||
cls.process = subprocess.Popen([sys.executable,'-u','server.py','docs:enabled'],cwd=cls.directory,env=env,stdout=cls.log,stderr=subprocess.STDOUT)
|
||||
try:
|
||||
for attempt in range(100):
|
||||
if cls.process.poll() is not None:
|
||||
cls.log.seek(0)
|
||||
raise RuntimeError(cls.log.read())
|
||||
try:
|
||||
if cls.request('GET','/')[0] == 200:
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
time.sleep(.1)
|
||||
else:
|
||||
raise RuntimeError('The test server did not start.')
|
||||
except Exception:
|
||||
cls.tearDownClass()
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls.process.poll() is None:
|
||||
cls.process.terminate()
|
||||
cls.process.wait(timeout=10)
|
||||
cls.log.close()
|
||||
# Windows can retain SQLite's shared-memory mapping briefly after exit.
|
||||
gc.collect()
|
||||
for attempt in range(20):
|
||||
try:
|
||||
cls.temporary.cleanup()
|
||||
break
|
||||
except (PermissionError, NotADirectoryError):
|
||||
if attempt == 19:
|
||||
raise
|
||||
time.sleep(.1)
|
||||
|
||||
@classmethod
|
||||
def request(cls, method, path, fields=None, *, cookie='', dsi=False, raw=None, content_type=None):
|
||||
connection=HTTPConnection('127.0.0.1',cls.port,timeout=10)
|
||||
headers={'Host':'flipnote.hatena.com' if dsi else 'localhost','Cookie':cookie}
|
||||
if dsi:
|
||||
headers['X-DSi-SID']='test-transport'
|
||||
body = urlencode(fields).encode() if fields is not None else raw
|
||||
if body is not None:
|
||||
headers['Content-Type']=content_type or 'application/x-www-form-urlencoded'
|
||||
connection.request(method,path,body=body,headers=headers)
|
||||
response=connection.getresponse()
|
||||
result=response.status,dict(response.getheaders()),response.read()
|
||||
connection.close()
|
||||
return result
|
||||
|
||||
def login(self):
|
||||
status,headers,body=self.request('GET','/login')
|
||||
self.assertEqual(status,200)
|
||||
token=re.search(rb'name="csrf" value="([a-f0-9]{64})"',body)[1].decode()
|
||||
cookie=headers['Set-Cookie'].split(';',1)[0]
|
||||
result=self.request('POST','/login',{'username':'reximemo','password':'alpine','csrf':token},cookie=cookie)
|
||||
self.assertEqual(result[0],303)
|
||||
return token,cookie
|
||||
|
||||
def test_web_dsi_and_management_end_to_end(self):
|
||||
for path in ('/','/browse?page=2','/search?q=Sample','/channels','/docs'):
|
||||
self.assertEqual(self.request('GET',path)[0],200,path)
|
||||
for region in ('xx','eu','us','jp'):
|
||||
status,_,menu=self.request('GET',f'/ds/v2-{region}/index.uls',dsi=True)
|
||||
self.assertEqual(status,200)
|
||||
self.assertTrue(menu.startswith(b'UGAR'))
|
||||
self.assertEqual(self.request('GET','/admin')[0],403)
|
||||
token,cookie=self.login()
|
||||
self.assertEqual(self.request('POST','/admin/channel-create',{'title':'Rejected'})[0],403)
|
||||
for tab in ('flipnotes','comments','channels','server','bans'):
|
||||
self.assertEqual(self.request('GET','/admin?tab='+tab,cookie=cookie)[0],200,tab)
|
||||
created=self.request('POST','/admin/channel-create',{'csrf':token,'title':'Music','description':'Songs and animation'},cookie=cookie)
|
||||
self.assertEqual(created[0],303)
|
||||
with closing(sqlite3.connect(self.directory/'database/reximemo.sqlite3')) as db:
|
||||
channel=db.execute("SELECT id FROM channels WHERE title='Music'").fetchone()[0]
|
||||
note=db.execute("SELECT id,creator_fsid,filename FROM flipnotes WHERE public_id='f_sample1'").fetchone()
|
||||
edited=self.request('POST','/watch/f_sample1/manage',{'csrf':token,'title':'Updated title','description':'A description <test>','channel_id':channel},cookie=cookie)
|
||||
self.assertEqual(edited[0],303)
|
||||
status,_,watch=self.request('GET','/watch/f_sample1',cookie=cookie)
|
||||
self.assertEqual(status,200)
|
||||
self.assertIn(b'A description <test>',watch)
|
||||
dsi_path=f'/ds/v2-xx/movie/{note[1]}/{note[2]}.ppm'
|
||||
original=(self.directory/'database/Creators'/note[1]/(note[2]+'.ppm')).read_bytes()
|
||||
self.assertEqual(self.request('GET',dsi_path,dsi=True)[2],original)
|
||||
self.assertEqual(self.request('GET','/media/f_sample1.ppm',cookie=cookie)[2],original)
|
||||
self.assertEqual(self.request('POST','/admin/flipnote-visibility',{'csrf':token,'id':note[0],'hidden':'1','reason':'Review'},cookie=cookie)[0],303)
|
||||
self.assertEqual(self.request('GET',dsi_path,dsi=True)[0],404)
|
||||
self.assertEqual(self.request('GET','/thumb/f_sample1.png',cookie=cookie)[0],200)
|
||||
self.request('GET','/logout',cookie=cookie)
|
||||
for path in ('/watch/f_sample1','/media/f_sample1.ppm','/flipnote/f_sample1.ppm','/thumb/f_sample1.png'):
|
||||
self.assertEqual(self.request('GET',path)[0],404,path)
|
||||
token,cookie=self.login()
|
||||
self.assertEqual(self.request('POST','/admin/flipnote-visibility',{'csrf':token,'id':note[0],'hidden':'0'},cookie=cookie)[0],303)
|
||||
self.assertEqual(self.request('GET',dsi_path,dsi=True)[2],original)
|
||||
settings={'csrf':token,'site_name':'Friendly OSE','tagline':'A test server','welcome_message':'Welcome everyone','accent_colour':'#336699','max_upload_kib':'2048'}
|
||||
self.assertEqual(self.request('POST','/admin/server',settings,cookie=cookie)[0],303)
|
||||
self.assertIn(b'Friendly OSE',self.request('GET','/')[2])
|
||||
self.assertEqual(self.request('GET','/register')[0],403)
|
||||
settings['csrf']='bad'
|
||||
settings['site_name']='Not allowed'
|
||||
self.assertEqual(self.request('POST','/admin/server',settings,cookie=cookie)[0],403)
|
||||
self.assertIn(b'Friendly OSE',self.request('GET','/')[2])
|
||||
logo=(self.directory/'web/static/RexiMemo_OSE_Logo.png').read_bytes()
|
||||
boundary='ose-integration-boundary'
|
||||
raw=(f'--{boundary}\r\nContent-Disposition: form-data; name="csrf"\r\n\r\n{token}\r\n--{boundary}\r\nContent-Disposition: form-data; name="logo"; filename="logo.png"\r\nContent-Type: image/png\r\n\r\n'.encode()+logo+f'\r\n--{boundary}--\r\n'.encode())
|
||||
uploaded=self.request('POST','/admin/logo',cookie=cookie,raw=raw,content_type='multipart/form-data; boundary='+boundary)
|
||||
self.assertEqual(uploaded[0],303,uploaded[2][:1000])
|
||||
self.assertTrue(self.request('GET','/branding/logo.png')[2].startswith(b'\x89PNG'))
|
||||
self.assertEqual(self.request('POST','/admin/channel-delete',{'csrf':token,'id':channel,'target_channel_id':'1'},cookie=cookie)[0],303)
|
||||
with closing(sqlite3.connect(self.directory/'database/reximemo.sqlite3')) as db:
|
||||
self.assertEqual(db.execute('SELECT channel_id FROM flipnotes WHERE id=?',(note[0],)).fetchone()[0],1)
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user
