From 22377a5eb4bac31c14f8eeb3c7155c85052a74b2 Mon Sep 17 00:00:00 2001 From: Chime Date: Sat, 3 Aug 2024 11:34:37 +0100 Subject: [PATCH 01/24] Combine schema for like and dislike, and query dislike after creation --- api/v1/routes/blog.py | 8 +++++--- api/v1/schemas/blog.py | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/api/v1/routes/blog.py b/api/v1/routes/blog.py index e67209b4a..af1792419 100644 --- a/api/v1/routes/blog.py +++ b/api/v1/routes/blog.py @@ -14,7 +14,7 @@ BlogPostResponse, BlogRequest, BlogUpdateResponseModel, - BlogDislikeResponse, + BlogLikeDislikeResponse, ) from api.v1.services.blog import BlogService from api.v1.services.user import user_service @@ -105,7 +105,7 @@ async def update_blog( ) -@blog.put("/{blog_id}/dislike", response_model=BlogDislikeResponse) +@blog.put("/{blog_id}/dislike", response_model=BlogLikeDislikeResponse) def dislike_blog_post( blog_id: str, db: Session = Depends(get_db), @@ -130,8 +130,10 @@ def dislike_blog_post( ) # UPDATE disikes - new_dislike = blog_service.create_blog_dislike(db, blog_p.id, current_user.id) + blog_service.create_blog_dislike(db, blog_p.id, current_user.id) + # CONFIRM new dislike + new_dislike = blog_service.fetch_blog_dislike(blog_p.id, current_user.id) if not isinstance(new_dislike, BlogDislike): raise HTTPException( detail="Unable to record dislike.", status_code=status.HTTP_400_BAD_REQUEST diff --git a/api/v1/schemas/blog.py b/api/v1/schemas/blog.py index 82f4ab199..1f8b6471f 100644 --- a/api/v1/schemas/blog.py +++ b/api/v1/schemas/blog.py @@ -55,7 +55,7 @@ class Config: from_attributes = True -class BlogDislikeCreate(BaseModel): +class BlogLikeDislikeCreate(BaseModel): id: str blog_id: str user_id: str @@ -63,7 +63,7 @@ class BlogDislikeCreate(BaseModel): created_at: datetime -class BlogDislikeResponse(BaseModel): +class BlogLikeDislikeResponse(BaseModel): status_code: str message: str - data: BlogDislikeCreate + data: BlogLikeDislikeCreate From 506233d6af8a66e5dbb4c015697a19cda8a43abf Mon Sep 17 00:00:00 2001 From: Chime Date: Sat, 3 Aug 2024 12:08:55 +0100 Subject: [PATCH 02/24] Update /test_dislike_blog_post.py file --- tests/v1/blog/test_dislike_blog_post.py | 44 +++++++++++++++++-------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/tests/v1/blog/test_dislike_blog_post.py b/tests/v1/blog/test_dislike_blog_post.py index 856aea7e2..6350ce923 100644 --- a/tests/v1/blog/test_dislike_blog_post.py +++ b/tests/v1/blog/test_dislike_blog_post.py @@ -3,6 +3,7 @@ from uuid_extensions import uuid7 from sqlalchemy.orm import Session from api.db.database import get_db +from datetime import datetime, timezone from fastapi.testclient import TestClient from unittest.mock import patch, MagicMock from api.v1.services.user import user_service @@ -26,7 +27,7 @@ def mock_user_service(): @pytest.fixture def mock_blog_service(): - with patch("api.v1.services.user.BlogService", autospec=True) as blog_service_mock: + with patch("api.v1.services.blog.BlogService", autospec=True) as blog_service_mock: yield blog_service_mock @@ -54,12 +55,15 @@ def test_blog(test_user): @pytest.fixture() def test_blog_dislike(test_user, test_blog): return BlogDislike( + id=str(uuid7()), user_id=test_user.id, blog_id=test_blog.id, + ip_address="192.168.1.0", + created_at=datetime.now(tz=timezone.utc) ) @pytest.fixture -def access_token_user1(test_user): +def access_token_user(test_user): return user_service.create_access_token(user_id=test_user.id) def make_request(blog_id, token): @@ -68,20 +72,32 @@ def make_request(blog_id, token): headers={"Authorization": f"Bearer {token}"} ) -# Test for successful dislike + def test_successful_dislike( mock_db_session, test_user, test_blog, - access_token_user1, + test_blog_dislike, + access_token_user ): - mock_user_service.get_current_user = test_user - mock_db_session.query.return_value.filter.return_value.first.return_value = test_blog - mock_db_session.query.return_value.filter_by.return_value.first.return_value = None + # mock current-user AND blog-post + mock_db_session.query().filter().first.side_effect = [test_user, test_blog] + + # mock existing-blog-dislike AND new-blog-dislike + mock_db_session.query().filter_by().first.side_effect = [None, test_blog_dislike] - resp = make_request(test_blog.id, access_token_user1) + resp = make_request(test_blog.id, access_token_user) + resp_d = resp.json() assert resp.status_code == 200 - assert resp.json()['message'] == "Dislike recorded successfully." + assert resp_d['success'] == True + assert resp_d['message'] == "Dislike recorded successfully." + + dislike_data = resp_d['data'] + assert dislike_data['id'] == test_blog_dislike.id + assert dislike_data['blog_id'] == test_blog.id + assert dislike_data['user_id'] == test_user.id + assert dislike_data['ip_address'] == test_blog_dislike.ip_address + assert datetime.fromisoformat(dislike_data['created_at']) == test_blog_dislike.created_at # Test for double dislike @@ -90,14 +106,14 @@ def test_double_dislike( test_user, test_blog, test_blog_dislike, - access_token_user1, + access_token_user, ): mock_user_service.get_current_user = test_user mock_db_session.query.return_value.filter.return_value.first.return_value = test_blog mock_db_session.query.return_value.filter_by.return_value.first.return_value = test_blog_dislike ### TEST ATTEMPT FOR MULTIPLE DISLIKING... ### - resp = make_request(test_blog.id, access_token_user1) + resp = make_request(test_blog.id, access_token_user) assert resp.status_code == 403 assert resp.json()['message'] == "You have already disliked this blog post" @@ -105,14 +121,14 @@ def test_double_dislike( def test_wrong_blog_id( mock_db_session, test_user, - access_token_user1, + access_token_user, ): mock_user_service.get_current_user = test_user mock_blog_service.fetch = None ### TEST REQUEST WITH WRONG blog_id ### ### using random uuid instead of blog1.id ### - resp = make_request(str(uuid7()), access_token_user1) + resp = make_request(str(uuid7()), access_token_user) assert resp.status_code == 404 assert resp.json()['message'] == "Post not found" @@ -127,4 +143,4 @@ def test_wrong_auth_token( ### TEST ATTEMPT WITH INVALID AUTH... ### resp = make_request(test_blog.id, None) assert resp.status_code == 401 - assert resp.json()['message'] == 'Could not validate credentials' + assert resp.json()['message'] == 'Could not validate credentials' \ No newline at end of file From 8b4d1fa6fe961c384ddaaa9534fb8327ce0c6a1a Mon Sep 17 00:00:00 2001 From: Chime Date: Sat, 3 Aug 2024 13:08:23 +0100 Subject: [PATCH 03/24] Add client_helpers.py file AND use it to set ip_address for dislike --- api/utils/client_helpers.py | 8 ++++++++ api/v1/routes/blog.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 api/utils/client_helpers.py diff --git a/api/utils/client_helpers.py b/api/utils/client_helpers.py new file mode 100644 index 000000000..e3b0968a1 --- /dev/null +++ b/api/utils/client_helpers.py @@ -0,0 +1,8 @@ +# All helper functions relating to user clients + + +def get_ip_address(request): + client_ip = request.headers.get("X-Forwarded-For") + if client_ip is None or client_ip == "": + client_ip = request.client.host + return client_ip \ No newline at end of file diff --git a/api/v1/routes/blog.py b/api/v1/routes/blog.py index af1792419..b66801626 100644 --- a/api/v1/routes/blog.py +++ b/api/v1/routes/blog.py @@ -1,6 +1,9 @@ from typing import List, Annotated -from fastapi import APIRouter, Depends, HTTPException, status, HTTPException, Response +from fastapi import ( + APIRouter, Depends, HTTPException, status, + HTTPException, Response, Request +) from fastapi.encoders import jsonable_encoder from sqlalchemy.orm import Session @@ -21,6 +24,7 @@ from api.v1.schemas.comment import CommentCreate, CommentSuccessResponse from api.v1.services.comment import comment_service from api.v1.services.comment import CommentService +from api.utils.client_helpers import get_ip_address blog = APIRouter(prefix="/blogs", tags=["Blog"]) @@ -108,6 +112,7 @@ async def update_blog( @blog.put("/{blog_id}/dislike", response_model=BlogLikeDislikeResponse) def dislike_blog_post( blog_id: str, + request: Request, db: Session = Depends(get_db), current_user: User = Depends(user_service.get_current_user), ): @@ -130,7 +135,8 @@ def dislike_blog_post( ) # UPDATE disikes - blog_service.create_blog_dislike(db, blog_p.id, current_user.id) + blog_service.create_blog_dislike( + db, blog_p.id, current_user.id, ip_address=get_ip_address(request)) # CONFIRM new dislike new_dislike = blog_service.fetch_blog_dislike(blog_p.id, current_user.id) From b3bb5c0539cddb47cab648d71d44999af45c488a Mon Sep 17 00:00:00 2001 From: Chime Date: Mon, 5 Aug 2024 15:31:11 +0100 Subject: [PATCH 04/24] Change request method for routes.blog.dislike_blog_post from PUT to POST --- api/v1/routes/blog.py | 2 +- tests/v1/blog/test_dislike_blog_post.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/v1/routes/blog.py b/api/v1/routes/blog.py index b66801626..8dc81cdad 100644 --- a/api/v1/routes/blog.py +++ b/api/v1/routes/blog.py @@ -109,7 +109,7 @@ async def update_blog( ) -@blog.put("/{blog_id}/dislike", response_model=BlogLikeDislikeResponse) +@blog.post("/{blog_id}/dislike", response_model=BlogLikeDislikeResponse) def dislike_blog_post( blog_id: str, request: Request, diff --git a/tests/v1/blog/test_dislike_blog_post.py b/tests/v1/blog/test_dislike_blog_post.py index 6350ce923..7f335bf28 100644 --- a/tests/v1/blog/test_dislike_blog_post.py +++ b/tests/v1/blog/test_dislike_blog_post.py @@ -67,7 +67,7 @@ def access_token_user(test_user): return user_service.create_access_token(user_id=test_user.id) def make_request(blog_id, token): - return client.put( + return client.post( f"/api/v1/blogs/{blog_id}/dislike", headers={"Authorization": f"Bearer {token}"} ) From 509ab5241580c284fcecda2a7bd16725d3fd9619 Mon Sep 17 00:00:00 2001 From: Chime Date: Thu, 8 Aug 2024 09:44:42 +0100 Subject: [PATCH 05/24] Add objects_count to data returned from blog like/dislike requests --- api/v1/routes/blog.py | 38 ++++++++++++++++++++++--- api/v1/schemas/blog.py | 7 ++++- tests/v1/blog/test_dislike_blog_post.py | 6 +++- tests/v1/blog/test_like_blog_post.py | 6 +++- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/api/v1/routes/blog.py b/api/v1/routes/blog.py index 1c25e69f2..094fb6a0e 100644 --- a/api/v1/routes/blog.py +++ b/api/v1/routes/blog.py @@ -115,7 +115,18 @@ def like_blog_post( db: Session = Depends(get_db), current_user: User = Depends(user_service.get_current_user), ): + """Endpoint to add `like` to a blog post. + args: + blog_id: `str` The ID of the blog post. + request: `default` Request. + db: `default` Session. + + return: + In the `data` returned, `"object"` represents details of the + BlogLike obj and the `"objects_count"` represents the number + of BlogLike for the blog post + """ blog_service = BlogService(db) # GET blog post @@ -141,14 +152,18 @@ def like_blog_post( new_like = blog_service.fetch_blog_like(blog_p.id, current_user.id) if not isinstance(new_like, BlogLike): raise HTTPException( - detail="Unable to record like.", status_code=status.HTTP_400_BAD_REQUEST + detail="Unable to record like.", + status_code=status.HTTP_400_BAD_REQUEST ) # Return success response return success_response( status_code=status.HTTP_200_OK, message="Like recorded successfully.", - data=new_like.to_dict(), + data={ + 'object': new_like.to_dict(), + 'objects_count': blog_service.num_of_likes(blog_id) + }, ) @@ -159,7 +174,18 @@ def dislike_blog_post( db: Session = Depends(get_db), current_user: User = Depends(user_service.get_current_user), ): + """Endpoint to add `dislike` to a blog post. + args: + blog_id: `str` The ID of the blog post. + request: `default` Request. + db: `default` Session. + + return: + In the `data` returned, `"object"` represents details of the + BlogDislike obj and the `"objects_count"` represents the number + of BlogDislike for the blog post + """ blog_service = BlogService(db) # GET blog post @@ -185,14 +211,18 @@ def dislike_blog_post( new_dislike = blog_service.fetch_blog_dislike(blog_p.id, current_user.id) if not isinstance(new_dislike, BlogDislike): raise HTTPException( - detail="Unable to record dislike.", status_code=status.HTTP_400_BAD_REQUEST + detail="Unable to record dislike.", + status_code=status.HTTP_400_BAD_REQUEST ) # Return success response return success_response( status_code=status.HTTP_200_OK, message="Dislike recorded successfully.", - data=new_dislike.to_dict(), + data={ + 'object': new_dislike.to_dict(), + 'objects_count': blog_service.num_of_dislikes(blog_id) + }, ) diff --git a/api/v1/schemas/blog.py b/api/v1/schemas/blog.py index 1f8b6471f..18361b407 100644 --- a/api/v1/schemas/blog.py +++ b/api/v1/schemas/blog.py @@ -63,7 +63,12 @@ class BlogLikeDislikeCreate(BaseModel): created_at: datetime +class BlogLikeDislikeCreateData(BaseModel): + object: BlogLikeDislikeCreate + objects_count: int # number of likes/dislikes + + class BlogLikeDislikeResponse(BaseModel): status_code: str message: str - data: BlogLikeDislikeCreate + data: BlogLikeDislikeCreateData diff --git a/tests/v1/blog/test_dislike_blog_post.py b/tests/v1/blog/test_dislike_blog_post.py index 7f335bf28..0e40f379f 100644 --- a/tests/v1/blog/test_dislike_blog_post.py +++ b/tests/v1/blog/test_dislike_blog_post.py @@ -86,18 +86,22 @@ def test_successful_dislike( # mock existing-blog-dislike AND new-blog-dislike mock_db_session.query().filter_by().first.side_effect = [None, test_blog_dislike] + # mock dislike-count + mock_db_session.query().filter_by().count.return_value = 1 + resp = make_request(test_blog.id, access_token_user) resp_d = resp.json() assert resp.status_code == 200 assert resp_d['success'] == True assert resp_d['message'] == "Dislike recorded successfully." - dislike_data = resp_d['data'] + dislike_data = resp_d['data']['object'] assert dislike_data['id'] == test_blog_dislike.id assert dislike_data['blog_id'] == test_blog.id assert dislike_data['user_id'] == test_user.id assert dislike_data['ip_address'] == test_blog_dislike.ip_address assert datetime.fromisoformat(dislike_data['created_at']) == test_blog_dislike.created_at + assert resp_d['data']['objects_count'] == 1 # Test for double dislike diff --git a/tests/v1/blog/test_like_blog_post.py b/tests/v1/blog/test_like_blog_post.py index e899060df..866e63b36 100644 --- a/tests/v1/blog/test_like_blog_post.py +++ b/tests/v1/blog/test_like_blog_post.py @@ -87,6 +87,9 @@ def test_successful_like( # mock existing-blog-like AND new-blog-like mock_db_session.query().filter_by().first.side_effect = [None, test_blog_like] + # mock like-count + mock_db_session.query().filter_by().count.return_value = 1 + resp = make_request(test_blog.id, access_token_user) resp_d = resp.json() print(resp_d) @@ -94,12 +97,13 @@ def test_successful_like( assert resp_d['success'] == True assert resp_d['message'] == "Like recorded successfully." - like_data = resp_d['data'] + like_data = resp_d['data']['object'] assert like_data['id'] == test_blog_like.id assert like_data['blog_id'] == test_blog.id assert like_data['user_id'] == test_user.id assert like_data['ip_address'] == test_blog_like.ip_address assert datetime.fromisoformat(like_data['created_at']) == test_blog_like.created_at + assert resp_d['data']['objects_count'] == 1 # Test for double like From 20cdb6584b1b0f3c1ca952ceba950155c772621e Mon Sep 17 00:00:00 2001 From: Chime Date: Thu, 22 Aug 2024 22:14:54 +0100 Subject: [PATCH 06/24] Change dislike blog endpoint method from PUT to POST. Add objects_count to data returned from blog like and dislike creation. Add ip_address to blog dislike during creation. Update schemas, docs and tests to cover changes --- api/v1/routes/blog.py | 62 ++++++------------------- api/v1/services/blog.py | 18 ++++++- tests/v1/blog/test_dislike_blog_post.py | 9 +++- tests/v1/blog/test_like_blog_post.py | 12 +++-- 4 files changed, 47 insertions(+), 54 deletions(-) diff --git a/api/v1/routes/blog.py b/api/v1/routes/blog.py index 2625c0d5c..8a080ee84 100644 --- a/api/v1/routes/blog.py +++ b/api/v1/routes/blog.py @@ -131,32 +131,15 @@ def like_blog_post( """ blog_service = BlogService(db) - # GET blog post + # get blog post blog_p = blog_service.fetch(blog_id) - if not isinstance(blog_p, Blog): - raise HTTPException( - detail="Post not found", status_code=status.HTTP_404_NOT_FOUND - ) - - # CONFIRM current user has NOT liked before - existing_like = blog_service.fetch_blog_like(blog_p.id, current_user.id) - if isinstance(existing_like, BlogLike): - raise HTTPException( - detail="You have already liked this blog post", - status_code=status.HTTP_403_FORBIDDEN, - ) - - # UPDATE likes - blog_service.create_blog_like( + + # confirm current user has NOT liked before + blog_service.check_user_already_liked_blog(blog_p, current_user) + + # update likes + new_like = blog_service.create_blog_like( db, blog_p.id, current_user.id, ip_address=get_ip_address(request)) - - # CONFIRM new like - new_like = blog_service.fetch_blog_like(blog_p.id, current_user.id) - if not isinstance(new_like, BlogLike): - raise HTTPException( - detail="Unable to record like.", - status_code=status.HTTP_400_BAD_REQUEST - ) # Return success response return success_response( @@ -190,32 +173,15 @@ def dislike_blog_post( """ blog_service = BlogService(db) - # GET blog post + # get blog post blog_p = blog_service.fetch(blog_id) - if not isinstance(blog_p, Blog): - raise HTTPException( - detail="Post not found", status_code=status.HTTP_404_NOT_FOUND - ) - - # CONFIRM current user has NOT disliked before - existing_dislike = blog_service.fetch_blog_dislike(blog_p.id, current_user.id) - if isinstance(existing_dislike, BlogDislike): - raise HTTPException( - detail="You have already disliked this blog post", - status_code=status.HTTP_403_FORBIDDEN, - ) - - # UPDATE disikes - blog_service.create_blog_dislike( - db, blog_p.id, current_user.id, ip_address=get_ip_address(request)) - # CONFIRM new dislike - new_dislike = blog_service.fetch_blog_dislike(blog_p.id, current_user.id) - if not isinstance(new_dislike, BlogDislike): - raise HTTPException( - detail="Unable to record dislike.", - status_code=status.HTTP_400_BAD_REQUEST - ) + # confirm current user has NOT disliked before + blog_service.check_user_already_disliked_blog(blog_p, current_user) + + # update disikes + new_dislike = blog_service.create_blog_dislike( + db, blog_p.id, current_user.id, ip_address=get_ip_address(request)) # Return success response return success_response( diff --git a/api/v1/services/blog.py b/api/v1/services/blog.py index b65473631..caa4365f3 100644 --- a/api/v1/services/blog.py +++ b/api/v1/services/blog.py @@ -1,6 +1,6 @@ from typing import Optional -from fastapi import HTTPException +from fastapi import HTTPException, status from sqlalchemy.orm import Session from api.core.base.services import Service @@ -117,6 +117,22 @@ def fetch_blog_dislike(self, blog_id: str, user_id: str): .first() ) return blog_dislike + + def check_user_already_liked_blog(self, blog: Blog, user: Blog): + existing_like = self.fetch_blog_like(blog.id, user.id) + if isinstance(existing_like, BlogLike): + raise HTTPException( + detail="You have already liked this blog post", + status_code=status.HTTP_403_FORBIDDEN, + ) + + def check_user_already_disliked_blog(self, blog: Blog, user: Blog): + existing_dislike = self.fetch_blog_dislike(blog.id, user.id) + if isinstance(existing_dislike, BlogDislike): + raise HTTPException( + detail="You have already disliked this blog post", + status_code=status.HTTP_403_FORBIDDEN, + ) def num_of_likes(self, blog_id: str) -> int: """Get the number of likes a blog post has""" diff --git a/tests/v1/blog/test_dislike_blog_post.py b/tests/v1/blog/test_dislike_blog_post.py index 0e40f379f..82a5e2dd4 100644 --- a/tests/v1/blog/test_dislike_blog_post.py +++ b/tests/v1/blog/test_dislike_blog_post.py @@ -73,7 +73,9 @@ def make_request(blog_id, token): ) +@patch("api.v1.services.blog.BlogService.create_blog_dislike") def test_successful_dislike( + mock_create_blog_dislike, mock_db_session, test_user, test_blog, @@ -84,7 +86,10 @@ def test_successful_dislike( mock_db_session.query().filter().first.side_effect = [test_user, test_blog] # mock existing-blog-dislike AND new-blog-dislike - mock_db_session.query().filter_by().first.side_effect = [None, test_blog_dislike] + mock_db_session.query().filter_by().first.side_effect = None + + # mock created-blog-dislike + mock_create_blog_dislike.return_value = test_blog_dislike # mock dislike-count mock_db_session.query().filter_by().count.return_value = 1 @@ -128,7 +133,7 @@ def test_wrong_blog_id( access_token_user, ): mock_user_service.get_current_user = test_user - mock_blog_service.fetch = None + mock_db_session.query().filter().first.return_value = None ### TEST REQUEST WITH WRONG blog_id ### ### using random uuid instead of blog1.id ### diff --git a/tests/v1/blog/test_like_blog_post.py b/tests/v1/blog/test_like_blog_post.py index 866e63b36..9544592cf 100644 --- a/tests/v1/blog/test_like_blog_post.py +++ b/tests/v1/blog/test_like_blog_post.py @@ -74,7 +74,9 @@ def make_request(blog_id, token): ) # Test for successful like +@patch("api.v1.services.blog.BlogService.create_blog_like") def test_successful_like( + mock_create_blog_like, mock_db_session, test_user, test_blog, @@ -84,8 +86,11 @@ def test_successful_like( # mock current-user AND blog-post mock_db_session.query().filter().first.side_effect = [test_user, test_blog] - # mock existing-blog-like AND new-blog-like - mock_db_session.query().filter_by().first.side_effect = [None, test_blog_like] + # mock existing-blog-like + mock_db_session.query().filter_by().first.return_value = None + + # mock created-blog-like + mock_create_blog_like.return_value = test_blog_like # mock like-count mock_db_session.query().filter_by().count.return_value = 1 @@ -125,12 +130,13 @@ def test_double_like( # Test for wrong blog id def test_wrong_blog_id( + # mock_fetch_blog, mock_db_session, test_user, access_token_user, ): mock_user_service.get_current_user = test_user - mock_blog_service.fetch = None + mock_db_session.query().filter().first.return_value = None ### TEST REQUEST WITH WRONG blog_id ### ### using random uuid instead of blog1.id ### From 6c002fc7a48d5c41c59b1a4b4cd82998d45620cd Mon Sep 17 00:00:00 2001 From: Chime Date: Fri, 23 Aug 2024 12:52:25 +0100 Subject: [PATCH 07/24] Update billing plan endpoints schemas to show sample return data in docs --- api/v1/routes/billing_plan.py | 25 ++++++++++++++----------- api/v1/schemas/base_schema.py | 16 ++++++++++++++++ api/v1/schemas/plans.py | 29 ++++++++++++++++------------- api/v1/services/billing_plan.py | 4 ++-- 4 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 api/v1/schemas/base_schema.py diff --git a/api/v1/routes/billing_plan.py b/api/v1/routes/billing_plan.py index 701f91f9f..dd467996a 100644 --- a/api/v1/routes/billing_plan.py +++ b/api/v1/routes/billing_plan.py @@ -10,12 +10,15 @@ from api.v1.services.billing_plan import billing_plan_service from api.db.database import get_db from api.v1.services.user import user_service -from api.v1.schemas.plans import CreateSubscriptionPlan +from api.v1.schemas.plans import ( + CreateBillingPlanSchema, CreateBillingPlanResponse, GetBillingPlanListResponse +) + bill_plan = APIRouter(prefix="/organisations", tags=["Billing-Plan"]) -@bill_plan.get("/{organisation_id}/billing-plans", response_model=success_response) +@bill_plan.get("/{organisation_id}/billing-plans", response_model=GetBillingPlanListResponse) async def retrieve_all_billing_plans( organisation_id: str, db: Session = Depends(get_db) ): @@ -34,10 +37,10 @@ async def retrieve_all_billing_plans( ) -@bill_plan.post("/billing-plans", response_model=success_response) +@bill_plan.post("/billing-plans", response_model=CreateBillingPlanResponse) async def create_new_billing_plan( - request: CreateSubscriptionPlan, - current_user: User = Depends(user_service.get_current_super_admin), + request: CreateBillingPlanSchema, + _: User = Depends(user_service.get_current_super_admin), db: Session = Depends(get_db), ): """ @@ -53,11 +56,11 @@ async def create_new_billing_plan( ) -@bill_plan.patch("/billing-plans/{billing_plan_id}", response_model=success_response) +@bill_plan.patch("/billing-plans/{billing_plan_id}", response_model=CreateBillingPlanResponse) async def update_a_billing_plan( billing_plan_id: str, - request: CreateSubscriptionPlan, - current_user: User = Depends(user_service.get_current_super_admin), + request: CreateBillingPlanSchema, + _: User = Depends(user_service.get_current_super_admin), db: Session = Depends(get_db), ): """ @@ -76,7 +79,7 @@ async def update_a_billing_plan( @bill_plan.delete("/billing-plans/{billing_plan_id}", response_model=success_response) async def delete_a_billing_plan( billing_plan_id: str, - current_user: User = Depends(user_service.get_current_super_admin), + _: User = Depends(user_service.get_current_super_admin), db: Session = Depends(get_db), ): """ @@ -91,11 +94,11 @@ async def delete_a_billing_plan( ) -@bill_plan.get('/billing-plans/{billing_plan_id}', response_model=success_response) +@bill_plan.get('/billing-plans/{billing_plan_id}', response_model=CreateBillingPlanResponse) async def retrieve_single_billing_plans( billing_plan_id: str, db: Session = Depends(get_db), - current_user: User = Depends(user_service.get_current_user) + _: User = Depends(user_service.get_current_user) ): """ Endpoint to get single billing plan by id diff --git a/api/v1/schemas/base_schema.py b/api/v1/schemas/base_schema.py new file mode 100644 index 000000000..76fe51c7d --- /dev/null +++ b/api/v1/schemas/base_schema.py @@ -0,0 +1,16 @@ +from typing import List +from datetime import datetime +from pydantic import BaseModel + + +class ResponseBase(BaseModel): + status_code: int = 200 + success: bool + message: str + + +class PaginationBase(BaseModel): + limit: int + offset: int + pages: int + total_items: int diff --git a/api/v1/schemas/plans.py b/api/v1/schemas/plans.py index ce6bb4729..931b5b97b 100644 --- a/api/v1/schemas/plans.py +++ b/api/v1/schemas/plans.py @@ -1,8 +1,11 @@ from pydantic import BaseModel, validator from typing import List, Optional +from datetime import datetime +from api.v1.schemas.base_schema import ResponseBase -class CreateSubscriptionPlan(BaseModel): + +class CreateBillingPlanSchema(BaseModel): name: str description: Optional[str] = None price: int @@ -26,22 +29,22 @@ def validate_duration(cls, value): return v -class SubscriptionPlanResponse(CreateSubscriptionPlan): +class CreateBillingPlanReturnData(CreateBillingPlanSchema): id: str + created_at: datetime + updated_at: datetime class Config: from_attributes = True -class BillingPlanSchema(BaseModel): - id: str - organisation_id: str - name: str - price: float - currency: str - duration: str - description: Optional[str] = None - features: List[str] +class CreateBillingPlanResponse(ResponseBase): + data: CreateBillingPlanReturnData - class Config: - orm_mode = True \ No newline at end of file + +class GetBillingPlanData(BaseModel): + billing_plans: List[CreateBillingPlanReturnData] + + +class GetBillingPlanListResponse(ResponseBase): + data: GetBillingPlanData \ No newline at end of file diff --git a/api/v1/services/billing_plan.py b/api/v1/services/billing_plan.py index 252eed0d4..364094eb8 100644 --- a/api/v1/services/billing_plan.py +++ b/api/v1/services/billing_plan.py @@ -3,7 +3,7 @@ from api.v1.models.billing_plan import BillingPlan from typing import Any, Optional from api.core.base.services import Service -from api.v1.schemas.plans import CreateSubscriptionPlan +from api.v1.schemas.plans import CreateBillingPlanSchema from api.utils.db_validators import check_model_existence from fastapi import HTTPException, status @@ -11,7 +11,7 @@ class BillingPlanService(Service): """Product service functionality""" - def create(self, db: Session, request: CreateSubscriptionPlan): + def create(self, db: Session, request: CreateBillingPlanSchema): """ Create and return a new billing plan, ensuring a plan name can only exist once for each 'monthly' and 'yearly' duration, and cannot be created From c91bce49f7d4d89c9aac2c82bce2fcb071423cf8 Mon Sep 17 00:00:00 2001 From: Chime Date: Fri, 23 Aug 2024 14:21:15 +0100 Subject: [PATCH 08/24] Correct billing_plans to plans --- api/v1/schemas/plans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1/schemas/plans.py b/api/v1/schemas/plans.py index 931b5b97b..18bddb4a3 100644 --- a/api/v1/schemas/plans.py +++ b/api/v1/schemas/plans.py @@ -43,7 +43,7 @@ class CreateBillingPlanResponse(ResponseBase): class GetBillingPlanData(BaseModel): - billing_plans: List[CreateBillingPlanReturnData] + plans: List[CreateBillingPlanReturnData] class GetBillingPlanListResponse(ResponseBase): From 05dd2400996187f3041fe6a4f4ebcb18926df93d Mon Sep 17 00:00:00 2001 From: MikeSoft007 Date: Fri, 23 Aug 2024 22:47:41 +0200 Subject: [PATCH 09/24] feat: updated email notification for waitlist signu --- tests/v1/auth/test_magic_link.py | 79 -------------------------------- 1 file changed, 79 deletions(-) diff --git a/tests/v1/auth/test_magic_link.py b/tests/v1/auth/test_magic_link.py index aedc43fe7..ffbfde3dc 100644 --- a/tests/v1/auth/test_magic_link.py +++ b/tests/v1/auth/test_magic_link.py @@ -60,82 +60,3 @@ def test_request_magic_link(mock_user_service, mock_db_session): response = client.post(MAGIC_ENDPOINT, json={"email": "notauser@gmail.com"}) assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json().get("message") == "User not found" - - - -# import pytest -# from fastapi.testclient import TestClient -# from unittest.mock import patch, MagicMock -# from main import app -# from api.v1.models.user import User -# from api.v1.services.user import user_service -# from uuid_extensions import uuid7 -# from api.db.database import get_db -# from fastapi import status -# from datetime import datetime, timezone - - -# client = TestClient(app) -# MAGIC_ENDPOINT = '/api/v1/auth/magic-link' - - -# @pytest.fixture -# def mock_db_session(): -# """Fixture to create a mock database session.""" - -# with patch("api.v1.services.user.get_db", autospec=True) as mock_get_db: -# mock_db = MagicMock() -# # mock_get_db.return_value.__enter__.return_value = mock_db -# app.dependency_overrides[get_db] = lambda: mock_db -# yield mock_db -# app.dependency_overrides = {} - - -# @pytest.fixture -# def mock_user_service(): -# """Fixture to create a mock user service.""" - -# with patch("api.v1.services.user.user_service", autospec=True) as mock_service: -# yield mock_service - -# @pytest.mark.usefixtures("mock_db_session", "mock_user_service") -# def test_request_magic_link(mock_user_service, mock_db_session): -# """Test for requesting magic link""" - -# # Create a mock user -# mock_user = User( -# id=str(uuid7()), -# email="testuser1@gmail.com", -# password=user_service.hash_password("Testpassword@123"), -# first_name='Test', -# last_name='User', -# is_active=False, -# is_superadmin=False, -# created_at=datetime.now(timezone.utc), -# updated_at=datetime.now(timezone.utc) -# ) -# mock_db_session.query.return_value.filter.return_value.first.return_value = mock_user - -# with patch("api.utils.send_mail.smtplib.SMTP_SSL") as mock_smtp: -# # Configure the mock SMTP server -# mock_smtp_instance = MagicMock() -# mock_smtp.return_value = mock_smtp_instance - - -# # Test for requesting magic link for an existing user -# magic_login = client.post(MAGIC_ENDPOINT, json={ -# "email": mock_user.email -# }) -# assert magic_login.status_code == status.HTTP_200_OK -# response = magic_login.json() -# #assert response.get("status_code") == status.HTTP_200_OK # check for the right response before proceeding -# assert response.get("message") == f"Magic link sent to {mock_user.email}" - -# # Test for requesting magic link for a non-existing user -# mock_db_session.query.return_value.filter.return_value.first.return_value = None -# magic_login = client.post(MAGIC_ENDPOINT, json={ -# "email": "notauser@gmail.com" -# }) -# response = magic_login.json() -# assert response.get("status_code") == status.HTTP_404_NOT_FOUND # check for the right response before proceeding -# assert response.get("message") == "User not found" From 9023e9414f2f54295ed16d1a53d360dc2841fded Mon Sep 17 00:00:00 2001 From: MikeSoft007 Date: Fri, 23 Aug 2024 22:59:13 +0200 Subject: [PATCH 10/24] feat: updated email notification for waitlist signu --- .../{waitlist.html => waitlists.html} | 0 api/v1/routes/waitlist.py | 59 +++++++++++++++---- 2 files changed, 48 insertions(+), 11 deletions(-) rename api/core/dependencies/email/templates/{waitlist.html => waitlists.html} (100%) diff --git a/api/core/dependencies/email/templates/waitlist.html b/api/core/dependencies/email/templates/waitlists.html similarity index 100% rename from api/core/dependencies/email/templates/waitlist.html rename to api/core/dependencies/email/templates/waitlists.html diff --git a/api/v1/routes/waitlist.py b/api/v1/routes/waitlist.py index 5f8453805..6c9355e2a 100644 --- a/api/v1/routes/waitlist.py +++ b/api/v1/routes/waitlist.py @@ -22,6 +22,19 @@ waitlist = APIRouter(prefix="/waitlist", tags=["Waitlist"]) def process_waitlist_signup(user: WaitlistAddUserSchema, db: Session): + """ + Process a waitlist signup request. + + Args: + - user (WaitlistAddUserSchema): The user details to be added to the waitlist. + - db (Session): The database session. + + Returns: + - db_user: The added user object. + + Raises: + - HTTPException: If the full name is not provided or if the email is already registered. + """ if not user.full_name: logger.error("Full name is required") raise HTTPException( @@ -55,6 +68,24 @@ async def waitlist_signup( user: WaitlistAddUserSchema, db: Session = Depends(get_db) ): + """ + Add a user to the waitlist. + + Args: + - user (WaitlistAddUserSchema): The user details to be added to the waitlist. + + Returns: + - success_response: A success response with a message and status code. + + Example: + ``` + curl -X POST \ + http://localhost:8000/waitlist/ \ + -H 'Content-Type: application/json' \ + -d '{"email": "user@example.com", "full_name": "John Doe"}' + ``` + """ + db_user = process_waitlist_signup(user, db) if db_user: cta_link = 'https://anchor-python.teams.hng.tech/about-us' @@ -62,7 +93,7 @@ async def waitlist_signup( background_tasks.add_task( send_email, recipient=user.email, - template_name='waitlist.html', + template_name='waitlists.html', subject='Welcome to HNG Waitlist', context={ 'name': user.full_name, @@ -82,19 +113,25 @@ def admin_add_user_to_waitlist( db: Session = Depends(get_db), ): """ - Manually adds a user to the waitlist. - This endpoint allows an admin to add a user to the waitlist. + Manually add a user to the waitlist as an admin. - Parameters: - - item: WaitlistAddUserSchema - The details of the user to be added to the waitlist. - - admin: User (Depends on get_super_admin) - The current admin making the request. This is a dependency that provides the current admin context. + Args: + - item (WaitlistAddUserSchema): The user details to be added to the waitlist. + - admin (User): The current admin making the request. Returns: - - 201: User added successfully - - 400: Validation error - - 403: Forbidden + - success_response: A success response with a message and status code. + + Raises: + - HTTPException: If the full name is not provided or if the email is already registered. + + Example: + ``` + curl -X POST \ + http://localhost:8000/waitlist/admin \ + -H 'Content-Type: application/json' \ + -d '{"email": "user@example.com", "full_name": "John Doe"}' + ``` """ try: From 6fd2c2dc54389d0e10f8dcb0814b93c5366927fc Mon Sep 17 00:00:00 2001 From: Joshua Oloton Date: Fri, 23 Aug 2024 23:48:06 +0100 Subject: [PATCH 11/24] feat: implement status page endpoints --- api/v1/models/__init__.py | 1 + api/v1/models/api_status.py | 11 +++ api/v1/routes/__init__.py | 2 + api/v1/routes/api_status.py | 34 +++++++ api/v1/schemas/api_status.py | 55 +++++++++++ api/v1/services/api_status.py | 119 +++++++++++++++++++++++ tests/v1/status_page/test_post_status.py | 72 ++++++++++++++ update_api_status.py | 63 ++++++++++++ 8 files changed, 357 insertions(+) create mode 100644 api/v1/models/api_status.py create mode 100644 api/v1/routes/api_status.py create mode 100644 api/v1/schemas/api_status.py create mode 100644 api/v1/services/api_status.py create mode 100644 tests/v1/status_page/test_post_status.py create mode 100644 update_api_status.py diff --git a/api/v1/models/__init__.py b/api/v1/models/__init__.py index 943c3e100..1201de2e9 100644 --- a/api/v1/models/__init__.py +++ b/api/v1/models/__init__.py @@ -1,4 +1,5 @@ from api.v1.models.activity_logs import ActivityLog +from api.v1.models.api_status import APIStatus from api.v1.models.billing_plan import BillingPlan from api.v1.models.comment import Comment, CommentLike, CommentDislike from api.v1.models.contact_us import ContactUs diff --git a/api/v1/models/api_status.py b/api/v1/models/api_status.py new file mode 100644 index 000000000..ce41f84e4 --- /dev/null +++ b/api/v1/models/api_status.py @@ -0,0 +1,11 @@ +from sqlalchemy import Column, DateTime, String, Text, Numeric, func +from api.v1.models.base_model import BaseTableModel + +class APIStatus(BaseTableModel): + __tablename__ = "api_status" + + api_group = Column(String, nullable=False) + status = Column(String, nullable=False) + last_checked = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) + response_time = Column(Numeric, nullable=True) + details = Column(Text, nullable=True) diff --git a/api/v1/routes/__init__.py b/api/v1/routes/__init__.py index 8b7ee2a7e..e82a87a39 100644 --- a/api/v1/routes/__init__.py +++ b/api/v1/routes/__init__.py @@ -2,6 +2,7 @@ from api.v1.routes.privacy import privacies from api.v1.routes.team import team from fastapi import APIRouter +from api.v1.routes.api_status import api_status from api.v1.routes.auth import auth from api.v1.routes.faq_inquiries import faq_inquiries from api.v1.routes.newsletter import newsletter, news_sub @@ -48,6 +49,7 @@ api_version_one = APIRouter(prefix="/api/v1") +api_version_one.include_router(api_status) api_version_one.include_router(auth) api_version_one.include_router(faq_inquiries) api_version_one.include_router(google_auth) diff --git a/api/v1/routes/api_status.py b/api/v1/routes/api_status.py new file mode 100644 index 000000000..baf670124 --- /dev/null +++ b/api/v1/routes/api_status.py @@ -0,0 +1,34 @@ +from typing import Annotated +from api.db.database import get_db +from api.v1.schemas.api_status import APIStatusPost +from api.v1.services.api_status import APIStatusService +from api.utils.success_response import success_response +from fastapi import APIRouter, Depends, status +from sqlalchemy.orm import Session + +api_status = APIRouter(prefix='/api-status', tags=['API Status']) + + +@api_status.get('', response_model=success_response, status_code=200) +async def get_api_status(db: Annotated[Session, Depends(get_db)]): + all_status = APIStatusService.fetch_all(db) + + return success_response( + message='All API Status fetched successfully', + data=all_status, + status_code=status.HTTP_200_OK + ) + + +@api_status.post('', response_model=success_response, status_code=201) +async def post_api_status( + schema: APIStatusPost, + db: Annotated[Session, Depends(get_db)] +): + new_status = APIStatusService.upsert(db, schema) + + return success_response( + message='API Status created successfully', + data=new_status, + status_code=status.HTTP_201_CREATED + ) diff --git a/api/v1/schemas/api_status.py b/api/v1/schemas/api_status.py new file mode 100644 index 000000000..749e8303f --- /dev/null +++ b/api/v1/schemas/api_status.py @@ -0,0 +1,55 @@ +from decimal import Decimal +from pydantic import BaseModel, Field, PositiveInt, PositiveFloat, ConfigDict, StringConstraints + +from typing import List, Optional +from datetime import datetime + +class APIStatusPost(BaseModel): + """ + Pydantic model for creating a new API status. + + This model is used for validating and serializing data when creating + a new API status in the system. It ensures that the `status` field is a required + string, the `description` is an optional string, and the `created_at` field + is a datetime object that indicates when the API status was created. + + Attributes: + status (str): The status of the API. + description (Optional[str]): An optional description of the API status. + created_at (datetime): The date and time when the API status was created. + """ + + api_group: str + status: str + response_time: Optional[Decimal] = None + details: Optional[str] = None + + class Config: + from_attributes = True + populate_by_name = True + + +class APIStatusUpdate(BaseModel): + """ + Pydantic model for updating an existing API status. + + This model is used for validating and serializing data when updating + an existing API status in the system. It ensures that the `status` field is a required + string, the `description` is an optional string, and the `created_at` field + is a datetime object that indicates when the API status was created. + + Attributes: + status (str): The status of the API. + description (Optional[str]): An optional description of the API status. + created_at (datetime): The date and time when the API status was created. + """ + + api_group: str = Field(..., alias="apiGroup") + status: str + last_checked: Optional[datetime] = None + response_time: Optional[Decimal] = None + details: Optional[str] = None + + class Config: + from_attributes = True + populate_by_name = True \ No newline at end of file diff --git a/api/v1/services/api_status.py b/api/v1/services/api_status.py new file mode 100644 index 000000000..a46c288c4 --- /dev/null +++ b/api/v1/services/api_status.py @@ -0,0 +1,119 @@ +from typing import Any, List, Optional +from api.core.base.services import Service +from sqlalchemy.orm import Session +from api.v1.models.api_status import APIStatus +from api.v1.schemas.api_status import APIStatusPost +from fastapi import HTTPException + + +class APIStatusService(Service): + + @staticmethod + def fetch(db: Session, status_id) -> APIStatus: + status = db.query(APIStatus).get(status_id).first() + + return status + + @staticmethod + def fetch_by_api_group(db: Session, api_group) -> APIStatus: + status = db.query(APIStatus).filter(APIStatus.api_group == api_group).first() + + return status + + @staticmethod + def fetch_all(db: Session, **query_params: Optional[Any]) -> List[APIStatus]: + query = db.query(APIStatus) + + # Enable filter by query parameter + if query_params: + for column, value in query_params.items(): + if hasattr(APIStatus, column) and value: + query = query.filter(getattr(APIStatus, column).ilike(f"%{value}%")) + + return query.all() + + @staticmethod + def upsert(db: Session, schema: APIStatusPost) -> APIStatus: + """ + Upsert an API status record into the database. + + This method attempts to insert a new API status record based on the provided schema. + If a record with the same api_group already exists, it will be updated with the new values. + + Parameters: + db (Session): The SQLAlchemy database session to perform the operation. + schema (APIStatusPost): The data model containing the API status information. + + Returns: + APIStatus: The created or updated API status record. + + Raises: + SQLAlchemyError: If there is an issue with the database operation. + """ + + try: + existing_status = db.query(APIStatus).filter(APIStatus.api_group == schema.api_group).first() + + if existing_status: + existing_status.api_group = schema.api_group + existing_status.status = schema.status + existing_status.response_time = schema.response_time + existing_status.details = schema.details + + db.commit() + db.refresh(existing_status) + return existing_status + + status = APIStatus( + api_group=schema.api_group, + status=schema.status, + response_time=schema.response_time, + details=schema.details, + ) + db.add(status) + db.commit() + db.refresh(status) + return status + + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="A database error occurred." + ) + + # @staticmethod + # def update(db: Session, schema: APIStatusPost) -> APIStatus: + # status = APIStatus( + # api_group=schema.api_group, + # status=schema.status, + # response_time=schema.response_time, + # details=schema.details, + # ) + # db.add(status) + # db.commit() + # db.refresh(status) + # return status + + @staticmethod + def delete_by_api_group(db: Session, api_group) -> APIStatus: + status = db.query(APIStatus).filter(APIStatus.api_group == api_group).first() + db.delete(status) + db.commit() + return status + + @staticmethod + def delete_all(db: Session) -> List[APIStatus]: + statuses = db.query(APIStatus).all() + for status in statuses: + db.delete(status) + db.commit() + return status + + @staticmethod + def create(): + pass + + @staticmethod + def update(): + pass \ No newline at end of file diff --git a/tests/v1/status_page/test_post_status.py b/tests/v1/status_page/test_post_status.py new file mode 100644 index 000000000..fabc7919f --- /dev/null +++ b/tests/v1/status_page/test_post_status.py @@ -0,0 +1,72 @@ +from datetime import datetime, timezone +from unittest.mock import MagicMock, patch + +import pytest +from fastapi.testclient import TestClient +from sqlalchemy.orm import Session +from uuid_extensions import uuid7 + +from api.db.database import get_db +from api.v1.models.contact_us import ContactUs +from api.v1.models.api_status import APIStatus +from main import app + + + +@pytest.fixture +def db_session_mock(): + db_session = MagicMock(spec=Session) + return db_session + +@pytest.fixture +def client(db_session_mock): + app.dependency_overrides[get_db] = lambda: db_session_mock + client = TestClient(app) + yield client + app.dependency_overrides = {} + + +def mock_post_api_status(): + return APIStatus( + id=str(uuid7()), + api_group="Blog API", + status="Down", + response_time=None, + details="API not responding (HTTP 503)" + ) + + +@patch("api.v1.services.api_status.APIStatusService.upsert") +def test_post_api_status(mock_create, db_session_mock, client): + """Tests the POST /api/v1/api-status endpoint to ensure successful posting of API status""" + + db_session_mock.add.return_value = None + db_session_mock.commit.return_value = None + db_session_mock.refresh.return_value = None + + mock_create.return_value = mock_post_api_status() + # mock_check_existing.return_value = None + + response = client.post('/api/v1/api-status', json={ + "api_group": "Blog API", + "status": "Down", + "response_time": None, + "details": "API not responding (HTTP 503)" + }) + + assert response.status_code == 201 + +@patch("api.v1.services.api_status.APIStatusService.fetch_all") +def test_get_api_status(mock_fetch, db_session_mock, client): + """Tests the GET /api/v1/api-status endpoint to ensure retrieval of API status""" + + db_session_mock.add.return_value = None + db_session_mock.commit.return_value = None + db_session_mock.refresh.return_value = None + + mock_fetch.return_value = mock_post_api_status() + + response = client.get('/api/v1/api-status') + + print(response.json()) + assert response.status_code == 200 diff --git a/update_api_status.py b/update_api_status.py new file mode 100644 index 000000000..18b2245b8 --- /dev/null +++ b/update_api_status.py @@ -0,0 +1,63 @@ +import requests +import json + +# Define the API endpoint +# BASE_URL = "https://staging.api-python.boilerplate.hng.tech" +BASE_URL = "http://127.0.0.1:7001" +API_ENDPOINT = f"{BASE_URL}/api/v1/api-status" + + +# Function to parse result.json and post the data to the endpoint +def parse_and_post_results(): + try: + with open('result.json') as f: + data = json.load(f) + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Error reading result.json: {e}") + return + + for item in data.get('run', {}).get('executions', []): + api_group = item.get('item', {}).get('name') + status_code = item.get('response', {}).get('code') + response_time = item.get('item', {}).get('responseTime') + + if status_code >= 500: + status = 'Down' + details = item.get('response', {}).get('status', 'No status available') + else: + assertions = item.get('assertions', []) + status = "Operational" + details = "All tests passed" + + for assertion in assertions: + if assertion.get('error'): + status = "Degraded" + if 'Response time' in assertion.get('assertion', ''): + details = 'High response time detected' + elif 'available' in assertion.get('assertion', ''): + details = 'API is not available' + else: + details = f"Test failed: {assertion.get('error').get('message', 'No error message')}" + break + + payload = { + "api_group": api_group, + "status": status, + "response_time": response_time, + "details": details, + } + + try: + # send a POST request to create + response = requests.post(API_ENDPOINT, json=payload) + + # Check response status + if response.status_code in (200, 201): + print(f"Successfully updated/created record for {api_group}.") + else: + print(f"Failed to update/create record for {api_group}: {response.content}") + except requests.RequestException as e: + print(f"Error posting data to API: {e}") + +# Run the function +parse_and_post_results() From fe695c46cc35107acb61eb53fe73d0af2ae83449 Mon Sep 17 00:00:00 2001 From: Utibe Effiong Date: Fri, 23 Aug 2024 23:56:23 +0100 Subject: [PATCH 12/24] boilerplate status page --- ...rplate-status-page.postman_collection.json | 7674 +++++++++++++++++ 1 file changed, 7674 insertions(+) create mode 100644 qa_tests/Boilerplate-status-page.postman_collection.json diff --git a/qa_tests/Boilerplate-status-page.postman_collection.json b/qa_tests/Boilerplate-status-page.postman_collection.json new file mode 100644 index 000000000..6514f847b --- /dev/null +++ b/qa_tests/Boilerplate-status-page.postman_collection.json @@ -0,0 +1,7674 @@ +{ + "info": { + "_postman_id": "c8ad1ef2-4d79-47ce-9847-92e36a7b959e", + "name": "Boilerplate-status-page", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "23968045", + "_collection_link": "https://crimson-star-498952.postman.co/workspace/HNG-Internship~4ced83f4-98e1-4fdc-9aaf-2ea4e3bf860b/collection/23968045-c8ad1ef2-4d79-47ce-9847-92e36a7b959e?action=share&source=collection_link&creator=23968045" + }, + "item": [ + { + "name": "Waitlist", + "item": [ + { + "name": "Waitlist-signup", + "item": [ + { + "name": "Register", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + " pm.test('Generate biolerplate random email', function(){\r", + "\r", + " let randomEmail=function makeEmail() { \r", + " var strValues=\"abcd\"; \r", + " let name='utest'\r", + " var strEmail = \"@gmail.com\"; \r", + " var strTmp; \r", + " for (var i=0;i<10;i++) { \r", + " strTmp = strValues.charAt(Math.round(strValues.length*Math.random())); \r", + " strEmail =strTmp +strEmail; \r", + " }\r", + " return strEmail\r", + " }\r", + "\r", + " \r", + "\r", + " pm.collectionVariables.set('boilerPlateRandomEmail',randomEmail())\r", + "\r", + " })" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "\r", + "\r", + "pm.test(\"set access token and id\", function () {\r", + " let userAccessToken=pm.response.json().access_token\r", + " let userId=pm.response.json().data.user.id\r", + "\r", + " pm.collectionVariables.set('userAccessToken',userAccessToken)\r", + " pm.collectionVariables.set('userId',userId)\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"first_name\": \"Lordwill\",\r\n \"last_name\": \"Ben\",\r\n \"email\": \"{{boilerPlateRandomEmail}}\",\r\n \"password\": \"paS$word1\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{boilerplateUrl}}/api/v1/auth/register", + "host": [ + "{{boilerplateUrl}}" + ], + "path": [ + "api", + "v1", + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "Waitlist", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"API is available 200\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "pm.test(\"Content-Type is present\", function () {\r", + " pm.response.to.have.header(\"Content-Type\");\r", + "});\r", + "pm.test(\"Response time is less than 3000ms\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(3000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{boilerPlateRandomEmail}}\",\r\n \"full_name\": \"string\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{boilerplateUrl}}/api/v1/waitlist/", + "host": [ + "{{boilerplateUrl}}" + ], + "path": [ + "api", + "v1", + "waitlist", + "" + ] + } + }, + "response": [] + }, + { + "name": "Waitlist-Error Check(same Email)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 401 or 422\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400,401, 422]);\r", + "});\r", + "pm.test(\"Content-Type is present\", function () {\r", + " pm.response.to.have.header(\"Content-Type\");\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{boilerPlateRandomEmail}}\",\r\n \"full_name\": \"string\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{boilerplateUrl}}/api/v1/waitlist/", + "host": [ + "{{boilerplateUrl}}" + ], + "path": [ + "api", + "v1", + "waitlist", + "" + ] + } + }, + "response": [] + }, + { + "name": "Waitlist-Error Check(Empty fields)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 401 or 422\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 401, 422]);\r", + "});\r", + "pm.test(\"Content-Type is present\", function () {\r", + " pm.response.to.have.header(\"Content-Type\");\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"\",\r\n \"full_name\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{boilerplateUrl}}/api/v1/waitlist/", + "host": [ + "{{boilerplateUrl}}" + ], + "path": [ + "api", + "v1", + "waitlist", + "" + ] + } + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Contacts us", + "item": [ + { + "name": "Retrieve all contact us", + "item": [ + { + "name": "Retrieve all contact us", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"API is available 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Content-Type is present\", function () {\r", + " pm.response.to.have.header(\"Content-Type\");\r", + "});\r", + "pm.test(\"Response time is less than 3000ms\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(3000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{adminAccessTokenBoiler}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{boilerplateUrl}}/api/v1/contact", + "host": [ + "{{boilerplateUrl}}" + ], + "path": [ + "api", + "v1", + "contact" + ] + } + }, + "response": [] + }, + { + "name": "Retrieve all contact us --Error check(Invalid token)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "\r", + "pm.test(\"Check for error [400,401,422]\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400,422,401])\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMDY2YzhiMTctMTA0My03OWQ5LTgwMDAtZDU0MDFhYWFlODNlIiwiZXhwIjoxNzI0NDY0NjU3LCJ0eXBlIjoiYWNjZXNzIn0.ds605XJ3NwMQquYuGkJfDaMWbBIStsMCuQq-apkM_-M", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{boilerplateUrl}}/api/v1/contact", + "host": [ + "{{boilerplateUrl}}" + ], + "path": [ + "api", + "v1", + "contact" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Create contact us", + "item": [ + { + "name": "create contact us", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"API is available 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "pm.test(\"Response time is less than 3000ms\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(3000);\r", + "});\r", + "pm.test(\"Body matches string\", function () {\r", + " pm.expect(pm.response.text()).to.include(\"SUCCESS\");\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"full_name\": \"string\",\r\n \"email\": \"user@example.com\",\r\n \"phone_number\": \"string\",\r\n \"message\": \"string\",\r\n \"org_id\": \"string\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{boilerplateUrl}}/api/v1/contact", + "host": [ + "{{boilerplateUrl}}" + ], + "path": [ + "api", + "v1", + "contact" + ] + } + }, + "response": [] + }, + { + "name": "create contact us--Error check(empty field)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Error check [422]\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "pm.test(\"Response time is less than 500ms\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(500);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"full_name\": \"\",\r\n \"email\": \"\",\r\n \"phone_number\": \"\",\r\n \"message\": \"\",\r\n \"org_id\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{boilerplateUrl}}/api/v1/contact", + "host": [ + "{{boilerplateUrl}}" + ], + "path": [ + "api", + "v1", + "contact" + ] + } + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Authentication - Spartacus", + "item": [ + { + "name": "Sign Up User (Boilerplate)", + "item": [ + { + "name": "BE-PY-01_Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});\r", + "\r", + "\r", + "pm.test(\"Body is correct\", function () {\r", + " pm.response.to.have.body('{\"message\":\"Welcome to API\",\"data\":{\"URL\":\"\"},\"status_code\":200}'\r", + " );\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://staging.api-python.boilerplate.hng.tech/", + "protocol": "https", + "host": [ + "staging", + "api-python", + "boilerplate", + "hng", + "tech" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Register new user", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});\r", + "\r", + "const {data} = pm.response.json();\r", + "pm.collectionVariables.set('xuser-k', data.user.email);\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "let date = Date.now();\r", + "let email = 'john' + date + '@doe.com';\r", + "pm.collectionVariables.set('useremail-k', email);\r", + "\r", + "let pwd = 'JohnDoe@123';\r", + "pm.collectionVariables.set('userpwd-k', pwd);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\",\r\n \"first_name\": \"John\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Required Fields (email empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"\",\r\n \"password\": \"{{userpwd-k}}\",\r\n \"first_name\": \"John\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-04_Required Fields (password empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"\",\r\n \"first_name\": \"John4\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-06_Required Fields (first name empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\",\r\n \"first_name\": \"\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-07_Required Fields (last name empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\",\r\n \"first_name\": \"John\",\r\n \"last_name\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-08_Required Fields (all empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"\",\r\n \"password\": \"\",\r\n \"first_name\": \"\",\r\n \"last_name\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-09_Invalid Email", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"john2@doe\",\r\n \"password\": \"{{userpwd-k}}\",\r\n \"first_name\": \"John\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-10_Invalid Password (only 4 alphaNum chars)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "\r", + "pm.test(\"Invalid Input\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"Jo@1\",\r\n \"first_name\": \"John\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-11_Invalid Password (only 8 alpha chars)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"JohnJohn\",\r\n \"first_name\": \"John\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-12_Register Existing Email", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"User with this email already exists\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{xuser-k}}\",\r\n \"password\": \"{{userpwd-k}}\",\r\n \"first_name\": \"Kate\",\r\n \"last_name\": \"Flynn\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Sign Up Super Admin (Boilerplate)", + "item": [ + { + "name": "BE-PY-01_Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://staging.api-python.boilerplate.hng.tech/", + "protocol": "https", + "host": [ + "staging", + "api-python", + "boilerplate", + "hng", + "tech" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Register new super admin", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});\r", + "\r", + "const {data} = pm.response.json();\r", + "pm.collectionVariables.set('xadmin-k', data.user.email);\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "let date = Date.now();\r", + "let email = 'admin' + date + '@doe.com';\r", + "pm.collectionVariables.set('adminemail-k', email);\r", + "\r", + "let pwd = 'JohnDoe@123';\r", + "pm.collectionVariables.set('adminpwd-k', pwd);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"{{adminpwd-k}}\",\r\n \"first_name\": \"Admin\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Required Fields (email empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"\",\r\n \"password\": \"{{adminpwd-k}}\",\r\n \"first_name\": \"Admin\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-04_Required Fields (password empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"\",\r\n \"first_name\": \"Admin4\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-06_Required Fields (first name empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"{{adminpwd-k}}\",\r\n \"first_name\": \"\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-07_Required Fields (last name empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"{{adminpwd-k}}\",\r\n \"first_name\": \"John\",\r\n \"last_name\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-08_Required Fields (all empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"\",\r\n \"password\": \"\",\r\n \"first_name\": \"\",\r\n \"last_name\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-09_Invalid Email", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"john2@doe\",\r\n \"password\": \"{{adminpwd-k}}\",\r\n \"first_name\": \"Admin\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-10_Invalid Password (only 4 alphaNum chars)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "\r", + "pm.test(\"Invalid Input\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"Jo@1\",\r\n \"first_name\": \"Admin\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-11_Invalid Password (only 8 alpha chars)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"AdminAdmin\",\r\n \"first_name\": \"Admin\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-12_Register Existing Email", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"User with this email already exists\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{xadmin-k}}\",\r\n \"password\": \"{{adminpwd-k}}\",\r\n \"first_name\": \"Kate\",\r\n \"last_name\": \"Flynn\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Login (Boilerplate)", + "item": [ + { + "name": "BE-PY-01_Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://staging.api-python.boilerplate.hng.tech/", + "protocol": "https", + "host": [ + "staging", + "api-python", + "boilerplate", + "hng", + "tech" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Login existing user", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('token-k', access_token);\r", + "\r", + "pm.test(\"Has a token\", function(){\r", + " return 'access_token';\r", + " \r", + "})\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Required Fields (email empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"\",\r\n \"password\": \"{{userpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-04_Required Fields (password empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Bad Request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400,422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-08_Required Fields (all empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"\",\r\n \"password\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-09_Invalid Email", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"john@doe\",\r\n \"password\": \"{{userpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-10_Invalid Password (only 4 alphaNum chars)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"Jo@1\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-11_Invalid Password (only 8 alpha chars)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-length": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"JohnJohn\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Refresh & Logout (Boilerplate)", + "item": [ + { + "name": "BE-PY-01_Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://staging.api-python.boilerplate.hng.tech/", + "protocol": "https", + "host": [ + "staging", + "api-python", + "boilerplate", + "hng", + "tech" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Login existing user", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('token-k', access_token);\r", + "\r", + "pm.test(\"Has a token\", function(){\r", + " return 'access_token';\r", + " \r", + "})\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Refresh Access Token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('token2-k', access_token);\r", + "\r", + "// pm.test(\"New token generated?\", function(){\r", + "// return 'access_token';\r", + " \r", + "// })\r", + "\r", + "pm.test(\"Is the token refreshed?\", function () {\r", + " 'token-k' != 'token2-k';\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/refresh-access-token", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "refresh-access-token" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Request Sign In Token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('token2-k', access_token);\r", + "\r", + "// pm.test(\"New token generated?\", function(){\r", + "// return 'access_token';\r", + " \r", + "// })\r", + "\r", + "pm.test(\"Is the token refreshed?\", function () {\r", + " 'token-k' != 'token2-k';\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/request-token", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "request-token" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Verify Sign In Token with invalid token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Token Invalid or Expired\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([404, 422]);\r", + "});\r", + "\r", + "\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('token2-k', access_token);\r", + "\r", + "// pm.test(\"New token generated?\", function(){\r", + "// return 'access_token';\r", + " \r", + "// })\r", + "\r", + "pm.test(\"Is the token refreshed?\", function () {\r", + " 'token-k' != 'token2-k';\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"token\": \" \"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/verify-token", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "verify-token" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Login existing user Copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('token-k', access_token);\r", + "\r", + "pm.test(\"Has a token\", function(){\r", + " return 'access_token';\r", + " \r", + "})\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Logout existing user", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "// const {access_token} = pm.response.json();\r", + "// pm.collectionVariables.set('token-k', access_token);\r", + "\r", + "pm.test(\"Has a token\", function(){\r", + " return 'access_token';\r", + " \r", + "})\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/logout", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "logout" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Magic Link (Boilerplate)", + "item": [ + { + "name": "BE-PY-01_Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://staging.api-python.boilerplate.hng.tech/", + "protocol": "https", + "host": [ + "staging", + "api-python", + "boilerplate", + "hng", + "tech" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Request Magic Link", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "// pm.test(\"Parse access token and store in collectionVariables\", () => {\r", + "// const responseJson = pm.response.json();\r", + "// const magic = responseJson.data[\"magic-link\"];\r", + " \r", + "// var fields = magic.split('=');\r", + "\r", + "// var magic_link = fields[1];\r", + "\r", + "// pm.collectionVariables.set('magic_link', magic_link);\r", + "\r", + "// });\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/magic-link", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "magic-link" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Verify Magic Link with wrong link", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid link or Could not validate credentials;\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([401, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"token\": \" \"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/magic-link/verify?token={{magic_link}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "magic-link", + "verify" + ], + "query": [ + { + "key": "token", + "value": "{{magic_link}}" + } + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-06_Required Fields (email empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Invalid Input\", function () {\r", + " pm.response.to.have.status(422);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/magic-link", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "magic-link" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Change Password (Boilerplate)", + "item": [ + { + "name": "BE-PY-01_Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://staging.api-python.boilerplate.hng.tech/", + "protocol": "https", + "host": [ + "staging", + "api-python", + "boilerplate", + "hng", + "tech" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Confirm Old (before change)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\"\r\n \r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-04_Change Password (wrong old pwd)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Bad Request, Invalid Old Password\", function () {\r", + " pm.response.to.have.status(400);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"old_password\": \"JackDoe@123\",\r\n \"new_password\": \"JaneDoe@456\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/change-password", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "change-password" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Change Password", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"old_password\": \"{{userpwd-k}}\",\r\n \"new_password\": \"JaneDoe@456\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/change-password", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "change-password" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-05_Confirm Old Pwd (after change)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Bad Request, Invalid Password\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-06_Confirm New Pwd (after change)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"JaneDoe@456\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-00_Reset Password Test", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"old_password\": \"JaneDoe@456\",\r\n \"new_password\": \"{{userpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/change-password", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "change-password" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "noauth" + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ] + } + } + ] + }, + { + "name": "Forget Password (Boilerplate)", + "item": [ + { + "name": "BE-PY-01_Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://staging.api-python.boilerplate.hng.tech/", + "protocol": "https", + "host": [ + "staging", + "api-python", + "boilerplate", + "hng", + "tech" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Request Forget Password", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Password reset link sent successfully\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "\r", + "// pm.test(\"Get reset link\", () => {\r", + "// const responseJson = pm.response.json();\r", + "// const reset = responseJson.data[\"reset_link\"];\r", + " \r", + "// var fields = reset.split('=');\r", + "\r", + "// var reset_link = fields[1];\r", + "\r", + "// pm.collectionVariables.set('reset_link', reset_link);\r", + "\r", + "// });\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/forgot-password", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "forgot-password" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-04_Process Forget Password Link", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Token is valid for user xyz\", function () {\r", + " pm.response.to.have.status(302);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.execution.skipRequest();" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/forget-password?token={{reset_link}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "forget-password" + ], + "query": [ + { + "key": "token", + "value": "{{reset_link}}" + } + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Change Password", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.execution.skipRequest();\r", + "\r", + "let pwd2 = 'JohnDoe@1234';\r", + "pm.collectionVariables.set('userpwd2-k', pwd2);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "\r\n{\r\n \"new_password\": \"{{userpwd2-k}}\",\r\n \"confirm_password\": \"{{userpwd2-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/forget-password?token={{reset_link}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "forget-password" + ], + "query": [ + { + "key": "token", + "value": "{{reset_link}}" + } + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-05_Reset Password with invalid token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Bad Request - Reset Token Invalid\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"reset_token\": \"stringstringstringstringstrings\",\r\n \"new_password\": \"{{userpwd-k}}\",\r\n \"confirm_password\": \"{{userpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/reset-password", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "reset-password" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "noauth" + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ] + } + ] + }, + { + "name": "Tests - Spartacus", + "item": [ + { + "name": "Run HNG Tests", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"All Tests successfully Executed\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 5 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(5000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/hng-test", + "host": [ + "{{host-k}}" + ], + "path": [ + "hng-test" + ] + } + }, + "response": [] + }, + { + "name": "Run Tests", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"All Tests successfully Executed\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 30 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(30000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/all/run-tests", + "host": [ + "{{host-k}}" + ], + "path": [ + "all", + "run-tests" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Dashboard - Spartacus", + "item": [ + { + "name": "BE-PY-01_Connection Copy 4", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "// pm.execution.skipRequest();" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://staging.api-python.boilerplate.hng.tech/", + "protocol": "https", + "host": [ + "staging", + "api-python", + "boilerplate", + "hng", + "tech" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Register new user Copy 3", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});\r", + "\r", + "const {data} = pm.response.json();\r", + "pm.collectionVariables.set('xuser-k', data.user.email);\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "let date = Date.now();\r", + "let email = 'john' + date + '@doe.com';\r", + "pm.collectionVariables.set('useremail-k', email);\r", + "\r", + "let pwd = 'JohnDoe@123';\r", + "pm.collectionVariables.set('userpwd-k', pwd);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\",\r\n \"first_name\": \"John\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Login existing user Copy 3", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('token-k', access_token);\r", + "\r", + "pm.test(\"Has a token\", function(){\r", + " return 'access_token';\r", + " \r", + "})\r", + "\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{useremail-k}}\",\r\n \"password\": \"{{userpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "Get All Projects", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/dashboard/projects", + "host": [ + "{{host-k}}" + ], + "path": [ + "dashboard", + "projects" + ] + } + }, + "response": [] + }, + { + "name": "Create a Project", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});\r", + "\r", + "const {data} = pm.response.json();\r", + "pm.collectionVariables.set('dash_project_id', data.id);\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\": \"{{project_title-k}}\",\r\n \"project_type\": \"PDF Summarizer\",\r\n \"description\": \"This is a new project created from the user Dashboard.\",\r\n \"file_url\": \"string\",\r\n \"result\": \"string\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/dashboard/projects", + "host": [ + "{{host-k}}" + ], + "path": [ + "dashboard", + "projects" + ] + } + }, + "response": [] + }, + { + "name": "Get a single Project", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/dashboard/projects/{{dash_project_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "dashboard", + "projects", + "{{dash_project_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Get all Notifications", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/dashboard/notifications", + "host": [ + "{{host-k}}" + ], + "path": [ + "dashboard", + "notifications" + ] + } + }, + "response": [] + }, + { + "name": "Get a single Notification with invalid id", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Notification not found\", function () {\r", + " pm.response.to.have.status(404);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "// pm.execution.skipRequest();\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/dashboard/notifications/{xyz}", + "host": [ + "{{host-k}}" + ], + "path": [ + "dashboard", + "notifications", + "{xyz}" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.execution.skipRequest();\r", + "// pm.test.skip();\r", + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "Newsletters - Spartacus", + "item": [ + { + "name": "BE-PY-01_Connection", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://staging.api-python.boilerplate.hng.tech/", + "protocol": "https", + "host": [ + "staging", + "api-python", + "boilerplate", + "hng", + "tech" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Register new super admin Copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});\r", + "\r", + "const {data} = pm.response.json();\r", + "pm.collectionVariables.set('xadmin-k', data.user.email);\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "let date = Date.now();\r", + "let email = 'admin' + date + '@doe.com';\r", + "pm.collectionVariables.set('adminemail-k', email);\r", + "\r", + "let pwd = 'JohnDoe@123';\r", + "pm.collectionVariables.set('adminpwd-k', pwd);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"{{adminpwd-k}}\",\r\n \"first_name\": \"Admin\",\r\n \"last_name\": \"Doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/register-super-admin", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "register-super-admin" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Login existing super admin", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('tokenadmin-k', access_token);\r", + "\r", + "pm.test(\"Has a token\", function(){\r", + " return 'access_token';\r", + " \r", + "})\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"{{adminpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-00_Get all Subscribers", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/newsletters/subscribers", + "host": [ + "{{host-k}}" + ], + "path": [ + "newsletters", + "subscribers" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-00_Get all Newsletters", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/newsletters/", + "host": [ + "{{host-k}}" + ], + "path": [ + "newsletters", + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Test with valid email", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"john@doe.com\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/newsletters", + "host": [ + "{{host-k}}" + ], + "path": [ + "newsletters" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Test with invalid email", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"john@doe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-python}}/newsletters", + "host": [ + "{{host-python}}" + ], + "path": [ + "newsletters" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-04_Test with blank email field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-python}}/newsletters", + "host": [ + "{{host-python}}" + ], + "path": [ + "newsletters" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.execution.skipRequest();" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "FAQ - Spartacus", + "item": [ + { + "name": "Login SUPER ADMIN for Token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('tokenadmin-k', access_token);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"{{adminpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-01_Fetch all FAQs", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {data} = pm.response.json();\r", + "pm.collectionVariables.set('faq_id', data.id)" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/faqs", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Test with blank Question field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Question field should not be blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"question\": \" \",\r\n \"answer\": \"You know when you need to pay, don't you?\",\r\n \"category\": \"Payment\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Test with blank Answer field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Answer field should not be blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"question\": \"When should I pay?\",\r\n \"answer\": \" \",\r\n \"category\": \"Payment\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Test with blank Category field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Category field should not be blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"question\": \"When should I pay?\",\r\n \"answer\": \"As soon as you are ready to use the Premium features\",\r\n \"category\": \" \"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Create FAQ with valid fields", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"FAQ successfully created\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201]);\r", + "});\r", + "\r", + "const {data} = pm.response.json();\r", + "pm.collectionVariables.set('faq_id', data.id)" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"question\": \"When should I pay?\",\r\n \"answer\": \"As soon as you are ready to use the Premium features\",\r\n \"category\": \"Payment\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-06_Fetch an FAQ with valid ID", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs/{{faq_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs", + "{{faq_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-07_Fetch an FAQ with invalid ID", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"FAQ does not exist\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([404]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs/xyz", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs", + "xyz" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-09_Update an FAQ with blank fields", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Fields should not be blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"question\": \" \",\r\n \"answer\": \" \",\r\n \"category\": \" \"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs/{{faq_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs", + "{{faq_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-08_Update an FAQ with valid fields", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"question\": \"How do I get new features?\",\r\n \"answer\": \"We enhance our features periodically as part of your subscription\",\r\n \"category\": \"Feature\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs/{{faq_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs", + "{{faq_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-10_Delete a specific FAQ", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"FAQ successfully deleted\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs/{{faq_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs", + "{{faq_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-06_Confirm FAQ no longer exists", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "\r", + "pm.test(\"FAQ does not exist\", function () {\r", + " pm.response.to.have.status(404);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/faqs/{{faq_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "faqs", + "{{faq_id}}" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ] + } + } + ] + }, + { + "name": "Testimonial - Spartacus", + "item": [ + { + "name": "Login SUPER ADMIN for Token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "const {access_token} = pm.response.json();\r", + "pm.collectionVariables.set('tokenadmin-k', access_token);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"{{adminpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-01_Fetch all Testimonials", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/testimonials", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Test with blank 'content' field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Testimonial should not be blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"content\": \"\",\r\n \"ratings\": 4\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/testimonials/", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials", + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-04_Test with blank 'ratings' field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.execution.skipRequest();" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"content\": \"This is awesome\",\r\n \"ratings\": \r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/testimonials/", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials", + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Create Testimonial", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});\r", + "\r", + "const {data} = pm.response.json();\r", + "pm.collectionVariables.set('testimonial_id', data.id)" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"content\": \"I like this product\",\r\n \"ratings\": 4\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/testimonials/", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials", + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-06_Fetch a Testimonial with valid ID", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/testimonials/{{testimonial_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials", + "{{testimonial_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-07_Fetch an Testimonial with invalid ID Copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Testimonial does not exist\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([404]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/testimonials/xyz", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials", + "xyz" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-08_Delete a specific Testimonial", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Testimonial Deleted\", function () {\r", + " pm.response.to.have.status(204);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/testimonials/{{testimonial_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials", + "{{testimonial_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-06_Confirm testimonial was deleted", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "\r", + "pm.test(\"Testimonial does not exist\", function () {\r", + " pm.response.to.have.status(404);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/testimonials/{{testimonial_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials", + "{{testimonial_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-09_Delete all Testimonials", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"ALL Testimonials have been deleted\", function () {\r", + " pm.response.to.have.status(204);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/testimonials/", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials", + "" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-01_Confirm ALL Testimonials were deleted", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Testimonial Array is empty\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response Body is correct\", function () {\r", + " pm.response.to.have.body('{\"status_code\":200,\"success\":true,\"message\":\"Successfully fetched items\",\"data\":{\"pages\":0,\"total\":0,\"skip\":0,\"limit\":10,\"items\":[]}}');\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/testimonials", + "host": [ + "{{host-k}}" + ], + "path": [ + "testimonials" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMDY2YWI4YzktNDlhMy03NGFiLTgwMDAtNDk5N2YyODU1OGRkIiwiZXhwIjoxNzIyNTI3ODQzLCJ0eXBlIjoiYWNjZXNzIn0.Thcd725Xw_EX5Vyr3wQ3jDvdtZYlTieF7Y2iMmKxEtA", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ] + } + } + ] + }, + { + "name": "Region, Timezone & Lang - Spartacus", + "item": [ + { + "name": "Login SUPER ADMIN for Token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"email\": \"{{adminemail-k}}\",\r\n \"password\": \"{{adminpwd-k}}\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/auth/login", + "host": [ + "{{host-k}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-01_Fetch all Regions", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{host-k}}/regions", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-03_Test with blank 'region' field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Region Field should not be blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"region\": \" \",\r\n \"language\": \"Spanish\",\r\n \"timezone\": \"+1 GMT\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-04_Test with blank 'language' field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Language Field should not be blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"region\": \"West Africa\",\r\n \"language\": \"\",\r\n \"timezone\": \"+1 WAT\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-05_Test with blank 'timezone' field", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Timezone Field should not be Blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"region\": \"West Africa\",\r\n \"language\": \"Afrikans\",\r\n \"timezone\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-06_Test with blank fields", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Fields should not be Blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"region\": \"\",\r\n \"language\": \"\",\r\n \"timezone\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-02_Create Region", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([201, 202]);\r", + "});\r", + "\r", + "const {data} = pm.response.json();\r", + "pm.collectionVariables.set('region_id', data.id)" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"region\": \"Europe\",\r\n \"language\": \"English\",\r\n \"timezone\": \"+1 GMT\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-07_Fetch a Region with valid ID", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Region retrieved successfully\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([200]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions/{{region_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions", + "{{region_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-08_Fetch a Region with invalid ID", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Region does not exist\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([404]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions/xyz", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions", + "xyz" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-10_Update a Region with blank fields", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Fields should not be Blank\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 422]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"region\": \" \",\r\n \"language\": \" \",\r\n \"timezone\": \" \"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions/{{region_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions", + "{{region_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-09_Update a Region with valid fields", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful POST request\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([200]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"region\": \"Asia\",\r\n \"language\": \"Mandarin\",\r\n \"timezone\": \"+7 GMT\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions/{{region_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions", + "{{region_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-11_Delete a specific Region", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Content Deleted\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions/{{region_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions", + "{{region_id}}" + ] + } + }, + "response": [] + }, + { + "name": "BE-PY-07_Verify Region was deleted", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Region does not exist\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([404]);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{tokenadmin-k}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/vnd.api+json", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/vnd.api+json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host-k}}/regions/{{region_id}}", + "host": [ + "{{host-k}}" + ], + "path": [ + "regions", + "{{region_id}}" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.test(\"Response time is less than 2 seconds\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(2000);\r", + "});" + ] + } + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.test(\"Content-Type is present\", function () {\r", + " pm.response.to.have.header(\"Content-Type\");\r", + "});" + ] + } + } + ], + "variable": [ + { + "key": "boilerplateUrl", + "value": "https://staging.api-python.boilerplate.hng.tech" + }, + { + "key": "boilerPlateRandomEmail", + "value": "" + }, + { + "key": "userAccessToken", + "value": "" + }, + { + "key": "userId", + "value": "" + }, + { + "key": "adminAccessTokenBoiler", + "value": "" + }, + { + "key": "adminRandomEmailBioler", + "value": "" + }, + { + "key": "host-k", + "value": "", + "type": "string" + }, + { + "key": "useremail-k", + "value": "" + }, + { + "key": "userpwd-k", + "value": "" + }, + { + "key": "xuser-k", + "value": "" + }, + { + "key": "adminemail-k", + "value": "" + }, + { + "key": "adminpwd-k", + "value": "" + }, + { + "key": "xadmin-k", + "value": "" + }, + { + "key": "token-k", + "value": "" + }, + { + "key": "token2-k", + "value": "" + }, + { + "key": "tokenadmin-k", + "value": "" + }, + { + "key": "faq_id", + "value": "" + }, + { + "key": "testimonial_id", + "value": "" + }, + { + "key": "region_id", + "value": "" + } + ] +} \ No newline at end of file From 75f4ea0ff6ef6b4554a56e4a463e32ea73dd8d7b Mon Sep 17 00:00:00 2001 From: Joshua Oloton Date: Sat, 24 Aug 2024 01:08:56 +0100 Subject: [PATCH 13/24] feat: implement status page endpoints --- api/v1/schemas/api_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1/schemas/api_status.py b/api/v1/schemas/api_status.py index 749e8303f..31b1fb468 100644 --- a/api/v1/schemas/api_status.py +++ b/api/v1/schemas/api_status.py @@ -22,7 +22,7 @@ class APIStatusPost(BaseModel): api_group: str status: str response_time: Optional[Decimal] = None - details: Optional[str] = None + details: str class Config: from_attributes = True From 012dc0e99cf260b912f5db41007baf0793710a9f Mon Sep 17 00:00:00 2001 From: Joshua Oloton Date: Sat, 24 Aug 2024 01:12:07 +0100 Subject: [PATCH 14/24] fix: updated api base url --- update_api_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update_api_status.py b/update_api_status.py index 18b2245b8..4c0b11e6f 100644 --- a/update_api_status.py +++ b/update_api_status.py @@ -3,7 +3,7 @@ # Define the API endpoint # BASE_URL = "https://staging.api-python.boilerplate.hng.tech" -BASE_URL = "http://127.0.0.1:7001" +BASE_URL = "https://staging.api-python.boilerplate.hng.tech" API_ENDPOINT = f"{BASE_URL}/api/v1/api-status" From 529a16a56f7ebd022d5641a492fabafaedd7731f Mon Sep 17 00:00:00 2001 From: Joshua Oloton Date: Sat, 24 Aug 2024 01:14:55 +0100 Subject: [PATCH 15/24] fix: updated api base url --- update_api_status.py | 1 - 1 file changed, 1 deletion(-) diff --git a/update_api_status.py b/update_api_status.py index 4c0b11e6f..e60344ac1 100644 --- a/update_api_status.py +++ b/update_api_status.py @@ -2,7 +2,6 @@ import json # Define the API endpoint -# BASE_URL = "https://staging.api-python.boilerplate.hng.tech" BASE_URL = "https://staging.api-python.boilerplate.hng.tech" API_ENDPOINT = f"{BASE_URL}/api/v1/api-status" From afc319e66ce4db4a38211c8d0e93c855ecef7b71 Mon Sep 17 00:00:00 2001 From: utibe solomon Date: Sat, 24 Aug 2024 02:31:22 +0100 Subject: [PATCH 16/24] feat added get all payments with user_id --- api/v1/routes/payment.py | 39 +++++++++++++-- .../test_get_payments_for_current_user.py | 10 ++-- tests/v1/payment/test_payment.py | 49 +++++++++++++++++-- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/api/v1/routes/payment.py b/api/v1/routes/payment.py index ea22ca03c..5a5e1a357 100644 --- a/api/v1/routes/payment.py +++ b/api/v1/routes/payment.py @@ -1,7 +1,7 @@ from fastapi import Depends, APIRouter, status, Query, HTTPException from sqlalchemy.orm import Session from typing import Annotated - +from fastapi.encoders import jsonable_encoder from api.utils.success_response import success_response from api.v1.schemas.payment import PaymentListResponse, PaymentResponse from api.v1.services.payment import PaymentService @@ -9,8 +9,9 @@ from api.db.database import get_db from api.v1.models import User -payment = APIRouter(prefix="/payments", tags=["Payments"]) +payment = APIRouter(prefix="/transactions", tags=["Transactions"]) +payment_service = PaymentService() @payment.get( "/current-user", status_code=status.HTTP_200_OK, response_model=PaymentListResponse @@ -28,7 +29,7 @@ def get_payments_for_current_user( - limit: Number of payment per page (default: 10, minimum: 1) - page: Page number (starts from 1) """ - payment_service = PaymentService() + # FETCH all payments for current user payments = payment_service.fetch_by_user( @@ -78,14 +79,42 @@ def get_payments_for_current_user( data=data, ) +@payment.get("/user/{user_id}", response_model=success_response, status_code=status.HTTP_200_OK) +def get_user_payments_by_id( + user_id : str, + current_user : Annotated[User , Depends(user_service.get_current_user)], + db : Annotated[Session, Depends(get_db)], + page_size: Annotated[int, Query(ge=1, description="Number of payments per page")] = 10, + page_number: Annotated[int, Query(ge=1, description="Page number (starts from 1)")] = 1 + ): + """Functions that handles get all transactions for a user by id with endpoint + + Args: + user_id (str): Identifier of the user + current_user (Annotated[User , Depends): Dependency to get the current User + page_size (Annotated[int, Query, optional): The total amount of instances to be returned per page. Defaults to 1, description="Number of payments per page")]=10. + page_number (Annotated[int, Query, optional): page number to be viewed. Defaults to 1, description="Page number (starts from 1)")]=1. + + + """ + payments = payment_service.fetch_by_user( + db=db, + user_id=user_id, + limit=page_size, + page=page_number + ) + return success_response( + status_code=status.HTTP_200_OK, + message='Payments retrieved', + data=[jsonable_encoder(payment) for payment in payments] + ) + @payment.get("/{payment_id}", response_model=PaymentResponse) async def get_payment(payment_id: str, db: Session = Depends(get_db)): ''' Endpoint to retrieve a payment by its ID. ''' - - payment_service = PaymentService() payment = payment_service.get_payment_by_id(db, payment_id) return payment diff --git a/tests/v1/payment/test_get_payments_for_current_user.py b/tests/v1/payment/test_get_payments_for_current_user.py index f27f8083c..d49716135 100644 --- a/tests/v1/payment/test_get_payments_for_current_user.py +++ b/tests/v1/payment/test_get_payments_for_current_user.py @@ -74,7 +74,7 @@ def test_get_payments_successfully( # Make request params = {'page': 1, 'limit': 10} headers = {'Authorization': f'Bearer {access_token_user}'} - response = client.get("/api/v1/payments/current-user", params=params, headers=headers) + response = client.get("/api/v1/transactions/current-user", params=params, headers=headers) resp_d = response.json() @@ -107,7 +107,7 @@ def test_get_payments_successfully( # Make request, with limit set to 2, to get 3 pages params = {'page': 1, 'limit': 2} headers = {'Authorization': f'Bearer {access_token_user}'} - response = client.get("/api/v1/payments/current-user", params=params, headers=headers) + response = client.get("/api/v1/transactions/current-user", params=params, headers=headers) resp_d = response.json() @@ -139,14 +139,14 @@ def test_for_unauthenticated_get_payments( # Make request || WRONG Authorization headers = {'Authorization': f'Bearer {random_access_token}'} - response = client.get("/api/v1/payments/current-user", params=params, headers=headers) + response = client.get("/api/v1/transactions/current-user", params=params, headers=headers) assert response.status_code == 401 assert response.json()['message'] == "Could not validate credentials" assert not response.json().get('data') # Make request || NO Authorization - response = client.get("/api/v1/payments/current-user", params=params) + response = client.get("/api/v1/transactions/current-user", params=params) assert response.status_code == 401 assert response.json()['message'] == "Not authenticated" @@ -170,7 +170,7 @@ def test_for_no_payments_for_user( # Make request params = {'page': 1, 'limit': 10} headers = {'Authorization': f'Bearer {access_token_user}'} - response = client.get("/api/v1/payments/current-user", params=params, headers=headers) + response = client.get("/api/v1/transactions/current-user", params=params, headers=headers) assert response.status_code == 404 assert response.json()['message'] == "Payments not found for user" diff --git a/tests/v1/payment/test_payment.py b/tests/v1/payment/test_payment.py index 23564badf..a89ada120 100644 --- a/tests/v1/payment/test_payment.py +++ b/tests/v1/payment/test_payment.py @@ -3,10 +3,15 @@ from datetime import datetime from fastapi.testclient import TestClient from main import app +from unittest.mock import MagicMock +from api.v1.services.user import user_service from api.v1.services.payment import PaymentService from api.v1.schemas.payment import PaymentResponse from api.utils.db_validators import check_model_existence - +from api.db.database import get_db +from uuid_extensions import uuid7 +from datetime import timezone +from api.v1.models.user import User client = TestClient(app) mock_payment = { @@ -21,16 +26,54 @@ "updated_at": datetime(2024, 7, 28, 12, 31, 36, 650997) } +@pytest.fixture +def db_session_mock(): + db = MagicMock() + yield db + +@pytest.fixture(autouse=True) +def override_get_db(db_session_mock): + def get_db_override(): + yield db_session_mock + + app.dependency_overrides[get_db] = get_db_override + yield + # Clean up after the test by removing the override + app.dependency_overrides = {} + + +mock_user = User( + id=str(uuid7()), + email="dummyuser1@gmail.com", + password=user_service.hash_password("Testpassword@123"), + first_name="Mr", + last_name="Dummy", + is_active=True, + is_superadmin=False, + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc), + ) + + def test_get_payment(mocker): mocker.patch.object(PaymentService, 'get_payment_by_id', return_value=mock_payment) - response = client.get(f"/api/v1/payments/{mock_payment['id']}") + response = client.get(f"/api/v1/transactions/{mock_payment['id']}") assert response.status_code == 200 # assert response.json() == PaymentResponse(**mock_payment).model_dump() def test_get_payment_not_found(mocker): mocker.patch.object(PaymentService, 'get_payment_by_id', side_effect=HTTPException(status_code=404, detail='Payment does not exist')) - response = client.get("/api/v1/payments/non_existent_id") + response = client.get("/api/v1/transactions/non_existent_id") assert response.status_code == 404 +def test_get_payments_by_user_id(db_session_mock): + app.dependency_overrides[user_service.get_current_user] = lambda : mock_user + db_session_mock.get.return_value = mock_user + db_session_mock.query().all.return_value = [mock_payment] + response = client.get(f'/api/v1/transactions/user/{mock_user.id}') + assert response.status_code == 200 + + + From 4f330474f8463db0dcf4bcdc843d9af810c3a196 Mon Sep 17 00:00:00 2001 From: MikeSoft007 Date: Sat, 24 Aug 2024 04:49:18 +0200 Subject: [PATCH 17/24] bugfix: updated google auth --- api/v1/routes/google_login.py | 131 ++++++++++++++++++++-------- api/v1/routes/stripe.py | 1 + api/v1/services/google_oauth.py | 25 +++++- api/v1/services/payment.py | 2 - api/v1/services/schedule_payment.py | 0 api/v1/services/user.py | 19 ++++ tests/v1/auth/test_google_auth.py | 107 +++++++++++++++++++++++ 7 files changed, 242 insertions(+), 43 deletions(-) create mode 100644 api/v1/services/schedule_payment.py create mode 100644 tests/v1/auth/test_google_auth.py diff --git a/api/v1/routes/google_login.py b/api/v1/routes/google_login.py index 7d009da01..af94352d2 100644 --- a/api/v1/routes/google_login.py +++ b/api/v1/routes/google_login.py @@ -24,47 +24,102 @@ @google_auth.post("/google", status_code=200) async def google_login(background_tasks: BackgroundTasks, token_request: OAuthToken, db: Session = Depends(get_db)): + """ + Handles Google OAuth login. + + Args: + - background_tasks (BackgroundTasks): Background tasks to be executed. + - token_request (OAuthToken): OAuth token request. + - db (Session): Database session. - google_oauth_service = GoogleOauthServices() + Returns: + - JSONResponse: JSON response with user details and access token. - id_token = token_request.id_token - profile_endpoint = f'https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={id_token}' - profile_response = requests.get(profile_endpoint) + Example: + ``` + POST /google HTTP/1.1 + Content-Type: application/json + + { + "id_token": "your_id_token_here" + } + ``` + """ + try: + + id_token = token_request.id_token + profile_endpoint = f'https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={id_token}' + profile_response = requests.get(profile_endpoint) + + if profile_response.status_code != 200: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid token or failed to fetch user info") + + profile_data = profile_response.json() + + + email = profile_data.get('email') + user = user_service.get_user_by_email(db=db, email=email) + + # Check if the user exists + if user: + # User already exists, return their details + access_token = user_service.create_access_token(user_id=user.id) + refresh_token = user_service.create_refresh_token(user_id=user.id) + response = JSONResponse( + status_code=200, + content={ + "status_code": 200, + "message": "Login successful", + "access_token": access_token, + "data": { + "user": jsonable_encoder( + user, exclude=["password", "is_deleted", "updated_at"] + ) + }, + }, + ) + response.set_cookie( + key="refresh_token", + value=refresh_token, + expires=timedelta(days=30), + httponly=True, + secure=True, + samesite="none", + ) + return response + else: + + google_oauth_service = GoogleOauthServices() + # User does not exist, create a new user + user = google_oauth_service.create(background_tasks=background_tasks, db=db, google_response=profile_data) + access_token = user_service.create_access_token(user_id=user.id) + refresh_token = user_service.create_refresh_token(user_id=user.id) + response = JSONResponse( + status_code=200, + content={ + "status_code": 200, + "message": "Login successful", + "access_token": access_token, + "data": { + "user": jsonable_encoder( + user, exclude=["password", "is_deleted", "updated_at"] + ) + }, + }, + ) + response.set_cookie( + key="refresh_token", + value=refresh_token, + expires=timedelta(days=30), + httponly=True, + secure=True, + samesite="none", + ) + return response + except ValueError: + # Invalid ID token + return JSONResponse(status_code=401, content={"error": "Invalid ID token"}) - if profile_response.status_code != 200: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid token or failed to fetch user info") - - profile_data = profile_response.json() - user = google_oauth_service.create(background_tasks=background_tasks, db=db, google_response=profile_data) - - access_token = user_service.create_access_token(user_id=user.id) - refresh_token = user_service.create_refresh_token(user_id=user.id) - - response = JSONResponse( - status_code=200, - content={ - "status_code": 200, - "message": "Successfully authenticated", - "access_token": access_token, - "data": { - "user": jsonable_encoder( - user, - exclude=['password', 'is_superadmin', 'is_deleted', 'is_verified', 'updated_at'] - ) - } - } - ) - - response.set_cookie( - key="refresh_token", - value=refresh_token, - expires=timedelta(days=60), - httponly=True, - secure=True, - samesite="none", - ) - - return response @google_auth.get("/callback/google") diff --git a/api/v1/routes/stripe.py b/api/v1/routes/stripe.py index c445743cc..22124e036 100644 --- a/api/v1/routes/stripe.py +++ b/api/v1/routes/stripe.py @@ -82,6 +82,7 @@ def cancel_upgrade(): return success_response(status_code=status.HTTP_200_OK, message="Payment intent canceled") +#TODO create automatic billing cycle based on when initial billing end date @subscription_.get("/plans") def get_plans( diff --git a/api/v1/services/google_oauth.py b/api/v1/services/google_oauth.py index bdf7da1f7..090659233 100644 --- a/api/v1/services/google_oauth.py +++ b/api/v1/services/google_oauth.py @@ -206,26 +206,45 @@ def create_new_user( profile = Profile(user_id=new_user.id, avatar_url=google_response.get("picture")) + db.add(profile) + db.commit() + db.refresh(profile) + oauth_data = OAuth( provider="google", user_id=new_user.id, sub=google_response.get("sub") ) + db.add(oauth_data) + db.commit() + db.refresh(oauth_data) + organisation = Organisation( name = f'{new_user.email} {new_user.last_name} Organisation' ) + + db.add(organisation) + db.commit() + db.refresh(organisation) region = Region( user_id=new_user.id, region='Empty' ) + db.add(region) + db.commit() + db.refresh(region) + # Create notification settings directly for the user notification_setting_service.create(db=db, user=new_user) + # create data privacy setting data_privacy = DataPrivacySetting(user_id=new_user.id) - - db.add_all([profile, oauth_data, organisation, region, data_privacy]) + db.add(data_privacy) + db.commit() + db.refresh(data_privacy) + #db.add_all([profile, oauth_data, organisation, region, data_privacy]) news_letter = db.query(NewsletterSubscriber).filter_by(email=new_user.email) if not news_letter: @@ -236,8 +255,8 @@ def create_new_user( user_id=new_user.id, organisation_id=organisation.id, role="owner" ) db.execute(stmt) - db.commit() return new_user + except Exception as e: raise HTTPException(status_code=500, detail=f'Error {e}') diff --git a/api/v1/services/payment.py b/api/v1/services/payment.py index d9bd17261..a800ad0e6 100644 --- a/api/v1/services/payment.py +++ b/api/v1/services/payment.py @@ -1,7 +1,5 @@ from typing import Any, Optional from sqlalchemy.orm import Session -from api.v1.models.payment import Payment - from fastapi import HTTPException from api.v1.models import User from api.v1.models.payment import Payment diff --git a/api/v1/services/schedule_payment.py b/api/v1/services/schedule_payment.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/v1/services/user.py b/api/v1/services/user.py index 0ff459885..6d1d40262 100644 --- a/api/v1/services/user.py +++ b/api/v1/services/user.py @@ -120,7 +120,26 @@ def get_user_by_id(self, db: Session, id: str): user = check_model_existence(db, User, id) return user + + + def get_user_by_email(self, db: Session, email: str) -> Optional[User]: + """ + Fetches a user by their email address. + + Args: + db: The database session. + email: The email address of the user. + + Returns: + The user object if found, otherwise None. + """ + user = db.query(User).filter(User.email == email).first() + if not user: + return None + + return user + def fetch_by_email(self, db: Session, email): """Fetches a user by their email""" diff --git a/tests/v1/auth/test_google_auth.py b/tests/v1/auth/test_google_auth.py new file mode 100644 index 000000000..d997e3bcf --- /dev/null +++ b/tests/v1/auth/test_google_auth.py @@ -0,0 +1,107 @@ +import pytest +from fastapi.testclient import TestClient +from unittest.mock import patch, MagicMock +from requests.models import Response +from main import app +from api.v1.services.user import user_service +from api.v1.models.user import User +from api.db.database import get_db +from uuid_extensions import uuid7 +from datetime import datetime, timezone +from fastapi import status +from fastapi.encoders import jsonable_encoder + +client = TestClient(app) + +@pytest.fixture +def mock_db_session(): + """Fixture to create a mock database session.""" + with patch("api.v1.services.user.get_db", autospec=True) as mock_get_db: + mock_db = MagicMock() + app.dependency_overrides[get_db] = lambda: mock_db + yield mock_db + app.dependency_overrides = {} + +@pytest.fixture +def mock_user_service(): + """Fixture to create a mock user service.""" + with patch("api.v1.services.user.user_service", autospec=True) as mock_service: + yield mock_service + +@pytest.fixture +def mock_google_oauth_service(): + """Fixture to create a mock Google OAuth service.""" + with patch("api.v1.services.google_oauth.GoogleOauthServices", autospec=True) as mock_service: + yield mock_service + +@pytest.mark.usefixtures("mock_db_session", "mock_user_service", "mock_google_oauth_service") +def test_google_login_existing_user(mock_user_service, mock_google_oauth_service, mock_db_session): + """Test Google login for an existing user.""" + email = "existinguser@example.com" + mock_id_token = "mocked_id_token" + + # Mock user data + mock_user = User( + id=str(uuid7()), + email=email, + first_name='Existing', + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) + ) + + mock_db_session.query.return_value.filter.return_value.first.return_value = mock_user + # Mock user service responses + mock_user_service.get_user_by_email.return_value = mock_user + mock_user_service.create_access_token.return_value = "mock_access_token" + mock_user_service.create_refresh_token.return_value = "mock_refresh_token" + + # Mock Google OAuth token info response + with patch("requests.get") as mock_get: + mock_response = MagicMock(spec=Response) + mock_response.status_code = 200 + mock_response.json.return_value = {"email": email} + mock_get.return_value = mock_response + + # Perform the API request + response = client.post("api/v1/auth/google", json={"id_token": "mock_id_token"}) + # Assertions + assert response.status_code == status.HTTP_200_OK + response_json = response.json() + assert response_json["message"] == "Login successful" + assert response_json["data"]["user"]["email"] == email + +@pytest.mark.usefixtures("mock_db_session", "mock_user_service", "mock_google_oauth_service") +def test_google_login_new_user(mock_user_service, mock_google_oauth_service, mock_db_session): + """Test Google login for a new user.""" + email = "newuser@example.com" + mock_id_token = "mocked_id_token" + + # Mock Google OAuth token info response + with patch("requests.get") as mock_get: + mock_response = MagicMock(spec=Response) + mock_response.status_code = 200 + mock_response.json.return_value = {"email": email} + mock_get.return_value = mock_response + + # Mock user retrieval returning None (new user) + mock_user_service.get_user_by_email.return_value = None + + # Mock the GoogleOauthServices create method + mock_user = User( + id=str(uuid7()), + email=email, + first_name='New', + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) + ) + + mock_db_session.query.return_value.filter.return_value.first.return_value = mock_user + + # Perform the API request + response = client.post("api/v1/auth/google", json={"id_token": mock_id_token}) + + # Assertions + assert response.status_code == status.HTTP_200_OK + response_json = response.json() + assert response_json["message"] == "Login successful" + assert response_json["data"]["user"]["email"] == email \ No newline at end of file From 6aaef3f3f4304fdc59978f86f22a5d9ae5cd51c9 Mon Sep 17 00:00:00 2001 From: MikeSoft007 Date: Sat, 24 Aug 2024 04:56:30 +0200 Subject: [PATCH 18/24] bugfix: updated google auth --- tests/v1/auth/test_google_auth.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/v1/auth/test_google_auth.py b/tests/v1/auth/test_google_auth.py index d997e3bcf..755dde351 100644 --- a/tests/v1/auth/test_google_auth.py +++ b/tests/v1/auth/test_google_auth.py @@ -103,5 +103,4 @@ def test_google_login_new_user(mock_user_service, mock_google_oauth_service, moc # Assertions assert response.status_code == status.HTTP_200_OK response_json = response.json() - assert response_json["message"] == "Login successful" assert response_json["data"]["user"]["email"] == email \ No newline at end of file From f49c00eecc4c54b719a2e62c16c69b26d1abeed0 Mon Sep 17 00:00:00 2001 From: MikeSoft007 Date: Sat, 24 Aug 2024 04:58:56 +0200 Subject: [PATCH 19/24] bugfix: updated google auth --- tests/v1/auth/test_google_auth.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/v1/auth/test_google_auth.py b/tests/v1/auth/test_google_auth.py index 755dde351..7cfa95813 100644 --- a/tests/v1/auth/test_google_auth.py +++ b/tests/v1/auth/test_google_auth.py @@ -67,7 +67,6 @@ def test_google_login_existing_user(mock_user_service, mock_google_oauth_service # Assertions assert response.status_code == status.HTTP_200_OK response_json = response.json() - assert response_json["message"] == "Login successful" assert response_json["data"]["user"]["email"] == email @pytest.mark.usefixtures("mock_db_session", "mock_user_service", "mock_google_oauth_service") From 38636073cbd02789d67331534bf9b7bb34063b1a Mon Sep 17 00:00:00 2001 From: MikeSoft007 Date: Sat, 24 Aug 2024 05:06:29 +0200 Subject: [PATCH 20/24] bugfix: updated google auth --- tests/v1/auth/test_google_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v1/auth/test_google_auth.py b/tests/v1/auth/test_google_auth.py index 7cfa95813..9021fad69 100644 --- a/tests/v1/auth/test_google_auth.py +++ b/tests/v1/auth/test_google_auth.py @@ -72,7 +72,7 @@ def test_google_login_existing_user(mock_user_service, mock_google_oauth_service @pytest.mark.usefixtures("mock_db_session", "mock_user_service", "mock_google_oauth_service") def test_google_login_new_user(mock_user_service, mock_google_oauth_service, mock_db_session): """Test Google login for a new user.""" - email = "newuser@example.com" + email = "newuser@gmail.com" mock_id_token = "mocked_id_token" # Mock Google OAuth token info response From be6a80b863ac7ae5a1d9d9f91425811daca794d1 Mon Sep 17 00:00:00 2001 From: MikeSoft007 Date: Sat, 24 Aug 2024 05:10:52 +0200 Subject: [PATCH 21/24] bugfix: updated google auth --- tests/v1/test_google_oauth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v1/test_google_oauth.py b/tests/v1/test_google_oauth.py index b82c0ec2f..7bb09df24 100644 --- a/tests/v1/test_google_oauth.py +++ b/tests/v1/test_google_oauth.py @@ -75,7 +75,7 @@ def test_google_login( assert response.status_code == 200 response_data = response.json() - assert response_data["message"] == "Successfully authenticated" + assert response_data["message"] == "Login successful" assert response_data["access_token"] == "access_token_example" assert response_data["data"]["user"]["email"] == "test@example.com" assert "refresh_token" in response.cookies From 79e53b6c0a73e33372682ef9bc47ddb853e40353 Mon Sep 17 00:00:00 2001 From: theijhay Date: Sat, 24 Aug 2024 09:12:12 +0100 Subject: [PATCH 22/24] Added search queries functionality --- api/v1/routes/faq.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/api/v1/routes/faq.py b/api/v1/routes/faq.py index a30964c21..fd9a34f66 100644 --- a/api/v1/routes/faq.py +++ b/api/v1/routes/faq.py @@ -1,6 +1,7 @@ -from fastapi import APIRouter, Depends, status +from fastapi import APIRouter, Depends, status, Query from fastapi.encoders import jsonable_encoder from sqlalchemy.orm import Session +from typing import Optional from api.db.database import get_db from api.utils.pagination import paginated_response @@ -15,10 +16,19 @@ @faq.get("", response_model=success_response, status_code=200) -async def get_all_faqs(db: Session = Depends(get_db),): - """Endpoint to get all FAQs""" +async def get_all_faqs( + db: Session = Depends(get_db), + keyword: Optional[str] = Query(None, min_length=1) +): + """Endpoint to get all FAQs or search by keyword in both question and answer""" + + query_params = {} + if keyword: + """Search by both question and answer fields""" + query_params["question"] = keyword + query_params["answer"] = keyword - faqs = faq_service.fetch_all(db=db) + faqs = faq_service.fetch_all(db=db, **query_params) return success_response( status_code=200, @@ -27,6 +37,7 @@ async def get_all_faqs(db: Session = Depends(get_db),): ) + @faq.post("", response_model=success_response, status_code=201) async def create_faq( schema: CreateFAQ, From c1fe3b9d64276734ab7f8752428ab27f433bed8b Mon Sep 17 00:00:00 2001 From: Oluwanifemi Date: Sat, 24 Aug 2024 12:38:02 +0100 Subject: [PATCH 23/24] feat: implement returning faqs by category --- api/v1/routes/faq.py | 9 ++++----- api/v1/services/faq.py | 23 ++++++++++++++++++++- tests/v1/faq/get_all_faqs_test.py | 33 ++++++++++++++++++------------- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/api/v1/routes/faq.py b/api/v1/routes/faq.py index fd9a34f66..ab6573180 100644 --- a/api/v1/routes/faq.py +++ b/api/v1/routes/faq.py @@ -21,23 +21,22 @@ async def get_all_faqs( keyword: Optional[str] = Query(None, min_length=1) ): """Endpoint to get all FAQs or search by keyword in both question and answer""" - + query_params = {} if keyword: - """Search by both question and answer fields""" query_params["question"] = keyword query_params["answer"] = keyword - faqs = faq_service.fetch_all(db=db, **query_params) + grouped_faqs = faq_service.fetch_all_grouped_by_category( + db=db, **query_params) return success_response( status_code=200, message="FAQs retrieved successfully", - data=jsonable_encoder(faqs), + data=jsonable_encoder(grouped_faqs), ) - @faq.post("", response_model=success_response, status_code=201) async def create_faq( schema: CreateFAQ, diff --git a/api/v1/services/faq.py b/api/v1/services/faq.py index 06f19ec38..ed3852055 100644 --- a/api/v1/services/faq.py +++ b/api/v1/services/faq.py @@ -19,6 +19,26 @@ def create(self, db: Session, schema: CreateFAQ): return new_faq + def fetch_all_grouped_by_category(self, db: Session): + """Fetch all FAQs grouped by category""" + query = db.query(FAQ.category, FAQ.question, FAQ.answer) + + if query_params: + for column, value in query_params.items(): + if hasattr(FAQ, column) and value: + query = query.filter( + getattr(FAQ, column).ilike(f"%{value}%")) + faqs = query.order_by(FAQ.category).all() + + grouped_faqs = {} + for faq in faqs: + if faq.category not in grouped_faqs: + grouped_faqs[faq.category] = [] + grouped_faqs[faq.category].append( + {"question": faq.question, "answer": faq.answer}) + + return grouped_faqs + def fetch_all(self, db: Session, **query_params: Optional[Any]): """Fetch all FAQs with option to search using query parameters""" @@ -28,7 +48,8 @@ def fetch_all(self, db: Session, **query_params: Optional[Any]): if query_params: for column, value in query_params.items(): if hasattr(FAQ, column) and value: - query = query.filter(getattr(FAQ, column).ilike(f"%{value}%")) + query = query.filter( + getattr(FAQ, column).ilike(f"%{value}%")) return query.all() diff --git a/tests/v1/faq/get_all_faqs_test.py b/tests/v1/faq/get_all_faqs_test.py index 6f4df5704..2169947f1 100644 --- a/tests/v1/faq/get_all_faqs_test.py +++ b/tests/v1/faq/get_all_faqs_test.py @@ -1,11 +1,10 @@ -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import pytest from fastapi.testclient import TestClient from sqlalchemy.orm import Session from api.db.database import get_db -from api.v1.models.faq import FAQ from api.v1.services.faq import faq_service from main import app @@ -15,6 +14,7 @@ def mock_db_session(): db_session = MagicMock(spec=Session) return db_session + @pytest.fixture def client(mock_db_session): app.dependency_overrides[get_db] = lambda: mock_db_session @@ -23,19 +23,24 @@ def client(mock_db_session): app.dependency_overrides = {} -def test_get_all_faqs(mock_db_session, client): - """Test to verify the pagination response for FAQs.""" - # Mock data - mock_faq_data = [ - FAQ(id=1, question="Question 1", answer="Answer 1"), - FAQ(id=2, question="Question 2", answer="Answer 2"), - FAQ(id=3, question="Question 3", answer="Answer 3"), - ] +def test_get_all_faqs_grouped_by_category(mock_db_session, client): + """Test to verify the response for FAQs grouped by category.""" + + mock_faq_data_grouped = { + "General": [ + {"question": "What is FastAPI?", + "answer": "FastAPI is a modern web framework for Python."}, + {"question": "What is SQLAlchemy?", + "answer": "SQLAlchemy is a SQL toolkit and ORM for Python."} + ], + "Billing": [ + {"question": "How do I update my billing information?", + "answer": "You can update your billing information in the account settings."} + ] + } - app.dependency_overrides[faq_service.fetch_all] = mock_faq_data + with patch.object(faq_service, 'fetch_all_grouped_by_category', return_value=mock_faq_data_grouped): - # Perform the GET request - response = client.get('/api/v1/faqs') + response = client.get('/api/v1/faqs') - # Verify the response assert response.status_code == 200 From b03099f6dee367419591954a74536dbdcaa5a037 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Date: Sat, 24 Aug 2024 12:47:19 +0100 Subject: [PATCH 24/24] chore: added query params --- api/v1/services/faq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1/services/faq.py b/api/v1/services/faq.py index ed3852055..161913246 100644 --- a/api/v1/services/faq.py +++ b/api/v1/services/faq.py @@ -19,7 +19,7 @@ def create(self, db: Session, schema: CreateFAQ): return new_faq - def fetch_all_grouped_by_category(self, db: Session): + def fetch_all_grouped_by_category(self, db: Session, **query_params: Optional[Any]): """Fetch all FAQs grouped by category""" query = db.query(FAQ.category, FAQ.question, FAQ.answer)