diff --git a/app/connect.py b/app/connect.py index 772895b..439037b 100644 --- a/app/connect.py +++ b/app/connect.py @@ -186,6 +186,7 @@ def delete_entry(self, tablename, id_value): print(error) def drop_table(self, tablename): + try: drop_table_command = f"DROP TABLE {tablename} CASCADE" self.cursor.execute(drop_table_command) diff --git a/app/routes/routes.py b/app/routes/routes.py index f8bfb6a..c847687 100644 --- a/app/routes/routes.py +++ b/app/routes/routes.py @@ -3,12 +3,12 @@ from flask import Flask, Response, json, jsonify, request, url_for from flask_jwt_extended import (JWTManager, create_access_token, get_jwt_identity, jwt_required) - from werkzeug.security import check_password_hash + from app import app -from app.models import (Answer, Question, User, valid_answer, valid_login_data, - valid_question, valid_username, valid_signup_data) from app.connect import conn +from app.models import (Answer, Question, User, valid_answer, valid_login_data, + valid_question, valid_signup_data, valid_username) @app.route('/') @@ -48,7 +48,7 @@ def login(): ) msg = {'access_token': f'{access_token}'} - return jsonify({f'access token created for user {username}': msg}), 200 + return jsonify({f'User:{username} logged in.': msg}), 200 msg = {"error": "Invalid login data", "Hint": """required formart is: {'username':'xyz', 'password': 'xyh12',}"""} @@ -62,50 +62,60 @@ def signup(): return jsonify({'message': 'JSON missing in request!'}), 400 if valid_signup_data(request_data): + print('valid data') username = str(request_data['username']).split() email = request_data['email'] password = request_data['password'] repeat_password = request_data['repeat_password'] if not username: + print('not username') return jsonify({ 'message': 'Required parameter: username missing!' }), 400 - else: - if username: - if len(username) > 1: - username_ = username[0] + " " + username[1] - username = username_ - if not valid_username(username): - return jsonify({'message': f'username {username} already taken!'}), 401 - elif len(username) == 1: + elif username: + print('username') + if len(username) > 1: + username_ = username[0] + " " + username[1] + username = username_ + if not valid_username(username): + return jsonify({'message': f'Username: {username} already taken!'}), 401 + else: + if len(username) == 1: username = username[0] if not valid_username(username): - return jsonify({'message': f'username {username} already taken!'}), 401 - if not email: - return jsonify({'message': 'Required parameter: email missing!'}), 400 - elif not password: - return jsonify({'message': 'Required parameter: password missing!'}), 400 - else: - if not repeat_password: - msg = 'Required parameter: repeat_password missing!' - return jsonify({'message': f'{msg}'}), 400 - - if repeat_password == password: - - user = User(username, email, password) - conn.insert_new_record('users', user.__repr__()) - - return jsonify({ - 'success': f"{username}'s account created successfully" - }), 200 - else: - if repeat_password != password: + return jsonify({'message': f'Username: {username} already taken!'}), 401 + + if not email or len(email.strip()) == 0: + print("not email, valid username") + return jsonify({'message': 'Required parameter: email missing!'}), 400 + if username and email: + print('valid username and email') + if not password or len(str(password).strip()) == 0: + print("not password") + return jsonify({'message': 'Required parameter: password missing!'}), 400 + + if username and email and password: + if not repeat_password or len(str(repeat_password).strip()) == 0: + print("not_repeat pw") + msg = 'Required parameter: repeat_password missing!' + return jsonify({'message': f'{msg}'}), 400 + else: + if repeat_password: + if repeat_password == password: + user = User(username, email, password) + conn.insert_new_record('users', user.__repr__()) + return jsonify({ - 'message': 'Password does not match repeat_password' - }), 401 + 'success': f"{username}'s account created successfully" + }), 200 + else: + if repeat_password != password: + return jsonify({ + 'message': 'Password does not match repeat_password' + }), 401 msg = {"error": "Invalid signup data", - "Hint": """required formart is: {'username':'xyz', + "Hint": """required formart is: {'username':'xyz', 'email':'xyz@gmail.com', 'password': 'xyh12', 'repeat_password':'xyh12'}"""} @@ -126,47 +136,78 @@ def get_questions(): } questions.append(temp) return jsonify({'questions': questions}), 200 - return jsonify({'message': 'No Questions added yet'}), 404 + return jsonify({'message': 'No Questions added.'}), 404 @app.route('/api/v1/questions/', methods=['GET']) def get_question(questionId): questionsList = conn.query_all('questions') answersList = conn.query_all('answers') - answers = [[ans[2]] for ans in answersList if int(ans[1]) == questionId] + ans_list= [] + if questionsList: - for question in questionsList: - if int(question[4]) == questionId: + question = [qn for qn in questionsList if int(qn[4])==questionId] + if question and not answersList: + print(question) + temp = { + 'questionId': question[0][4], + 'topic': question[0][1], + 'body': question[0][2], + 'author': question[0][3] + } + return jsonify(temp), 200 + if question and answersList: + answers = [ans for ans in answersList if int(ans[1]) == questionId] + if answers: + for ans in answers: + temp1 = { + 'answerId': ans[3], + 'body': ans[2], + 'author': ans[4], + 'prefered': ans[5], + 'questionId': ans[1] + } + + ans_list.append(temp1) temp = { - 'questionId': question[4], - 'topic': question[1], - 'body': question[2], - 'author': question[3], - 'answers': answers + 'questionId': question[0][4], + 'topic': question[0][1], + 'body': question[0][2], + 'author': question[0][3], + 'answers': ans_list } return jsonify(temp), 200 + return Response(json.dumps(['Question not Found']), status=404, mimetype='application/json') - return jsonify({f'Question {questionId}': 'does not exist.'}), 200 + + return jsonify({'message': 'No questions added.'}), 200 @app.route('/api/v1/questions//answers', methods=['GET']) def get_answers(questionId): answersList = conn.query_all('answers') - answers = [] - if answersList: - for answer in answersList: - if int(answer[1]) == questionId: - temp = { - 'answerId': answer[3], - 'author': answer[4], - 'body': answer[2], - 'prefered': answer[5], - 'questionId': answer[1] - } - answers.append(temp) - return jsonify({'answers': answers}), 200 - return jsonify({'message': f'No Answers added for question {questionId}.'}), 404 + questionsList = conn.query_all('questions') + if questionsList: + questions = [qn for qn in questionsList if int(qn[4]) == questionId] + if questions: + answers = [] + if answersList: + for answer in answersList: + if int(answer[1]) == questionId: + temp = { + 'answerId': answer[3], + 'author': answer[4], + 'body': answer[2], + 'prefered': answer[5], + 'questionId': answer[1] + } + answers.append(temp) + return jsonify({'answers': answers}), 200 + return jsonify({'message': 'Answer not found!'}), 404 + return jsonify({'message': 'No Answers added.'}), 404 + return jsonify({'message': 'Question not found!'}), 404 + return jsonify({'message': 'No questions added!'}), 404 @app.route('/api/v1/questions//answers/', @@ -188,11 +229,9 @@ def get_answer(questionId, answerId): 'QuestionId': answer[1] } return jsonify(temp), 200 - return Response(json.dumps(['Answer not Found']), - status=404, mimetype='application/json') - return jsonify({ - f'Answer{answerId} for Question{questionId}': 'not found.' - }), 404 + return Response(json.dumps(['Answer not found!']), + status=404, mimetype='application/json') + return jsonify({'message': 'Question not found!'}), 404 @app.route('/api/v1/questions', methods=['POST']) @@ -216,8 +255,8 @@ def add_question(): conn.insert_new_record('questions', question.__repr__()) return jsonify({ - 'msg': f'Question {question.id} posted successfully', - 'Posted by': f'{current_user}' + 'message': 'Question posted successfully', + 'question': question.__repr__() }), 201 else: @@ -249,7 +288,7 @@ def add_answer(questionId): if questionsList: answer_check = valid_answer(request_data) ids = [int(qn[4]) for qn in questionsList] - if answer_check[0]: + if answer_check[0] and questionId in ids: temp = { 'Qn_Id': questionId, 'body': request_data['body'] @@ -259,7 +298,8 @@ def add_answer(questionId): conn.insert_new_record('answers', answer.__repr__()) return jsonify({ - 'msg': f'Answer {answer.answerId} posted successfully' + 'message': 'Answer posted successfully', + 'answer': answer.__repr__() }), 201 else: @@ -277,8 +317,8 @@ def add_answer(questionId): response = Response(json.dumps([bad_object]), status=400, mimetype='application/json') return response - return jsonify({f'Attempt to answer Question {questionId}': - f'Question {questionId} does not exist.'}), 404 + return jsonify({f'Attempt to answer Question with Id:{questionId}': + 'Question not found!.'}), 404 return jsonify({ 'message': 'To post an answer, you need to be logged in', @@ -291,40 +331,39 @@ def add_answer(questionId): def select_answer_as_preferred(questionId, answerId): current_user = get_jwt_identity() if current_user: - request_data = request.get_json() + # request_data = request.get_json() questionsList = conn.query_all('questions') + answersList = conn.query_all('answers') - if questionsList: + if answersList or questionsList: #answer_check = valid_answer(request_data) usr = [qn[3] for qn in questionsList if int(qn[4]) == questionId] + + answer = [ans for ans in answersList if int(ans[1]) == questionId and int(ans[3]) == answerId] if usr and usr[0] == current_user: - conn.update_answer(str(answerId)) - - return jsonify({ - 'msg': f"Answer {answerId} marked as preferred" - }), 201 - else: + if answer[0]: - if not answer_check[0] and len(answer_check) > 1: - reason = answer_check[1] - return jsonify({"error": f"{reason}"}) - else: - bad_object = { - "error": "Invalid answer object", - "hint": '''Request format should be { - 'body': 'this is the body', - 'Qn_Id': 2}''' + conn.update_answer(str(answerId)) + temp = { + 'answerId': answer[0][3], + 'body': answer[0][2], + 'author': answer[0][4], + 'prefered': True, + 'questionId': answer[0][1] } - response = Response(json.dumps([bad_object]), - status=400, mimetype='application/json') - return response + return jsonify({ + 'message': "Answer marked as preferred", + 'answer': temp + }), 201 + + return jsonify({'message': 'Answer not found!'}), 404 return jsonify({'Access denied': f'Only question auhtor:{current_user} can perform this action!'}) - return jsonify({f'Attempt to select answer to Question {questionId} as prefered': - f'Question {questionId} does not exist.'}), 404 + return jsonify({'message': + 'Question not found'}), 404 return jsonify({ 'message': 'To post an answer, you need to be logged in', @@ -360,8 +399,13 @@ def update_question(questionId): updated_question['topic'], updated_question['body'], str(questionId)) - msg = f'Question {questionId} updated successfully.' - return jsonify({'msg': msg}), 200 + temp = { + 'new_topic': updated_question['topic'], + 'new_body': updated_question['body'] + } + msg = 'Question updated successfully.' + return jsonify({'message': msg, + 'updated_question': temp}), 200 return jsonify({ 'msg': 'body and topic fields should not be empty'}) msg = f'Only question auhtor:{current_user} can perform this action!' @@ -394,7 +438,7 @@ def delete_question(questionId): conn.delete_entry('questions', str(questionId)) message = { - 'success': f"Question {questionId} deleted successfully!"} + 'success': f"Question deleted!"} response = Response( json.dumps(message), status=202, mimetype='application/json') return response diff --git a/tests/test_models.py b/tests/test_models.py index 7d31d7e..ebe8ce8 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -3,7 +3,8 @@ from tests import app from app.connect import DatabaseConnection -from app.models import Answer, Question +from app.models import (Answer, Question, User, valid_answer, valid_login_data, + valid_question, valid_signup_data, valid_username) from .base import APITestCase, answersList, questionsList @@ -13,14 +14,37 @@ class TestModels(APITestCase): def setUp(self): self.conn = DatabaseConnection() + self.conn.create_Questions_table() + self.conn.create_Answers_table() + self.conn.create_Users_table() + self.user1 = User('Peter', 'ptr@gmail.com','1234') + self.user2 = User('Jane', 'jenn@gmail.com','1234') self.question1 = Question('computers', 'what is python ?') + self.question1.author = self.user1.username self.question2 = Question('api', 'what is Flask ?') + self.question2.author = self.user2.username self.answer1 = Answer('it is a programming language', self.question1.id) + self.answer1.author = self.user1.username self.answer2 = Answer('it a microframework for building python apps', self.question2.id) - answersList.append(self.answer1.__repr__()) + self.answer2.author = self.user2.username + + def insert_new_records(queries): + for query in queries: + self.conn.insert_new_record(query[0], query[1]) + + self.queries = (('questions', self.question1.__repr__()), ('questions', self.question2.__repr__()), + ('answers', self.answer1.__repr__()), ('answers', self.answer2.__repr__()), + ('users', self.user1.__repr__()), ('users', self.user2.__repr__())) + + insert_new_records(self.queries) + + self.ansList = self.conn.query_all('answers') + self.qnsList = self.conn.query_all('questions') + self.usersList = self.conn.query_all('users') + def tearDown(self): @@ -29,32 +53,150 @@ def tearDown(self): self.conn.drop_table('answers') - def test_answerList_created_properly(self): - self.assertEqual(6, len(answersList)) - for answer in answersList: - self.assertIn('answerId', answer) - self.assertIn('body', answer) - self.assertIn('Qn_Id', answer) - def test_answers_questions_have_uniqueIds(self): self.assertTrue(self.question1.id != self.question2.id) self.assertTrue(self.answer1.answerId != self.answer2.answerId) + self.assertTrue(self.user1.id != self.user2.id) def test_question_answer_relationship(self): self.assertTrue(self.question1.id == self.answer1.Qn_Id) self.assertTrue(self.answer2.Qn_Id == self.question2.id) + self.assertTrue(self.question1.author == self.user1.username) + self.assertTrue(self.answer2.author == self.user2.username) - def test_questionsList_created_properly(self): - self.assertEqual(5, len(questionsList)) - for question in questionsList: - self.assertIn('questionId', question) - self.assertIn('topic', question) - self.assertIn('body', question) - - def test_repr_turnsObject_into_dict(self): res1 = self.answer1.__repr__() res2 = self.question1.__repr__() + res3 = self.user1.__repr__() self.assertTrue(type(res1) == dict) self.assertTrue(type(res2) == dict) + self.assertTrue(type(res3) == dict) + + def test_recors_inserted_properly(self): + if self.qnsList: + for question in self.qnsList: + self.assertEqual(5, len(question)) + self.assertEqual(2, len(self.qnsList)) + + if self.ansList: + for answer in self.ansList: + self.assertEqual(6, len(answer)) + self.assertEqual(2, len(self.ansList)) + + if self.usersList: + for user in self.usersList: + self.assertEqual(5, len(user)) + self.assertEqual(2, len(self.usersList)) + + def test_user_can_query_db(self): + question = Question('computer science', 'What is a program?') + question.author = self.user1.username + body = '''a program is a set of instructions + given to a computer to perform certain tasks''' + answer = Answer(body, question.id) + answer.author = self.user2.username + + self.conn.insert_new_record('questions', question.__repr__()) + self.conn.insert_new_record('answers', answer.__repr__()) + ansL = [ans for ans in self.conn.query_all('answers') if int(ans[1])==question.id] + + qnsL = [qn for qn in self.conn.query_all('questions') if int(qn[4])==question.id] + + self.assertEqual(body, ansL[0][2]) + self.assertEqual(self.user2.username, ansL[0][4]) + self.assertEqual('computer science', qnsL[0][1]) + self.assertEqual('What is a program?', qnsL[0][2]) + + def test_valid_question(self): + data = { + 'topic': 'computers and tech', + 'body': 'What is the relevance of computers in technology!' + } + res = valid_question(data) + self.assertEqual((True, ), res) + data['topic'] = 'api' + res = valid_question(data) + value = (False, "Question topic already exists!") + self.assertEqual(value, res) + topic = data['topic'] + body = data['body'] + condition1 = topic == ' ' or topic == '' or topic == 1234 + condition2 = body == ' ' or body == '' or body == 1234 + if condition1 or condition2: + res = valid_question(data) + value = (False, {"hint_1":"Question topic or body should not be empty!", + "hint_2":"body and topic fileds should not consist entirely of integer-type data"}) + self.assertEqual(value, res) + elif condition1 and condition2 : + res = valid_question(data) + value = (False, {"hint_1":"Question topic or body should not be empty!", + "hint_2":"body and topic fileds should not consist entirely of integer-type data"}) + self.assertEqual(value, res) + else: + data = {} + res = valid_question(data) + self.assertEqual((False, ), res) + + def test_valid_username(self): + res = valid_username('Peter') + self.assertTrue(res == False) + res = valid_username('Kangol') + self.assertTrue(res == True) + + def test_valid_answer(self): + data ={ + 'body': 'It is a scripting language', + } + res = valid_answer(data) + self.assertTrue(res == (True, )) + value = (False, {'hint_1': "Answer body should not be empty!", + 'hint_2': """body and Qn_Id fileds should not contain + numbers only and string-type data respectively"""}) + if len(data['body']) == 0 or type(data['body']) == int: + res = valid_answer(data) + self.assertTrue(value == res) + elif len(data['body']) == 0 and type(data['body']) == int: + res = valid_answer(data) + self.assertTrue(value == res) + else: + if data == {}: + res = valid_answer(data) + self.assertTrue((False, ) == res) + + def test_valid_signup_data(self): + data = { + 'username': 'Kangol', + 'email': 'kangol@gmail.com', + 'password': '123e', + 'repeat_password': '123e' + } + res = valid_signup_data(data) + self.assertTrue(res == True) + del data['username'] + res = valid_signup_data(data) + self.assertTrue(res == False) + del data['email'] + res = valid_signup_data(data) + self.assertTrue(res == False) + del data['password'] + res = valid_signup_data(data) + self.assertTrue(res == False) + del data['repeat_password'] + res = valid_signup_data(data) + self.assertTrue(res == False) + + def test_valid_login_data(self): + data = { + 'username': 'Tom Peter', + 'password': 1234 + } + res = valid_login_data(data) + self.assertTrue(res == True) + del data['username'] + res = valid_login_data(data) + self.assertTrue(res == False) + del data['password'] + res = valid_login_data(data) + res = valid_login_data(data) + self.assertTrue(res == False) \ No newline at end of file diff --git a/tests/test_route.py b/tests/test_route.py index 90aa141..c31e465 100644 --- a/tests/test_route.py +++ b/tests/test_route.py @@ -56,7 +56,7 @@ def test_user_can_get_questions(self): else: res = self.client.get('/api/v1/questions') self.assertEqual(res.status_code, 404) - self.assertEqual(res.json, {'message': 'No Questions added yet'}) + self.assertEqual(res.json, {'message': 'No Questions added.'}) def test_user_can_get_question(self): questionsList = self.conn.query_all('questions') @@ -66,7 +66,7 @@ def test_user_can_get_question(self): else: res = self.client.get('/api/v1/questions/2') self.assertEqual(res.status_code, 200) - self.assertEqual(res.json, {'Question 2': 'does not exist.'}) + self.assertEqual(res.json, {'message': 'No questions added.'}) def test_user_can_get_answers(self): answersList = self.conn.query_all('answers') @@ -99,7 +99,7 @@ def test_user_can_get_answer(self): else: res = self.client.get('/api/v1/questions/2/answers/3') self.assertEqual(res.status_code, 404) - self.assertEqual(res.json, {'Answer3 for Question2': 'not found.'}) + self.assertEqual(res.json, {'message': 'Question not found!'}) def test_user_can_post_question(self): question = {