test(integration): added integration tests
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
import json
|
||||
import uuid
|
||||
from decimal import Decimal
|
||||
from typing import override
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.events.tests.helpers import make_event_type, make_exposure_type
|
||||
from apps.experiments.services import (
|
||||
experiment_approve,
|
||||
experiment_start,
|
||||
experiment_submit_for_review,
|
||||
)
|
||||
from apps.experiments.tests.helpers import add_two_variants, make_experiment
|
||||
from apps.metrics.services import (
|
||||
experiment_metric_add,
|
||||
metric_definition_create,
|
||||
)
|
||||
from apps.reviews.services import review_settings_update
|
||||
from apps.reviews.tests.helpers import make_approver, make_experimenter
|
||||
|
||||
|
||||
class APIContractFlowTest(TestCase):
|
||||
@override
|
||||
def setUp(self) -> None:
|
||||
cache.clear()
|
||||
self.client = Client()
|
||||
|
||||
review_settings_update(
|
||||
default_min_approvals=1,
|
||||
allow_any_approver=True,
|
||||
)
|
||||
|
||||
owner = make_experimenter("_api")
|
||||
approver = make_approver("_api")
|
||||
|
||||
self.experiment = make_experiment(
|
||||
owner=owner,
|
||||
suffix="_api",
|
||||
traffic_allocation=Decimal("100.00"),
|
||||
)
|
||||
add_two_variants(self.experiment)
|
||||
|
||||
self.metric = metric_definition_create(
|
||||
key="ctr_api",
|
||||
name="CTR",
|
||||
metric_type="ratio",
|
||||
direction="higher_is_better",
|
||||
calculation_rule={
|
||||
"numerator_event": "api_click",
|
||||
"denominator_event": "api_exposure",
|
||||
},
|
||||
)
|
||||
experiment_metric_add(
|
||||
experiment=self.experiment,
|
||||
metric=self.metric,
|
||||
is_primary=True,
|
||||
)
|
||||
|
||||
make_exposure_type(name="api_exposure")
|
||||
make_event_type(
|
||||
name="api_click",
|
||||
display_name="Click",
|
||||
requires_exposure=True,
|
||||
)
|
||||
|
||||
self.experiment = experiment_submit_for_review(
|
||||
experiment=self.experiment, user=owner
|
||||
)
|
||||
self.experiment = experiment_approve(
|
||||
experiment=self.experiment, approver=approver
|
||||
)
|
||||
self.experiment = experiment_start(
|
||||
experiment=self.experiment, user=owner
|
||||
)
|
||||
|
||||
def test_decide_to_events_to_report_via_http(self) -> None:
|
||||
decide_resp = self.client.post(
|
||||
reverse("api-1:decide"),
|
||||
data=json.dumps(
|
||||
{
|
||||
"subject_id": "api_user_1",
|
||||
"flags": [self.experiment.flag.key],
|
||||
"subject_attributes": {},
|
||||
}
|
||||
),
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(decide_resp.status_code, 200)
|
||||
decide_data = decide_resp.json()
|
||||
|
||||
self.assertEqual(len(decide_data["decisions"]), 1)
|
||||
decision = decide_data["decisions"][0]
|
||||
self.assertEqual(decision["reason"], "experiment_assigned")
|
||||
self.assertIsNotNone(decision["decision_id"])
|
||||
self.assertIsNotNone(decision["variant_id"])
|
||||
|
||||
now = timezone.now().isoformat()
|
||||
events_resp = self.client.post(
|
||||
reverse("api-1:ingest_events"),
|
||||
data=json.dumps(
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"event_id": "api_exp_1",
|
||||
"event_type": "api_exposure",
|
||||
"decision_id": decision["decision_id"],
|
||||
"subject_id": "api_user_1",
|
||||
"timestamp": now,
|
||||
"properties": {},
|
||||
},
|
||||
{
|
||||
"event_id": "api_click_1",
|
||||
"event_type": "api_click",
|
||||
"decision_id": decision["decision_id"],
|
||||
"subject_id": "api_user_1",
|
||||
"timestamp": now,
|
||||
"properties": {},
|
||||
},
|
||||
]
|
||||
}
|
||||
),
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(events_resp.status_code, 200)
|
||||
events_data = events_resp.json()
|
||||
self.assertEqual(events_data["accepted"], 2)
|
||||
self.assertEqual(events_data["rejected"], 0)
|
||||
self.assertEqual(events_data["duplicates"], 0)
|
||||
|
||||
report_resp = self.client.get(
|
||||
reverse(
|
||||
"api-1:get_experiment_report",
|
||||
args=[self.experiment.pk],
|
||||
),
|
||||
)
|
||||
self.assertEqual(report_resp.status_code, 200)
|
||||
report = report_resp.json()
|
||||
|
||||
self.assertEqual(report["experiment_id"], str(self.experiment.pk))
|
||||
total_exposures = sum(v["exposures"] for v in report["variants"])
|
||||
self.assertEqual(total_exposures, 1)
|
||||
self.assertEqual(len(report["variants"]), 2)
|
||||
|
||||
for variant in report["variants"]:
|
||||
if variant["exposures"] > 0:
|
||||
self.assertEqual(len(variant["metrics"]), 1)
|
||||
self.assertEqual(
|
||||
variant["metrics"][0]["metric_key"], "ctr_api"
|
||||
)
|
||||
|
||||
def test_decide_returns_all_requested_flags(self) -> None:
|
||||
resp = self.client.post(
|
||||
reverse("api-1:decide"),
|
||||
data=json.dumps(
|
||||
{
|
||||
"subject_id": "api_user_2",
|
||||
"flags": [
|
||||
self.experiment.flag.key,
|
||||
"nonexistent_flag",
|
||||
],
|
||||
}
|
||||
),
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = resp.json()
|
||||
|
||||
self.assertEqual(len(data["decisions"]), 2)
|
||||
reasons = {d["flag"]: d["reason"] for d in data["decisions"]}
|
||||
self.assertEqual(
|
||||
reasons[self.experiment.flag.key], "experiment_assigned"
|
||||
)
|
||||
self.assertEqual(reasons["nonexistent_flag"], "flag_not_found")
|
||||
|
||||
def test_events_dedup_via_http(self) -> None:
|
||||
cache.clear()
|
||||
decide_resp = self.client.post(
|
||||
reverse("api-1:decide"),
|
||||
data=json.dumps(
|
||||
{
|
||||
"subject_id": "api_user_3",
|
||||
"flags": [self.experiment.flag.key],
|
||||
}
|
||||
),
|
||||
content_type="application/json",
|
||||
)
|
||||
decision = decide_resp.json()["decisions"][0]
|
||||
now = timezone.now().isoformat()
|
||||
|
||||
event_payload = json.dumps(
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"event_id": "api_ddup_1",
|
||||
"event_type": "api_exposure",
|
||||
"decision_id": decision["decision_id"],
|
||||
"subject_id": "api_user_3",
|
||||
"timestamp": now,
|
||||
"properties": {},
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
r1 = self.client.post(
|
||||
reverse("api-1:ingest_events"),
|
||||
data=event_payload,
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(r1.json()["accepted"], 1)
|
||||
|
||||
r2 = self.client.post(
|
||||
reverse("api-1:ingest_events"),
|
||||
data=event_payload,
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(r2.json()["duplicates"], 1)
|
||||
self.assertEqual(r2.json()["accepted"], 0)
|
||||
|
||||
def test_report_404_for_unknown_experiment(self) -> None:
|
||||
resp = self.client.get(
|
||||
reverse(
|
||||
"api-1:get_experiment_report",
|
||||
args=[uuid.uuid4()],
|
||||
),
|
||||
)
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
Reference in New Issue
Block a user