extended the api to create users and groups, added some tests, and added some helpful commands w/ burnettk

This commit is contained in:
jasquat 2022-05-17 16:35:38 -04:00
parent 672e517528
commit 3c8ded606e
19 changed files with 396 additions and 103 deletions

View File

@ -7,12 +7,18 @@ function error_handler() {
trap 'error_handler ${LINENO} $?' ERR
set -o errtrace -o errexit -o nounset -o pipefail
rm -rf migrations/
rm -f ./src/spiff_workflow_webapp/db.sqlite3
tasks=""
if [[ "${1:-}" == "clean" ]]; then
tasks="$tasks init"
mysql -uroot -e "DROP DATABASE IF EXISTS spiff_workflow_webapp_development"
mysql -uroot -e "CREATE DATABASE spiff_workflow_webapp_development"
rm -rf migrations/
rm -f ./src/spiff_workflow_webapp/db.sqlite3
for task in init migrate upgrade ; do
mysql -uroot -e "DROP DATABASE IF EXISTS spiff_workflow_webapp_development"
mysql -uroot -e "CREATE DATABASE spiff_workflow_webapp_development"
fi
tasks="$tasks migrate upgrade"
for task in $tasks ; do
FLASK_ENV=development FLASK_APP=src/spiff_workflow_webapp poetry run flask db "$task"
done

10
bin/run_server_locally Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
function error_handler() {
>&2 echo "Exited with BAD EXIT CODE '${2}' in ${0} script at line: ${1}."
exit "$2"
}
trap 'error_handler ${LINENO} $?' ERR
set -o errtrace -o errexit -o nounset -o pipefail
FLASK_ENV=development FLASK_APP=src/spiff_workflow_webapp poetry run flask run

View File

@ -12,7 +12,7 @@ if [[ "${1:-}" == "c" ]]; then
elif grep -qE '^[0-9]$' <<<"${1:-}" ; then
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d "{ \"task_identifier\": \"${1}\"}"
else
./bin/recreate_db
./bin/recreate_db clean
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Product Name": "G", "Quantity": "2"}}'
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Sleeve Type": "Short"}}'
curl --fail localhost:5000/run_process -H "Content-type: application/json" -X POST -d '{ "task_identifier": "1", "answer": {"Continue shopping?": "N"}}'

8
conftest.py Normal file
View File

@ -0,0 +1,8 @@
import pytest
from spiff_workflow_webapp import create_app
@pytest.fixture(scope='session')
def app():
app = create_app()
return app

97
poetry.lock generated
View File

@ -74,7 +74,7 @@ wrapt = ">=1.11,<2"
name = "atomicwrites"
version = "1.4.0"
description = "Atomic file writes."
category = "dev"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
@ -82,7 +82,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
name = "attrs"
version = "21.4.0"
description = "Classes Without Boilerplate"
category = "dev"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@ -804,7 +804,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev"
category = "main"
optional = false
python-versions = "*"
@ -1049,7 +1049,7 @@ test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytes
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@ -1108,11 +1108,19 @@ category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "psycopg2"
version = "2.9.3"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@ -1177,7 +1185,7 @@ diagrams = ["railroad-diagrams", "jinja2"]
name = "pytest"
version = "6.2.5"
description = "pytest: simple powerful testing with Python"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@ -1195,6 +1203,54 @@ toml = "*"
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
name = "pytest-flask"
version = "1.2.0"
description = "A set of py.test fixtures to test Flask applications."
category = "main"
optional = false
python-versions = ">=3.5"
[package.dependencies]
Flask = "*"
pytest = ">=5.2"
Werkzeug = ">=0.7"
[package.extras]
docs = ["sphinx", "sphinx-rtd-theme"]
[[package]]
name = "pytest-flask-sqlalchemy"
version = "1.1.0"
description = "A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
Flask-SQLAlchemy = ">=2.3"
packaging = ">=14.1"
pytest = ">=3.2.1"
pytest-mock = ">=1.6.2"
SQLAlchemy = ">=1.2.2"
[package.extras]
tests = ["pytest-postgresql (>=2.4.0,<4.0.0)", "psycopg2-binary", "pytest (>=6.0.1)"]
[[package]]
name = "pytest-mock"
version = "3.7.0"
description = "Thin-wrapper around the mock package for easier use with pytest"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
pytest = ">=5.0"
[package.extras]
dev = ["pre-commit", "tox", "pytest-asyncio"]
[[package]]
name = "python-dateutil"
version = "2.8.2"
@ -1618,7 +1674,7 @@ python-versions = ">=3.6.1"
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev"
category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
@ -1815,7 +1871,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "0294c8b538946b050d6db1ebeaad601bbb3288e036604f970e86bd3aea4e1c3e"
content-hash = "93a1364b751e27cf2cca33214753d89549b2a9a82d01b90ff43702f34a87a0b1"
[metadata.files]
alabaster = [
@ -2525,6 +2581,19 @@ protobuf = [
{file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"},
{file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"},
]
psycopg2 = [
{file = "psycopg2-2.9.3-cp310-cp310-win32.whl", hash = "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362"},
{file = "psycopg2-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"},
{file = "psycopg2-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56"},
{file = "psycopg2-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305"},
{file = "psycopg2-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2"},
{file = "psycopg2-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461"},
{file = "psycopg2-2.9.3-cp38-cp38-win32.whl", hash = "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7"},
{file = "psycopg2-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf"},
{file = "psycopg2-2.9.3-cp39-cp39-win32.whl", hash = "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126"},
{file = "psycopg2-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c"},
{file = "psycopg2-2.9.3.tar.gz", hash = "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981"},
]
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
@ -2557,6 +2626,18 @@ pytest = [
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
{file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
]
pytest-flask = [
{file = "pytest-flask-1.2.0.tar.gz", hash = "sha256:46fde652f77777bf02dc91205aec4ce20cdf2acbbbd66a918ab91f5c14693d3d"},
{file = "pytest_flask-1.2.0-py3-none-any.whl", hash = "sha256:fe25b39ad0db09c3d1fe728edecf97ced85e774c775db259a6d25f0270a4e7c9"},
]
pytest-flask-sqlalchemy = [
{file = "pytest-flask-sqlalchemy-1.1.0.tar.gz", hash = "sha256:db71a57b90435e5d854b21c37a2584056d6fc3ddb28c09d8d0a2546bd6e390ff"},
{file = "pytest_flask_sqlalchemy-1.1.0-py3-none-any.whl", hash = "sha256:b9f272d5c4092fcbe4a6284e402a37cad84f5b9be3c0bbe1a11927f24c99ff83"},
]
pytest-mock = [
{file = "pytest-mock-3.7.0.tar.gz", hash = "sha256:5112bd92cc9f186ee96e1a92efc84969ea494939c3aead39c50f421c4cc69534"},
{file = "pytest_mock-3.7.0-py3-none-any.whl", hash = "sha256:6cff27cec936bf81dc5ee87f07132b807bcda51106b5ec4b90a04331cba76231"},
]
python-dateutil = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},

View File

@ -32,6 +32,9 @@ sentry-sdk = "0.14.4"
sphinx-autoapi = "^1.8.4"
flask-bpmn = {develop = true, path = "/home/jason/projects/github/sartography/flask-bpmn"}
mysql-connector-python = "^8.0.29"
pytest-flask = "^1.2.0"
pytest-flask-sqlalchemy = "^1.1.0"
psycopg2 = "^2.9.3"
[tool.poetry.dev-dependencies]

View File

@ -1,8 +1,8 @@
"""__init__."""
from flask import Flask
from .routes.api import api
from .routes.main import main
from spiff_workflow_webapp.routes.api import api
from spiff_workflow_webapp.routes.user_blueprint import user_blueprint
from flask_bpmn.models.db import db
from flask_bpmn.models.db import migrate
@ -10,14 +10,15 @@ from flask_bpmn.models.db import migrate
def create_app():
"""Create_app."""
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite3"
# app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+mysqlconnector://root:@localhost/spiff_workflow_webapp_development"
# app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite3"
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+mysqlconnector://root:@localhost/spiff_workflow_webapp_development"
# app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://crc_user:crc_pass@localhost:5432/spiff_test"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(app)
migrate.init_app(app, db)
app.register_blueprint(main)
app.register_blueprint(user_blueprint)
app.register_blueprint(api)
return app

Binary file not shown.

View File

@ -1,6 +0,0 @@
"""Extensions."""
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
migrate = Migrate()

View File

@ -1,7 +0,0 @@
from flask_bpmn.models.db import db
class CartModel(db.Model):
__tablename__ = 'carts'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))

View File

@ -0,0 +1,11 @@
from flask_bpmn.models.db import db
from flask_bpmn.models.group import FlaskBpmnGroupModel
from sqlalchemy.orm import relationship
class GroupModel(FlaskBpmnGroupModel):
__tablename__ = "group"
__table_args__ = {'extend_existing': True}
new_name_two = db.Column(db.String(255))
user_group_assignments = relationship("UserGroupAssignmentModel", cascade="all, delete")
users = relationship("UserModel", secondary="user_group_assignment")

View File

@ -3,6 +3,6 @@ from sqlalchemy.orm import deferred
class ProcessModel(db.Model):
__tablename__ = 'process_models'
__tablename__ = 'process_model'
id = db.Column(db.Integer, primary_key=True)
bpmn_json = deferred(db.Column(db.JSON))

View File

@ -1,9 +1,15 @@
"""User."""
from ..extensions import db
from flask_bpmn.models.db import db
from sqlalchemy.orm import relationship
class User(db.Model):
"""User."""
class UserModel(db.Model):
"""UserModel."""
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), nullable=False, unique=True)
name = db.Column(db.String(50))
email = db.Column(db.String(50))
user_group_assignments = relationship("UserGroupAssignmentModel", cascade="all, delete")
groups = relationship("GroupModel", secondary="user_group_assignment")

View File

@ -0,0 +1,21 @@
"""UserGroupAssignment."""
from flask_bpmn.models.db import db
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
from spiff_workflow_webapp.models.group import GroupModel
from spiff_workflow_webapp.models.user import UserModel
class UserGroupAssignmentModel(db.Model):
"""UserGroupAssignmentModel."""
__tablename__ = "user_group_assignment"
__table_args__ = (
db.UniqueConstraint('user_id', 'group_id', name='user_group_assignment_unique'),
)
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(ForeignKey(UserModel.id), nullable=False)
group_id = db.Column(ForeignKey(GroupModel.id), nullable=False)
group = relationship("GroupModel")
user = relationship("UserModel")

View File

@ -4,7 +4,7 @@ import os
from flask import Blueprint
from flask import request
from ..models.user import User
from ..models.user import UserModel
from spiff_workflow_webapp.spiff_workflow_connector import parse
from spiff_workflow_webapp.spiff_workflow_connector import run
@ -20,14 +20,6 @@ serializer = BpmnWorkflowSerializer(wf_spec_converter)
api = Blueprint("api", __name__)
@api.route("/user/<name>")
def create_user(name):
"""Create_user."""
user = User.query.filter_by(name=name).first()
return {"user": user.name}
@api.route("/run_process", methods=['POST'])
def run_process():
"""Run_process."""

View File

@ -1,17 +0,0 @@
"""Main."""
from flask import Blueprint
from ..extensions import db
from ..models.user import User
main = Blueprint("main", __name__)
@main.route("/user/<name>")
def create_user(name):
"""Create_user."""
user = User(name=name)
db.session.add(user)
db.session.commit()
return "Created User!"

View File

@ -0,0 +1,108 @@
"""Main."""
import json
from flask import Blueprint
from flask import Response
from flask import request
from sqlalchemy.exc import IntegrityError
from flask_bpmn.models.db import db
from spiff_workflow_webapp.models.group import GroupModel
from spiff_workflow_webapp.models.user import UserModel
from spiff_workflow_webapp.models.user_group_assignment import UserGroupAssignmentModel
user_blueprint = Blueprint("main", __name__)
@user_blueprint.route("/user/<username>", methods=["GET"])
def create_user(username):
"""Create_user."""
user = UserModel.query.filter_by(username=username).first()
if user is not None:
return Response(json.dumps({"error": f"User already exists: {username}"}), status=409, mimetype='application/json')
user = UserModel(username=username)
try:
db.session.add(user)
except IntegrityError as exception:
return Response(json.dumps({"error": repr(exception)}), status=500, mimetype='application/json')
db.session.commit()
return Response(json.dumps({"id": user.id}), status=201, mimetype='application/json')
@user_blueprint.route("/user/<username>", methods=["DELETE"])
def delete_user(username):
"""Delete_user."""
user = UserModel.query.filter_by(username=username).first()
if user is None:
return Response(json.dumps({"error": f"User cannot be found: {username}"}), status=400, mimetype='application/json')
db.session.delete(user)
db.session.commit()
return Response(json.dumps({"ok": True}), status=204, mimetype='application/json')
@user_blueprint.route("/group/<group_name>", methods=["GET"])
def create_group(group_name):
"""Create_group."""
group = GroupModel.query.filter_by(name=group_name).first()
if group is not None:
return Response(json.dumps({"error": f"Group already exists: {group_name}"}), status=409, mimetype='application/json')
group = GroupModel(name=group_name)
try:
db.session.add(group)
except IntegrityError as exception:
return Response(json.dumps({"error": repr(exception)}), status=500, mimetype='application/json')
db.session.commit()
return Response(json.dumps({"id": group.id}), status=201, mimetype='application/json')
@user_blueprint.route("/group/<group_name>", methods=["DELETE"])
def delete_group(group_name):
"""Delete_group."""
group = GroupModel.query.filter_by(name=group_name).first()
if group is None:
return Response(json.dumps({"error": f"Group cannot be found: {group_name}"}), status=400, mimetype='application/json')
db.session.delete(group)
db.session.commit()
return Response(json.dumps({"ok": True}), status=204, mimetype='application/json')
@user_blueprint.route("/assign_user_to_group", methods=["POST"])
def assign_user_to_group():
"""Assign_user_to_group."""
content = request.json
user_id = content.get("user_id")
group_id = content.get("group_id")
if user_id is None:
return Response("{error:'user_id required'}", status=400, mimetype='application/json')
if group_id is None:
return Response("{error:'group_id required'}", status=400, mimetype='application/json')
user = UserModel.query.filter_by(id=user_id).first()
if user is None:
return Response(json.dumps({"error": f"User cannot be found: {user_id}"}), status=400, mimetype='application/json')
group = GroupModel.query.filter_by(id=group_id).first()
if group is None:
return Response(json.dumps({"error": f"Group cannot be found: {group_id}"}), status=400, mimetype='application/json')
user_group_assignment = UserGroupAssignmentModel.query.filter_by(user_id=user.id, group_id=group.id).first()
if user_group_assignment is not None:
return Response(
json.dumps({"error": f"User ({user.id}) is already in group ({group.id})"}),
status=409, mimetype='application/json'
)
user_group_assignment = UserGroupAssignmentModel(user_id=user.id, group_id=group.id)
db.session.add(user_group_assignment)
db.session.commit()
return Response(json.dumps({"id": user_group_assignment.id}), status=201, mimetype='application/json')

View File

@ -22,9 +22,6 @@ from SpiffWorkflow.task import Task
from flask_bpmn.models.db import db
from spiff_workflow_webapp.models.process_model import ProcessModel
from flask_bpmn.models.group import GroupModel
from spiff_workflow_webapp.models.cart import CartModel
# from custom_script_engine import CustomScriptEngine
wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter(
@ -181,41 +178,3 @@ def run(workflow, task_identifier=None, answer=None):
# dump.write(state)
tasks_status["next_activity"] = formatted_options
return tasks_status
if __name__ == "__main__":
parser = argparse.ArgumentParser("Simple BPMN runner")
parser.add_argument(
"-p", "--process", dest="process", help="The top-level BPMN Process ID"
)
parser.add_argument(
"-b", "--bpmn", dest="bpmn", nargs="+", help="BPMN files to load"
)
parser.add_argument("-d", "--dmn", dest="dmn", nargs="*", help="DMN files to load")
parser.add_argument(
"-r",
"--restore",
dest="restore",
metavar="FILE",
help="Restore state from %(metavar)s",
)
parser.add_argument(
"-s",
"--step",
dest="step",
action="store_true",
help="Display state after each step",
)
args = parser.parse_args()
try:
if args.restore is not None:
with open(args.restore) as state:
wf = serializer.deserialize_json(state.read())
else:
wf = parse(args.process, args.bpmn, args.dmn)
run(wf, args.step)
except Exception:
sys.stderr.write(traceback.format_exc())
sys.exit(1)

View File

@ -0,0 +1,117 @@
"""Test User Blueprint."""
import json
from spiff_workflow_webapp.models.user import UserModel
from spiff_workflow_webapp.models.group import GroupModel
def test_user_can_be_created_and_deleted(client):
username = "joe"
response = client.get(f"/user/{username}")
assert response.status_code == 201
user = UserModel.query.filter_by(username=username).first()
assert user.username == username
response = client.delete(f"/user/{username}")
assert response.status_code == 204
user = UserModel.query.filter_by(username=username).first()
assert user is None
def test_delete_returns_an_error_if_user_is_not_found(client):
username = "joe"
response = client.delete(f"/user/{username}")
assert response.status_code == 400
def test_create_returns_an_error_if_user_exists(client):
username = "joe"
response = client.get(f"/user/{username}")
assert response.status_code == 201
user = UserModel.query.filter_by(username=username).first()
assert user.username == username
response = client.get(f"/user/{username}")
assert response.status_code == 409
response = client.delete(f"/user/{username}")
assert response.status_code == 204
user = UserModel.query.filter_by(username=username).first()
assert user is None
def test_group_can_be_created_and_deleted(client):
group_name = "administrators"
response = client.get(f"/group/{group_name}")
assert response.status_code == 201
group = GroupModel.query.filter_by(name=group_name).first()
assert group.name == group_name
response = client.delete(f"/group/{group_name}")
assert response.status_code == 204
group = GroupModel.query.filter_by(name=group_name).first()
assert group is None
def test_delete_returns_an_error_if_group_is_not_found(client):
group_name = "administrators"
response = client.delete(f"/group/{group_name}")
assert response.status_code == 400
def test_create_returns_an_error_if_group_exists(client):
group_name = "administrators"
response = client.get(f"/group/{group_name}")
assert response.status_code == 201
group = GroupModel.query.filter_by(name=group_name).first()
assert group.name == group_name
response = client.get(f"/group/{group_name}")
assert response.status_code == 409
response = client.delete(f"/group/{group_name}")
assert response.status_code == 204
group = GroupModel.query.filter_by(name=group_name).first()
assert group is None
def test_user_can_be_assigned_to_a_group(client):
user = create_user(client, "joe")
group = create_group(client, "administrators")
response = client.post("/assign_user_to_group", content_type='application/json', data=json.dumps({"user_id": user.id, "group_id": group.id}))
assert response.status_code == 201
user = UserModel.query.filter_by(id=user.id).first()
assert len(user.user_group_assignments) == 1
assert user.user_group_assignments[0].group_id == group.id
delete_user(client, user.username)
delete_group(client, group.name)
def create_user(client, username):
response = client.get(f"/user/{username}")
assert response.status_code == 201
user = UserModel.query.filter_by(username=username).first()
assert user.username == username
return user
def delete_user(client, username):
response = client.delete(f"/user/{username}")
assert response.status_code == 204
user = UserModel.query.filter_by(username=username).first()
assert user is None
def create_group(client, group_name):
response = client.get(f"/group/{group_name}")
assert response.status_code == 201
group = GroupModel.query.filter_by(name=group_name).first()
assert group.name == group_name
return group
def delete_group(client, group_name):
response = client.delete(f"/group/{group_name}")
assert response.status_code == 204
group = GroupModel.query.filter_by(name=group_name).first()
assert group is None