diff --git a/app/api/dao/organization.py b/app/api/dao/organization.py index 128f25e..c2e39e4 100644 --- a/app/api/dao/organization.py +++ b/app/api/dao/organization.py @@ -3,6 +3,8 @@ from http import HTTPStatus from flask import json from sqlalchemy import func +from sqlalchemy.exc import IntegrityError + from app.database.models.bit_schema.organization import OrganizationModel from app.database.models.bit_schema.user_extension import UserExtensionModel from app import messages @@ -73,8 +75,14 @@ def update_organization(data): return messages.NOT_ORGANIZATION_REPRESENTATIVE, HTTPStatus.FORBIDDEN except AttributeError: return messages.NOT_ORGANIZATION_REPRESENTATIVE, HTTPStatus.FORBIDDEN + except IntegrityError as e: + if "organizations_name" in e.orig.args[0]: + return messages.ORGANIZATION_NAME_ALREADY_USED, HTTPStatus.CONFLICT + return messages.INVALID_REQUEST_DATA, HTTPStatus.BAD_REQUEST + except Exception: + return messages.UNEXPECTED_ERROR, HTTPStatus.INTERNAL_SERVER_ERROR + - @staticmethod def list_organizations( name, diff --git a/app/database/models/bit_schema/organization.py b/app/database/models/bit_schema/organization.py index af1d096..2a14ed3 100644 --- a/app/database/models/bit_schema/organization.py +++ b/app/database/models/bit_schema/organization.py @@ -1,5 +1,7 @@ import time from sqlalchemy import null +from sqlalchemy.exc import DBAPIError + from app.database.sqlalchemy_extension import db from app.utils.bitschema_utils import OrganizationStatus, Timezone from app.database.models.bit_schema.program import ProgramModel @@ -113,8 +115,12 @@ def find_by_email(cls, email: str) -> "OrganizationModel": def save_to_db(self) -> None: """Adds an organization to the database. """ - db.session.add(self) - db.session.commit() + try: + db.session.add(self) + db.session.commit() + except DBAPIError: + db.session.rollback() + raise def delete_from_db(self) -> None: """Deletes an organization from the database. """ diff --git a/app/messages.py b/app/messages.py index 2c44e2f..888a8fa 100644 --- a/app/messages.py +++ b/app/messages.py @@ -376,4 +376,10 @@ } NOT_ORGANIZATION_REPRESENTATIVE = { "message": "You have not declared that you are representing an organization." -} \ No newline at end of file +} +INVALID_REQUEST_DATA = {"message": "Data you provided cannot be processed. Make sure your data is acceptable."} +UNEXPECTED_ERROR = {"message": "An unexpected error occurred. Please, try again later or contact the administrator."} + +# Non-unique value messages +ORGANIZATION_NAME_ALREADY_USED = {"message": "Organization with that name already exists."} + diff --git a/tests/organizations/test_api_update_organization.py b/tests/organizations/test_api_update_organization.py index aacd509..f9cde2b 100644 --- a/tests/organizations/test_api_update_organization.py +++ b/tests/organizations/test_api_update_organization.py @@ -12,7 +12,7 @@ from tests.base_test_case import BaseTestCase from app.api.request_api_utils import post_request, get_request, BASE_MS_API_URL, AUTH_COOKIE from app.api.models.organization import get_organization_response_model, update_organization_request_model -from tests.test_data import user1 +from tests.test_data import user1, user2 from app.database.models.ms_schema.user import UserModel from app.database.models.bit_schema.user_extension import UserExtensionModel from app.database.models.bit_schema.organization import OrganizationModel @@ -25,7 +25,7 @@ class TestUpdateOrganizationApi(BaseTestCase): def setUp(self, mock_login, mock_get_user): super(TestUpdateOrganizationApi, self).setUp() # set access expiry 4 weeks from today's date (sc*min*hrrs*days) - access_expiry = time.time() + 60*60*24*28 + access_expiry = time.time() + 60 * 60 * 24 * 28 success_message = {"access_token": "this is fake token", "access_expiry": access_expiry} success_code = HTTPStatus.OK @@ -36,19 +36,19 @@ def setUp(self, mock_login, mock_get_user): mock_login.raise_for_status = json.dumps(success_code) expected_user = marshal(user1, full_user_api_model) - + mock_get_response = Mock() mock_get_response.json.return_value = expected_user mock_get_response.status_code = success_code mock_get_user.return_value = mock_get_response mock_get_user.raise_for_status = json.dumps(success_code) - + user_login_success = { "username": user1.get("username"), "password": user1.get("password") } - + with self.client: login_response = self.client.post( "/login", @@ -56,21 +56,18 @@ def setUp(self, mock_login, mock_get_user): follow_redirects=True, content_type="application/json", ) - test_user1 = UserModel( name=user1["name"], username=user1["username"], - password=user1["password"], - email=user1["email"], + password=user1["password"], + email=user1["email"], terms_and_conditions_checked=user1["terms_and_conditions_checked"] ) test_user1.need_mentoring = user1["need_mentoring"] test_user1.available_to_mentor = user1["available_to_mentor"] - test_user1.save_to_db() self.test_user1_data = UserModel.find_by_email(test_user1.email) AUTH_COOKIE["user"] = marshal(self.test_user1_data, full_user_api_model) - self.correct_payload_organization = { "representative_department": "H&R Department", "name": "Company ABC", @@ -83,7 +80,6 @@ def setUp(self, mock_login, mock_get_user): "status": "Draft", } - def test_api_dao_create_organization_successfully(self): test_user_extension = UserExtensionModel( user_id=self.test_user1_data.id, @@ -91,23 +87,70 @@ def test_api_dao_create_organization_successfully(self): ) test_user_extension.is_organization_rep = True test_user_extension.save_to_db() - response = self.client.put( "/organization", headers={"Authorization": AUTH_COOKIE["Authorization"].value}, data=json.dumps( - dict(self.correct_payload_organization) - ), + dict(self.correct_payload_organization) + ), follow_redirects=True, content_type="application/json", ) self.assertEqual(HTTPStatus.CREATED, response.status_code) self.assertEqual(messages.ORGANIZATION_SUCCESSFULLY_CREATED, json.loads(response.data)) + def test_api_dao_create_organization_when_name_not_unique(self): + # new user + test_user = UserModel( + name=user2["name"], + username=user2["username"], + password=user2["password"], + email=user2["email"], + terms_and_conditions_checked=user2["terms_and_conditions_checked"] + ) + test_user.need_mentoring = user2["need_mentoring"] + test_user.available_to_mentor = user2["available_to_mentor"] + + test_user.save_to_db() + test_user_extension = UserExtensionModel( + user_id=self.test_user1_data.id, + timezone="AUSTRALIA_MELBOURNE" + ) + test_user_extension.is_organization_rep = True + test_user_extension.save_to_db() + organization = OrganizationModel( + rep_id=test_user.id, + name="Company ABC", + email="companyabc@mail.com", + address="506 Elizabeth St, Melbourne VIC 3000, Australia", + website="https://www.ames.net.au", + timezone="AUSTRALIA_MELBOURNE", + ) + organization.rep_department = "H&R Department" + organization.about = "This is about ABC" + organization.phone = "321-456-789" + organization.status = "DRAFT" + # joined one month prior to access date + organization.join_date = time.time() - 60 * 60 * 24 * 7 + organization.save_to_db() + + # test creating the org with the same name (with another user) + response = self.client.put( + "/organization", + headers={"Authorization": AUTH_COOKIE["Authorization"].value}, + data=json.dumps( + dict(self.correct_payload_organization) + ), + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(HTTPStatus.CONFLICT, response.status_code) + self.assertEqual(messages.ORGANIZATION_NAME_ALREADY_USED, json.loads(response.data)) + def test_api_dao_update_organization_successfully(self): # prepare existing organization organization = OrganizationModel( - rep_id=self.test_user1_data.id, + rep_id=self.test_user1_data.id, name="Company ABC", email="companyabc@mail.com", address="506 Elizabeth St, Melbourne VIC 3000, Australia", @@ -119,18 +162,17 @@ def test_api_dao_update_organization_successfully(self): organization.phone = "321-456-789" organization.status = "DRAFT" # joined one month prior to access date - organization.join_date = time.time() - 60*60*24*7 + organization.join_date = time.time() - 60 * 60 * 24 * 7 db.session.add(organization) db.session.commit() - + test_user_extension = UserExtensionModel( user_id=self.test_user1_data.id, timezone="AUSTRALIA_MELBOURNE" ) test_user_extension.is_organization_rep = True test_user_extension.save_to_db() - update_payload_organization = { "representative_department": "H&R Department", "name": "Company ABC", @@ -142,13 +184,12 @@ def test_api_dao_update_organization_successfully(self): "phone": "321-456-789", "status": "Publish", } - response = self.client.put( "/organization", headers={"Authorization": AUTH_COOKIE["Authorization"].value}, data=json.dumps( - dict(update_payload_organization) - ), + dict(update_payload_organization) + ), follow_redirects=True, content_type="application/json", ) @@ -162,15 +203,14 @@ def test_api_dao_get_organization_not_representative(self): ) test_user_extension.is_organization_rep = False test_user_extension.save_to_db() - response = self.client.put( "/organization", headers={"Authorization": AUTH_COOKIE["Authorization"].value}, data=json.dumps( - dict(self.correct_payload_organization) - ), + dict(self.correct_payload_organization) + ), follow_redirects=True, content_type="application/json", ) self.assertEqual(HTTPStatus.FORBIDDEN, response.status_code) - self.assertEqual(messages.NOT_ORGANIZATION_REPRESENTATIVE, response.json) \ No newline at end of file + self.assertEqual(messages.NOT_ORGANIZATION_REPRESENTATIVE, response.json)