Skip to content

Commit 877c321

Browse files
authored
fix: Identity overrides empty in SDK document with v2 versioning (#6840)
1 parent 4e4d835 commit 877c321

4 files changed

Lines changed: 171 additions & 0 deletions

File tree

api/features/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,8 @@ def type(self) -> str:
618618
@property
619619
def is_live(self) -> bool:
620620
if self.environment.use_v2_feature_versioning: # type: ignore[union-attr]
621+
if self.identity_id is not None:
622+
return True
621623
return (
622624
self.environment_feature_version_id is not None
623625
and self.environment_feature_version.is_live # type: ignore[union-attr]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import json
2+
3+
from django.urls import reverse
4+
from rest_framework import status
5+
from rest_framework.test import APIClient
6+
7+
from features.versioning.tasks import enable_v2_versioning
8+
9+
10+
def test_get_environment_document__v2_versioning_with_identity_override__identity_features_populated(
11+
admin_client: APIClient,
12+
server_side_sdk_client: APIClient,
13+
feature: int,
14+
identity_featurestate: int,
15+
environment: int,
16+
environment_api_key: str,
17+
) -> None:
18+
"""Reproduce https://github.com/Flagsmith/flagsmith/issues/6839
19+
20+
When Feature Versioning v2 is enabled, identity overrides should still
21+
appear in the SDK document with their identity_features populated.
22+
"""
23+
24+
# Given - identity overrides are enabled in local eval
25+
url = reverse(
26+
"api-v1:environments:environment-detail",
27+
args=[environment_api_key],
28+
)
29+
response = admin_client.get(url)
30+
environment_data = response.json()
31+
environment_data["use_identity_overrides_in_local_eval"] = True
32+
admin_client.put(
33+
url,
34+
data=json.dumps(environment_data),
35+
content_type="application/json",
36+
)
37+
38+
# and v2 feature versioning is enabled after identity override exists
39+
enable_v2_versioning(environment)
40+
41+
# When
42+
response = server_side_sdk_client.get("/api/v1/environment-document/")
43+
44+
# Then
45+
assert response.status_code == status.HTTP_200_OK
46+
47+
identity_overrides = response.json()["identity_overrides"]
48+
assert len(identity_overrides) == 1
49+
50+
identity_override = identity_overrides[0]
51+
assert len(identity_override["identity_features"]) > 0
52+
assert identity_override["identity_features"][0]["feature"]["id"] == feature

api/tests/unit/environments/test_unit_environments_views_sdk_environment.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import time
22
from typing import TYPE_CHECKING
3+
from unittest.mock import ANY
34

45
import pytest
56
from django.urls import reverse
67
from django.utils import timezone
78
from django.utils.http import http_date
9+
from pytest_lazyfixture import lazy_fixture # type: ignore[import-untyped]
810
from rest_framework import status
911
from rest_framework.test import APIClient
1012

@@ -121,6 +123,105 @@ def test_get_environment_document(
121123
)
122124

123125

126+
@pytest.mark.parametrize(
127+
"environment_",
128+
(
129+
lazy_fixture("environment"),
130+
lazy_fixture("environment_v2_versioning"),
131+
),
132+
)
133+
def test_get_environment_document__identity_overrides(
134+
project: Project,
135+
environment_: Environment,
136+
) -> None:
137+
# Given
138+
api_key = EnvironmentAPIKey.objects.create(environment=environment_)
139+
client = APIClient()
140+
client.credentials(HTTP_X_ENVIRONMENT_KEY=api_key.key)
141+
142+
feature = Feature.objects.create(name="test_feature", project=project)
143+
for i in range(2):
144+
identity = Identity.objects.create(
145+
environment=environment_,
146+
identifier=f"identity_{i}",
147+
)
148+
identity_feature_state = FeatureState.objects.create(
149+
identity=identity,
150+
feature=feature,
151+
environment=environment_,
152+
)
153+
FeatureStateValue.objects.filter(feature_state=identity_feature_state).update(
154+
string_value="overridden",
155+
type=STRING,
156+
)
157+
158+
url = reverse("api-v1:environment-document")
159+
160+
# When
161+
response = client.get(url)
162+
163+
# Then
164+
assert response.status_code == status.HTTP_200_OK
165+
assert sorted(
166+
response.data["identity_overrides"],
167+
key=lambda x: x["identifier"],
168+
) == [
169+
{
170+
"composite_key": ANY,
171+
"created_date": ANY,
172+
"django_id": ANY,
173+
"environment_api_key": environment_.api_key,
174+
"identifier": "identity_0",
175+
"dashboard_alias": None,
176+
"identity_features": [
177+
{
178+
"django_id": ANY,
179+
"enabled": False,
180+
"feature": {
181+
"id": feature.id,
182+
"name": "test_feature",
183+
"type": "STANDARD",
184+
},
185+
"feature_segment": None,
186+
"feature_state_value": "overridden",
187+
"featurestate_uuid": ANY,
188+
"multivariate_feature_state_values": [],
189+
}
190+
],
191+
"identity_traits": [],
192+
"identity_uuid": ANY,
193+
},
194+
{
195+
"composite_key": ANY,
196+
"created_date": ANY,
197+
"django_id": ANY,
198+
"environment_api_key": environment_.api_key,
199+
"identifier": "identity_1",
200+
"dashboard_alias": None,
201+
"identity_features": [
202+
{
203+
"django_id": ANY,
204+
"enabled": False,
205+
"feature": {
206+
"id": feature.id,
207+
"name": "test_feature",
208+
"type": "STANDARD",
209+
},
210+
"feature_segment": None,
211+
"feature_state_value": "overridden",
212+
"featurestate_uuid": ANY,
213+
"multivariate_feature_state_values": [],
214+
}
215+
],
216+
"identity_traits": [],
217+
"identity_uuid": ANY,
218+
},
219+
]
220+
assert response.headers[FLAGSMITH_UPDATED_AT_HEADER] == str(
221+
environment_.updated_at.timestamp()
222+
)
223+
224+
124225
def test_get_environment_document_fails_with_invalid_key(
125226
organisation_one: "Organisation",
126227
organisation_one_project_one: "Project",

api/tests/unit/features/test_unit_features_models.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,22 @@ def test_feature_state_is_live(version, live_from, expected_is_live, environment
720720
)
721721

722722

723+
def test_feature_state_is_live__identity_override_v2_versioning__returns_true(
724+
environment_v2_versioning: Environment,
725+
identity: Identity,
726+
feature: Feature,
727+
) -> None:
728+
# Given
729+
feature_state = FeatureState.objects.create(
730+
identity=identity,
731+
feature=feature,
732+
environment=environment_v2_versioning,
733+
)
734+
735+
# When / Then
736+
assert feature_state.is_live is True
737+
738+
723739
def test_creating_a_feature_with_defaults_does_not_set_defaults_if_disabled( # type: ignore[no-untyped-def]
724740
project, environment
725741
):

0 commit comments

Comments
 (0)