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

feat: Create Update Member API + Implement Member API unit tests #96

Merged
merged 3 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 21 additions & 2 deletions chalicelib/api/members.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@
members_api = Blueprint(__name__)


@members_api.route("/member/{user_id}", methods=["GET"], cors=True)
@auth(members_api, roles=["admin", "member"])
def get_member(user_id):
member = member_service.get_by_id(user_id)
return member if member else {}


@members_api.route("/member/{user_id}", methods=["PUT"], cors=True)
@auth(members_api, roles=[Roles.MEMBER, Roles.ADMIN])
def update_member(user_id):
data = members_api.current_request.json_body
return member_service.update(
user_id=user_id, data=data, headers=members_api.current_request.headers
)


@members_api.route("/members", methods=["GET"], cors=True)
@auth(members_api, roles=["admin", "member"])
def get_all_members():
Expand All @@ -17,6 +33,7 @@ def get_all_members():
@auth(members_api, roles=[])
def onboard_member(user_id):
data = members_api.current_request.json_body
# TODO: If isNewUser is False, reject onboarding
data["isNewUser"] = False

if member_service.onboard(user_id, data):
Expand All @@ -25,7 +42,7 @@ def onboard_member(user_id):
"message": "User updated successfully.",
}
else:
{ "status": False}
return {"status": False}


@members_api.route("/members", methods=["POST"], cors=True)
Expand All @@ -34,14 +51,16 @@ def create_member():
data = members_api.current_request.json_body
return member_service.create(data)


@members_api.route("/members", methods=["DELETE"], cors=True)
@auth(members_api, roles=[Roles.ADMIN])
def delete_members():
data = members_api.current_request.json_body
return member_service.delete(data)


@members_api.route("/members/{user_id}/roles", methods=["PATCH"], cors=True)
@auth(members_api, roles=[Roles.ADMIN])
def update_member_roles(user_id):
data = members_api.current_request.json_body
return member_service.update_roles(user_id, data["roles"])
return member_service.update_roles(user_id, data["roles"])
40 changes: 36 additions & 4 deletions chalicelib/services/MemberService.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from chalicelib.modules.mongo import mongo_module
from chalice import ConflictError, NotFoundError
from chalice import ConflictError, NotFoundError, UnauthorizedError

import json
from bson import ObjectId
import json
import jwt
import boto3


class MemberService:
Expand Down Expand Up @@ -40,7 +42,7 @@ def create(self, data):
"success": True,
"message": "User created successfully",
}

def delete(self, data: list[str]) -> dict:
"""
Deletes user documents based on the provided IDs.
Expand Down Expand Up @@ -70,7 +72,10 @@ def delete(self, data: list[str]) -> dict:
"success": True,
"message": "Documents deleted successfully",
}


def get_by_id(self, user_id: str):
data = mongo_module.get_document_by_id(self.collection, user_id)
return json.dumps(data, cls=self.BSONEncoder)

def get_all(self):
data = mongo_module.get_data_from_collection(self.collection)
Expand All @@ -79,6 +84,33 @@ def get_all(self):
def onboard(self, document_id=str, data=dict) -> bool:
return mongo_module.update_document_by_id(self.collection, document_id, data)

def update(self, user_id: str, data: dict, headers: dict) -> bool:
ssm_client = boto3.client("ssm")
auth_header = headers.get("Authorization", None)

if not auth_header:
raise UnauthorizedError("Authorization header is missing.")

_, token = auth_header.split(" ", 1) if " " in auth_header else (None, None)

if not token:
raise UnauthorizedError("Token is missing.")

auth_secret = ssm_client.get_parameter(
Name="/Zap/AUTH_SECRET", WithDecryption=True
)["Parameter"]["Value"]
decoded = jwt.decode(token, auth_secret, algorithms=["HS256"])

if user_id != decoded["_id"]:
raise UnauthorizedError(
"User {user_id} is not authorized to update this user."
)

# NOTE: Performing an update on the path '_id' would modify the immutable field '_id'
data.pop("_id", None)

return mongo_module.update_document_by_id(self.collection, user_id, data)

def update_roles(self, document_id=str, roles=list) -> bool:
return mongo_module.update_document(
self.collection,
Expand Down
256 changes: 256 additions & 0 deletions tests/api/test_members.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
from chalice.test import Client
from unittest.mock import patch
from chalice.config import Config
from chalice.local import LocalGateway

from app import app
import json


lg = LocalGateway(app, Config())


TEST_MEMBER_DATA = [
{
"_id": "12a34bc678df27ead9388708",
"name": "Name Name",
"email": "[email protected]",
"class": "Lambda",
"college": "CAS",
"family": "Poseidon",
"graduationYear": "2026",
"isEboard": "no",
"major": "Computer Science",
"minor": "",
"isNewUser": False,
"team": "technology",
"roles": ["admin", "eboard", "member"],
"big": "Name Name",
},
{
"_id": "12a34bc678df27ead9388709",
"name": "Name Name",
"email": "[email protected]",
"class": "Lambda",
"college": "QST",
"family": "Atlas",
"graduationYear": "2027",
"isEboard": "no",
"major": "Business Administration",
"minor": "",
"isNewUser": True,
"team": "operations",
"roles": ["member"],
"big": "Name Name",
},
]


def test_get_member():
# Create a Chalice test client
with Client(app) as client:
with patch("chalicelib.decorators.jwt.decode") as mock_decode:
mock_decode.return_value = {"role": "member"}

with patch(
"chalicelib.services.MemberService.member_service.get_by_id"
) as mock_get:
mock_get.return_value = TEST_MEMBER_DATA[0]
response = client.http.get(
f"/member/{TEST_MEMBER_DATA[0]['_id']}",
headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"},
)
# Check the response status code and body
assert response.status_code == 200
assert response.json_body == TEST_MEMBER_DATA[0]


def test_get_member_non_existent():
# Create a Chalice test client
with Client(app) as client:
with patch("chalicelib.decorators.jwt.decode") as mock_decode:
mock_decode.return_value = {"role": "member"}

with patch(
"chalicelib.services.MemberService.member_service.get_by_id"
) as mock_get:
mock_get.return_value = {}
response = client.http.get(
"/member/123",
headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"},
)
# Check the response status code and body
assert response.status_code == 200
assert response.json_body == {}


def test_update_member():
with Client(app) as client:
with patch("chalicelib.decorators.jwt.decode") as mock_decode:
mock_decode.return_value = {"role": "member"}

with patch(
"chalicelib.services.MemberService.member_service.update"
) as mock_update:
# Make copy of TEST_MEMBER_DATA[0]
update_member_data = TEST_MEMBER_DATA[0].copy()
update_member_data["name"] = "New Name"
mock_update.return_value = update_member_data

response = client.http.put(
f"/member/{TEST_MEMBER_DATA[0]['_id']}",
body=update_member_data,
headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"},
)
# Check the response status code and body
assert response.status_code == 200
assert response.json_body == update_member_data


def test_get_all_members():
# Create a Chalice test client
with Client(app) as client:
with patch("chalicelib.decorators.jwt.decode") as mock_decode:
mock_decode.return_value = {"role": "member"}

with patch(
"chalicelib.services.MemberService.member_service.get_all"
) as mock_get_all:
mock_get_all.return_value = TEST_MEMBER_DATA
response = client.http.get(
"/members",
headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"},
)
# Check the response status code and body
assert response.status_code == 200
assert response.json_body == TEST_MEMBER_DATA


def test_onboard_member():
with patch("chalicelib.decorators.jwt.decode") as mock_decode:
mock_decode.return_value = {"role": "member"}

with patch(
"chalicelib.services.MemberService.member_service.onboard"
) as mock_onboard:
mock_onboard.return_value = True

# Utilize local gateway for passing in body
response = lg.handle_request(
method="POST",
path=f"/members/onboard/{TEST_MEMBER_DATA[1]['_id']}",
headers={
"Content-Type": "application/json",
"Authorization": "Bearer SAMPLE_TOKEN_STRING",
},
body=json.dumps(TEST_MEMBER_DATA[1]),
)

# Check the response status code and body
assert response["statusCode"] == 200
assert json.loads(response["body"]) == {
"status": True,
"message": "User updated successfully.",
}


def test_onboard_member_fail_on_mongo():
with patch("chalicelib.decorators.jwt.decode") as mock_decode:
mock_decode.return_value = {"role": "member"}

with patch(
"chalicelib.services.MemberService.member_service.onboard"
) as mock_onboard:
mock_onboard.return_value = False
response = lg.handle_request(
method="POST",
path=f"/members/onboard/{TEST_MEMBER_DATA[1]['_id']}",
headers={
"Content-Type": "application/json",
"Authorization": "Bearer SAMPLE_TOKEN_STRING",
},
body=json.dumps(TEST_MEMBER_DATA[1]),
)
print(response)
# Check the response status code and body
assert response["statusCode"] == 200
assert json.loads(response["body"]) == {
"status": False,
}


def test_create_member():
with Client(app) as client:
with patch("chalicelib.decorators.jwt.decode") as mock_decode:
mock_decode.return_value = {"role": "admin"}

with patch(
"chalicelib.services.MemberService.member_service.create"
) as mock_create:
mock_create.return_value = {
"success": True,
"message": "User created successfully",
}
response = client.http.post(
"/members",
body=json.dumps(TEST_MEMBER_DATA[0]),
headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"},
)
# Check the response status code and body
assert response.status_code == 200
assert response.json_body == {
"success": True,
"message": "User created successfully",
}


def test_delete_members():
with Client(app) as client:
with patch("chalicelib.decorators.jwt.decode") as mock_decode:
mock_decode.return_value = {"role": "admin"}

with patch(
"chalicelib.services.MemberService.member_service.delete"
) as mock_delete:
mock_delete.return_value = {
"success": True,
"message": "Documents deleted successfully",
}
response = client.http.delete(
"/members",
body=json.dumps(TEST_MEMBER_DATA),
headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"},
)
# Check the response status code and body
assert response.status_code == 200
assert response.json_body == {
"success": True,
"message": "Documents deleted successfully",
}


def test_update_member_roles():
with patch("chalicelib.decorators.jwt.decode") as mock_decode:
mock_decode.return_value = {"role": "admin"}

with patch(
"chalicelib.services.MemberService.member_service.update_roles"
) as mock_update:
update_member_data = TEST_MEMBER_DATA[0].copy()
update_member_data["roles"] = ["admin"]
mock_update.return_value = update_member_data

response = lg.handle_request(
method="PATCH",
path=f"/members/{TEST_MEMBER_DATA[0]['_id']}/roles",
headers={
"Content-Type": "application/json",
"Authorization": "Bearer SAMPLE_TOKEN_STRING",
},
body=json.dumps(update_member_data),
)

print(response)
# Check the response status code and body
assert response["statusCode"] == 200
assert json.loads(response["body"]) == update_member_data
Loading
Loading