From 071fad0b25093659dfa4e8977403ea3d04cdd1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Odini?= Date: Fri, 6 Dec 2024 14:23:21 +0100 Subject: [PATCH] feat(API): allow adding prices on proofs not owned (only PRICE_TAG proofs) (#609) --- open_prices/api/prices/tests.py | 42 +++++++++++++++++++++------------ open_prices/prices/factories.py | 1 + open_prices/prices/models.py | 8 +++++-- open_prices/prices/tests.py | 25 ++++++++++++++++---- open_prices/proofs/constants.py | 6 ++++- open_prices/proofs/factories.py | 1 + 6 files changed, 61 insertions(+), 22 deletions(-) diff --git a/open_prices/api/prices/tests.py b/open_prices/api/prices/tests.py index af7cd2b5..18320260 100644 --- a/open_prices/api/prices/tests.py +++ b/open_prices/api/prices/tests.py @@ -110,13 +110,13 @@ class PriceListFilterApiTest(TestCase): def setUpTestData(cls): cls.url = reverse("api:prices-list") cls.user_session = SessionFactory() - cls.user_proof = ProofFactory( + cls.user_proof_receipt = ProofFactory( type=proof_constants.TYPE_RECEIPT, owner=cls.user_session.user.user_id ) cls.user_price = PriceFactory( **PRICE_8001505005707, receipt_quantity=2, - proof_id=cls.user_proof.id, + proof_id=cls.user_proof_receipt.id, owner=cls.user_session.user.user_id, ) PriceFactory( @@ -327,15 +327,16 @@ class PriceCreateApiTest(TestCase): def setUpTestData(cls): cls.url = reverse("api:prices-list") cls.user_session = SessionFactory() - cls.user_proof = ProofFactory( + cls.user_proof_gdpr = ProofFactory( type=proof_constants.TYPE_RECEIPT, owner=cls.user_session.user.user_id ) - cls.proof_2 = ProofFactory() + cls.proof_price_tag = ProofFactory(type=proof_constants.TYPE_PRICE_TAG) + cls.proof_receipt = ProofFactory(type=proof_constants.TYPE_RECEIPT) cls.data = { **PRICE_8001505005707, "location_osm_id": 652825274, "location_osm_type": "NODE", - "proof_id": cls.user_proof.id, + "proof_id": cls.user_proof_gdpr.id, "source": "test", } @@ -383,14 +384,6 @@ def test_price_create_with_proof(self): content_type="application/json", ) self.assertEqual(response.status_code, 400) - # not proof owner - response = self.client.post( - self.url, - {**self.data, "proof_id": self.proof_2.id}, - headers={"Authorization": f"Bearer {self.user_session.token}"}, - content_type="application/json", - ) - self.assertEqual(response.status_code, 400) # authenticated response = self.client.post( self.url, @@ -408,11 +401,11 @@ def test_price_create_with_proof(self): self.assertEqual(response.data["owner"], self.user_session.user.user_id) # with proof, product & location self.assertTrue("proof_id" in response.data) - self.assertEqual(response.data["proof"]["id"], self.user_proof.id) + self.assertEqual(response.data["proof"]["id"], self.user_proof_gdpr.id) self.assertEqual( response.data["proof"]["price_count"], 0 ) # not yet incremented - self.assertEqual(Proof.objects.get(id=self.user_proof.id).price_count, 1) + self.assertEqual(Proof.objects.get(id=self.user_proof_gdpr.id).price_count, 1) self.assertTrue("product_id" in response.data) self.assertEqual(response.data["product"]["code"], "8001505005707") self.assertEqual( @@ -430,6 +423,25 @@ def test_price_create_with_proof(self): p = Price.objects.last() self.assertEqual(p.source, "API") # default value + def test_price_create_with_proof_not_owned(self): + # not proof owner and proof is not a PRICE_TAG: NOK + response = self.client.post( + self.url, + {**self.data, "proof_id": self.proof_receipt.id}, + headers={"Authorization": f"Bearer {self.user_session.token}"}, + content_type="application/json", + ) + self.assertEqual(response.status_code, 400) + # not proof owner and proof is a PRICE_TAG: OK ! + response = self.client.post( + self.url, + {**self.data, "proof_id": self.proof_price_tag.id}, + headers={"Authorization": f"Bearer {self.user_session.token}"}, + content_type="application/json", + ) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.data["owner"], self.user_session.user.user_id) + def test_price_create_with_location_id(self): location_osm = LocationFactory(**LOCATION_OSM_NODE_652825274) location_online = LocationFactory(type=location_constants.TYPE_ONLINE) diff --git a/open_prices/prices/factories.py b/open_prices/prices/factories.py index d815eaca..a151de77 100644 --- a/open_prices/prices/factories.py +++ b/open_prices/prices/factories.py @@ -40,3 +40,4 @@ class Params: ) location_osm_type = factory.fuzzy.FuzzyChoice(location_constants.OSM_TYPE_LIST) date = date.fromisoformat("2023-10-30") + # owner = factory.Faker("user_name") diff --git a/open_prices/prices/models.py b/open_prices/prices/models.py index da152611..be7ba285 100644 --- a/open_prices/prices/models.py +++ b/open_prices/prices/models.py @@ -463,11 +463,15 @@ def clean(self, *args, **kwargs): ) if proof: - if proof.owner != self.owner: + if ( + proof.owner != self.owner + and proof.type + not in proof_constants.TYPE_ALLOW_ANY_USER_PRICE_ADD_LIST + ): validation_errors = utils.add_validation_error( validation_errors, "proof", - "Proof does not belong to the current user", + "Proof does not belong to the current user. Adding a price to a proof a user does not own is only allowed for {proof_constants.TYPE_ALLOW_ANY_USER_PRICE_ADD_LIST} proofs", ) if not self.id: # skip these checks on update if proof.type in proof_constants.TYPE_SINGLE_SHOP_LIST: diff --git a/open_prices/prices/tests.py b/open_prices/prices/tests.py index 6f01b774..b4d08d81 100644 --- a/open_prices/prices/tests.py +++ b/open_prices/prices/tests.py @@ -358,7 +358,14 @@ def test_price_proof_validation(self): currency="EUR", owner=user_session.user.user_id, ) - proof_2 = ProofFactory() + proof_gdpr = ProofFactory( + type=proof_constants.TYPE_GDPR_REQUEST, + location_osm_id=169450062, + location_osm_type=location_constants.OSM_TYPE_NODE, + date="2024-10-01", + currency="EUR", + ) + proof_price_tag = ProofFactory(type=proof_constants.TYPE_PRICE_TAG) # proof not set PriceFactory(proof_id=None, owner=user_proof_receipt.owner) # proof unknown @@ -374,16 +381,26 @@ def test_price_proof_validation(self): currency=user_proof_receipt.currency, owner=user_proof_receipt.owner, ) - # different price & proof owner + # difference price and proof owner: raise a ValidationError + # if the proof type is different than PRICE_TAG self.assertRaises( ValidationError, PriceFactory, - proof_id=proof_2.id, # different + proof_id=proof_gdpr.id, # gdpr proof + location_osm_id=proof_gdpr.location_osm_id, + location_osm_type=proof_gdpr.location_osm_type, + date=proof_gdpr.date, + currency=proof_gdpr.currency, + owner=user_proof_receipt.owner, # different owner + ) + # different price & proof owner: ok for PRICE_TAG proofs + PriceFactory( + proof_id=proof_price_tag.id, location_osm_id=user_proof_receipt.location_osm_id, location_osm_type=user_proof_receipt.location_osm_type, date=user_proof_receipt.date, currency=user_proof_receipt.currency, - owner=user_proof_receipt.owner, + owner=user_proof_receipt.owner, # different owner ) # proof location_osm_id & location_osm_type self.assertRaises( diff --git a/open_prices/proofs/constants.py b/open_prices/proofs/constants.py index 6b64425d..b3f4a461 100644 --- a/open_prices/proofs/constants.py +++ b/open_prices/proofs/constants.py @@ -6,10 +6,14 @@ TYPE_LIST = [TYPE_PRICE_TAG, TYPE_RECEIPT, TYPE_GDPR_REQUEST, TYPE_SHOP_IMPORT] TYPE_CHOICES = [(key, key) for key in TYPE_LIST] -# 1 proof = 1 shop + 1 date +# SINGLE_SHOP: same location, date & currency TYPE_SINGLE_SHOP_LIST = [TYPE_PRICE_TAG, TYPE_RECEIPT, TYPE_SHOP_IMPORT] +# SHOPPING_SESSION: extra fields TYPE_SHOPPING_SESSION_LIST = [TYPE_RECEIPT, TYPE_GDPR_REQUEST] +# MULTIPLE_SHOP TYPE_MULTIPLE_SHOP_LIST = [TYPE_GDPR_REQUEST] +# ALLOW_ANY_USER_PRICE_ADD +TYPE_ALLOW_ANY_USER_PRICE_ADD_LIST = [TYPE_PRICE_TAG] PROOF_PREDICTION_OBJECT_DETECTION_TYPE = "OBJECT_DETECTION" PROOF_PREDICTION_CLASSIFICATION_TYPE = "CLASSIFICATION" diff --git a/open_prices/proofs/factories.py b/open_prices/proofs/factories.py index 4cac16b4..10f71a20 100644 --- a/open_prices/proofs/factories.py +++ b/open_prices/proofs/factories.py @@ -18,6 +18,7 @@ class Meta: # date = factory.Faker("date") # currency = factory.Faker("currency_symbol") # price_count = factory.LazyAttribute(lambda x: random.randrange(0, 100)) + # owner = factory.Faker("user_name") class ProofPredictionFactory(DjangoModelFactory):