diff --git a/backend/app/core/db.py b/backend/app/core/db.py index 4da47064..4611012c 100644 --- a/backend/app/core/db.py +++ b/backend/app/core/db.py @@ -2,7 +2,6 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, DeclarativeBase - _db_engine = None _DbSessionFactory = None @@ -19,7 +18,7 @@ def db_engine(settings): global _db_engine if _db_engine is None: _db_engine = create_engine(settings.DATABASE_URL, - connect_args={"check_same_thread": False}) + connect_args={"check_same_thread": False}) return _db_engine @@ -27,6 +26,6 @@ def db_session_factory(engine): global _DbSessionFactory if _DbSessionFactory is None: _DbSessionFactory = sessionmaker(autocommit=False, - autoflush=False, - bind=engine) + autoflush=False, + bind=engine) return _DbSessionFactory diff --git a/backend/app/health.py b/backend/app/health.py new file mode 100644 index 00000000..7454e399 --- /dev/null +++ b/backend/app/health.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, status + +health_router = APIRouter() + + +@health_router.get("/", status_code=status.HTTP_200_OK) +def health(): + return "UP" diff --git a/backend/app/main.py b/backend/app/main.py index 169c0821..b2bb0c51 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from contextlib import asynccontextmanager +from .health import health_router from app.modules.router import api_router import app.core.db as db import app.core.config as config @@ -17,4 +18,5 @@ async def lifespan(app: FastAPI): app = FastAPI(lifespan=lifespan) -app.include_router(api_router, prefix="/api") \ No newline at end of file +app.include_router(api_router, prefix="/api") +app.include_router(health_router, prefix="/api/health", tags=["health"]) diff --git a/backend/app/modules/access/hosts_controller.py b/backend/app/modules/access/hosts_controller.py new file mode 100644 index 00000000..6c5b1aa7 --- /dev/null +++ b/backend/app/modules/access/hosts_controller.py @@ -0,0 +1,17 @@ +from .user_roles import UserRole +from . import schemas +from .user_repo import UserRepository + +from app.modules.deps import DbSessionDep + +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/") +def get_hosts(db_session: DbSessionDep) -> list[schemas.User]: + with db_session.begin(): + user_repo = UserRepository(db_session) + all_users = user_repo.get_users_with_role(UserRole.HOST) + return all_users diff --git a/backend/app/modules/access/models.py b/backend/app/modules/access/models.py index 36877ead..f2b1cf31 100644 --- a/backend/app/modules/access/models.py +++ b/backend/app/modules/access/models.py @@ -35,23 +35,3 @@ class Role(Base): type = Column(String, nullable=False, unique=True) users = relationship("User", back_populates="role") - - -class UnmatchedGuestCase(Base): - __tablename__ = "unmatched_guest_case" - id = Column(Integer, primary_key=True, index=True) - guest_id = Column(Integer, ForeignKey('user.id'), nullable=False) - coordinator_id = Column(Integer, ForeignKey('user.id'), nullable=False) - status_id = Column(Integer, - ForeignKey('unmatched_guest_case_status.id'), - nullable=False) - status = relationship("UnmatchedGuestCaseStatus", back_populates="cases") - - -class UnmatchedGuestCaseStatus(Base): - __tablename__ = "unmatched_guest_case_status" - id = Column(Integer, primary_key=True, index=True) - status_text = Column(String(255), nullable=False, unique=True) - cases = relationship("UnmatchedGuestCase", back_populates="status") - - diff --git a/backend/app/modules/access/schemas.py b/backend/app/modules/access/schemas.py index 49202f78..0eaf33a0 100644 --- a/backend/app/modules/access/schemas.py +++ b/backend/app/modules/access/schemas.py @@ -49,14 +49,17 @@ class UserSignInResponse(BaseModel): class RefreshTokenResponse(BaseModel): token: str + class ForgotPasswordRequest(BaseModel): email: EmailStr + class ConfirmForgotPasswordRequest(BaseModel): email: EmailStr code: str password: str - + + class ConfirmForgotPasswordResponse(BaseModel): message: str @@ -87,18 +90,8 @@ class ConfirmForgotPasswordResponse(BaseModel): # model_config = ConfigDict(from_attributes=True) -# class UnmatchedCaseSchema(BaseModel): - -# model_config = ConfigDict(from_attributes=True) - -# class UnmatchedCaseStatusSchema(BaseModel): - -# model_config = ConfigDict(from_attributes=True) - # class UserSchema(BaseModel): # model_config = ConfigDict(from_attributes=True) # user_schema = UserSchema() # users_schema = UserSchema(many=True) -# unmatched_cs_schema = UnmatchedCaseStatusSchema() -# unmatched_c_schema = UnmatchedCaseSchema() diff --git a/backend/app/modules/access/user_repo.py b/backend/app/modules/access/user_repo.py index cec2082d..97d41bf6 100644 --- a/backend/app/modules/access/user_repo.py +++ b/backend/app/modules/access/user_repo.py @@ -1,36 +1,5 @@ -from app.modules.access.models import UnmatchedGuestCase, UnmatchedGuestCaseStatus, User, Role -from app.modules.access.user_roles import UmatchedCaseStatus, UserRole - - -class UnmatchedCaseRepository: - - def __init__(self, session): - self.session = session - - def add_case(self, guest_id: int, - coordinator_id: int) -> UnmatchedGuestCase: - status_id = self.session.query(UnmatchedGuestCaseStatus).filter_by( - status_text=UmatchedCaseStatus.IN_PROGRESS).first().id - new_guest_case = UnmatchedGuestCase(guest_id=guest_id, - coordinator_id=coordinator_id, - status_id=status_id) - self.session.add(new_guest_case) - self.session.commit() - - return new_guest_case - - def delete_case_for_guest(self, guest_id: int) -> bool: - guest_case = self.session.query(UnmatchedGuestCaseStatus).filter_by( - guest_id=guest_id).first() - if guest_case: - self.session.delete(guest_case) - self.session.commit() - return True - return False - - def get_case_for_guest(self, guest_id: int) -> UnmatchedGuestCase: - return self.session.query(UnmatchedGuestCase).filter_by( - guest_id=guest_id).first() +from app.modules.access.models import User, Role +from app.modules.access.user_roles import UserRole class UserRepository: diff --git a/backend/app/modules/access/user_roles.py b/backend/app/modules/access/user_roles.py index cc46272a..198fbaca 100644 --- a/backend/app/modules/access/user_roles.py +++ b/backend/app/modules/access/user_roles.py @@ -6,8 +6,3 @@ class UserRole(Enum): GUEST = "guest" HOST = "host" COORDINATOR = "coordinator" - - -class UmatchedCaseStatus(Enum): - IN_PROGRESS = "In Progress" - COMPLETE = "Complete" diff --git a/backend/app/modules/deps.py b/backend/app/modules/deps.py index cf7d5c1f..b75f3ce2 100644 --- a/backend/app/modules/deps.py +++ b/backend/app/modules/deps.py @@ -13,6 +13,30 @@ import app.core.db as db import app.core.config as config +################################################################################ +# Loading forms JSON description from disk +FORM_1 = None +FORM_2 = None + + +def get_form_1(): + global FORM_1 + if FORM_1 is None: + import json + with open("form_data/form1.json", "r") as f: + FORM_1 = json.load(f) + return FORM_1 + + +def get_form_2(): + global FORM_2 + if FORM_2 is None: + import json + with open("form_data/form2.json", "r") as f: + FORM_2 = json.load(f) + return FORM_2 +################################################################################ + SettingsDep = Annotated[config.Settings, Depends(config.get_settings)] diff --git a/backend/app/modules/intake_profile/controller.py b/backend/app/modules/intake_profile/controller.py index c183f0d9..cfe79cba 100644 --- a/backend/app/modules/intake_profile/controller.py +++ b/backend/app/modules/intake_profile/controller.py @@ -1,31 +1,63 @@ +from typing import Annotated +from fastapi import APIRouter, HTTPException, status, Depends -from fastapi import Depends, APIRouter, HTTPException, Response, Security -# from fastapi.responses import RedirectResponse - -# from app.modules.deps import ( -# DbSessionDep, -# CognitoIdpDep, -# ) +from app.modules.deps import DbSessionDep, get_form_1, get_form_2 router = APIRouter() -# @router.post("/guest/") -# def post_guest_intake_profile(body, guest: Depends(aim_guest)): -# forms_repo = FormsRepository(DataAccessLayer.session()) - -# form_id = forms_repo.add_form(body) -# form = forms_repo.get_form_json(form_id) -# if form: -# return form, 200 -# return {}, 404 - - -# @router.get("/guest/{form_id}") -# def get_guest_intake_profile(form_id, guest: Depends(aim_guest)): -# forms_repo = FormsRepository(DataAccessLayer.session()) - -# form = forms_repo.get_form_json(form_id) -# if form: -# return form, 200 -# return f"Form with id {form_id} does not exist.", 404 +@router.put("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) +def update_intake_profile_responses(user_id, body, db_session: DbSessionDep): + pass + # TODO: Implement update intake profile responses + # with db_session.begin() as session: + # user_repo = UserRepository(session) + # forms_repo = FormsRepository(session) + # user = user_repo.get_user(token_info['Username']) + + # form = forms_repo.get_form(form_id) + # if not form: + # return f"Form with id {form_id} does not exist.", 404 + + # valid_field_ids = form.get_field_ids() + # for response in body: + # response["user_id"] = user.id + # if response["field_id"] not in valid_field_ids: + # return f"Form {form_id} does not contain field id {response['field_id']}", 400 + + # forms_repo.add_user_responses(user.id, body) + + # return {}, 204 + + +@router.get("/responses/{user_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED) +def get_intake_profile_responses(user_id, db_session: DbSessionDep): + pass + # TODO: Implement get Intake Profile responses + # with db_session.begin() as session: + # user_repo = UserRepository(session) + # forms_repo = FormsRepository(session) + + # form = forms_repo.get_form_json(form_id) + # if not form: + # return f"Form with id {form_id} does not exist.", 404 + + # user = user_repo.get_user(token_info['Username']) + # responses = forms_repo.get_user_responses(user.id, form_id) + # if responses: + # return responses, 200 + # return [], 202 + + +@router.get("/form/{profile_id}", status_code=status.HTTP_200_OK) +def get_intake_profile_form(profile_id: int, + profile_form_1: Annotated[str, Depends(get_form_1)], + profile_form_2: Annotated[str, Depends(get_form_2)]): + """Get the Intake Profile form definition.""" + if profile_id == 1: + return profile_form_1 + if profile_id == 2: + return profile_form_2 + + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, + detail=f"Form with id {profile_id} does not exist.") diff --git a/backend/app/modules/intake_profile/forms/forms.py b/backend/app/modules/intake_profile/forms/forms.py deleted file mode 100644 index 66bd8bc9..00000000 --- a/backend/app/modules/intake_profile/forms/forms.py +++ /dev/null @@ -1,79 +0,0 @@ -from sqlalchemy import Column, Integer, String, ForeignKey, Text, Boolean, DateTime -from sqlalchemy.sql import func -from app.core.db import Base - - -class Form(Base): - __tablename__ = 'forms' - form_id = Column(Integer, primary_key=True) - title = Column(String(255), nullable=False) - description = Column(Text) - created_at = Column(DateTime, default=func.current_timestamp()) - - def get_field_ids(self) -> List[int]: - return [ - field.field_id for group in self.field_groups - for field in group.fields - ] - - -class FieldProperties(Base): - __tablename__ = 'field_properties' - properties_id = Column(Integer, primary_key=True) - description = Column(Text) - field_type = Column(String(50), nullable=False) - choices = Column(JSON) - - __table_args__ = (CheckConstraint( - "field_type IN ('date', 'dropdown', 'multiple_choice', 'email', 'file_upload', 'group', 'long_text', 'number', 'short_text', 'yes_no')", - name='chk_field_type'), ) - - -class FieldValidations(Base): - __tablename__ = 'field_validations' - validations_id = Column(Integer, primary_key=True) - required = Column(Boolean, nullable=False, default=False) - max_length = Column(Integer) # NULL if not applicable - - -class FieldGroup(Base): - __tablename__ = 'field_groups' - group_id = Column(Integer, primary_key=True) - form_id = Column(Integer, ForeignKey('forms.form_id'), nullable=False) - title = Column(String(255), nullable=False) - description = Column(Text) - form = relationship("Form", back_populates="field_groups") - - -class Field(Base): - __tablename__ = 'fields' - field_id = Column(Integer, primary_key=True) - ref = Column(String(255), nullable=False) - properties_id = Column(Integer, - ForeignKey('field_properties.properties_id'), - nullable=False) - validations_id = Column(Integer, - ForeignKey('field_validations.validations_id'), - nullable=False) - group_id = Column(Integer, ForeignKey('field_groups.group_id')) - properties = relationship("FieldProperties") - validations = relationship("FieldValidations") - group = relationship("FieldGroup", back_populates="fields") - - -class Response(Base): - __tablename__ = 'responses' - answer_id = Column(Integer, primary_key=True) - user_id = Column(Integer, ForeignKey('user.id'), nullable=False) - field_id = Column(Integer, ForeignKey('fields.field_id'), nullable=False) - answer_text = Column(Text) - user = relationship("User") - field = relationship("Field") - - -Form.field_groups = relationship("FieldGroup", - order_by=FieldGroup.group_id, - back_populates="form") -FieldGroup.fields = relationship("Field", - order_by=Field.field_id, - back_populates="group") diff --git a/backend/app/modules/intake_profile/forms/model.py b/backend/app/modules/intake_profile/forms/model.py new file mode 100644 index 00000000..b269d0c6 --- /dev/null +++ b/backend/app/modules/intake_profile/forms/model.py @@ -0,0 +1,88 @@ +from typing import Annotated + +from sqlalchemy import ForeignKey, Text, DateTime, JSON +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column +from sqlalchemy.orm import relationship +from sqlalchemy.schema import CheckConstraint +from sqlalchemy.sql import func +from app.core.db import Base + +intpk = Annotated[int, mapped_column(primary_key=True)] + + +class Form(Base): + __tablename__ = 'forms' + form_id: Mapped[intpk] + title: Mapped[str] = mapped_column(nullable=False) + description: Mapped[Text] + created_at: Mapped[DateTime] = mapped_column( + default=func.current_timestamp()) + + def get_field_ids(self) -> list[int]: + return [ + field.field_id for group in self.field_groups + for field in group.fields + ] + + +class FieldProperties(Base): + __tablename__ = 'field_properties' + properties_id: Mapped[intpk] + description: Mapped[Text] + field_type: Mapped[str] = mapped_column(nullable=False) + choices: Mapped[JSON] + + __table_args__ = (CheckConstraint( + "field_type IN ('date', 'dropdown', 'multiple_choice', 'email', 'file_upload', 'group', 'long_text', 'number', 'short_text', 'yes_no')", + name='chk_field_type'), ) + + +class FieldValidations(Base): + __tablename__ = 'field_validations' + validations_id: Mapped[intpk] + required: Mapped[bool] = mapped_column(nullable=False, default=False) + max_length: Mapped[int] + + +class FieldGroup(Base): + __tablename__ = 'field_groups' + group_id: Mapped[intpk] + form_id: Mapped[int] = mapped_column(ForeignKey('forms.form_id'), + nullable=False) + title: Mapped[str] = mapped_column(nullable=False) + description: Mapped[Text] + form = relationship("Form", back_populates="field_groups") + + +class Field(Base): + __tablename__ = 'fields' + field_id: Mapped[intpk] + ref: Mapped[str] = mapped_column(nullable=False) + properties_id: Mapped[int] = mapped_column( + ForeignKey('field_properties.properties_id'), nullable=False) + validations_id: Mapped[int] = mapped_column( + ForeignKey('field_validations.validations_id'), nullable=False) + group_id: Mapped[int] = mapped_column(ForeignKey('field_groups.group_id')) + properties = relationship("FieldProperties") + validations = relationship("FieldValidations") + group = relationship("FieldGroup", back_populates="fields") + + +class Response(Base): + __tablename__ = 'responses' + answer_id: Mapped[intpk] + user_id: Mapped[int] = mapped_column(ForeignKey('user.id'), nullable=False) + field_id: Mapped[int] = mapped_column(ForeignKey('fields.field_id'), + nullable=False) + answer_text: Mapped[Text] + user = relationship("User") + field = relationship("Field") + + +Form.field_groups = relationship("FieldGroup", + order_by=FieldGroup.group_id, + back_populates="form") +FieldGroup.fields = relationship("Field", + order_by=Field.field_id, + back_populates="group") diff --git a/backend/app/modules/intake_profile/model.py b/backend/app/modules/intake_profile/model.py index c4530b09..21475c0a 100644 --- a/backend/app/modules/intake_profile/model.py +++ b/backend/app/modules/intake_profile/model.py @@ -14,8 +14,7 @@ class IntakeProfile: def __init__(self, form_id: int): if form_id is None: raise Exception("IntakeProfile is not valid without a Form") - self.intake_form_id: form_id = form_id - self.attachments: list[bytes] = [] + self.intake_form_id: int = form_id self.status: IntakeProfileStatus = IntakeProfileStatus.NEW diff --git a/backend/app/modules/router.py b/backend/app/modules/router.py index 57d65048..04cc28f1 100644 --- a/backend/app/modules/router.py +++ b/backend/app/modules/router.py @@ -1,22 +1,25 @@ from fastapi import APIRouter -from app.modules.access import auth_controller, users_controller +from app.modules.access import auth_controller, hosts_controller, users_controller from app.modules.intake_profile import controller as intake_profile from app.modules.tenant_housing_orgs import controller as housing_org - +from app.modules.workflow.dashboards.coordinator import coordinator_dashboard api_router = APIRouter() - -api_router.include_router( - auth_controller.router, prefix="/auth", tags=["auth"] -) -api_router.include_router( - users_controller.router, prefix="/users", tags=["users"] -) -api_router.include_router( - intake_profile.router, prefix="/intake-profile", tags=["intake_profile"] -) -api_router.include_router( - housing_org.router, prefix="/housing-orgs", tags=["tenant_housing_orgs"] -) +api_router.include_router(auth_controller.router, + prefix="/auth", + tags=["auth"]) +api_router.include_router(hosts_controller.router, + prefix="/hosts", + tags=["hosts"]) +api_router.include_router(users_controller.router, + prefix="/users", + tags=["users"]) +api_router.include_router(intake_profile.router, + prefix="/intake-profile", + tags=["intake_profile"]) +api_router.include_router(housing_org.router, + prefix="/housing-orgs", + tags=["tenant_housing_orgs"]) +api_router.include_router(coordinator_dashboard.router, tags=["coordinator"]) diff --git a/backend/app/modules/workflow/__init__.py b/backend/app/modules/workflow/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/modules/workflow/dashboards/coordinator/coordinator_dashboard.py b/backend/app/modules/workflow/dashboards/coordinator/coordinator_dashboard.py new file mode 100644 index 00000000..57082da8 --- /dev/null +++ b/backend/app/modules/workflow/dashboards/coordinator/coordinator_dashboard.py @@ -0,0 +1,74 @@ +from fastapi import APIRouter, status +from fastapi.responses import JSONResponse + +from ...unmatched_guest_case import UnmatchedCaseRepository +from app.modules.access.user_repo import UserRepository +from app.modules.access.user_roles import UserRole +from app.modules.deps import DbSessionDep + +router = APIRouter() + + +@router.get("/coordinator/dashboard/all", status_code=status.HTTP_200_OK) +def get_dashboard_data(db_session: DbSessionDep) -> JSONResponse: + """ + + userName: + type: string + caseStatus: + type: string + coordinatorName: + type: string + userType: + type: string + lastUpdated: + type: string + notes: + type: string + """ + with db_session.begin(): + user_repo = UserRepository(db_session) + coordinator_users_by_id = { + x.id: x + for x in user_repo.get_users_with_role(UserRole.COORDINATOR) + } + case_repo = UnmatchedCaseRepository(db_session) + + all_users = [] + for guest in user_repo.get_users_with_role(UserRole.GUEST): + case_status = case_repo.get_case_for_guest(int(guest.id)) + coordinator = coordinator_users_by_id[case_status.coordinator_id] + all_users.append({ + 'id': guest.id, + 'userName': f'{guest.firstName} {guest.lastName}', + 'caseStatus': 'In Progress', + 'userType': 'GUEST', + 'coordinatorName': + f'{coordinator.firstName} {coordinator.lastName}', + 'lastUpdated': '2024-08-25', + 'Notes': 'N/A' + }) + + for host in user_repo.get_users_with_role(UserRole.HOST): + all_users.append({ + 'id': host.id, + 'userName': f'{host.firstName} {host.lastName}', + 'caseStatus': 'In Progress', + 'userType': 'HOST', + 'coordinatorName': f'N/A', + 'lastUpdated': '2024-08-25', + 'Notes': 'N/A' + }) + + for coordinator in user_repo.get_users_with_role(UserRole.COORDINATOR): + all_users.append({ + 'id': coordinator.id, + 'userName': f'{coordinator.firstName} {coordinator.lastName}', + 'caseStatus': 'N/A', + 'userType': 'COORDINATOR', + 'coordinatorName': f'N/A', + 'lastUpdated': '2024-08-25', + 'Notes': 'N/A' + }) + + return JSONResponse(content={'dashboardItems': all_users}) diff --git a/backend/app/modules/workflow/dashboards/coordinator/schemas.py b/backend/app/modules/workflow/dashboards/coordinator/schemas.py new file mode 100644 index 00000000..5d5d8e30 --- /dev/null +++ b/backend/app/modules/workflow/dashboards/coordinator/schemas.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel, ConfigDict + + +class UnmatchedCaseSchema(BaseModel): + model_config = ConfigDict(from_attributes=True) + + +class UnmatchedCaseStatusSchema(BaseModel): + model_config = ConfigDict(from_attributes=True) diff --git a/backend/app/modules/workflow/models.py b/backend/app/modules/workflow/models.py new file mode 100644 index 00000000..ea244574 --- /dev/null +++ b/backend/app/modules/workflow/models.py @@ -0,0 +1,29 @@ +from typing import Annotated +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column +from sqlalchemy.orm import relationship + +from app.core.db import Base + +intpk = Annotated[int, mapped_column(primary_key=True)] + + +class UnmatchedGuestCase(Base): + __tablename__ = "unmatched_guest_case" + id: Mapped[intpk] + guest_id: Mapped[int] = mapped_column(ForeignKey('user.id'), + nullable=False) + coordinator_id: Mapped[int] = mapped_column(ForeignKey('user.id'), + nullable=False) + status_id: Mapped[int] = mapped_column( + ForeignKey('unmatched_guest_case_status.id'), nullable=False) + status: Mapped["UnmatchedGuestCaseStatus"] = relationship( + back_populates="cases") + + +class UnmatchedGuestCaseStatus(Base): + __tablename__ = "unmatched_guest_case_status" + id: Mapped[intpk] + status_text: Mapped[str] = mapped_column(nullable=False, unique=True) + cases: Mapped["UnmatchedGuestCase"] = relationship(back_populates="status") diff --git a/backend/app/modules/workflow/unmatched_guest_case.py b/backend/app/modules/workflow/unmatched_guest_case.py new file mode 100644 index 00000000..b0c7d1ee --- /dev/null +++ b/backend/app/modules/workflow/unmatched_guest_case.py @@ -0,0 +1,38 @@ +from .models import UnmatchedGuestCase, UnmatchedGuestCaseStatus + +from enum import Enum + +class UmatchedCaseStatus(Enum): + IN_PROGRESS = "In Progress" + COMPLETE = "Complete" + + +class UnmatchedCaseRepository: + + def __init__(self, session): + self.session = session + + def add_case(self, guest_id: int, + coordinator_id: int) -> UnmatchedGuestCase: + status_id = self.session.query(UnmatchedGuestCaseStatus).filter_by( + status_text=UmatchedCaseStatus.IN_PROGRESS).first().id + new_guest_case = UnmatchedGuestCase(guest_id=guest_id, + coordinator_id=coordinator_id, + status_id=status_id) + self.session.add(new_guest_case) + self.session.commit() + + return new_guest_case + + def delete_case_for_guest(self, guest_id: int) -> bool: + guest_case = self.session.query(UnmatchedGuestCaseStatus).filter_by( + guest_id=guest_id).first() + if guest_case: + self.session.delete(guest_case) + self.session.commit() + return True + return False + + def get_case_for_guest(self, guest_id: int) -> UnmatchedGuestCase: + return self.session.query(UnmatchedGuestCase).filter_by( + guest_id=guest_id).first() diff --git a/backend/form_data/form1.json b/backend/form_data/form1.json new file mode 100644 index 00000000..db8ced8b --- /dev/null +++ b/backend/form_data/form1.json @@ -0,0 +1 @@ +{"id":"1","fieldGroups":[{"id":"1293","title":"Basic Information","fields":[{"id":"3922","title":"First Name","type":"short_text","properties":{},"validations":{"required":true}},{"id":"5572","title":"Last Name","type":"short_text","properties":{},"validations":{"required":true}},{"id":"2188","title":"Date of Birth","type":"date","properties":{},"validations":{"required":true}},{"id":"3924","title":"Gender Identity","type":"dropdown","properties":{"choices":[{"id":"1359","label":"Woman"},{"id":"5342","label":"Man"},{"id":"3151","label":"Questioning"},{"id":"2178","label":"Transgender"},{"id":"3667","label":"Non-binary"},{"id":"3708","label":"Doesn’t know or prefers not to answer\\t"}]},"validations":{"required":true}}]},{"id":"1348","title":"Contact Information","fields":[{"id":"7813","title":"Email","type":"email","properties":{},"validations":{"required":true}},{"id":"2044","title":"Phone Number","type":"number","properties":{},"validations":{"required":true}},{"id":"7504","title":"What is the best way to contact you?","type":"contact_method","properties":{},"validations":{"required":true},"linkedFields":{"emailFieldId":"3158","phoneFieldId":"9365"}}]},{"id":"6577","title":"Other Guests/Pets","fields":[{"id":"7709","title":"Additional Guests","type":"additional_guests","properties":{},"validations":{}},{"id":"7740","title":"Do you have any pets in your care?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"5056","title":"Pet Type","type":"pets","properties":{},"validations":{"required":false}}]},{"id":"1676","title":"Employment Information","fields":[{"id":"5478","title":"Are you currently employed?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"8533","title":"If yes, please describe your employment.","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"5478","value":"yes"}}},{"id":"8487","title":"If no, are you currently looking for work? If so, what type?","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"5478","value":"no"}}}]},{"id":"5996","title":"Education","fields":[{"id":"6478","title":"Are you enrolled in an Educational Program?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7222","title":"If yes, please describe the program.","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"6478","value":"yes"}}},{"id":"7661","title":"If no, are you hoping to enroll in an Educational Program? If so, what type?","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"6478","value":"no"}}}]},{"id":"4480","title":"Language Proficiency","fields":[{"id":"5479","title":"Are you bilingual or multilingual?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"2359","title":"If yes, what languages do you speak?","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"5479","value":"yes"}}}]},{"id":"5282","title":"Substance Use","fields":[{"id":"8229","title":"Do you smoke cigarettes?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7844","title":"Do you drink alcohol?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7494","title":"Do you use any other substances?","type":"yes_no","properties":{},"validations":{"required":true}}]},{"id":"3691","title":"Mental Health","fields":[{"id":"3831","title":"Do you suffer mental illness?","type":"yes_no","properties":{},"validations":{"required":true}}]},{"id":"7816","title":"Interest in Being a Guest","fields":[{"id":"1885","title":"Please share how you think participating in the Host Homes Program will help you obtain long-term housing and meet your educational and/or employment goals:","type":"long_text","properties":{},"validations":{"required":true}},{"id":"1185","title":"What kind of relationship do you hope to have with your host home?","type":"long_text","properties":{},"validations":{"required":true}},{"id":"3749","title":"Please describe any challenges you foresee encountering in participating in the Host Homes Program.","type":"long_text","properties":{},"validations":{"required":true}}]},{"id":"8837","title":"About You","fields":[{"id":"3411","title":"Please take some time to write an introduction of yourself that you would feel comfortable with the Host Homes Coordinator sharing with a potential host. Feel free to talk about your interests, your story or anything else that you think would be important to share:","type":"long_text","properties":{},"validations":{"required":true}}]}]} diff --git a/backend/form_data/form2.json b/backend/form_data/form2.json new file mode 100644 index 00000000..a2ef54db --- /dev/null +++ b/backend/form_data/form2.json @@ -0,0 +1 @@ +{"id":"2","fieldGroups":[{"id":"5487","title":"Personal Information","fields":[{"id":"6738","title":"First Name","type":"short_text","properties":{},"validations":{"required":true}},{"id":"4011","title":"Last Name","type":"short_text","properties":{},"validations":{"required":true}},{"id":"3427","title":"Date of Birth","type":"short_text","properties":{},"validations":{"required":true}}]},{"id":"9145","title":"Home Information","fields":[{"id":"2636","title":"Do you have an extra bedroom or private space in their home?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"2995","title":"Are you able to provide Guest with access to a kitchen in which to prepare meals, store food and access to shared or private bathroom?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"4274","title":"Can you commit to hosting youth Guest for at least 3-6 months?","type":"yes_no","properties":{},"validations":{"required":true}}]},{"id":"1019","title":"Restrictions","fields":[{"id":"6870","title":"Do you or anyone in your houshold smoke?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"3367","title":"Is smoking allowed inside your home?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"2830","title":"Do you or anyone in your household drink alcohol?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"9846","title":"Do you have concerns about your drinking?","type":"yes_no","properties":{},"validations":{"required":true}},{"id":"7637","title":"If yes, please explain why you are concerned","type":"long_text","properties":{},"validations":{"required_if":{"field_id":"9846","value":"yes"}}}]}]} diff --git a/backend/tests/integration/test_authentication.py b/backend/tests/integration/test_authentication.py index 5f5d5ae8..1ede3eee 100644 --- a/backend/tests/integration/test_authentication.py +++ b/backend/tests/integration/test_authentication.py @@ -69,9 +69,9 @@ def create_user(client: TestClient, def signin_user(client: TestClient, email: str, password: str) -> str: - """ - Signin a user and return the JWT. Fail the test if the - signin operation fails. + """Sign-in a user and return the access token. + + Fail the test if the sign-in operation fails. """ response = client.post(PATH + '/signin', json={ @@ -163,13 +163,14 @@ def test_session_without_cookie(client): PATH + '/session', headers={"Authorization": "Bearer fake_jwt_token_here"}) assert response.status_code == 401 - assert "Missing session cookies" in response.json()['detail'] + assert "Missing refresh token or id token" in response.json()['detail'] def test_incorrect_JWT_fail_auth(client): """Test attempts to use an incorrect JWT with the user endpoint.""" response = client.get( - '/api/user', headers={"Authorization": "Bearer fake_jwt_token_here"}) + '/api/users/current', + headers={"Authorization": "Bearer fake_jwt_token_here"}) assert response.status_code == 401 assert "Missing id token" in response.json()['detail'] @@ -187,7 +188,7 @@ def _signup_unconfirmed(signup_endpoint, role, client, expect_user_confirmed): assert signup_response.status_code == 200, signup_response.text assert signup_response.json( - )["UserConfirmed"] == expect_user_confirmed, signup_response.text + )["message"] == "User sign up successful", signup_response.text signin_response = client.post(PATH + '/signin', json={ @@ -283,12 +284,13 @@ def test_basic_auth_flow(client, api_settings, cognito_client): }) assert response.status_code == 200, "Signin failed" - assert 'token' in response.json(), 'Signin succeeded but token field missing from response' + assert 'token' in response.json( + ), 'Signin succeeded but token field missing from response' jwt = response.json()['token'] assert jwt is not None, 'Signin succeeded but returned empty jwt' assert len(jwt) > 0 - response = client.get('/api/user', + response = client.get('/api/users/current', headers={"Authorization": f"Bearer {jwt}"}) assert response.status_code == 200, '/user authentication failed' @@ -316,22 +318,26 @@ def test_signin_returns_refresh_token(client, api_settings, cognito_client): all_cookies = response.cookies assert all_cookies.get("refresh_token"), "Session cookie is empty" - +@pytest.mark.skip(reason="""moto is not behaving as expected. +It is calculating the secret hash using email while AWS Cognito wants it to be calculated using username. +This causes moto to give false negatives when requesting a refresh token.""") def test_refresh_endpoint(client, api_settings, cognito_client): """Test refreshing a JWT using the /refresh endpoint.""" EMAIL = f'{secretsGenerator.randint(1_000, 2_000)}@email.com' PASSWORD = 'Fake4!@#$2589FFF' - create_and_signin_user(client, api_settings, cognito_client, EMAIL, - PASSWORD) + jwt = create_and_signin_user(client, api_settings, cognito_client, EMAIL, + PASSWORD) - # The test_client automatically attaches the session cookie to the request - # The session cookie stores the refresh token. - response = client.get(PATH + '/refresh', ) + response = client.get(PATH + '/refresh', + headers={"Authorization": f"Bearer {jwt}"}) assert response.status_code == 200, response.text assert 'token' in response.json(), response.text +@pytest.mark.skip(reason="""moto is not behaving as expected. +It is calculating the secret hash using email while AWS Cognito wants it to be calculated using username. +This causes moto to give false negatives when requesting a refresh token.""") def test_session_endpoint(client, api_settings, cognito_client): """Test refreshing a JWT using the /session endpoint.""" EMAIL = f'{secretsGenerator.randint(1_000, 2_000)}@email.com' @@ -339,8 +345,6 @@ def test_session_endpoint(client, api_settings, cognito_client): jwt = create_and_signin_user(client, api_settings, cognito_client, EMAIL, PASSWORD) - # The test_client automatically attaches the session cookie to the request - # The session cookie stores the refresh token. response = client.get(PATH + '/session', headers={"Authorization": f"Bearer {jwt}"}) @@ -348,7 +352,8 @@ def test_session_endpoint(client, api_settings, cognito_client): assert 'token' in response.json(), response.text -def test_user_signup_rollback(client, api_settings, cognito_client, session_factory): +def test_user_signup_rollback(client, api_settings, cognito_client, + session_factory): """Test that a failed sign-up with Cognito. Ensure the local DB entry of the user's email is deleted.""" diff --git a/backend/tests/integration/test_forms.py b/backend/tests/integration/test_forms.py deleted file mode 100644 index 0a9113c3..00000000 --- a/backend/tests/integration/test_forms.py +++ /dev/null @@ -1,134 +0,0 @@ -from types import MappingProxyType - -from app.repositories.forms import FormsRepository -from app.repositories.user_repo import UserRepository, UserRole - -TEST_FORM_READ_ONLY = MappingProxyType({ - "title": - "Employee Onboarding", - "description": - "Collect necessary employee data.", - "field_groups": [{ - "title": - "Personal Details", - "description": - "Please enter your personal details.", - "fields": [{ - "ref": "position", - "properties": { - "description": "Position in the company", - "field_type": "dropdown", - "choices": ['Manager', 'Developer', 'Designer'], - }, - "validations": { - "required": True, - "max_length": 12 - } - }, { - "ref": "service_length", - "properties": { - "description": "Years in the company", - "field_type": "number", - "choices": None, - }, - "validations": { - "required": False, - "max_length": None - } - }] - }, { - "title": - "Second Group", - "description": - "A second field group.", - "fields": [{ - "ref": "start date", - "properties": { - "description": "Start date", - "field_type": "date", - "choices": "11-22-2005", - }, - "validations": { - "required": True, - "max_length": 12 - } - }] - }] -}) - - -def assert_form_equal(actual_form: dict, expected_form: dict): - """ - Do a deep equality check of a form, excluding dynamically - assigned values like timestamps and primary key ids. - """ - actual_copy = actual_form.copy() - del actual_copy['created_at'] - for group in actual_copy['field_groups']: - del group['form'] - for field in group['fields']: - del field['field_id'] - del field['group'] - - assert actual_copy == expected_form - - -def test_add_form_valid_json(empty_db_session_provider): - form_json = dict(TEST_FORM_READ_ONLY) - - form_repo = FormsRepository(empty_db_session_provider.session()) - created_form_id = form_repo.add_form(form_json) - retrieved_form = form_repo.get_form_json(created_form_id) - - assert_form_equal(retrieved_form, form_json) - - -def test_add_get_responses(empty_db_session_provider): - with empty_db_session_provider.session() as session: - user_repo = UserRepository(session) - form_repo = FormsRepository(session) - - user_repo.add_user('fake@email.com', UserRole.COORDINATOR, 'firstname') - user_id = user_repo.get_user_id('fake@email.com') - created_form_id = form_repo.add_form(TEST_FORM_READ_ONLY) - retrieved_form = form_repo.get_form_json(created_form_id) - - def _get_field_id(lcl_form, ref): - for group in lcl_form['field_groups']: - for field in group['fields']: - if field['ref'] == ref: - return int(field['field_id']) - raise ValueError(f'ref {ref} not found in test form') - - expected_responses = [{ - "user_id": - user_id, - "field_id": - _get_field_id(retrieved_form, 'position'), - "answer_text": - "Designer" - }, { - "user_id": - user_id, - "field_id": - _get_field_id(retrieved_form, 'service_length'), - "answer_text": - "5" - }, { - "user_id": - user_id, - "field_id": - _get_field_id(retrieved_form, 'start date'), - "answer_text": - '2024-05-19' - }] - form_repo.add_user_responses(user_id, expected_responses) - - retrieved_answers = form_repo.get_user_responses( - user_id, created_form_id) - - assert len(retrieved_answers) == 3 - for expected, actual in zip(expected_responses, retrieved_answers): - assert expected['answer_text'] == actual['answer_text'] - assert expected['user_id'] == actual['user']['id'] - assert expected['field_id'] == actual['field']['field_id'] diff --git a/backend/tests/integration/test_forms_schema.py b/backend/tests/integration/test_forms_schema.py deleted file mode 100644 index 66aef7b3..00000000 --- a/backend/tests/integration/test_forms_schema.py +++ /dev/null @@ -1,238 +0,0 @@ -from types import MappingProxyType -import pytest -from marshmallow import ValidationError - -from openapi_server.models.schema import ( - form_schema, - FieldSchema, - FieldValidationsSchema, - FieldPropertiesSchema, - FieldGroupSchema -) - -VALID_FORM_JSON = MappingProxyType({ - "title": "Employee Onboarding", - "description": "Collect necessary employee data.", - "field_groups": [ - { - "title": "Personal Details", - "description": "Please enter your personal details.", - "fields": [ - { - "ref": "position", - "properties": { - "description": "Position in the company", - "field_type": "dropdown", - "choices": ['Manager', 'Developer', 'Designer'], - }, - "validations": { - "required": True, - "max_length": 12 - } - }, - { - "ref": "service_length", - "properties": { - "description": "Years in the company", - "field_type": "number", - "choices": None, - }, - "validations": { - "required": False, - "max_length": None - } - } - ] - }, - { - "title": "Second Group", - "description": "A second field group.", - "fields": [ - { - "ref": "start date", - "properties": { - "description": "Start date", - "field_type": "date", - "choices": "11-22-2005", - }, - "validations": { - "required": True, - "max_length": 12 - } - } - ] - } - ] - } - ) - - -def test_serialize_form_no_questions(empty_db_session): - form_json = {"title": "mytitle", "description": "mydesc", "field_groups": []} - form = form_schema.load(form_json, session=empty_db_session) - - assert "mytitle" == form.title - assert "mydesc" == form.description - assert list() == form.field_groups - -def test_deserialize_field_validations(empty_db_session): - validation_json = { - "required": True, - "max_length": None - } - validation = FieldValidationsSchema().load(validation_json, session=empty_db_session) - assert validation.required - assert validation.max_length is None - -def test_deserialize_field_property(empty_db_session): - property_json = { - "description": "sample desc", - "field_type": "long_text", - "choices": ['one', 'two','three'] - } - property = FieldPropertiesSchema().load(property_json, session=empty_db_session) - assert property_json["field_type"] == property.field_type - assert property_json["description"] == property.description - -def test_deserialize_field(empty_db_session): - single_field_json = { - "ref": "position", - "properties": { - "description": "Position in the company", - "field_type": "dropdown", - "choices": ['Manager', 'Developer', 'Designer'], - }, - "validations": { - "required": True, - "max_length": 12 - } - } - field = FieldSchema().load(single_field_json, session=empty_db_session) - assert single_field_json["ref"] == field.ref - assert single_field_json["properties"]["description"] == field.properties.description - assert single_field_json["properties"]["choices"] == field.properties.choices - assert single_field_json["validations"]["max_length"] == field.validations.max_length - assert field.validations.required - -def test_deserialize_fields(empty_db_session): - multiple_fields = [ - { - "ref": "position", - "properties": { - "description": "Position in the company", - "field_type": "dropdown", - "choices": ['Manager', 'Developer', 'Designer'], - }, - "validations": { - "required": True, - "max_length": 12 - } - }, - { - "ref": "service_length", - "properties": { - "description": "Years in the company", - "field_type": "number", - "choices": None, - }, - "validations": { - "required": False, - "max_length": None - } - } - ] - fields = FieldSchema(many=True).load(multiple_fields, session=empty_db_session) - assert 2 == len(fields) - for expected, actual in zip(multiple_fields, fields): - assert expected['properties']['description'] == actual.properties.description - assert expected['properties']['field_type'] == actual.properties.field_type - -def test_deserialize_field_group(empty_db_session): - group_json = [ - { - "title": "Personal Details", - "description": "Please enter your personal details.", - "fields": [ - { - "ref": "position", - "properties": { - "description": "Position in the company", - "field_type": "dropdown", - "choices": ['Manager', 'Developer', 'Designer'], - }, - "validations": { - "required": True, - "max_length": 12 - } - }, - { - "ref": "service_length", - "properties": { - "description": "Years in the company", - "field_type": "number", - "choices": None, - }, - "validations": { - "required": False, - "max_length": None - } - } - ] - }, - { - "title": "Second Group", - "description": "A second field group.", - "fields": [ - { - "ref": "start date", - "properties": { - "description": "Start date", - "field_type": "date", - "choices": "11-22-2005", - }, - "validations": { - "required": True, - "max_length": 12 - } - } - ] - } - ] - groups = FieldGroupSchema(many=True).load(group_json, session=empty_db_session) - assert len(group_json) == len(groups) - for expected_group, actual_group in zip(group_json, groups): - assert expected_group['title'] == actual_group.title - assert expected_group['description'] == actual_group.description - for expected_fields, actual_fields in zip(expected_group['fields'], actual_group.fields): - assert expected_fields['ref'] == actual_fields.ref - assert expected_fields['validations']['required'] == actual_fields.validations.required - assert expected_fields['validations']['max_length'] == actual_fields.validations.max_length - assert expected_fields['properties']['description'] == actual_fields.properties.description - assert expected_fields['properties']['field_type'] == actual_fields.properties.field_type - assert expected_fields['properties']['choices'] == actual_fields.properties.choices - -def test_deserialize_form_happypath(empty_db_session): - form_json = dict(VALID_FORM_JSON) - form = form_schema.load(form_json, session=empty_db_session) - assert form_json["title"] == form.title - assert form_json["description"] == form.description - assert 2 == len(form.field_groups) - for expected, actual in zip(form_json["field_groups"], form.field_groups): - assert expected["title"] == actual.title - assert expected["description"] == actual.description - assert len(expected["fields"]) == len(actual.fields) - - -def test_deserialize_form_extra_key(empty_db_session): - invalid_form_json = dict(VALID_FORM_JSON) - invalid_form_json['extra_key'] = 'extra_value' - - with pytest.raises(ValidationError, match=r"Unknown field"): - form_schema.load(invalid_form_json, session=empty_db_session) - -def test_deserialize_form_missing_key(empty_db_session): - invalid_form_json = dict(VALID_FORM_JSON) - del invalid_form_json['title'] - - with pytest.raises(ValidationError, match=r"Missing data for required field"): - form_schema.load(invalid_form_json, session=empty_db_session) \ No newline at end of file diff --git a/backend/tests/integration/test_host_controller.py b/backend/tests/integration/test_host_controller.py index bca493f7..04ae5d19 100644 --- a/backend/tests/integration/test_host_controller.py +++ b/backend/tests/integration/test_host_controller.py @@ -1,66 +1,60 @@ -from openapi_server.models.database import User, DataAccessLayer -from openapi_server.repositories.user_repo import UserRepository -from openapi_server.models.user_roles import UserRole +from app.modules.access.user_repo import UserRepository +from app.modules.access.user_roles import UserRole -def test_signup_host(client): - """ - Test creating a new host using a simulated post request. Verify that the - response is correct, and that the app database was properly updated. - """ - - NEW_HOST = { - "email" : "test@email.com", - "password": "Test!@123", - "firstName": "Josh", - "middleName": "Ray", - "lastName": "Douglas" - } - response = client.post( - '/api/auth/signup/host', - json=NEW_HOST) - - assert response.status_code == 200, f'Response body is: {response.json}' - # Make sure the database was updated to persist the values - with DataAccessLayer.session() as session: - user_repo = UserRepository(session) - test_host = user_repo.get_user(NEW_HOST['email']) - assert test_host is not None - assert test_host.email == NEW_HOST['email'] - assert test_host.firstName == NEW_HOST['firstName'] - assert test_host.middleName == NEW_HOST['middleName'] - assert test_host.lastName == NEW_HOST['lastName'] - assert test_host.role.name == UserRole.HOST.value - -def test_get_hosts(client): - """ - Test that get_hosts returns all hosts available in the database. The endpoint - should properly filter out all other user roles. +def test_get_hosts(client, session_factory): + """Test that get_hosts returns all hosts available in the database. + + The endpoint should properly filter out all other user roles. """ # Arrange - with DataAccessLayer.session() as session: + with session_factory() as session: user_repo = UserRepository(session) - user_repo.add_user(email="host0@email.com", role=UserRole.HOST, firstName="host0", middleName = None, lastName="host_last0") - user_repo.add_user(email="host1@email.com", role=UserRole.HOST, firstName="host1", middleName = None, lastName="host_last1") - user_repo.add_user(email="host2@email.com", role=UserRole.HOST, firstName="host2", middleName = None, lastName="host_last2") - user_repo.add_user(email="guest1@email.com", role=UserRole.GUEST, firstName="guest0", middleName = None, lastName="guest_last0") - user_repo.add_user(email="Admin2@email.com", role=UserRole.ADMIN, firstName="Admin0", middleName = None, lastName="cdmin_last0") - user_repo.add_user(email="Coordinator3@email.com", role=UserRole.COORDINATOR, firstName="coodinator0", middleName = None, lastName="coordinator_last0") - + user_repo.add_user(email="host0@email.com", + role=UserRole.HOST, + firstName="host0", + middleName=None, + lastName="host_last0") + user_repo.add_user(email="host1@email.com", + role=UserRole.HOST, + firstName="host1", + middleName=None, + lastName="host_last1") + user_repo.add_user(email="host2@email.com", + role=UserRole.HOST, + firstName="host2", + middleName=None, + lastName="host_last2") + user_repo.add_user(email="guest1@email.com", + role=UserRole.GUEST, + firstName="guest0", + middleName=None, + lastName="guest_last0") + user_repo.add_user(email="Admin2@email.com", + role=UserRole.ADMIN, + firstName="Admin0", + middleName=None, + lastName="cdmin_last0") + user_repo.add_user(email="Coordinator3@email.com", + role=UserRole.COORDINATOR, + firstName="coodinator0", + middleName=None, + lastName="coordinator_last0") + # Act - response = client.get('/api/host') + response = client.get('/api/hosts') # Assert - assert response.status_code == 200, f'Response body is: {response.json}' - assert isinstance(response.json, list) - assert len(response.json) == 3 + assert response.status_code == 200, response.json() + assert isinstance(response.json(), list) + assert len(response.json()) == 3 host_emails_set = set() - for host in response.json: + for host in response.json(): assert 'host' in host["email"] assert 'host' in host["firstName"] assert 'host_last' in host["lastName"] - assert host["role"]["name"] == UserRole.HOST.value + assert host["role"]["type"] == UserRole.HOST.value assert host["middleName"] == None host_emails_set.add(host["email"]) - assert len(host_emails_set) == 3, "Duplicate hosts were returned!" \ No newline at end of file + assert len(host_emails_set) == 3, "Duplicate hosts were returned!" diff --git a/frontend/src/services/profile.ts b/frontend/src/services/profile.ts index 82823faa..1c313ca6 100644 --- a/frontend/src/services/profile.ts +++ b/frontend/src/services/profile.ts @@ -78,7 +78,7 @@ const injectedRtkApi = api.injectEndpoints({ endpoints: build => ({ getProfile: build.query({ query: queryArg => ({ - url: `/profile/${queryArg.profileId}`, + url: `/intake-profile/form/${queryArg.profileId}`, }), }), getResponses: build.query< @@ -86,7 +86,7 @@ const injectedRtkApi = api.injectEndpoints({ GetProfileResponsesApiArg >({ query: queryArg => ({ - url: `/profile/responses/${queryArg.userId}`, + url: `/intake-profile/responses/${queryArg.userId}`, }), }), }), diff --git a/frontend/src/utils/testing/handlers/profile.ts b/frontend/src/utils/testing/handlers/profile.ts index 7d2fe124..c6d33bd7 100644 --- a/frontend/src/utils/testing/handlers/profile.ts +++ b/frontend/src/utils/testing/handlers/profile.ts @@ -8,7 +8,7 @@ interface GetResponsesParams { } export const handlers = [ - http.get('/api/profile/:profileId', req => { + http.get('/api/intake-profile/form/:profileId', req => { const id = req.params.profileId; const profile = intakeProfiles.find(p => p.id === id); @@ -20,7 +20,7 @@ export const handlers = [ }), http.get( - '/api/profile/responses/:userId', + '/api/intake-profile/responses/:userId', () => { const fields = intakeProfiles[0].fieldGroups .map(fieldGroup => fieldGroup.fields)