From a84d837942f1f50fd236caf293c8545c322adffc Mon Sep 17 00:00:00 2001 From: ramvignesh-b Date: Fri, 24 Apr 2026 06:29:14 +0530 Subject: [PATCH] feat: update letter patch to check for type change to sent and auto set time --- backend/letters/models.py | 11 +++++++++++ backend/letters/tests.py | 36 ++++++++++++++++++++++++++++++++---- backend/letters/views.py | 12 ++++++++---- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/backend/letters/models.py b/backend/letters/models.py index 34f592c..d728ed3 100644 --- a/backend/letters/models.py +++ b/backend/letters/models.py @@ -1,4 +1,5 @@ import uuid +from datetime import UTC, datetime from django.conf import settings from django.core.exceptions import ValidationError @@ -39,6 +40,16 @@ class Letter(models.Model): if self.type == Letter.Type.VAULT and self.status == Letter.Status.SEALED and not self.unlock_at: raise ValidationError("A sealed VAULT letter must have an unlock_date.") + def save(self, *args, **kwargs): + """ + Override save method to auto set BURNED and SEALED timestamps. + """ + if self.status == Letter.Status.BURNED: + self.burned_at = datetime.now(UTC) + if self.status == Letter.Status.SEALED: + self.sealed_at = datetime.now(UTC) + super().save(*args, **kwargs) + def __str__(self): return f"{self.type} - {self.status}" diff --git a/backend/letters/tests.py b/backend/letters/tests.py index 26405dc..3f9f7ad 100644 --- a/backend/letters/tests.py +++ b/backend/letters/tests.py @@ -270,15 +270,43 @@ class LetterAPITest(APITestCase): "encrypted_dek": "enc_dek_new==", }, ) - response_burn = self.client.patch(self.url + letter.public_id + "/", {"status": "BURNED"}) self.assertEqual(response_update_content.status_code, 400) - self.assertEqual(response_update_content.data["error"], "Sealed letters can only be burned.") + self.assertEqual(response_update_content.data["error"], "Sealed letters can only be burned or sent.") self.assertEqual(Letter.objects.get().encrypted_content, "enc_content==") - self.assertEqual(response_burn.status_code, 200) + from datetime import UTC, datetime + + from freezegun import freeze_time + + current_time = datetime.now(UTC) + with freeze_time(current_time): + response_burn = self.client.patch(self.url + letter.public_id + "/", {"status": "BURNED"}) + + self.assertEqual(response_burn.status_code, 200) + self.assertEqual(Letter.objects.count(), 1) + self.assertEqual(Letter.objects.get().status, "BURNED") + self.assertEqual(Letter.objects.get().burned_at, current_time) + + def test_send_sealed_letter(self): + """ + Test that a sealed letter can be sent. + """ + letter = Letter.objects.create( + user=self.user, + type="KEPT", + status="SEALED", + public_id="4281edcc-5459-4ff2-bb5e-669fb44e0757", + encrypted_content="enc_content==", + encrypted_metadata="enc_meta==", + encrypted_dek="enc_dek==", + ) + + response_sent = self.client.patch(self.url + letter.public_id + "/", {"type": "SENT"}) + + self.assertEqual(response_sent.status_code, 200) self.assertEqual(Letter.objects.count(), 1) - self.assertEqual(Letter.objects.get().status, "BURNED") + self.assertEqual(Letter.objects.get().type, "SENT") class LetterImageModelTest(TestCase): diff --git a/backend/letters/views.py b/backend/letters/views.py index ea6354e..96769f6 100644 --- a/backend/letters/views.py +++ b/backend/letters/views.py @@ -66,13 +66,17 @@ class LetterDetailView(generics.RetrieveUpdateDestroyAPIView): def patch(self, request, public_id): """ Updates an existing letter. + Can update type and status only when sealed, sent and burned. """ letter = Letter.objects.get(public_id=public_id, user=request.user) - if letter.status == Letter.Status.SEALED and ( - len(request.data) > 1 or request.data.get("status") != Letter.Status.BURNED - ): - return Response({"error": "Sealed letters can only be burned."}, status=400) + if letter.status == Letter.Status.SEALED: + if ( + len(request.data) > 1 + or (request.data.get("status") != Letter.Status.BURNED and request.data.get("status") is not None) + or (request.data.get("type") != Letter.Type.SENT and request.data.get("type") is not None) + ): + return Response({"error": "Sealed letters can only be burned or sent."}, status=400) write_serializer = self.get_serializer(letter, data=request.data, partial=True) write_serializer.is_valid(raise_exception=True)