Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend/feature/user endpoint #30

Merged
merged 40 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c4eca1c
start setup
warreprovoost Feb 24, 2024
c9b49e2
simple post
warreprovoost Feb 24, 2024
774601b
dockerfile requirements fixed
warreprovoost Feb 24, 2024
de11124
users update added
warreprovoost Feb 25, 2024
7b3f7f9
users get added
warreprovoost Feb 25, 2024
4cb75d9
delete added
warreprovoost Feb 25, 2024
958ea14
confest changed
warreprovoost Feb 25, 2024
c6a2e99
confest changed
warreprovoost Feb 25, 2024
f072cfc
cleaned up the code
warreprovoost Feb 25, 2024
3006e43
fixed linter
warreprovoost Feb 25, 2024
69c95ad
fixed linter
warreprovoost Feb 25, 2024
1f25ba2
requested changes
warreprovoost Feb 25, 2024
0aad08f
requested changes
warreprovoost Feb 26, 2024
5f4a612
linter complaints
warreprovoost Feb 26, 2024
46b0db5
requested style change added
warreprovoost Feb 29, 2024
d2f3a2b
pylnt complaints
warreprovoost Feb 29, 2024
2c7322e
moved fixture
warreprovoost Feb 29, 2024
cb4823c
disabled pylint message
warreprovoost Mar 2, 2024
b262dd2
disabled str
warreprovoost Mar 2, 2024
5c3dbc5
pylint
warreprovoost Mar 2, 2024
ad864fc
Merge remote-tracking branch 'origin/development' into backend/featur…
warreprovoost Mar 2, 2024
1cf7dac
merged with dev
warreprovoost Mar 2, 2024
4025e55
typo
warreprovoost Mar 2, 2024
3dff6ba
pylint changes
warreprovoost Mar 2, 2024
2583ce4
pylint changes
warreprovoost Mar 2, 2024
5b567f3
pylint changes
warreprovoost Mar 2, 2024
8ca7d82
typo
warreprovoost Mar 3, 2024
19cb424
Merge branch 'development' into backend/feature/user-endpoint
warreprovoost Mar 7, 2024
907c1f4
Message changes
warreprovoost Mar 7, 2024
fdc4e25
added data to requests
warreprovoost Mar 7, 2024
fb556fb
pylint complaints
warreprovoost Mar 7, 2024
2afa822
placed return under try
warreprovoost Mar 8, 2024
34399b5
pylint
warreprovoost Mar 8, 2024
2114668
Merge branch 'development' into backend/feature/user-endpoint
warreprovoost Mar 8, 2024
c125718
query get users
warreprovoost Mar 8, 2024
60e0183
small comment
warreprovoost Mar 8, 2024
09f1325
pylint
warreprovoost Mar 8, 2024
66c74f9
requested changes
warreprovoost Mar 9, 2024
0c6a35b
Merge branch 'development' into backend/feature/user-endpoint
warreprovoost Mar 9, 2024
4f3fbdb
merge conflict
warreprovoost Mar 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/project/__init__.py
warreprovoost marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def create_app():
Returns:
Flask -- A Flask application instance
"""
from .endpoints.index.index import index_bp
from .endpoints.users import users_bp
from .endpoints.index.index import index_bp # pylint: disable=import-outside-toplevel
from .endpoints.users import users_bp # pylint: disable=import-outside-toplevel

app = Flask(__name__)
app.register_blueprint(index_bp)
Expand Down
31 changes: 14 additions & 17 deletions backend/project/endpoints/users.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Users api endpoint"""
from flask import Blueprint, request, jsonify
from flask_restful import Resource, Api
from project import db
from project.models.users import Users as UserModel
from project import db # pylint: disable=import-error ; there is no error
warreprovoost marked this conversation as resolved.
Show resolved Hide resolved
from project.models.users import Users as UserModel # pylint: disable=import-error ; there is no error

users_bp = Blueprint("users", __name__)
users_api = Api(users_bp)
Expand All @@ -25,9 +25,6 @@ def post(self):
This function will respond to post requests made to /users.
It should create a new user and return a success message.
"""
if not request.is_json:
return {"Message": "Unsupported Media Type. Expected JSON."}, 415

uid = request.json.get('uid')
is_teacher = request.json.get('is_teacher')
is_admin = request.json.get('is_admin')
Expand All @@ -46,28 +43,28 @@ def post(self):
if user is not None:
# bad request, error code could be 409 but is rarely used
return {"Message": f"User {uid} already exists"}, 400
# Code to create a new user in the database using the uid, is_teacher, and is_admin values
# Code to create a new user in the database using the uid, is_teacher, and is_admin
new_user = UserModel(uid=uid, is_teacher=is_teacher, is_admin=is_admin)
db.session.add(new_user)
db.session.commit()

except Exception as e:
except Exception as e: # pylint: disable=broad-exception-caught ;
warreprovoost marked this conversation as resolved.
Show resolved Hide resolved
# every exception should result in a rollback
db.session.rollback()
return {"Message": f"An error occurred while creating the user: {str(e)}"}, 500
warreprovoost marked this conversation as resolved.
Show resolved Hide resolved

return {"Message": "User created successfully!"}, 201

users_api.add_resource(Users, "/users")


class User(Resource):
"""Api endpoint for the /users/{user_id} route"""

def get(self, user_id):
"""
This function will respond to GET requests made to /users/<user_id>.
It should return the user with the given user_id from the database.
"""
user = db.session.get(UserModel,user_id)
user = db.session.get(UserModel, user_id)
if user is None:
return {"Message": "User not found!"}, 404
warreprovoost marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -81,13 +78,10 @@ def patch(self, user_id):
dict: A dictionary containing the message indicating the success
or failure of the update.
"""
if not request.is_json:
return {"Message": "Unsupported Media Type. Expected JSON."}, 415

is_teacher = request.json.get('is_teacher')
is_admin = request.json.get('is_admin')
try:
user = db.session.get(UserModel,user_id)
user = db.session.get(UserModel, user_id)
if user is None:
return {"Message": "User not found!"}, 404

Expand All @@ -98,7 +92,8 @@ def patch(self, user_id):

# Save the changes to the database
db.session.commit()
except Exception as e:
except Exception as e: # pylint: disable=broad-exception-caught ;
warreprovoost marked this conversation as resolved.
Show resolved Hide resolved
# every exception should result in a rollback
db.session.rollback()
return {"Message": f"An error occurred while patching the user: {str(e)}"}, 500
return {"Message": "User updated successfully!"}
Expand All @@ -109,16 +104,18 @@ def delete(self, user_id):
It should delete the user with the given user_id from the database.
"""
try:
user = db.session.get(UserModel,user_id)
user = db.session.get(UserModel, user_id)
if user is None:
return {"Message": "User not found!"}, 404

db.session.delete(user)
db.session.commit()
except Exception as e:
except Exception as e: # pylint: disable=broad-exception-caught ;
warreprovoost marked this conversation as resolved.
Show resolved Hide resolved
# every exception should result in a rollback
db.session.rollback()
return {"Message": f"An error occurred while deleting the user: {str(e)}"}, 500
return {"Message": "User deleted successfully!"}


users_api.add_resource(Users, "/users")
users_api.add_resource(User, "/users/<string:user_id>")
6 changes: 2 additions & 4 deletions backend/pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
init-hook='import sys; sys.path.append(".")'

[MESSAGES CONTROL]
disable=W0621, # Redefining name %r from outer scope (line %s)
C0415 # Import outside toplevel (needed to prevent circular imports in project/__init__.py)
disable=W0621 # Redefining name %r from outer scope (line %s)


[test-files:*_test.py]
Expand All @@ -12,5 +11,4 @@ disable=

[modules:project/modules/*]
disable=
R0903, # Too few public methods (modules don't require us to have public methods)
C0415 # Import outside toplevel
R0903 # Too few public methods (modules don't require us to have public methods)
25 changes: 25 additions & 0 deletions backend/tests/endpoints/conftest.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
""" Configuration for pytest, Flask, and the test client."""
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from project import create_app_with_db
from project import db
from project.models.users import Users
from tests import db_url


engine = create_engine(db_url)
Session = sessionmaker(bind=engine)
@pytest.fixture
def user_db_session():
"""Create a new database session for the user tests.
After the test, all changes are rolled back and the session is closed."""
db.metadata.create_all(engine)
session = Session()
session.add_all(
[Users(uid="del", is_admin=False, is_teacher=True),
Users(uid="pat", is_admin=False, is_teacher=True),
Users(uid="u_get", is_admin=False, is_teacher=True)
]
)
session.commit()
yield session
session.rollback()
session.close()
# Truncate all tables
for table in reversed(db.metadata.sorted_tables):
session.execute(table.delete())
session.commit()


@pytest.fixture
def app():
Expand Down
142 changes: 66 additions & 76 deletions backend/tests/endpoints/user_test.py
warreprovoost marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,88 +7,78 @@
- test_patch_user: Tests user update functionality and error handling for updating
non-existent user.
"""
def test_post_delete_user(client):
"""Test whether the users page is accessible"""
response = client.post("/users", json={
'uid': 'del',
'is_teacher': True,
'is_admin': False
})
assert response.status_code == 201 or response.status_code == 400 # already present
# Delete the user
response = client.delete("/users/del")
assert response.status_code == 200
assert response.json == {"Message": "User deleted successfully!"}
class TestUserEndpoint:
"""Class to test user management endpoints."""

# Try to delete the user again
response = client.delete("/users/del")
assert response.status_code == 404
# a test that should fail
response = client.post("/users", json={
'uid': '12',
'is_student': True, # wrong field name
'is_admin': False
})
assert response.status_code == 400
def test_delete_user(self, client,user_db_session): # pylint: disable=unused-argument ; pytest uses it
"""Test deleting a user."""
# Delete the user
response = client.delete("/users/del")
assert response.status_code == 200
assert response.json == {"Message": "User deleted successfully!"}

# Send a request with a media type that's not JSON
response = client.post("/users", data={
'uid': '12',
'is_teacher': True,
'is_admin': False
})
assert response.status_code == 415
def test_delete_not_present(self, client,user_db_session): # pylint: disable=unused-argument ; pytest uses it
"""Test deleting a user that does not exist."""
response = client.delete("/users/non")
assert response.status_code == 404

def test_get_users(client):
"""Test the get method of the Users class"""
response = client.get("/users")
assert response.status_code == 200
# Check that the response is a list (even if it's empty)
assert isinstance(response.json, list)
def test_wrong_form_post(self, client,user_db_session): # pylint: disable=unused-argument ; pytest uses it
"""Test posting with a wrong form."""
response = client.post("/users", json={
'uid': '12',
'is_student': True, # wrong field name
'is_admin': False
})
assert response.status_code == 400

response = client.post("/users", json={
'uid': 'u_get',
'is_teacher': True,
'is_admin': False
})
assert response.status_code == 201 or response.status_code == 400
response = client.get("users/u_get")
assert response.status_code == 200
assert response.json == {
'uid': 'u_get',
'is_teacher': True,
'is_admin': False
}
def test_wrong_datatype_post(self, client,user_db_session): # pylint: disable=unused-argument ; pytest uses it
"""Test posting with a wrong data type."""
response = client.post("/users", data={
'uid': '12',
'is_teacher': True,
'is_admin': False
})
assert response.status_code == 415

def test_get_all_users(self, client,user_db_session): # pylint: disable=unused-argument ; pytest uses it
"""Test getting all users."""
response = client.get("/users")
assert response.status_code == 200
# Check that the response is a list (even if it's empty)
assert isinstance(response.json, list)

def test_patch_user(client):
"""Test the update method of the Users class"""
# First, create a user to update
client.post("/users", json={
'uid': 'pat',
'is_teacher': True,
'is_admin': False
})
def test_get_one_user(self, client,user_db_session): # pylint: disable=unused-argument ; pytest uses it
"""Test getting a single user."""
response = client.get("users/u_get")
assert response.status_code == 200
assert response.json == {
'uid': 'u_get',
'is_teacher': True,
'is_admin': False
}

# Then, update the user
response = client.patch("/users/pat", json={
'is_teacher': False,
'is_admin': True
})
assert response.status_code == 200
assert response.json == {"Message": "User updated successfully!"}
def test_patch_user(self, client, user_db_session): # pylint: disable=unused-argument ; pytest uses it
"""Test updating a user."""
response = client.patch("/users/pat", json={
'is_teacher': False,
'is_admin': True
})
assert response.status_code == 200
assert response.json == {"Message": "User updated successfully!"}

# Try to update a non-existent user
response = client.patch("/users/non", json={
'is_teacher': False,
'is_admin': True
})
assert response.status_code == 404
def test_patch_non_existent(self, client,user_db_session): # pylint: disable=unused-argument ; pytest uses it
"""Test updating a non-existent user."""
response = client.patch("/users/non", json={
'is_teacher': False,
'is_admin': True
})
assert response.status_code == 404

# Send a request with a media type that's not JSON
response = client.post("/users", data={
'uid': '12',
'is_teacher': True,
'is_admin': False
})
assert response.status_code == 415
def test_patch_non_json(self, client,user_db_session): # pylint: disable=unused-argument ; pytest uses it
warreprovoost marked this conversation as resolved.
Show resolved Hide resolved
"""Test sending a non-JSON patch request."""
response = client.post("/users", data={
'uid': '12',
'is_teacher': True,
'is_admin': False
})
assert response.status_code == 415
Loading