From bed85bf50325f7312f004d0e32d0306e86f4ed94 Mon Sep 17 00:00:00 2001 From: anunita Date: Sun, 10 Nov 2024 22:24:46 +0530 Subject: [PATCH 01/10] initial commit --- backend/flaskr/__init__.py | 52 ++++++++++++++++++++++++++++++-------- backend/models.py | 2 +- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index d991c6bd5..8deee8f22 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -6,32 +6,64 @@ QUESTIONS_PER_PAGE = 10 -def create_app(test_config=None): - # create and configure the app - app = Flask(__name__) +def paginate_questions(request, selection): + page = request.args.get("page", 1, type=int) + start = (page - 1) * QUESTIONS_PER_PAGE + end = start + QUESTIONS_PER_PAGE + + questions = [question.format() for question in selection] + current_questions = questions[start:end] - if test_config is None: - setup_db(app) - else: - database_path = test_config.get('SQLALCHEMY_DATABASE_URI') - setup_db(app, database_path=database_path) + return current_questions """ @TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs """ - with app.app_context(): - db.create_all() + +def create_app(test_config=None): + # create and configure the app + app = Flask(__name__) + setup_db(app) + CORS(app) """ @TODO: Use the after_request decorator to set Access-Control-Allow """ + # CORS Headers + @app.after_request + def after_request(response): + response.headers.add( + "Access-Control-Allow-Headers", "Content-Type,Authorization,true" + ) + response.headers.add( + "Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS" + ) + return response + + with app.app_context(): + db.create_all() + + """ @TODO: Create an endpoint to handle GET requests for all available categories. """ + @app.route("/categories") + def retrieve_categories(): + category = Category.query.order_by(Category.id).all() + + if len(category) == 0: + abort(404) + + format_category = [everycategory.format() for everycategory in category] + result = { + "success": True, + "categories": format_category + } + return result """ @TODO: diff --git a/backend/models.py b/backend/models.py index 7647a6fbf..4e114b16b 100644 --- a/backend/models.py +++ b/backend/models.py @@ -2,7 +2,7 @@ from flask_sqlalchemy import SQLAlchemy database_name = 'trivia' database_user = 'postgres' -database_password = 'password' +database_password = 'Divit%402017' database_host = 'localhost:5432' database_path = f'postgresql://{database_user}:{database_password}@{database_host}/{database_name}' From e10081fe2a89bdf0f7ba964115244f288d7e3dab Mon Sep 17 00:00:00 2001 From: anunita Date: Wed, 13 Nov 2024 18:09:42 +0530 Subject: [PATCH 02/10] added some func --- backend/flaskr/__init__.py | 85 ++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 8deee8f22..a97c0ab24 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -50,17 +50,21 @@ def after_request(response): Create an endpoint to handle GET requests for all available categories. """ - @app.route("/categories") + @app.route("/categories", methods=["GET"]) def retrieve_categories(): - category = Category.query.order_by(Category.id).all() - + category = Category.query.order_by(Category.type).all() + if len(category) == 0: abort(404) - format_category = [everycategory.format() for everycategory in category] + categories = {} + for cat in category: + categories[cat.id] = cat.type + result = { - "success": True, - "categories": format_category + "successs": True, + "categories": categories + } return result @@ -71,13 +75,34 @@ def retrieve_categories(): including pagination (every 10 questions). This endpoint should return a list of questions, number of total questions, current category, categories. - + TEST: At this point, when you start the application you should see questions and categories generated, ten questions per page and pagination at the bottom of the screen for three pages. Clicking on the page numbers should update the questions. """ + @app.route("/questions", methods=["GET"]) + def retrieve_questions(): + selection = Question.query.order_by(Question.id).all() + current_questions = paginate_questions(request, selection) + if len(current_questions) == 0: + abort(404) + + category = Category.query.order_by(Category.type).all() + categories = {} + for cat in category: + categories[cat.id] = cat.type + + result = { + "success": True, + "questions": current_questions, + "total_questions": len(selection), + "categories": categories, + "current_category": None + } + return result + """ @TODO: Create an endpoint to DELETE question using a question ID. @@ -85,7 +110,27 @@ def retrieve_categories(): TEST: When you click the trash icon next to a question, the question will be removed. This removal will persist in the database and when you refresh the page. """ + @app.route("/questions/", methods=["DELETE"]) + def delete_question(question_id): + try: + question = Question.query.filter(Question.id == question_id).one_or_none() + + if question is None: + abort(404) + + question.delete() + selection = Question.query.order_by(Question.id).all() + current_questions = paginate_questions(request, selection) + return jsonify( + { + "success": True, + #"deleted": question_id, + } + ) + + except: + abort(422) """ @TODO: Create an endpoint to POST a new question, @@ -134,6 +179,32 @@ def retrieve_categories(): Create error handlers for all expected errors including 404 and 422. """ + @app.errorhandler(404) + def not_found(error): + return ( + jsonify({"success": False, "error": 404, "message": "resource not found"}), + 404, + ) + @app.errorhandler(422) + def unprocessable(error): + return ( + jsonify({"success": False, "error": 422, "message": "unprocessable"}), + 422, + ) + + @app.errorhandler(400) + def bad_request(error): + return ( + jsonify({"success": False, "error": 400, "message": "bad request"}), + 400, + ) + + @app.errorhandler(405) + def not_found(error): + return ( + jsonify({"success": False, "error": 405, "message": "method not allowed"}), + 405, + ) return app From 2844cbacf314e45d827c2c3cdf3f444c8fa1e567 Mon Sep 17 00:00:00 2001 From: anunita Date: Thu, 14 Nov 2024 22:39:01 +0530 Subject: [PATCH 03/10] functionalities for trivia api --- backend/flaskr/__init__.py | 94 +++++++++++++++++++++---- frontend/src/components/QuestionView.js | 2 +- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index a97c0ab24..15b391840 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -16,15 +16,16 @@ def paginate_questions(request, selection): return current_questions - """ - @TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs - """ + def create_app(test_config=None): # create and configure the app app = Flask(__name__) setup_db(app) - CORS(app) + """ + @TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs + """ + CORS(app, resources={r'/*': {'origins': '*'}}) """ @TODO: Use the after_request decorator to set Access-Control-Allow @@ -37,7 +38,7 @@ def after_request(response): "Access-Control-Allow-Headers", "Content-Type,Authorization,true" ) response.headers.add( - "Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS" + "Access-Control-Allow-Methods", "GET,PATCH,POST,DELETE,OPTIONS" ) return response @@ -61,13 +62,11 @@ def retrieve_categories(): for cat in category: categories[cat.id] = cat.type - result = { + return jsonify ({ "successs": True, "categories": categories - } - - return result + }) """ @TODO: @@ -94,14 +93,14 @@ def retrieve_questions(): for cat in category: categories[cat.id] = cat.type - result = { + return jsonify ( + { "success": True, "questions": current_questions, "total_questions": len(selection), "categories": categories, "current_category": None - } - return result + }) """ @TODO: @@ -125,7 +124,7 @@ def delete_question(question_id): return jsonify( { "success": True, - #"deleted": question_id, + "deleted": question_id, } ) @@ -141,6 +140,39 @@ def delete_question(question_id): the form will clear and the question will appear at the end of the last page of the questions list in the "List" tab. """ + @app.route("/questions", methods=["POST"]) + def create_question(): + body = request.get_json() + + # to check if the body has all the fields + if not ('question' in body and 'answer' in body and + 'difficulty' in body and 'category' in body): + abort(422) + + + new_question = body.get('question') + new_answer = body.get('answer') + new_difficulty = body.get('difficulty') + new_category = body.get('category') + + # to check if all fields are provided by user + if ((new_question is None or new_question == "") or (new_answer is None or new_answer == "") or + (new_difficulty is None or new_difficulty == "") or (new_category is None or new_category == "")): + abort(422) + + try: + create_quest = Question(question=new_question, answer=new_answer, + difficulty=new_difficulty,category=new_category) + create_quest.insert() + + return jsonify( + { + "success": True, + "created": create_quest.id, + } + ) + except: + abort(422) """ @TODO: @@ -152,7 +184,28 @@ def delete_question(question_id): only question that include that string within their question. Try using the word "title" to start. """ + @app.route("/questions/search", methods=["POST"]) + def search_questions(): + body = request.get_json() + search = body.get("searchTerm", None) + + try: + if search: + selection = Question.query.order_by(Question.id).filter( + Question.question.ilike("%{}%".format(search))).all() + current_questions = paginate_questions(request, selection) + + return jsonify( + { + "success": True, + "questions": current_questions, + "total_questions": len(selection), + "current_category": None + } + ) + except: + abort(404) """ @TODO: Create a GET endpoint to get questions based on category. @@ -161,7 +214,22 @@ def delete_question(question_id): categories in the left column will cause only questions of that category to be shown. """ + @app.route('/categories//questions', methods=['GET']) + def retrieve_questions_by_category(category_id): + selection = Question.query.order_by(Question.id).filter(Question.category==category_id).all() + current_questions = paginate_questions(request, selection) + + if len(current_questions) == 0: + abort(404) + return jsonify ( + { + "success": True, + "questions": current_questions, + "total_questions": len(selection), + "current_category": category_id + }) + """ @TODO: Create a POST endpoint to get questions to play the quiz. diff --git a/frontend/src/components/QuestionView.js b/frontend/src/components/QuestionView.js index a86b1f8db..74901a329 100755 --- a/frontend/src/components/QuestionView.js +++ b/frontend/src/components/QuestionView.js @@ -84,7 +84,7 @@ class QuestionView extends Component { submitSearch = (searchTerm) => { $.ajax({ - url: `/questions`, //TODO: update request URL + url: `/questions/search`, //TODO: update request URL type: 'POST', dataType: 'json', contentType: 'application/json', From 8fe61ad4f0d0abe5191ce365ddcf95f7307a31dc Mon Sep 17 00:00:00 2001 From: anunita Date: Sat, 16 Nov 2024 10:31:04 +0530 Subject: [PATCH 04/10] all functionalities completed --- backend/flaskr/__init__.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 15b391840..6da3544f8 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -241,6 +241,36 @@ def retrieve_questions_by_category(category_id): one question at a time is displayed, the user is allowed to answer and shown whether they were correct or not. """ + @app.route('/quizzes', methods=['POST']) + def get_random_question_for_quiz(): + + body = request.get_json() + if not ('quiz_category' in body and 'previous_questions' in body): + abort(422) + + quiz_category = body.get('quiz_category') + previous_questions = body.get('previous_questions') + + if ((quiz_category is None) or (previous_questions is None)): + abort(400) + + category_id = quiz_category['id'] + + if category_id == 0: + quiz_questions = Question.query.filter(Question.id.notin_((previous_questions))).all() + else: + quiz_questions = Question.query.filter(Question.category==category_id).filter( + Question.id.notin_((previous_questions))).all() + + if len(quiz_questions) > 0: + new_question = random.choice(quiz_questions).format() + else: + new_question = None + + return jsonify({ + 'success': True, + 'question': new_question + }) """ @TODO: @@ -267,12 +297,5 @@ def bad_request(error): jsonify({"success": False, "error": 400, "message": "bad request"}), 400, ) - - @app.errorhandler(405) - def not_found(error): - return ( - jsonify({"success": False, "error": 405, "message": "method not allowed"}), - 405, - ) return app From c1a34f5fdf13674e38c6b24e2806a143d5d37414 Mon Sep 17 00:00:00 2001 From: anunita Date: Sat, 16 Nov 2024 11:08:33 +0530 Subject: [PATCH 05/10] test cases --- backend/flaskr/__init__.py | 2 +- backend/test_flaskr.py | 26 +++++++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 6da3544f8..34a7b0259 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -63,7 +63,7 @@ def retrieve_categories(): categories[cat.id] = cat.type return jsonify ({ - "successs": True, + "success": True, "categories": categories }) diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index 20a355b90..1f538567b 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -1,5 +1,6 @@ import os import unittest +import json from flaskr import create_app from models import db, Question, Category @@ -12,7 +13,7 @@ def setUp(self): """Define test variables and initialize app.""" self.database_name = "trivia_test" self.database_user = "postgres" - self.database_password = "password" + self.database_password = "Divit%402017" self.database_host = "localhost:5432" self.database_path = f"postgresql://{self.database_user}:{self.database_password}@{self.database_host}/{self.database_name}" @@ -30,14 +31,29 @@ def setUp(self): def tearDown(self): """Executed after each test""" - with self.app.app_context(): - db.session.remove() - db.drop_all() - + # with self.app.app_context(): + # db.session.remove() + # db.drop_all() + pass """ TODO Write at least one test for each test for successful operation and for expected errors. """ + def test_get_categories(self): + res = self.client.get("/categories") + data = json.loads(res.data) + + self.assertEqual(res.status_code, 200) + self.assertEqual(data["success"], True) + self.assertTrue(len(data["categories"])) + + def test_404_sent_requesting_categories(self): + res = self.client.get("/categories/1000") + data = json.loads(res.data) + + self.assertEqual(res.status_code, 404) + self.assertEqual(data["success"], False) + self.assertEqual(data["message"], "resource not found") # Make the tests conveniently executable From 52ce50f298a60319c6b54ec26605ac089887b0ee Mon Sep 17 00:00:00 2001 From: anunita Date: Sun, 17 Nov 2024 11:34:38 +0530 Subject: [PATCH 06/10] unit test cases --- backend/flaskr/__init__.py | 33 +++++----- backend/test_flaskr.py | 128 +++++++++++++++++++++++++++++++++++-- 2 files changed, 139 insertions(+), 22 deletions(-) diff --git a/backend/flaskr/__init__.py b/backend/flaskr/__init__.py index 34a7b0259..f82de6042 100644 --- a/backend/flaskr/__init__.py +++ b/backend/flaskr/__init__.py @@ -109,7 +109,7 @@ def retrieve_questions(): TEST: When you click the trash icon next to a question, the question will be removed. This removal will persist in the database and when you refresh the page. """ - @app.route("/questions/", methods=["DELETE"]) + @app.route("/questions/", methods=["DELETE"]) def delete_question(question_id): try: question = Question.query.filter(Question.id == question_id).one_or_none() @@ -190,22 +190,21 @@ def search_questions(): search = body.get("searchTerm", None) - try: - if search: - selection = Question.query.order_by(Question.id).filter( - Question.question.ilike("%{}%".format(search))).all() - current_questions = paginate_questions(request, selection) - - return jsonify( - { - "success": True, - "questions": current_questions, - "total_questions": len(selection), - "current_category": None - } - ) - except: - abort(404) + if search: + selection = Question.query.order_by(Question.id).filter( + Question.question.ilike("%{}%".format(search))).all() + current_questions = paginate_questions(request, selection) + + return jsonify( + { + "success": True, + "questions": current_questions, + "total_questions": len(selection), + "current_category": None + } + ) + abort(404) + """ @TODO: Create a GET endpoint to get questions based on category. diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index 1f538567b..a22dec2e5 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -3,7 +3,7 @@ import json from flaskr import create_app -from models import db, Question, Category +from models import db, Question, Category, setup_db class TriviaTestCase(unittest.TestCase): @@ -31,9 +31,6 @@ def setUp(self): def tearDown(self): """Executed after each test""" - # with self.app.app_context(): - # db.session.remove() - # db.drop_all() pass """ TODO @@ -47,7 +44,7 @@ def test_get_categories(self): self.assertEqual(data["success"], True) self.assertTrue(len(data["categories"])) - def test_404_sent_requesting_categories(self): + def test_404_sent_requesting_invalid_categories(self): res = self.client.get("/categories/1000") data = json.loads(res.data) @@ -55,6 +52,127 @@ def test_404_sent_requesting_categories(self): self.assertEqual(data["success"], False) self.assertEqual(data["message"], "resource not found") + def test_get_paginated_questions(self): + res = self.client.get("/questions") + data = json.loads(res.data) + + self.assertEqual(res.status_code, 200) + self.assertEqual(data["success"], True) + self.assertTrue(data["total_questions"]) + self.assertTrue(len(data["questions"])) + self.assertTrue(len(data["categories"])) + + def test_404_sent_requesting_questions_beyond_valid_page(self): + res = self.client.get("/questions?page=10000", json={"rating": 1}) + data = json.loads(res.data) + + self.assertEqual(res.status_code, 404) + self.assertEqual(data["success"], False) + self.assertEqual(data["message"], "resource not found") + + def test_delete_question(self): + with self.app.app_context(): + sample_question = Question(question="what is the sample question?", answer="this", + difficulty = 1, category = 3) + sample_question.insert() + sample_question_id = sample_question.id + + res = self.client.delete(f'/questions/{sample_question_id}') + data = json.loads(res.data) + + question = Question.query.filter(Question.id == sample_question_id).one_or_none() + + self.assertEqual(res.status_code, 200) + self.assertEqual(data["success"], True) + self.assertEqual(data["deleted"], str(sample_question_id)) + self.assertEqual(question, None) + + def test_422_if_question_does_not_exist(self): + res = self.client.delete("/questions/-1") + data = json.loads(res.data) + + self.assertEqual(res.status_code, 422) + self.assertEqual(data["success"], False) + self.assertEqual(data["message"], "unprocessable") + + def test_create_new_question(self): + sample_question = {"question": "what is the sample question?", + "answer": "this", + "difficulty": 1, + "category": 3} + res = self.client.post("/questions", json=sample_question) + data = json.loads(res.data) + + self.assertEqual(res.status_code, 200) + self.assertEqual(data["success"], True) + self.assertTrue(data["created"]) + + def test_422_if_question_creation_not_allowed(self): + sample_question = {"question": "what is the sample question?", + "answer": "this", + "difficulty": 1} + res = self.client.post("/questions", json=sample_question) + data = json.loads(res.data) + + self.assertEqual(res.status_code, 422) + self.assertEqual(data["success"], False) + self.assertEqual(data["message"], "unprocessable") + + def test_get_question_search_with_results(self): + res = self.client.post("/questions/search", json={"searchTerm": "is"}) + data = json.loads(res.data) + + self.assertEqual(res.status_code, 200) + self.assertEqual(data["success"], True) + self.assertIsNotNone(data['questions']) + self.assertIsNotNone(data['total_questions']) + + def test_get_book_search_without_results(self): + res = self.client.post("/questions/search", json={"searchTerm": {}}) + data = json.loads(res.data) + + self.assertEqual(res.status_code, 404) + self.assertEqual(data["success"], False) + self.assertEqual(data["message"], "resource not found") + + def test_get_questions_by_category(self): + res = self.client.get('/categories/1/questions') + data = json.loads(res.data) + + self.assertEqual(res.status_code, 200) + self.assertEqual(data['success'], True) + self.assertTrue(len(data['questions'])) + self.assertTrue(data['total_questions']) + self.assertTrue(data['current_category']) + + def test_404_get_questions_by_category(self): + res = self.client.get('/categories/-1/questions') + data = json.loads(res.data) + + self.assertEqual(res.status_code, 404) + self.assertEqual(data["success"], False) + self.assertEqual(data["message"], "resource not found") + + def test_play_quiz(self): + quiz = {'previous_questions': [], + 'quiz_category': {'type': 'click', 'id': 0}} + + res = self.client.post('/quizzes', json=quiz) + data = json.loads(res.data) + + self.assertEqual(res.status_code, 200) + self.assertEqual(data['success'], True) + + def test_422_play_quiz_fields_missing(self): + quiz = {'quiz_category': {'type': 'click', 'id': 0}} + res = self.client.post('/quizzes', json=quiz) + data = json.loads(res.data) + + self.assertEqual(res.status_code, 422) + self.assertEqual(data["success"], False) + self.assertEqual(data["message"], "unprocessable") + + # Make the tests conveniently executable if __name__ == "__main__": From c106dda466ef75c92e857c6bd3f68c9a797b29d2 Mon Sep 17 00:00:00 2001 From: anunita Date: Sun, 17 Nov 2024 19:41:54 +0530 Subject: [PATCH 07/10] final commit --- backend/README.md | 246 ++++++++++++++++++++++++++++++++++++++--- backend/models.py | 3 +- backend/test_flaskr.py | 14 ++- 3 files changed, 240 insertions(+), 23 deletions(-) diff --git a/backend/README.md b/backend/README.md index d5318811d..a1a475b7a 100644 --- a/backend/README.md +++ b/backend/README.md @@ -67,32 +67,244 @@ One note before you delve into your tasks: for each endpoint, you are expected t 8. Create a `POST` endpoint to get questions to play the quiz. This endpoint should take a category and previous question parameters and return a random questions within the given category, if provided, and that is not one of the previous questions. 9. Create error handlers for all expected errors including 400, 404, 422, and 500. -## Documenting your Endpoints +### API Documentation -You will need to provide detailed documentation of your API endpoints including the URL, request parameters, and the response body. Use the example below as a reference. +**** +GET "\categories" +curl -X GET 'http://127.0.0.1:5000/categories' -### Documentation Example +- Fetches a dictionary of categories in which the keys are the ids and the value is the corresponding string of the category +- Request Parameters: None +- Response Body: -`GET '/api/v1.0/categories'` +categories: A dictionary containing Category ID and Category Type as a key value pair -- Fetches a dictionary of categories in which the keys are the ids and the value is the corresponding string of the category -- Request Arguments: None -- Returns: An object with a single key, `categories`, that contains an object of `id: category_string` key: value pairs. +{ + "categories": { + "1": "Science", + "2": "Art", + "3": "Geography", + "4": "History", + "5": "Entertainment", + "6": "Sports" + }, + "success": true +} + +**** +GET "\questions?page=" +curl -X GET 'http://127.0.0.1:5000/questions' +curl -X GET 'http://127.0.0.1:5000/questions?page=2' + +- Fetches a paginated dictionary of questions of all available categories. A page contains 10 questions. +- Request parameters (optional): page number in integer +- Response Body: + +categories: A dictionary containing Category ID and Category Type as a key value pair +current_category: Null +questions: List of questions +total_questions: Total Number of questions -```json { - "1": "Science", - "2": "Art", - "3": "Geography", - "4": "History", - "5": "Entertainment", - "6": "Sports" + "categories": { + "1": "Science", + "2": "Art", + "3": "Geography", + "4": "History", + "5": "Entertainment", + "6": "Sports" + }, + "current_category": null, + "questions": [ + { + "answer": "Tom Cruise", + "category": 5, + "difficulty": 4, + "id": 4, + "question": "What actor did author Anne Rice first denounce, then praise in the role of her beloved Lestat?" + }, + { + "answer": "Maya Angelou", + "category": 4, + "difficulty": 2, + "id": 5, + "question": "Whose autobiography is entitled 'I Know Why the Caged Bird Sings'?" + }, + { + "answer": "Edward Scissorhands", + "category": 5, + "difficulty": 3, + "id": 6, + "question": "What was the title of the 1990 fantasy directed by Tim Burton about a young man with multi-bladed appendages?" + }, + { + "answer": "Brazil", + "category": 6, + "difficulty": 3, + "id": 10, + "question": "Which is the only team to play in every soccer World Cup tournament?" + }, + { + "answer": "Uruguay", + "category": 6, + "difficulty": 4, + "id": 11, + "question": "Which country won the first ever soccer World Cup in 1930?" + }, + { + "answer": "George Washington Carver", + "category": 4, + "difficulty": 2, + "id": 12, + "question": "Who invented Peanut Butter?" + }, + { + "answer": "Lake Victoria", + "category": 3, + "difficulty": 2, + "id": 13, + "question": "What is the largest lake in Africa?" + }, + { + "answer": "Agra", + "category": 3, + "difficulty": 2, + "id": 15, + "question": "The Taj Mahal is located in which Indian city?" + }, + { + "answer": "Escher", + "category": 2, + "difficulty": 1, + "id": 16, + "question": "Which Dutch graphic artist\u2013initials M C was a creator of optical illusions?" + }, + { + "answer": "Jackson Pollock", + "category": 2, + "difficulty": 2, + "id": 19, + "question": "Which American artist was a pioneer of Abstract Expressionism, and a leading exponent of action painting?" + } + ], + "success": true, + "total_questions": 46 } -``` -## Testing +**** +DELETE "/questions/" +curl -X DELETE 'http://127.0.0.1:5000/questions/2' + +- Delete an existing questions from the available questions based on the question ID +- Request Parameters: question_id in integer that needs to be deleted +- Response Body: + +deleted: Question ID that is deleted +{ + "deleted": "49", + "success": true +} + +**** +POST /questions +curl -X POST -H "Content-Type: application/json" -d '{"question":"What is the capital city of India?", "answer":"New Delhi", "difficulty":2, "category":3}' 'http://127.0.0.1:5000/questions' + + +- Add a new question to the list of available questions +- Request Paremeter: Need to provide the new question and its answer, difficulty level and category ID in the following format +{question:string, answer:string, difficulty:int, category:int} +- Response Body: + +created: Question ID that is created +{ + "created": 91, + "success": true +} + +**** +POST "/questions/search" +curl -X POST -H "Content-Type: application/json" -d '{"searchTerm":"Taj"}' 'http://127.0.0.1:5000/questions/search' + +- Fetches all questions based on the search string provided (not case-sensitive) +- Request body: Need to provide the search string in the following format +{searchTerm:string} +- Response Body: + +current_category: Null +questions: List of questions having the search string (not case sensitive) +total_questions: Total Number of questions having the search string +{ + "current_category": null, + "questions": [ + { + "answer": "Agra", + "category": 3, + "difficulty": 2, + "id": 15, + "question": "The Taj Mahal is located in which Indian city?" + } + ], + "success": true, + "total_questions": 1 +} + +**** +GET "/categories//questions" +curl -X GET 'http://127.0.0.1:5000/categories/2/questions' + +- Fetches a dictionary of questions for the given category ID +- Request Parameter: Category ID for questions should be in integer +- Response Body: -Write at least one test for the success and at least one error behavior of each endpoint using the unittest library. +current_category: Current category ID +questions: List of questions under the given category +total_questions: Total Number of questions under the given category +{ + "current_category": 2, + "questions": [ + { + "answer": "Escher", + "category": 2, + "difficulty": 1, + "id": 16, + "question": "Which Dutch graphic artist\u2013initials M C was a creator of optical illusions?" + }, + { + "answer": "Jackson Pollock", + "category": 2, + "difficulty": 2, + "id": 19, + "question": "Which American artist was a pioneer of Abstract Expressionism, and a leading exponent of action painting?" + } + ], + "success": true, + "total_questions": 2 +} + +**** +POST "/quizzes" +For a particular category : +curl -X POST -H "Content-Type: application/json" -d '{"previous_questions": [], "quiz_category": {"type": "Sports", "id": "6"}}' 'http://127.0.0.1:5000/quizzes' +For All categories: +curl -X POST -H "Content-Type: application/json" -d '{"previous_questions": [], "quiz_category": {"type": "click", "id": 0}}' 'http://127.0.0.1:5000/quizzes' + +- Fetches one random question within a specified category or all categories based on the option chosen. It does not repeat the previous question. +- Request Parameter: The request parameter consists of previous questions and quiz category containing category ID and category type +{previous_questions: arr, quiz_category: {id:int, type:string}} +- Response Body: + +question: Random questions under the given or any category based on the option chosen +{ + "question": { + "answer": "The Liver", + "category": 1, + "difficulty": 4, + "id": 20, + "question": "What is the heaviest organ in the human body?" + }, + "success": true +} +## Testing To deploy the tests, run diff --git a/backend/models.py b/backend/models.py index 4e114b16b..0ac8be9ed 100644 --- a/backend/models.py +++ b/backend/models.py @@ -2,9 +2,8 @@ from flask_sqlalchemy import SQLAlchemy database_name = 'trivia' database_user = 'postgres' -database_password = 'Divit%402017' database_host = 'localhost:5432' -database_path = f'postgresql://{database_user}:{database_password}@{database_host}/{database_name}' +database_path = f'postgresql://{database_user}@{database_host}/{database_name}' db = SQLAlchemy() diff --git a/backend/test_flaskr.py b/backend/test_flaskr.py index a22dec2e5..d43974956 100644 --- a/backend/test_flaskr.py +++ b/backend/test_flaskr.py @@ -13,9 +13,8 @@ def setUp(self): """Define test variables and initialize app.""" self.database_name = "trivia_test" self.database_user = "postgres" - self.database_password = "Divit%402017" self.database_host = "localhost:5432" - self.database_path = f"postgresql://{self.database_user}:{self.database_password}@{self.database_host}/{self.database_name}" + self.database_path = f"postgresql://{self.database_user}@{self.database_host}/{self.database_name}" # Create app with the test configuration self.app = create_app({ @@ -36,6 +35,7 @@ def tearDown(self): TODO Write at least one test for each test for successful operation and for expected errors. """ + # test cases for getting valid and invalid categories def test_get_categories(self): res = self.client.get("/categories") data = json.loads(res.data) @@ -52,6 +52,7 @@ def test_404_sent_requesting_invalid_categories(self): self.assertEqual(data["success"], False) self.assertEqual(data["message"], "resource not found") + # test cases for getting questions for valid and invalid number of pages def test_get_paginated_questions(self): res = self.client.get("/questions") data = json.loads(res.data) @@ -63,13 +64,14 @@ def test_get_paginated_questions(self): self.assertTrue(len(data["categories"])) def test_404_sent_requesting_questions_beyond_valid_page(self): - res = self.client.get("/questions?page=10000", json={"rating": 1}) + res = self.client.get("/questions?page=10000") data = json.loads(res.data) self.assertEqual(res.status_code, 404) self.assertEqual(data["success"], False) self.assertEqual(data["message"], "resource not found") + # test cases for deleting valid and invalid question id def test_delete_question(self): with self.app.app_context(): sample_question = Question(question="what is the sample question?", answer="this", @@ -95,6 +97,7 @@ def test_422_if_question_does_not_exist(self): self.assertEqual(data["success"], False) self.assertEqual(data["message"], "unprocessable") + # test cases for creating valid and invalid questions def test_create_new_question(self): sample_question = {"question": "what is the sample question?", "answer": "this", @@ -117,7 +120,8 @@ def test_422_if_question_creation_not_allowed(self): self.assertEqual(res.status_code, 422) self.assertEqual(data["success"], False) self.assertEqual(data["message"], "unprocessable") - + + # test cases for searching valid and invalid keywords in questions def test_get_question_search_with_results(self): res = self.client.post("/questions/search", json={"searchTerm": "is"}) data = json.loads(res.data) @@ -135,6 +139,7 @@ def test_get_book_search_without_results(self): self.assertEqual(data["success"], False) self.assertEqual(data["message"], "resource not found") + # test cases for getting valid and invalid questions by categories def test_get_questions_by_category(self): res = self.client.get('/categories/1/questions') data = json.loads(res.data) @@ -153,6 +158,7 @@ def test_404_get_questions_by_category(self): self.assertEqual(data["success"], False) self.assertEqual(data["message"], "resource not found") + # test cases for playing quiz with valid and invalid inputs sent def test_play_quiz(self): quiz = {'previous_questions': [], 'quiz_category': {'type': 'click', 'id': 0}} From 4642540025068273fb5bdffd59363dca1db2b7f6 Mon Sep 17 00:00:00 2001 From: anunita <81161885+anunita@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:46:14 +0530 Subject: [PATCH 08/10] Update README.md --- backend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/README.md b/backend/README.md index a1a475b7a..adca5fa8b 100644 --- a/backend/README.md +++ b/backend/README.md @@ -70,7 +70,7 @@ One note before you delve into your tasks: for each endpoint, you are expected t ### API Documentation **** -GET "\categories" +GET "\categories" curl -X GET 'http://127.0.0.1:5000/categories' - Fetches a dictionary of categories in which the keys are the ids and the value is the corresponding string of the category From 3a13b0edfadb2cf8f1b97b70da99074a5c00c8e4 Mon Sep 17 00:00:00 2001 From: anunita <81161885+anunita@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:47:06 +0530 Subject: [PATCH 09/10] Update README.md --- backend/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/README.md b/backend/README.md index adca5fa8b..0e43cf7e7 100644 --- a/backend/README.md +++ b/backend/README.md @@ -92,8 +92,8 @@ categories: A dictionary containing Category ID and Category Type as a key value } **** -GET "\questions?page=" -curl -X GET 'http://127.0.0.1:5000/questions' +GET "\questions?page=" +curl -X GET 'http://127.0.0.1:5000/questions' curl -X GET 'http://127.0.0.1:5000/questions?page=2' - Fetches a paginated dictionary of questions of all available categories. A page contains 10 questions. @@ -192,7 +192,7 @@ total_questions: Total Number of questions } **** -DELETE "/questions/" +DELETE "/questions/" curl -X DELETE 'http://127.0.0.1:5000/questions/2' - Delete an existing questions from the available questions based on the question ID @@ -206,7 +206,7 @@ deleted: Question ID that is deleted } **** -POST /questions +POST /questions curl -X POST -H "Content-Type: application/json" -d '{"question":"What is the capital city of India?", "answer":"New Delhi", "difficulty":2, "category":3}' 'http://127.0.0.1:5000/questions' @@ -222,7 +222,7 @@ created: Question ID that is created } **** -POST "/questions/search" +POST "/questions/search" curl -X POST -H "Content-Type: application/json" -d '{"searchTerm":"Taj"}' 'http://127.0.0.1:5000/questions/search' - Fetches all questions based on the search string provided (not case-sensitive) @@ -249,7 +249,7 @@ total_questions: Total Number of questions having the search string } **** -GET "/categories//questions" +GET "/categories//questions" curl -X GET 'http://127.0.0.1:5000/categories/2/questions' - Fetches a dictionary of questions for the given category ID @@ -282,9 +282,9 @@ total_questions: Total Number of questions under the given category } **** -POST "/quizzes" +POST "/quizzes" For a particular category : -curl -X POST -H "Content-Type: application/json" -d '{"previous_questions": [], "quiz_category": {"type": "Sports", "id": "6"}}' 'http://127.0.0.1:5000/quizzes' +curl -X POST -H "Content-Type: application/json" -d '{"previous_questions": [], "quiz_category": {"type": "Sports", "id": "6"}}' 'http://127.0.0.1:5000/quizzes' For All categories: curl -X POST -H "Content-Type: application/json" -d '{"previous_questions": [], "quiz_category": {"type": "click", "id": 0}}' 'http://127.0.0.1:5000/quizzes' From edcb3c3b4f91fd57f47f4a76effe032441235607 Mon Sep 17 00:00:00 2001 From: anunita <81161885+anunita@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:51:15 +0530 Subject: [PATCH 10/10] Update README.md