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

Add participants to hacknight endpoint with tests (fixes #93) #166

Merged
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
UserLogin, UserLogout, RefreshAccessToken, RevokeRefreshToken
)
from backend.resources.contact import SendMessage
from backend.resources.hacknight import HacknightList, HacknightDetails
from backend.resources.hacknight import HacknightDetails, HacknightList
from backend.resources.participant import ParticipantDetails, ParticipantsList


Expand Down
36 changes: 35 additions & 1 deletion backend/resources/hacknight.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
from marshmallow import ValidationError

from backend.extensions import db
from backend.models import Hacknight
from backend.models import Hacknight, Participant
from backend.serializers.hacknight_serializer import HacknightSchema
from backend.serializers.participant_serializer import ParticipantSchema


class HacknightList(Resource):
Expand Down Expand Up @@ -53,3 +54,36 @@ def get(self, id):
return {'hacknights': hacknight_schema.dump(Hacknight.query.get_or_404(
id))
}, HTTPStatus.OK

@jwt_required
def patch(self, id):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't we agree to have separate url for that (like /hacknights/<id>/participants)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hear it for first time. No one discussed it with me, although there was plenty of time - it is 4 months since I proposed this pr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

hacknight = Hacknight.query.get_or_404(id)
participants = [
participant.id for participant in hacknight.participants
]

json_data = request.get_json(force=True)
ids_schema = ParticipantSchema(only=('participants_ids',))
try:
data = ids_schema.load(json_data)
except ValidationError as err:
return (err.messages), HTTPStatus.BAD_REQUEST
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no reason for () here, also 422 could be applied here.


new_participants = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand that this endpoint should be able to change the list of participants for particular hacknight.

I see that you implemented only adding new participants? How the plan for removing a participant from hacknight looks like?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title of these ticket is "Create add participant to hacknight endpoint", and I believe that delete participant from hacknight, is great topic for next one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chenged to post on /hacknights//participants @ngiersz going to do delete method

id for id in data['participants_ids'] if id not in participants
]

if not new_participants:
return {'message': 'No new participant has been provided'}, \
HTTPStatus.BAD_REQUEST

for new_participant in new_participants:
hacknight.participants.append(
Participant.query.get_or_404(new_participant)
)
db.session.add(hacknight)
db.session.commit()

hacknight_schema = HacknightSchema()
return {"hacknight": hacknight_schema.dump(hacknight)}, \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove "hacknight".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason for hacknight as a key here, we should return it directly.

HTTPStatus.OK
4 changes: 3 additions & 1 deletion backend/serializers/participant_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
class ParticipantSchema(Schema):
class Meta:
fields = (
'id', 'name', 'lastname', 'email', 'github', 'hacknights', 'phone'
'id', 'participants_ids', 'name', 'lastname',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure, if that's a good wat to do that with participant_ids.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first I delegated it to separate schema, but as we discussed, it wasn't best solution. So I pull it into ParticipantSchema. Do you have some suggestions. I certenly want serialize somehow that payload.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@w1stler please solve this discussion and merge

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could make it inline inside Resource:

ids_schema = Schema.from_dict({"participants_ids": fields.List(fields.Int())})
        try:
            data = ids_schema().load(json_data)

and remove from participant serializer.
What do you think about that solution?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that is a better solution. We can find some examples with good practices for similar problem, as I am still not 100% confident if that's the best approach. :)

'email', 'github', 'hacknights', 'phone'
)
dump_only = ('id', 'hacknights')

participants_ids = fields.List(fields.Int())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, how the participants_ids as a list can be part of a ParticipantSchema? I can agree, that list in such shape will be used to attach participants to a hacknight, but it should be defined in probably different place.

name = fields.Str(required=True, validate=[validate.Length(max=50)])
lastname = fields.Str(validate=[validate.Length(max=50)])
email = fields.Email(validate=[validate.Length(max=200)])
Expand Down
79 changes: 79 additions & 0 deletions backend/tests/test_hacknight.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from http import HTTPStatus
import json

from backend.models import Hacknight, Participant


def test_get_hacknights_when_logged_in(
Expand Down Expand Up @@ -31,3 +34,79 @@ def test_get_hacknights_unauthorized(client, add_hacknights):
response = rv.get_json()
assert rv.status_code == HTTPStatus.UNAUTHORIZED
assert response['msg'] == 'Missing Authorization Header'


def test_add_participants_to_hacknight(
access_token, add_hacknights, add_participants, client
):
"""Test add new participant to hacknight."""
payload = {'participants_ids': [1, 3]}
rv = client.patch(
'/hacknights/1/',
headers={'Authorization': 'Bearer {}'.format(access_token)},
data=json.dumps(payload)
)
resp = rv.get_json()
assert rv.status_code == HTTPStatus.OK

hacknight_db = Hacknight.query.get(1)
ids_from_db = [participant.id for participant in hacknight_db.participants]
for participant in resp['hacknight']['participants']:
assert participant['id'] in payload['participants_ids']
assert ids_from_db == payload['participants_ids']


def test_add_participants_to_hacknight_unauthorized(
add_hacknights, add_participants, client
):
"""Test add participants to hacknight when user is not logged in."""
payload = {'participants_ids': [1, 3]}
rv = client.patch('/hacknights/1/', data=json.dumps(payload))
response = rv.get_json()
assert rv.status_code == HTTPStatus.UNAUTHORIZED
assert response['msg'] == 'Missing Authorization Header'


def test_add_nonexisting_participants_to_hacknight(
access_token, add_hacknights, client
):
"""Test add non-existing participants ids to hacknight."""
payload = {'participants_ids': [1, 3]}
rv = client.patch(
'/hacknights/1/',
headers={'Authorization': 'Bearer {}'.format(access_token)},
data=json.dumps(payload)
)
assert rv.status_code == HTTPStatus.NOT_FOUND


def test_add_participants_to_nonexisting_hacknight(
access_token, add_participants, client
):
"""Test add participants to non-existing hacknight."""
payload = {'participants_ids': [1, 3]}
rv = client.patch(
'/hacknights/1/',
headers={'Authorization': 'Bearer {}'.format(access_token)},
data=json.dumps(payload)
)
assert rv.status_code == HTTPStatus.NOT_FOUND


def test_duplicate_participant_in_hacknight(
access_token, add_hacknights, add_participants, client, _db
):
"""Test add participant who is already in hacknight."""
hacknight = _db.session.query(Hacknight).first()
participant = _db.session.query(Participant).first()
hacknight.participants.append(participant)

payload = {'participants_ids': [participant.id]}
rv = client.patch(
'/hacknights/{}/'.format(hacknight.id),
headers={'Authorization': 'Bearer {}'.format(access_token)},
data=json.dumps(payload)
)
response = rv.get_json()
assert rv.status_code == HTTPStatus.BAD_REQUEST
assert response['message'] == 'No new participant has been provided'