Renaming project to json-api for clarity sake
This commit is contained in:
2
json-api/client-tests/.gitignore
vendored
Normal file
2
json-api/client-tests/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
bin/
|
||||
lib/
|
||||
1
json-api/client-tests/__init__.py
Normal file
1
json-api/client-tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import web
|
||||
220
json-api/client-tests/client.py
Normal file
220
json-api/client-tests/client.py
Normal file
@@ -0,0 +1,220 @@
|
||||
import time
|
||||
import subprocess, os, sys
|
||||
import json, requests
|
||||
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['id']
|
||||
self.secret = self.basic_creds['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:
|
||||
import sys
|
||||
print('TESTBOT UNABLE TO LOAD JSON DATA FROM -c flag', file=sys.stderr)
|
||||
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'
|
||||
|
||||
'''
|
||||
if type(auth) == str:
|
||||
opts['id'] = self.id
|
||||
if auth == 'basic':
|
||||
opts['secret'] = self.secret
|
||||
else:
|
||||
opts['jwt'] = auth
|
||||
return opts
|
||||
else:
|
||||
# if its not a string we don't add anything in
|
||||
return opts
|
||||
|
||||
def logs(self):
|
||||
ids = sorted(self.requests.keys()) # shared keys in requests/responses
|
||||
for key in ids:
|
||||
self.responses[key].log()
|
||||
|
||||
# Logg the provided data to ensure that _it_ wasn't the cause for error
|
||||
resp = self.responses[key]
|
||||
if resp.code != resp.expected:
|
||||
opts = self.requests[key].params
|
||||
print(f'\tParams: {opts}')
|
||||
if self.body_logs[key] is True:
|
||||
print(f'\tBody: {self.responses[key].body}')
|
||||
|
||||
|
||||
def request(self, method: str, path: str, auth: str, opts: dict, expectation: int, show_body=False):
|
||||
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)
|
||||
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},
|
||||
# Just a regular test no saving for this one
|
||||
{'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},
|
||||
|
||||
# save this and compare its results to the previous
|
||||
{'init': ['get', '/channels/list', {}], 'auth': jwt, 'hope': 200},
|
||||
{'init': ['get', '/channels/list', {'random-param': 123}], 'auth': jwt, 'hope': 200},
|
||||
]
|
||||
|
||||
for test in channel_tests:
|
||||
method, path, opts = test['init']
|
||||
auth = test['auth']
|
||||
hope = test['hope']
|
||||
|
||||
worker.request(method, path, auth, opts, hope)
|
||||
|
||||
msg_chan_name = time.time()
|
||||
_id = worker.request('post', '/channels/create', jwt, {
|
||||
'name': str(msg_chan_name),
|
||||
'kind': TEXT_CHAN,
|
||||
}, 200)
|
||||
chan_d = worker.responses[_id].json()
|
||||
|
||||
message_tests = [
|
||||
# bs message spam
|
||||
{'init': ['post', '/message/send', {'channel': chan_d['id'], 'content': 'bs content'}], 'auth': jwt, 'hope': 200},
|
||||
{'init': ['post', '/message/send', {'channel': chan_d['id'], 'content': 'bs content'}], 'auth': jwt, 'hope': 200},
|
||||
{'init': ['post', '/message/send', {'channel': chan_d['id'], 'content': 'bs content'}], 'auth': jwt, 'hope': 200},
|
||||
{'init': ['post', '/message/send', {'channel': chan_d['id'], 'content': 'bs content'}], 'auth': jwt, 'hope': 200},
|
||||
|
||||
# can we get them back tho?
|
||||
{
|
||||
'init': [
|
||||
'get', '/message/get_range', {'channel': 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': chan_d['id'], 'end-time': int(msg_chan_name)}
|
||||
],
|
||||
'auth': jwt, 'hope': 400
|
||||
},
|
||||
{
|
||||
'init': [
|
||||
'get', '/message/get_range', {'channel': 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': chan_d['id'], 'end-time': int(msg_chan_name), 'start-time': int(msg_chan_name)}
|
||||
],
|
||||
'auth': jwt, 'hope': 400
|
||||
},
|
||||
# two tests that follow the rules
|
||||
{
|
||||
'init': [
|
||||
'get', '/message/from_id', {'start': 1, 'channel': 3}
|
||||
],
|
||||
'auth': jwt, 'hope': 200, 'body': True
|
||||
},
|
||||
{
|
||||
'init': [
|
||||
'get', '/message/from_id', {'start':1, 'channel':3, 'limit':2}
|
||||
],
|
||||
'auth': jwt, 'hope': 200, 'body': True
|
||||
},
|
||||
# tests that don't follow the api's rules
|
||||
{
|
||||
# channel doesn't exist so a 404 seems to be inorder
|
||||
'init': [
|
||||
'get', '/message/from_id', {'start': 1, 'channel':9}
|
||||
],
|
||||
'auth': jwt, 'hope': 404
|
||||
},
|
||||
{
|
||||
'init': [
|
||||
# good channel but id is tooo high
|
||||
'get', '/message/from_id', {'start': 5, 'channel':3}
|
||||
],
|
||||
'auth': jwt, 'hope': 404
|
||||
},
|
||||
]
|
||||
|
||||
for test in message_tests:
|
||||
method, path, opts = test['init']
|
||||
auth = test['auth']
|
||||
hope = test['hope']
|
||||
if 'body' in test:
|
||||
worker.request(method, path, auth, opts, hope, show_body=test['body'])
|
||||
else:
|
||||
worker.request(method, path, auth, opts, hope)
|
||||
|
||||
|
||||
|
||||
worker.logs()
|
||||
|
||||
if __name__ == '__main__':
|
||||
worker = Worker('http://localhost:8888', create_admin=True)
|
||||
run(worker)
|
||||
8
json-api/client-tests/pyvenv.cfg
Normal file
8
json-api/client-tests/pyvenv.cfg
Normal file
@@ -0,0 +1,8 @@
|
||||
home = /usr
|
||||
implementation = CPython
|
||||
version_info = 3.9.1.final.0
|
||||
virtualenv = 20.2.2
|
||||
include-system-site-packages = false
|
||||
base-prefix = /usr
|
||||
base-exec-prefix = /usr
|
||||
base-executable = /usr/bin/python3
|
||||
5
json-api/client-tests/requirements.txt
Normal file
5
json-api/client-tests/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
certifi==2020.12.5
|
||||
chardet==4.0.0
|
||||
idna==2.10
|
||||
requests==2.25.1
|
||||
urllib3==1.26.2
|
||||
1
json-api/client-tests/web/__init__.py
Normal file
1
json-api/client-tests/web/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import http
|
||||
126
json-api/client-tests/web/http.py
Normal file
126
json-api/client-tests/web/http.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import sys
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
|
||||
class RequestError(Exception):
|
||||
pass
|
||||
|
||||
class Response:
|
||||
'''
|
||||
Response is wrapper for reading + extracting information we care about
|
||||
Primarily created by Requests that get `make`'d.
|
||||
'''
|
||||
def __init__(self, method: str, url: str, body: str, code: int, expected: int, out=sys.stdout, color=True,
|
||||
truncate_long_body=True):
|
||||
|
||||
self.method = method
|
||||
self.url = url
|
||||
self.body = body #typically a string before parsing anything
|
||||
self.code = code #u16
|
||||
self.expected = expected #u16
|
||||
|
||||
self.out = out # file handle to write to normally sys.stdout
|
||||
self.color = color # bool telling if log should color anything (on by default)
|
||||
self.truncate_long_body = truncate_long_body
|
||||
|
||||
def _color(self, cc, string):
|
||||
nc = '\033[0m'
|
||||
return f'{cc}{string}{nc}'
|
||||
|
||||
def _color_failing(self, string):
|
||||
red = '\033[1;31m'
|
||||
return self._color(red, string)
|
||||
|
||||
|
||||
def _color_passing(self, string):
|
||||
green = '\033[1;32m'
|
||||
return self._color(green, string)
|
||||
|
||||
def __write_msg(self, s):
|
||||
# mega dumb wrapper to reduce visual noise i think
|
||||
if (len(s) == 1 or len(s) == 0) is False: print(s, file=self.out)
|
||||
|
||||
def _log_body(self):
|
||||
if self.truncate_long_body:
|
||||
if len(self.body) > 80:
|
||||
msg = self.body
|
||||
while len(msg) > 80:
|
||||
msg = msg[:len(msg)//2] + msg[len(msg)//2+1:]
|
||||
msg = '.....'.join([msg[:40], msg[44]])
|
||||
|
||||
self.__write_msg(msg)
|
||||
else:
|
||||
self.__write_msg(f'\t{self.body}')
|
||||
else:
|
||||
self.__write_msg(f'\t{self.body}')
|
||||
|
||||
def log(self):
|
||||
if self.code != self.expected:
|
||||
msg = f'Failed {self.method.upper()} {self.url}\t{self.code} expected {self.expected}'
|
||||
if self.color:
|
||||
msg = self._color_failing(msg)
|
||||
|
||||
self.__write_msg(msg)
|
||||
self._log_body()
|
||||
else:
|
||||
msg = f'Passing: {self.method} {self.url}'
|
||||
if self.color:
|
||||
msg = self._color_passing(msg)
|
||||
|
||||
self.__write_msg(msg)
|
||||
|
||||
def json(self):
|
||||
'''
|
||||
Force an interpretation of json from the body
|
||||
NOTE: this method is rather obtuse and a massive afterthough so its usage
|
||||
should be limited as much as possible
|
||||
'''
|
||||
try:
|
||||
return json.loads(self.body)
|
||||
except:
|
||||
return {}
|
||||
|
||||
def __str__(self):
|
||||
'''
|
||||
Returns: str(Response) -> `code => response.bdoy`
|
||||
'''
|
||||
return f'{self.code} => {self.body}'
|
||||
|
||||
class Request:
|
||||
def __init__(self, method: str, url: str, params: dict):
|
||||
assert(method in ['get', 'post', 'delete'])
|
||||
|
||||
self.method = method
|
||||
self.url = url
|
||||
self.params = params
|
||||
|
||||
def _make_request(self, method: str, hope: int):
|
||||
# Lower driver for actuall making the request we are looking for
|
||||
method = method.lower()
|
||||
|
||||
params = json.dumps(self.params)
|
||||
if method == 'get':
|
||||
resp = requests.get(self.url, data=params)
|
||||
return Response('get', self.url, resp.text, resp.status_code, hope)
|
||||
elif method == 'post':
|
||||
resp = requests.post(self.url, data=params)
|
||||
return Response('post', self.url, resp.text, resp.status_code, hope)
|
||||
elif method == 'delete':
|
||||
resp = requests.delete(self.url, data=params)
|
||||
return Response('delete', self.url, resp.text, resp.status_code, hope)
|
||||
|
||||
else:
|
||||
raise RequestError('Invalid method passed')
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def make(self, hope: int) -> Response:
|
||||
'''
|
||||
@param hope: int -> status code we hope to get back
|
||||
@return Response -> Wrapper around server http response
|
||||
'''
|
||||
return self._make_request(self.method, hope)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user