Skip to content

Commit a974906

Browse files
committed
improve typing, coverage
1 parent 4b1092e commit a974906

5 files changed

Lines changed: 384 additions & 80 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import pytest
2+
from django.conf import settings
3+
4+
5+
@pytest.fixture
6+
def skip_if_no_analytics_db() -> None:
7+
"""
8+
Skip tests if no analytics database is configured.
9+
This is useful to avoid running tests that require a specific database setup.
10+
"""
11+
if "analytics" not in settings.DATABASES:
12+
pytest.skip("No analytics database configured, skipping")
13+
14+
15+
@pytest.fixture(autouse=True)
16+
def skip_if_no_analytics_db_marked(request: pytest.FixtureRequest) -> None:
17+
"""
18+
Automatically skip tests that are marked with 'skip_if_no_analytics_db'.
19+
This allows for selective skipping of tests based on the database configuration.
20+
"""
21+
if request.node.get_closest_marker("skip_if_no_analytics_db"):
22+
request.getfixturevalue("skip_if_no_analytics_db")

api/tests/unit/app_analytics/test_analytics_db_service.py

Lines changed: 228 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from datetime import UTC, date, datetime, timedelta
22

33
import pytest
4-
from django.conf import settings
54
from django.utils import timezone
65
from pytest_django.fixtures import SettingsWrapper
76
from pytest_mock import MockerFixture
@@ -14,10 +13,8 @@
1413
get_usage_data,
1514
get_usage_data_from_local_db,
1615
)
17-
from app_analytics.constants import (
18-
CURRENT_BILLING_PERIOD,
19-
PREVIOUS_BILLING_PERIOD,
20-
)
16+
from app_analytics.constants import CURRENT_BILLING_PERIOD, PREVIOUS_BILLING_PERIOD
17+
from app_analytics.dataclasses import FeatureEvaluationData, UsageData
2118
from app_analytics.models import (
2219
APIUsageBucket,
2320
FeatureEvaluationBucket,
@@ -47,10 +44,7 @@ def cache(organisation: Organisation) -> OrganisationSubscriptionInformationCach
4744
)
4845

4946

50-
@pytest.mark.skipif(
51-
"analytics" not in settings.DATABASES,
52-
reason="Skip test if analytics database is configured",
53-
)
47+
@pytest.mark.skip_if_no_analytics_db
5448
@pytest.mark.django_db(databases=["analytics", "default"])
5549
def test_get_usage_data_from_local_db(organisation, environment, settings): # type: ignore[no-untyped-def]
5650
environment_id = environment.id
@@ -108,10 +102,7 @@ def test_get_usage_data_from_local_db(organisation, environment, settings): # t
108102
assert data.day == today - timedelta(days=29 - count)
109103

110104

111-
@pytest.mark.skipif(
112-
"analytics" not in settings.DATABASES,
113-
reason="Skip test if analytics database is configured",
114-
)
105+
@pytest.mark.skip_if_no_analytics_db
115106
@pytest.mark.django_db(databases=["analytics", "default"])
116107
def test_get_usage_data_from_local_db_project_id_filter( # type: ignore[no-untyped-def]
117108
organisation: Organisation,
@@ -159,10 +150,127 @@ def test_get_usage_data_from_local_db_project_id_filter( # type: ignore[no-unty
159150
assert list(usage_data_for_project_two)[0].flags == total_count # 1 environment
160151

161152

162-
@pytest.mark.skipif(
163-
"analytics" not in settings.DATABASES,
164-
reason="Skip test if analytics database is configured",
165-
)
153+
@pytest.mark.skip_if_no_analytics_db
154+
@pytest.mark.django_db(databases=["analytics", "default"])
155+
def test_get_usage_data_from_local_db__environment_filter__returns_expected(
156+
organisation: Organisation,
157+
environment: Environment,
158+
settings: SettingsWrapper,
159+
) -> None:
160+
# Given
161+
environment_id = environment.id
162+
now = timezone.now()
163+
read_bucket_size = 15
164+
settings.ANALYTICS_BUCKET_SIZE = read_bucket_size
165+
166+
bucket_created_at = now - timedelta(days=1)
167+
earlier_bucket_created_at = bucket_created_at - timedelta(minutes=read_bucket_size)
168+
169+
APIUsageBucket.objects.create(
170+
environment_id=environment_id,
171+
resource=Resource.FLAGS,
172+
total_count=10,
173+
bucket_size=read_bucket_size,
174+
created_at=bucket_created_at,
175+
)
176+
APIUsageBucket.objects.create(
177+
environment_id=99999,
178+
resource=Resource.FLAGS,
179+
total_count=10,
180+
bucket_size=read_bucket_size,
181+
created_at=earlier_bucket_created_at,
182+
)
183+
184+
# When
185+
usage_data_list = get_usage_data_from_local_db(organisation, environment_id)
186+
187+
# Then
188+
assert usage_data_list == [
189+
UsageData(
190+
day=bucket_created_at.date(),
191+
flags=10,
192+
traits=0,
193+
identities=0,
194+
environment_document=0,
195+
labels={},
196+
),
197+
]
198+
199+
200+
@pytest.mark.skip_if_no_analytics_db
201+
@pytest.mark.django_db(databases=["analytics", "default"])
202+
def test_get_usage_data_from_local_db__labels_filter__returns_expected(
203+
organisation: Organisation,
204+
environment: Environment,
205+
settings: SettingsWrapper,
206+
) -> None:
207+
# Given
208+
environment_id = environment.id
209+
now = timezone.now()
210+
read_bucket_size = 15
211+
settings.ANALYTICS_BUCKET_SIZE = read_bucket_size
212+
213+
bucket_created_at = now - timedelta(days=1)
214+
earlier_bucket_created_at = bucket_created_at - timedelta(minutes=read_bucket_size)
215+
216+
APIUsageBucket.objects.create(
217+
environment_id=environment_id,
218+
resource=Resource.FLAGS,
219+
total_count=10,
220+
bucket_size=read_bucket_size,
221+
created_at=bucket_created_at,
222+
labels={
223+
"client_application_name": "test-app",
224+
"client_application_version": "1.0.0",
225+
},
226+
)
227+
APIUsageBucket.objects.create(
228+
environment_id=environment_id,
229+
resource=Resource.FLAGS,
230+
total_count=10,
231+
bucket_size=read_bucket_size,
232+
created_at=earlier_bucket_created_at,
233+
labels={"client_application_name": "test-app"},
234+
)
235+
APIUsageBucket.objects.create(
236+
environment_id=environment_id,
237+
resource=Resource.FLAGS,
238+
total_count=10,
239+
bucket_size=read_bucket_size,
240+
created_at=earlier_bucket_created_at,
241+
labels={"client_application_name": "another-test-app"},
242+
)
243+
244+
# When
245+
usage_data_list = get_usage_data_from_local_db(
246+
organisation, labels_filter={"client_application_name": "test-app"}
247+
)
248+
249+
# Then
250+
assert usage_data_list == [
251+
UsageData(
252+
day=bucket_created_at.date(),
253+
flags=10,
254+
traits=0,
255+
identities=0,
256+
environment_document=0,
257+
labels={"client_application_name": "test-app"},
258+
),
259+
UsageData(
260+
day=earlier_bucket_created_at.date(),
261+
flags=10,
262+
traits=0,
263+
identities=0,
264+
environment_document=0,
265+
labels={
266+
"client_application_name": "test-app",
267+
"client_application_version": "1.0.0",
268+
},
269+
),
270+
]
271+
272+
273+
@pytest.mark.skip_if_no_analytics_db
166274
@pytest.mark.django_db(databases=["analytics", "default"])
167275
def test_get_total_events_count(organisation, environment, settings): # type: ignore[no-untyped-def]
168276
settings.USE_POSTGRES_FOR_ANALYTICS = True
@@ -214,14 +322,13 @@ def test_get_total_events_count(organisation, environment, settings): # type: i
214322
assert total_events_count == 20 * len(Resource) * 30
215323

216324

217-
@pytest.mark.skipif(
218-
"analytics" not in settings.DATABASES,
219-
reason="Skip test if analytics database is configured",
220-
)
325+
@pytest.mark.skip_if_no_analytics_db
221326
@pytest.mark.django_db(databases=["analytics", "default"])
222-
def test_get_feature_evaluation_data_from_local_db( # type: ignore[no-untyped-def]
223-
feature: Feature, environment: Environment, settings: SettingsWrapper
224-
):
327+
def test_get_feature_evaluation_data_from_local_db(
328+
feature: Feature,
329+
environment: Environment,
330+
settings: SettingsWrapper,
331+
) -> None:
225332
environment_id = environment.id
226333
feature_name = feature.name
227334
now = timezone.now()
@@ -284,6 +391,76 @@ def test_get_feature_evaluation_data_from_local_db( # type: ignore[no-untyped-d
284391
assert data.day == today - timedelta(days=29 - i)
285392

286393

394+
@pytest.mark.skip_if_no_analytics_db
395+
@pytest.mark.django_db(databases=["analytics", "default"])
396+
def test_get_feature_evaluation_data_from_local_db__labels_filter__returns_expected(
397+
feature: Feature,
398+
environment: Environment,
399+
settings: SettingsWrapper,
400+
) -> None:
401+
# Given
402+
environment_id = environment.id
403+
feature_name = feature.name
404+
now = timezone.now()
405+
read_bucket_size = 15
406+
settings.ANALYTICS_BUCKET_SIZE = read_bucket_size
407+
408+
bucket_created_at = now - timedelta(days=1)
409+
earlier_bucket_created_at = bucket_created_at - timedelta(minutes=read_bucket_size)
410+
411+
FeatureEvaluationBucket.objects.create(
412+
environment_id=environment_id,
413+
feature_name=feature_name,
414+
total_count=10,
415+
bucket_size=read_bucket_size,
416+
created_at=bucket_created_at,
417+
labels={
418+
"client_application_name": "test-app",
419+
"client_application_version": "1.0.0",
420+
},
421+
)
422+
FeatureEvaluationBucket.objects.create(
423+
environment_id=environment_id,
424+
feature_name=feature_name,
425+
total_count=10,
426+
bucket_size=read_bucket_size,
427+
created_at=earlier_bucket_created_at,
428+
labels={"client_application_name": "test-app"},
429+
)
430+
FeatureEvaluationBucket.objects.create(
431+
environment_id=environment_id,
432+
feature_name=feature_name,
433+
total_count=10,
434+
bucket_size=read_bucket_size,
435+
created_at=earlier_bucket_created_at,
436+
labels={"client_application_name": "another-test-app"},
437+
)
438+
439+
# When
440+
usage_data_list = get_feature_evaluation_data_from_local_db(
441+
feature,
442+
environment_id,
443+
labels_filter={"client_application_name": "test-app"},
444+
)
445+
446+
# Then
447+
assert usage_data_list == [
448+
FeatureEvaluationData(
449+
day=earlier_bucket_created_at.date(),
450+
count=10,
451+
labels={"client_application_name": "test-app"},
452+
),
453+
FeatureEvaluationData(
454+
day=bucket_created_at.date(),
455+
count=10,
456+
labels={
457+
"client_application_name": "test-app",
458+
"client_application_version": "1.0.0",
459+
},
460+
),
461+
]
462+
463+
287464
def test_get_usage_data_calls_get_usage_data_from_influxdb_if_postgres_not_configured(
288465
mocker: MockerFixture,
289466
settings: SettingsWrapper,
@@ -337,6 +514,33 @@ def test_get_usage_data_calls_get_usage_data_from_local_db_if_postgres_is_config
337514
)
338515

339516

517+
def test_get_usage_data__no_analytics_configured__no_calls_expected(
518+
settings: SettingsWrapper,
519+
mocker: MockerFixture,
520+
organisation: Organisation,
521+
) -> None:
522+
# Given
523+
settings.USE_POSTGRES_FOR_ANALYTICS = False
524+
settings.INFLUXDB_TOKEN = None
525+
526+
mocked_get_usage_data_from_influxdb = mocker.patch(
527+
"app_analytics.analytics_db_service.get_usage_data_from_influxdb",
528+
autospec=True,
529+
)
530+
mocked_get_usage_data_from_local_db = mocker.patch(
531+
"app_analytics.analytics_db_service.get_usage_data_from_local_db",
532+
autospec=True,
533+
)
534+
535+
# When
536+
result = get_usage_data(organisation)
537+
538+
# Then
539+
assert result == []
540+
mocked_get_usage_data_from_influxdb.assert_not_called()
541+
mocked_get_usage_data_from_local_db.assert_not_called()
542+
543+
340544
def test_get_total_events_count_calls_influx_method_if_postgres_not_configured( # type: ignore[no-untyped-def]
341545
mocker, settings, organisation
342546
):

api/tests/unit/app_analytics/test_migrate_to_pg.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
import pytest
2-
from django.conf import settings
32
from django.utils import timezone
43
from pytest_mock import MockerFixture
54

65
from app_analytics.migrate_to_pg import migrate_feature_evaluations
76
from app_analytics.models import FeatureEvaluationBucket
87

98

10-
@pytest.mark.skipif(
11-
"analytics" not in settings.DATABASES,
12-
reason="Skip test if analytics database is not configured",
13-
)
9+
@pytest.mark.skip_if_no_analytics_db
1410
@pytest.mark.django_db(databases=["analytics", "default"])
1511
def test_migrate_feature_evaluations(mocker: MockerFixture) -> None:
1612
# Given

0 commit comments

Comments
 (0)