generated from jric11/baseProject
Chat's updates for security, db and workflow
Security and workflow regressions / regression (push) Has been cancelled
Security and workflow regressions / regression (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
"""Disposable HTTP test application. Requires an explicitly supplied test database server."""
|
||||
import http.cookiejar
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
PHP=json.loads(os.environ.get('PHP_TEST_COMMAND','["php"]'))
|
||||
|
||||
class Application:
|
||||
def __init__(self):
|
||||
if not os.environ.get('HCR_TEST_DSN'):
|
||||
raise RuntimeError('An isolated HCR_TEST_DSN is required.')
|
||||
self.env=os.environ.copy()
|
||||
self.env.update(HCR_APPLICATION_URL='',HCR_ENVIRONMENT='Development')
|
||||
self.database=self.fixture('create')['database']
|
||||
self.env['HCR_DB_NAME']=self.database
|
||||
with socket.socket() as available:
|
||||
available.bind(('127.0.0.1',0));port=available.getsockname()[1]
|
||||
self.base=f'http://127.0.0.1:{port}'
|
||||
self.log=tempfile.TemporaryFile(mode='w+b')
|
||||
self.server=subprocess.Popen(PHP+['-S',f'127.0.0.1:{port}','bin/dev-router.php'],cwd=ROOT,env=self.env,stdout=self.log,stderr=self.log)
|
||||
self.client=urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
||||
for _ in range(100):
|
||||
try:self.request();break
|
||||
except urllib.error.URLError:time.sleep(.1)
|
||||
else:self.close();raise RuntimeError('Test HTTP server did not start.')
|
||||
def fixture(self,action,*extra):
|
||||
args=PHP+['tests/http_fixture.php',action]+([self.database] if hasattr(self,'database') else [])+list(extra)
|
||||
result=subprocess.run(args,cwd=ROOT,env=self.env,capture_output=True,text=True)
|
||||
if result.returncode:raise RuntimeError(result.stdout+result.stderr)
|
||||
return json.loads(result.stdout.splitlines()[-1]) if result.stdout.strip() else {}
|
||||
def request(self,path='/',data=None,ajax=False):
|
||||
req=urllib.request.Request(self.base+path,data=urllib.parse.urlencode(data).encode() if data is not None else None,headers={'X-Requested-With':'XMLHttpRequest'} if ajax else {})
|
||||
try:
|
||||
with self.client.open(req,timeout=30) as response:return response.status,response.read(),response.headers
|
||||
except urllib.error.HTTPError as error:return error.code,error.read(),error.headers
|
||||
@staticmethod
|
||||
def csrf(body):
|
||||
match=re.search(rb'name="csrf(?:_token|-token)"[^>]*(?:value|content)="([a-f0-9]+)"',body)
|
||||
if not match:raise AssertionError('Missing CSRF token: '+body[:500].decode(errors='replace'))
|
||||
return match.group(1).decode()
|
||||
def login(self,email='admin@example.test',password='Admin test password 123!'):
|
||||
_,body,_=self.request()
|
||||
status,body,_=self.request(data={'action':'SignIn.login','csrf_token':self.csrf(body),'user_email':email,'user_password':password})
|
||||
if status!=200 or b'name="csrf-token"' not in body:raise AssertionError('Login failed: '+body[:500].decode(errors='replace'))
|
||||
self.token=self.csrf(body)
|
||||
def post(self,action,**values):
|
||||
return self.request(data={'action':action,'csrf_token':self.token,**values},ajax=True)
|
||||
def logout(self):self.post('signout')
|
||||
def close(self):
|
||||
self.server.terminate();self.server.wait(timeout=10)
|
||||
self.fixture('drop')
|
||||
self.log.seek(0);folder=ROOT/'tests/.local';folder.mkdir(exist_ok=True);(folder/'last-http.log').write_bytes(self.log.read());self.log.close()
|
||||
Reference in New Issue
Block a user