feat: added advertisers endpoints
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
import typing
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from ninja import ModelSchema
|
||||||
|
|
||||||
|
from apps.advertiser.models import Advertiser as AdvertiserModel
|
||||||
|
from apps.mlscore.models import Mlscore as MlscoreModel
|
||||||
|
|
||||||
|
|
||||||
|
class Advertiser(ModelSchema):
|
||||||
|
advertiser_id: UUID
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AdvertiserModel
|
||||||
|
exclude: typing.ClassVar[tuple[str]] = (AdvertiserModel.id.field.name,)
|
||||||
|
|
||||||
|
|
||||||
|
class Mlscore(ModelSchema):
|
||||||
|
class Meta:
|
||||||
|
model = MlscoreModel
|
||||||
|
exclude: typing.ClassVar[tuple[str]] = (MlscoreModel.id.field.name,)
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
from http import HTTPStatus as status
|
||||||
|
|
||||||
|
from django.test import TestCase, Client
|
||||||
|
from apps.advertiser.models import Advertiser
|
||||||
|
from apps.client.models import Client as ClientModel
|
||||||
|
from apps.mlscore.models import Mlscore
|
||||||
|
|
||||||
|
|
||||||
|
class TestMlscoreEndpoint(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
self.advertiser = Advertiser.objects.create(name="Test Advertiser")
|
||||||
|
self.client_obj = ClientModel.objects.create(
|
||||||
|
login="test_client",
|
||||||
|
age=14,
|
||||||
|
location="test_location",
|
||||||
|
gender=ClientModel.GenderChoices.FEMALE,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.url = "/advertisers/ml-scores"
|
||||||
|
|
||||||
|
def test_create_mlscore_success(self):
|
||||||
|
data = {
|
||||||
|
"advertiser_id": str(self.advertiser.id),
|
||||||
|
"client_id": str(self.client_obj.id),
|
||||||
|
"score": 90,
|
||||||
|
}
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
mlscore = Mlscore.objects.first()
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.OK)
|
||||||
|
self.assertEqual(mlscore.score, 90)
|
||||||
|
|
||||||
|
def test_update_mlscore_success(self):
|
||||||
|
mlscore = Mlscore.objects.create(
|
||||||
|
advertiser=self.advertiser,
|
||||||
|
client=self.client_obj,
|
||||||
|
score=80,
|
||||||
|
)
|
||||||
|
data = {
|
||||||
|
"advertiser_id": str(self.advertiser.id),
|
||||||
|
"client_id": str(self.client_obj.id),
|
||||||
|
"score": 85,
|
||||||
|
}
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
mlscore.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.OK)
|
||||||
|
self.assertEqual(mlscore.score, 85)
|
||||||
|
|
||||||
|
def test_missing_required_field(self):
|
||||||
|
data = {"score": 90}
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.BAD_REQUEST)
|
||||||
|
|
||||||
|
def test_invalid_uuid_format(self):
|
||||||
|
data = {
|
||||||
|
"advertiser_id": "invalid-uuid",
|
||||||
|
"client_id": str(self.client_obj.id),
|
||||||
|
"score": 90,
|
||||||
|
}
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.BAD_REQUEST)
|
||||||
|
|
||||||
|
def test_non_existing_client(self):
|
||||||
|
data = {
|
||||||
|
"advertiser_id": str(self.advertiser.id),
|
||||||
|
"client_id": str(uuid.uuid4()),
|
||||||
|
"score": 90,
|
||||||
|
}
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.BAD_REQUEST)
|
||||||
|
|
||||||
|
def test_non_existing_advertiser(self):
|
||||||
|
data = {
|
||||||
|
"advertiser_id": str(uuid.uuid4()),
|
||||||
|
"client_id": str(self.client_obj.id),
|
||||||
|
"score": 90,
|
||||||
|
}
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBulkAdvertisersEndpoint(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
self.url = "/advertisers/bulk"
|
||||||
|
self.advertiser = Advertiser.objects.create(name="Advertiser 1")
|
||||||
|
|
||||||
|
def test_bulk_create_success(self):
|
||||||
|
uuid1 = self.advertiser.id
|
||||||
|
uuid2 = uuid.uuid4()
|
||||||
|
data = [
|
||||||
|
{"advertiser_id": str(uuid1), "name": "Advertiser 4"},
|
||||||
|
{"advertiser_id": str(uuid2), "name": "Advertiser 1"},
|
||||||
|
{"advertiser_id": str(uuid2), "name": "Advertiser 5"},
|
||||||
|
{"advertiser_id": str(uuid2), "name": "Advertiser 2"},
|
||||||
|
{"advertiser_id": str(uuid1), "name": "Advertiser 2"},
|
||||||
|
]
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
self.advertiser.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.CREATED)
|
||||||
|
self.assertEqual(self.advertiser.name, "Advertiser 2")
|
||||||
|
self.assertEqual(Advertiser.objects.count(), 2)
|
||||||
|
|
||||||
|
def test_bulk_update_success(self):
|
||||||
|
advertiser = Advertiser.objects.create(name="Old Name")
|
||||||
|
data = [{"advertiser_id": str(advertiser.id), "name": "New Name"}]
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
advertiser.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.CREATED)
|
||||||
|
self.assertEqual(advertiser.name, "New Name")
|
||||||
|
|
||||||
|
def test_duplicate_advertiser_ids(self):
|
||||||
|
adv_id = uuid.uuid4()
|
||||||
|
data = [
|
||||||
|
{"advertiser_id": str(adv_id), "name": "First"},
|
||||||
|
{"advertiser_id": str(adv_id), "name": "Last"},
|
||||||
|
]
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
advertiser = Advertiser.objects.get(id=adv_id)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.CREATED)
|
||||||
|
self.assertEqual(advertiser.name, "Last")
|
||||||
|
|
||||||
|
def test_invalid_advertiser_id_format(self):
|
||||||
|
data = [{"advertiser_id": "invalid", "name": "Invalid"}]
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps(data), content_type="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.BAD_REQUEST)
|
||||||
|
|
||||||
|
def test_empty_bulk_request(self):
|
||||||
|
response = self.client.post(
|
||||||
|
self.url, json.dumps([]), content_type="application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.CREATED)
|
||||||
|
self.assertEqual(len(response.json()), 0)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetAdvertiserEndpoint(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
self.advertiser = Advertiser.objects.create(name="Test Advertiser")
|
||||||
|
self.url = "/advertisers"
|
||||||
|
self.valid_url = f"{self.url}/{self.advertiser.id}"
|
||||||
|
|
||||||
|
def test_get_advertiser_success(self):
|
||||||
|
response = self.client.get(self.valid_url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.OK)
|
||||||
|
self.assertEqual(
|
||||||
|
response.json()["advertiser_id"], str(self.advertiser.id)
|
||||||
|
)
|
||||||
|
self.assertEqual(response.json()["name"], self.advertiser.name)
|
||||||
|
|
||||||
|
def test_non_existent_advertiser(self):
|
||||||
|
non_existent_url = f"{self.url}/{uuid.uuid4()}"
|
||||||
|
response = self.client.get(non_existent_url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.NOT_FOUND)
|
||||||
|
|
||||||
|
def test_invalid_uuid_format(self):
|
||||||
|
response = self.client.get(f"{self.url}/invalid-uuid")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.BAD_REQUEST)
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
from http import HTTPStatus as status
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from ninja import Router
|
||||||
|
|
||||||
|
from api.v1 import schemas as global_schemas
|
||||||
|
from api.v1.advertisers import schemas
|
||||||
|
from apps.advertiser.models import Advertiser
|
||||||
|
from apps.mlscore.models import Mlscore
|
||||||
|
|
||||||
|
router = Router(tags=["advertisers"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/ml-scores",
|
||||||
|
response={
|
||||||
|
status.OK: schemas.Mlscore,
|
||||||
|
status.BAD_REQUEST: global_schemas.BadRequestError,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def create_or_update_mlscore(
|
||||||
|
request: HttpRequest, mlscore: schemas.Mlscore
|
||||||
|
) -> tuple[status, schemas.Mlscore]:
|
||||||
|
mlscore_instance, _ = Mlscore.objects.update_or_create(
|
||||||
|
advertiser_id=mlscore.advertiser,
|
||||||
|
client_id=mlscore.client,
|
||||||
|
defaults={"score": mlscore.score},
|
||||||
|
)
|
||||||
|
|
||||||
|
return status.OK, mlscore_instance
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/bulk",
|
||||||
|
response={
|
||||||
|
status.CREATED: list[schemas.Advertiser],
|
||||||
|
status.BAD_REQUEST: global_schemas.BadRequestError,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def bulk_create_or_update(
|
||||||
|
request: HttpRequest, data: list[schemas.Advertiser]
|
||||||
|
) -> tuple[status, list[Advertiser]]:
|
||||||
|
latest_advertisers: dict[UUID, schemas.Advertiser] = defaultdict(
|
||||||
|
lambda: None
|
||||||
|
)
|
||||||
|
|
||||||
|
for item in reversed(data):
|
||||||
|
if latest_advertisers[item.advertiser_id] is None:
|
||||||
|
Advertiser(
|
||||||
|
id=item.advertiser_id, **item.dict(exclude={"client_id"})
|
||||||
|
).validate(
|
||||||
|
validate_unique=False,
|
||||||
|
validate_constraints=False,
|
||||||
|
)
|
||||||
|
latest_advertisers[item.advertiser_id] = item
|
||||||
|
|
||||||
|
unique_advertisers = reversed(list(latest_advertisers.values()))
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for advertiser in unique_advertisers:
|
||||||
|
advertiser_instance, _ = Advertiser.objects.update_or_create(
|
||||||
|
id=advertiser.advertiser_id,
|
||||||
|
defaults={**dict(advertiser)},
|
||||||
|
)
|
||||||
|
result.append(advertiser_instance)
|
||||||
|
|
||||||
|
return status.CREATED, result
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/{advertiser_id}",
|
||||||
|
response={
|
||||||
|
status.OK: schemas.Advertiser,
|
||||||
|
status.BAD_REQUEST: global_schemas.BadRequestError,
|
||||||
|
status.NOT_FOUND: global_schemas.NotFoundError,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get_advertiser(
|
||||||
|
request: HttpRequest, advertiser_id: UUID
|
||||||
|
) -> tuple[status, Advertiser]:
|
||||||
|
return status.OK, get_object_or_404(Advertiser, id=advertiser_id)
|
||||||
Reference in New Issue
Block a user