Przeglądaj źródła

fix volunteer delection cascade on sms

tripeur 1 rok temu
rodzic
commit
bfad59aaa0
4 zmienionych plików z 103 dodań i 57 usunięć
  1. 18 46
      app/models.py
  2. 42 1
      app/tests/test_sms.py
  3. 42 9
      app/tests/test_volunteer.py
  4. 1 1
      pyproject.toml

+ 18 - 46
app/models.py

@@ -31,33 +31,25 @@ class Base(DeclarativeBase):
 
 def uid_column() -> Mapped[str]:
     """Returns a postgreSQL UUID column for SQL ORM"""
-    return mapped_column(
-        UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4())
-    )
+    return mapped_column(UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4()))
 
 
 class User(Base):
     __tablename__ = "user_model"
 
     id: Mapped[str] = uid_column()
-    email: Mapped[str] = mapped_column(
-        String(254), nullable=False, unique=True, index=True
-    )
+    email: Mapped[str] = mapped_column(String(254), nullable=False, unique=True, index=True)
     hashed_password: Mapped[str] = mapped_column(String(128), nullable=False)
 
 
 class Project(Base):
     __tablename__ = "projects"
     id: Mapped[str] = uid_column()
-    created_at: Mapped[datetime] = mapped_column(
-        DateTime(timezone=True), server_default=func.now()
-    )
+    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
     updated_at: Mapped[datetime] = mapped_column(
         DateTime(timezone=True), default=datetime.now, onupdate=func.now()
     )
-    name: Mapped[str] = mapped_column(
-        String(128), nullable=False, unique=True, index=True
-    )
+    name: Mapped[str] = mapped_column(String(128), nullable=False, unique=True, index=True)
     is_public: Mapped[bool] = mapped_column(Boolean())
     volunteers: Mapped[list["Volunteer"]] = relationship(
         back_populates="project", cascade="delete, delete-orphan"
@@ -91,13 +83,9 @@ association_table_volunteer_slot = Table(
 class Volunteer(Base):
     __tablename__ = "volunteers"
     id: Mapped[str] = uid_column()
-    project_id: Mapped[str] = mapped_column(
-        ForeignKey("projects.id", ondelete="CASCADE")
-    )
+    project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
     project: Mapped["Project"] = relationship(back_populates="volunteers")
-    created_at: Mapped[datetime] = mapped_column(
-        DateTime(timezone=True), server_default=func.now()
-    )
+    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
     updated_at: Mapped[datetime] = mapped_column(
         DateTime(timezone=True), default=datetime.now, onupdate=func.now()
     )
@@ -110,6 +98,7 @@ class Volunteer(Base):
         secondary=association_table_volunteer_slot, back_populates="volunteers"
     )
     comment: Mapped[str] = mapped_column(String(), default="")
+    sms: Mapped[list["Sms"]] = relationship(back_populates="volunteer", cascade="all, delete")
 
     @hybrid_property
     def slots_id(self) -> list[str]:
@@ -120,14 +109,10 @@ class Slot(Base):
     __tablename__ = "slots"
 
     id: Mapped[str] = uid_column()
-    project_id: Mapped[str] = mapped_column(
-        ForeignKey("projects.id", ondelete="CASCADE")
-    )
+    project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
     project: Mapped["Project"] = relationship(back_populates="slots")
 
-    created_at: Mapped[datetime] = mapped_column(
-        DateTime(timezone=True), server_default=func.now()
-    )
+    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
     updated_at: Mapped[datetime] = mapped_column(
         DateTime(timezone=True), default=datetime.now, onupdate=func.now()
     )
@@ -168,14 +153,10 @@ class SlotTag(Base):
     __tablename__ = "slot_tags"
 
     id: Mapped[str] = uid_column()
-    project_id: Mapped[str] = mapped_column(
-        ForeignKey("projects.id", ondelete="CASCADE")
-    )
+    project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
     project: Mapped["Project"] = relationship(back_populates="tags")
 
-    created_at: Mapped[datetime] = mapped_column(
-        DateTime(timezone=True), server_default=func.now()
-    )
+    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
     updated_at: Mapped[datetime] = mapped_column(
         DateTime(timezone=True), default=datetime.now, onupdate=func.now()
     )
@@ -194,14 +175,10 @@ class SlotTemplate(Base):
     __tablename__ = "slot_templates"
 
     id: Mapped[str] = uid_column()
-    project_id: Mapped[str] = mapped_column(
-        ForeignKey("projects.id", ondelete="CASCADE")
-    )
+    project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
     project: Mapped["Project"] = relationship(back_populates="templates")
 
-    created_at: Mapped[datetime] = mapped_column(
-        DateTime(timezone=True), server_default=func.now()
-    )
+    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
     updated_at: Mapped[datetime] = mapped_column(
         DateTime(timezone=True), default=datetime.now, onupdate=func.now()
     )
@@ -233,23 +210,18 @@ class Sms(Base):
     id: Mapped[str] = mapped_column(
         UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4())
     )
-    project_id: Mapped[str] = mapped_column(
-        ForeignKey("projects.id", ondelete="CASCADE")
-    )
+    project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
     project: Mapped["Project"] = relationship(back_populates="sms")
 
     volunteer_id: Mapped[str] = mapped_column(
-        ForeignKey("volunteers.id"), nullable=True
-    )
-    created_at: Mapped[datetime] = mapped_column(
-        DateTime(timezone=True), server_default=func.now()
+        ForeignKey("volunteers.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=True
     )
+    volunteer: Mapped["Volunteer"] = relationship(back_populates="sms", cascade="all, delete")
+    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
     updated_at: Mapped[datetime] = mapped_column(
         DateTime(timezone=True), default=datetime.now, onupdate=func.now()
     )
     content: Mapped[str] = mapped_column(String(), nullable=False)
     phone_number: Mapped[str] = mapped_column(String(24))
-    sending_time: Mapped[datetime] = mapped_column(
-        DateTime(timezone=True), default=datetime.now
-    )
+    sending_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now)
     send_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True)

+ 42 - 1
app/tests/test_sms.py

@@ -5,7 +5,7 @@ from sqlalchemy import select
 from sqlalchemy.orm import Session
 
 from app.main import app
-from app.models import Project, Sms, Sms
+from app.models import Project, Sms, Sms, Volunteer
 from app.tests.conftest import default_project_id, default_volunteer_id, default_sms_id
 
 
@@ -221,3 +221,44 @@ async def test_delete_sms(
         headers=default_user_headers,
     )
     assert response.status_code == 422
+
+
+async def test_delete_sms_with_volunteer(
+    client: AsyncClient,
+    default_user_headers: dict,
+    session: Session,
+    default_public_project: Project,
+):
+    result = session.execute(select(Volunteer).where(Volunteer.id == default_volunteer_id))
+    volunteer = result.scalars().first()
+    assert volunteer is not None
+    assert len(volunteer.sms) == 0
+
+    result = session.execute(select(Sms).where(Sms.id == default_sms_id))
+    sms = result.scalars().first()
+    assert sms is not None
+    sms.volunteer_id = default_volunteer_id
+    session.commit()
+
+    session.refresh(volunteer)
+    assert len(volunteer.sms) == 1, "Sms not added to volunteer"
+
+    response = await client.delete(
+        app.url_path_for(
+            "delete_sms",
+            project_id=default_project_id,
+            sms_id=default_sms_id,
+        ),
+        headers=default_user_headers,
+    )
+    assert response.status_code == 200
+    result = session.execute(select(Sms).where(Sms.id == default_sms_id))
+    assert result.scalars().first() is None
+
+    session.refresh(volunteer)
+    assert len(volunteer.sms) == 0, "Sms should be deleted"
+
+    result = session.execute(select(Volunteer).where(Volunteer.id == default_volunteer_id))
+    volunteer_result = result.scalars().first()
+    assert volunteer_result is not None, "Volunteer should not be deleted"
+    assert len(volunteer_result.sms) == 0, "No more sms associated to volunteer"

+ 42 - 9
app/tests/test_volunteer.py

@@ -77,9 +77,7 @@ async def test_create_volunteer(
     assert response.json()["id"] != default_project_id
     assert response.json()["name"] == "Lancelot"
     assert response.json()["comment"] == ""
-    result = session.execute(
-        select(Volunteer).where(Volunteer.project_id == default_project_id)
-    )
+    result = session.execute(select(Volunteer).where(Volunteer.project_id == default_project_id))
     volunteers = result.scalars().all()
     assert len(volunteers) > 1
 
@@ -266,9 +264,7 @@ async def test_delete_volunteer(
         )
     )
     assert response.status_code == 401
-    result = session.execute(
-        select(Volunteer).where(Volunteer.id == default_volunteer_id)
-    )
+    result = session.execute(select(Volunteer).where(Volunteer.id == default_volunteer_id))
     volunteer = result.scalars().first()
     assert volunteer is not None
 
@@ -282,9 +278,7 @@ async def test_delete_volunteer(
         headers=default_user_headers,
     )
     assert response.status_code == 200
-    result = session.execute(
-        select(Volunteer).where(Volunteer.id == default_volunteer_id)
-    )
+    result = session.execute(select(Volunteer).where(Volunteer.id == default_volunteer_id))
     volunteer = result.scalars().first()
     assert volunteer is None
 
@@ -305,6 +299,13 @@ async def test_delete_volunteer(
     )
     assert response.status_code == 200
 
+
+async def test_delete_volunteer_any_uuid(
+    client: AsyncClient,
+    default_user_headers: dict,
+    session: Session,
+    default_public_project: Project,
+):
     # can delete random uuid
     response = await client.delete(
         app.url_path_for(
@@ -322,3 +323,35 @@ async def test_delete_volunteer(
         headers=default_user_headers,
     )
     assert response.status_code == 422
+
+
+async def test_delete_volunteer_with_sms(
+    client: AsyncClient,
+    default_user_headers: dict,
+    session: Session,
+    default_public_project: Project,
+):
+    sms = Sms()
+    sms.project_id = default_project_id
+    sms.content = "coucou"
+    sms.phone_number = "02 66 66 66 66 66"
+    sms.volunteer_id = default_volunteer_id
+    session.add(sms)
+    session.commit()
+    session.refresh(sms)
+
+    response = await client.delete(
+        app.url_path_for(
+            "delete_volunteer",
+            project_id=default_project_id,
+            volunteer_id=default_volunteer_id,
+        ),
+        headers=default_user_headers,
+    )
+    assert response.status_code == 200
+    # Volunteer must be deleted
+    result = session.execute(select(Volunteer).where(Volunteer.id == default_volunteer_id))
+    assert result.scalar_one_or_none() is None
+
+    # Sms must be deleted
+    assert session.execute(select(Sms).where(Sms.id == sms.id)).scalar_one_or_none() is None

+ 1 - 1
pyproject.toml

@@ -1,6 +1,6 @@
 [tool.poetry]
 authors = ["clovis jaquin <clovis@jaquin.fr>"]
-description = "FastAPI project API that can parse gsheet planning for brass dans la garonne event and manage creating automatic SMS notification for volunteer"
+description = "FastAPI project that can parse gsheet planning for brass dans la garonne event and manage creating automatic SMS notification for volunteer"
 name = "bdlg-2023"
 version = "0.2.2"
 package-mode = false