sachet-server/tests/conftest.py

274 lines
6.9 KiB
Python
Raw Permalink Normal View History

import pytest
2023-05-07 21:08:45 -04:00
import uuid
from math import ceil
2023-03-10 13:57:18 -05:00
from sachet.server.users import manage
from click.testing import CliRunner
from sachet.server import app, db, storage
from sachet.server.models import Permissions, User
2023-05-07 21:08:45 -04:00
from werkzeug.datastructures import FileStorage
from io import BytesIO
2023-03-27 21:54:20 -04:00
from bitmask import Bitmask
from pathlib import Path
import random
2023-03-27 21:54:20 -04:00
2023-03-30 20:20:09 -04:00
@pytest.fixture
def rand():
"""Deterministic random data generator.
Be sure to seed 0 with each test!
"""
r = random.Random()
r.seed(0)
return r
2023-04-09 17:49:26 -04:00
2023-04-09 15:04:22 -04:00
def clear_filesystem():
if app.config["SACHET_STORAGE"] == "filesystem":
2023-04-10 22:17:12 -04:00
for file in storage._files_directory.iterdir():
2023-04-09 15:04:22 -04:00
if file.is_relative_to(Path(app.instance_path)) and file.is_file():
file.unlink()
else:
2023-04-09 17:49:26 -04:00
raise OSError(f"Attempted to delete {file}: please delete it yourself.")
@pytest.fixture
def client(config={}):
"""Flask application with DB already set up and ready."""
with app.test_client() as client:
with app.app_context():
for k, v in config.items():
app.config[k] = v
db.drop_all()
db.create_all()
db.session.commit()
2023-04-09 15:04:22 -04:00
clear_filesystem()
2023-03-10 13:57:18 -05:00
yield client
2023-04-09 15:04:22 -04:00
clear_filesystem()
db.session.remove()
db.drop_all()
2023-03-30 20:20:09 -04:00
@pytest.fixture
def flask_app_bare():
"""Flask application with empty DB."""
with app.test_client() as client:
with app.app_context():
yield client
db.drop_all()
2023-03-10 13:57:18 -05:00
@pytest.fixture
def users(client):
2023-04-26 19:51:09 -04:00
"""Create all the test users.
2023-03-10 13:57:18 -05:00
Returns a dictionary with all the info for each user.
"""
2023-03-27 21:54:20 -04:00
userinfo = dict(
2023-04-10 22:17:12 -04:00
jeff=dict(
2023-04-15 16:33:50 -04:00
password="1234",
permissions=Bitmask(
Permissions.CREATE,
Permissions.READ,
Permissions.DELETE,
Permissions.MODIFY,
2023-04-26 19:51:09 -04:00
Permissions.LIST,
Permissions.LOCK,
2023-04-15 16:33:50 -04:00
),
),
dave=dict(
2023-04-10 22:17:12 -04:00
password="1234",
permissions=Bitmask(
Permissions.CREATE,
Permissions.READ,
Permissions.DELETE,
Permissions.MODIFY,
2023-04-10 22:17:12 -04:00
),
),
2023-04-15 16:33:50 -04:00
# admins don't have the other permissions by default,
# but admins can add perms to themselves
no_create_user=dict(
password="password",
permissions=Bitmask(
Permissions.READ,
Permissions.DELETE,
Permissions.ADMIN,
Permissions.MODIFY,
),
),
no_read_user=dict(
password="password",
permissions=Bitmask(
Permissions.CREATE,
Permissions.DELETE,
Permissions.ADMIN,
Permissions.MODIFY,
),
),
no_modify_user=dict(
password="password",
permissions=Bitmask(
Permissions.CREATE,
Permissions.DELETE,
Permissions.ADMIN,
Permissions.READ,
),
),
no_lock_user=dict(
password="password",
permissions=Bitmask(
Permissions.CREATE,
Permissions.MODIFY,
Permissions.DELETE,
Permissions.ADMIN,
Permissions.READ,
),
),
no_admin_user=dict(
password="password",
permissions=Bitmask(
Permissions.CREATE,
Permissions.MODIFY,
Permissions.DELETE,
Permissions.LOCK,
Permissions.READ,
),
),
2023-03-30 20:20:09 -04:00
administrator=dict(password="4321", permissions=Bitmask(Permissions.ADMIN)),
)
2023-03-10 13:57:18 -05:00
for user, info in userinfo.items():
info["username"] = user
2023-03-30 20:20:09 -04:00
manage.create_user(info["permissions"], info["username"], info["password"])
2023-03-10 13:57:18 -05:00
return userinfo
@pytest.fixture
def validate_info(users):
2023-03-27 21:54:20 -04:00
"""Given a response, deserialize and validate the information against a given user's info."""
verify_fields = [
"username",
2023-03-27 21:54:20 -04:00
"permissions",
]
2023-03-10 13:57:18 -05:00
def _validate(user, info):
schema = User.get_schema(User)
dumped = schema.dump(users[user])
2023-03-27 21:54:20 -04:00
for k in verify_fields:
assert dumped[k] == info[k]
2023-03-10 13:57:18 -05:00
return _validate
@pytest.fixture
def tokens(client, users):
"""Logs in all test users.
Returns a dictionary of auth tokens for all test users.
"""
toks = {}
for user, creds in users.items():
2023-03-30 20:20:09 -04:00
resp = client.post(
"/users/login",
json={"username": creds["username"], "password": creds["password"]},
)
2023-03-10 13:57:18 -05:00
resp_json = resp.get_json()
token = resp_json.get("auth_token")
assert token is not None and token != ""
toks[creds["username"]] = token
return toks
@pytest.fixture
def cli():
"""click's testing fixture"""
return CliRunner()
2023-04-13 13:30:53 -04:00
@pytest.fixture
def auth(tokens):
"""Generate auth headers.
Parameters
----------
username : str
Username to authenticate as.
data : dict
Extra headers to add.
Returns
-------
dict
Dictionary of all headers.
"""
2023-04-15 16:33:50 -04:00
2023-04-13 13:30:53 -04:00
def auth_headers(username, data={}):
ret = {"Authorization": f"bearer {tokens[username]}"}
ret.update(data)
return ret
return auth_headers
2023-05-07 21:08:45 -04:00
@pytest.fixture
def upload(client):
"""Perform chunked upload of some data.
Parameters
----------
url : str
URL to upload to.
data : BytesIO
Stream of data to upload.
You can use BytesIO(data) to convert raw bytes to a stream.
headers : dict, optional
Headers to upload with.
chunk_size : int, optional
Size of chunks in bytes.
method : function
Method like client.post or client.put to use.
"""
2023-05-08 18:54:40 -04:00
2023-05-07 21:08:45 -04:00
def upload(url, data, headers={}, chunk_size=int(2e6), method=client.post):
data_size = len(data.getbuffer())
buf = data.getbuffer()
upload_uuid = uuid.uuid4()
total_chunks = int(ceil(data_size / chunk_size))
resp = None
for chunk_idx in range(total_chunks):
start = chunk_size * chunk_idx
end = min(chunk_size * (chunk_idx + 1), data_size)
resp = method(
url,
headers=headers,
data={
"upload": FileStorage(
stream=BytesIO(buf[start:end]), filename="upload"
),
"dzuuid": str(upload_uuid),
"dzchunkindex": chunk_idx,
"dztotalchunks": total_chunks,
},
content_type="multipart/form-data",
)
if not resp.status_code == 200 or resp.status_code == 201:
break
return resp
return upload