freechat/json-api/client-tests/client.py
shockrah 38ff0edd39 ! Massive test suite overhaul see details below !
Problem: the old test suite was extremely inflexible
This meant that making new tests took way too much time.

+ This new rework makes the new client's backend much thinner and less "magical"
With less magic going on we can pass way more data more easily to the actual
http-request engine making the convenience wrapper over top it much more flexible

Translating old tests to the new engine might take a while but for now the old
client codebase is completely deprecated and will no longer be used+updated
2021-04-21 17:22:16 -07:00

258 lines
8.8 KiB
Python

import time
import subprocess, os, sys
import json, requests
from json import JSONDecodeError
from web import http
class Worker:
def __init__(self, domain: str, create_admin: bool):
'''
@opt:base = base url string
@opt:create_admin = creates admin account directly off cargo -c <NAME>
@opt:admin = optionally pass in dictionary with some admin credentials
potentially from another instance to run multiple tests at once
'''
self.domain = domain
self.requests = {}
self.responses = {}
self.body_logs = {}
self.jwt = None # never gets assigned until /login is hit
self.basic_creds = self.__create_admin()
self.id = self.basic_creds['user']['id']
self.secret = self.basic_creds['user']['secret']
def __create_admin(self):
# /home/$user/.cargo/bin/cargo <- normally
# NOTE: the above path is prolly some awful shit on ci pipeline
CARGO_BIN = os.getenv('CARGO_BIN')
proc = subprocess.run(f'cargo run --release -- -c dev-test'.split(), text=True, capture_output=True)
try:
return json.loads(proc.stdout)
except JSONDecodeError as err:
import sys
print('TESTBOT UNABLE TO LOAD JSON DATA FROM -c flag', file=sys.stderr)
print(err)
exit(1)
def auth(self, auth_opt: str):
if auth_opt == 'basic':
return self.basic_creds
else:
return {'id': self.id, 'jwt': auth_opt}
def _append_auth(self, opts: dict, auth: str):
'''
Default auth fallback type is jwt because that's they only other type of auth
FC cares about really
@param opts: Dictionary of parameters to pass to the endpoint
@auth: Denotes if we use basic auth or jwt if its not 'basic'
'''
# id is basically always required by the api so we mindlessly add it here
opts['id'] = self.id
if auth == 'basic':
opts['secret'] = self.secret
else:
opts['jwt'] = auth
return opts
def run_test(self, test):
# {
# 'init': [method, uri, query_string_dict],
# 'auth':jwt|none,
# 'hope': <status-code>
# 'body': <bool> # show response body or not (optional
# }
# not 'try'ing these as misconfigurations are completely fatal anyway
method, path, opts = test['init']
auth = test['auth']
hope = test['hope']
if 'body' in test:
# Only checking for headers if a body is present as we really only use
# headers with the body
head = test['headers'] if 'headers' in test else None
self.request(method, path, auth, opts, hope, show_body=test['body'], headers=head)
else:
self.request(method, path, auth, opts, hope)
def logs(self):
ids = sorted(self.requests.keys()) # shared keys in requests/responses
for key in ids:
self.responses[key].log()
# if body is request to be shown then dump it tabbed out by 1 tab
if self.body_logs[key] is True:
print(f'\tBody: {self.responses[key].body}')
def test_stats(self):
passc = 0
failc = 0
total = len(self.responses)
for key in self.responses:
hope = self.responses[key].expected
real = self.responses[key].code
if hope == real:
passc += 1
else:
failc += 1
print('=======')
print(f'\033[1;32mPassing\033[0m {passc}/{total}')
print(f'\033[1;31mFailing\033[0m {failc}/{total}')
print('=======')
def request(self, method: str, path: str, auth: str, opts: dict, expectation: int, show_body=False, headers={}):
assert(path[0] == '/')
# First make sure we add in the correct auth params that are requested
opts = self._append_auth(opts, auth)
# Build the request and store it in our structure
url = self.domain + path
req = http.Request(method, url, opts, headers=headers)
r_id = time.time()
resp = req.make(expectation)
# update log trackers
self.requests[r_id] = req
self.responses[r_id] = resp
self.body_logs[r_id] = show_body
return r_id
def run(worker: Worker):
VOICE_CHAN = 1
TEXT_CHAN = 2
# Basically every test requires a jwt to be passed in so we grab that here
# Should this fail so should nearly every other test from this point
req_login = worker.request('post', '/login', 'basic',{}, 200)
jwt = worker.responses[req_login].json()['jwt']
new_channel_name = time.time()
channel_tests = [
# sanity check
{'init': ['get', '/channels/list', {}], 'auth': None, 'hope': 401},
{'init': ['post', '/channels/list', {}], 'auth': jwt, 'hope': 404},
{'init': ['post', '/channels/create', {'name': str(new_channel_name), 'kind': TEXT_CHAN, 'description': 'asdf'}], 'auth': jwt, 'hope': 200},
{'init': ['post', '/channels/create', {'name': str(new_channel_name+1), 'kind': TEXT_CHAN,}], 'auth': jwt, 'hope': 200},
{'init': ['post', '/channels/create', {}], 'auth': jwt, 'hope': 400},
{'init': ['post', '/channels/create', {'name': 123, 'kind': 'adsf'}], 'auth': jwt, 'hope': 400},
{'init': ['get', '/channels/list', {'kind': TEXT_CHAN}], 'auth': jwt, 'hope': 200},
]
for test in channel_tests:
worker.run_test(test)
msg_chan_name = time.time()
_id = worker.request('post', '/channels/create', jwt, {
'name': str(msg_chan_name),
'kind': TEXT_CHAN,
'description': 'asdf'
}, 200)
chan_d = worker.responses[_id].json()
message_tests = [
# bs message spam
{
'init': ['post', '/message/send', {'type': 'text', 'channel_id': chan_d['id'], 'content': 'bs content'}],
'headers': {'content-type': 'text/plain'},
'auth': jwt, 'hope': 200, 'body': False
},
{
'init': ['post', '/message/send', {'type': 'text', 'channel_id': chan_d['id'], 'content': 'bs content'}],
'headers': {'content-type': 'text/plain'},
'auth': jwt, 'hope': 200
},
{
'init': ['post', '/message/send', {'type': 'text', 'channel_id': chan_d['id'], 'content': 'bs content'}],
'headers': {'content-type': 'text/plain'},
'auth': jwt, 'hope': 200
},
{
'init': ['post', '/message/send', {'type': 'text', 'channel_id': chan_d['id'], 'content': 'bs content'}],
'headers': {'content-type': 'text/plain'},
'auth': jwt, 'hope': 200
},
# can we get them back tho?
{
'init': [
'get', '/message/get_range', {'channel_id': chan_d['id'], 'start_time': int(msg_chan_name-10), 'end_time': int(msg_chan_name + 10)}
],
'auth': jwt, 'hope': 200
},
{
'init': [
'get', '/message/get_range', {'channel_id': chan_d['id'], 'end_time': int(msg_chan_name)}
],
'auth': jwt, 'hope': 400
},
{
'init': [
'get', '/message/get_range', {'channel_id': chan_d['id'], 'start_time': int(msg_chan_name), 'end_time': int(msg_chan_name)}
],
'auth': jwt, 'hope': 400
},
{
'init': [
'get', '/message/get_range', {'channel_id': chan_d['id'], 'end_time': int(msg_chan_name), 'start_time': int(msg_chan_name)}
],
'auth': jwt, 'hope': 400
},
]
for test in message_tests:
worker.run_test(test)
member_tests = [
{ 'init': ['get', '/members/me', {}], 'auth': jwt, 'hope': 200, 'body': True},
{ 'init': ['get', '/members/get_online', {}], 'auth': jwt, 'hope': 200, 'body': True},
{ 'init': ['post', '/members/me/nickname', {'nick': f'New name: {time.ctime()}'}], 'auth': jwt, 'hope': 200 },
{ 'init': ['get', '/members/me', {}], 'auth': jwt, 'hope': 200, 'body': True},
]
for test in member_tests:
worker.run_test(test)
invite_tests = [
{'init': ['post', '/invite/create', {}], 'auth': jwt, 'hope': 200, 'body': True},
{'init': ['get', '/invite/create', {}], 'auth': jwt, 'hope': 404, 'body': True},
]
for test in invite_tests:
worker.run_test(test)
# ad-hoc test for joining now
invite_req_id = worker.request('post', '/invite/create', jwt, {}, 200, True)
code = worker.responses[invite_req_id].json()['id']
worker.request('get', '/join', None, {'code': code}, 200, True)
worker.logs()
worker.test_stats()
if __name__ == '__main__':
worker = Worker('http://localhost:4536', create_admin=True)
run(worker)