Skip to content

Commit 3f5f936

Browse files
added tests for sample code in public doc
1 parent 3c41f5b commit 3f5f936

File tree

6 files changed

+368
-3
lines changed

6 files changed

+368
-3
lines changed

examples/sample_generate_identity_map.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from uid2_client import IdentityMapClient, IdentityMapInput
44

5-
5+
# !! Note: This is for an older version of identity map. For the latest version, see sample_generate_identity_map_v3.py
66
# this sample client takes email addresses as input and generates an IdentityMapResponse object which contains raw uid
77
# or the reason why it is unmapped
88

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import sys
2+
3+
from uid2_client import IdentityMapV3Client, IdentityMapV3Input
4+
5+
# !! Note: This is for the newest version of identity map. For the previous version, see sample_generate_identity_map.py
6+
# this sample client takes email addresses as input and generates an IdentityMapV3Response object which contains raw uid
7+
# or the reason why it is unmapped
8+
9+
def _usage():
10+
print('Usage: python3 sample_generate_identity_map_v3.py <base_url> <api_key> <client_secret> <email_1> <email_2> ... <email_n>'
11+
, file=sys.stderr)
12+
sys.exit(1)
13+
14+
15+
if len(sys.argv) <= 4:
16+
_usage()
17+
18+
base_url = sys.argv[1]
19+
api_key = sys.argv[2]
20+
client_secret = sys.argv[3]
21+
email_list = sys.argv[4:]
22+
first_email = sys.argv[4]
23+
24+
client = IdentityMapV3Client(base_url, api_key, client_secret)
25+
26+
identity_map_response = client.generate_identity_map(IdentityMapV3Input.from_emails(email_list))
27+
28+
mapped_identities = identity_map_response.mapped_identities
29+
unmapped_identities = identity_map_response.unmapped_identities
30+
31+
mapped_identity = mapped_identities.get(first_email)
32+
if mapped_identity is not None:
33+
current_uid = mapped_identity.current_raw_uid
34+
previous_uid = mapped_identity.previous_raw_uid
35+
refresh_from = mapped_identity.refresh_from
36+
print('current_uid =', current_uid)
37+
print('previous_uid =', previous_uid)
38+
print('refresh_from =', refresh_from)
39+
else:
40+
unmapped_identity = unmapped_identities.get(first_email)
41+
reason = unmapped_identity.reason
42+
print('reason =', reason)

tests/test_doc_sample_code.py

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
import unittest
2+
import os
3+
from datetime import datetime, timezone, timedelta
4+
from unittest.mock import Mock, patch
5+
6+
# Import all the classes we'll be testing from the documentation samples
7+
from uid2_client import (
8+
Uid2PublisherClient, IdentityMapV3Client, IdentityMapV3Input, IdentityMapV3Response,
9+
IdentityMapClient, IdentityMapInput, BidstreamClient, SharingClient,
10+
TokenGenerateInput, IdentityTokens, UnmappedIdentityReason, EncryptionStatus
11+
)
12+
13+
# !!!!! Do not refactor this code if you're not intending to change the SDK docs samples !!!!!
14+
15+
# Tests for sample code as used in https://unifiedid.com/docs/sdks/sdk-ref-python
16+
# The tests are designed to have sections of almost exactly copy/pasted code samples so there are
17+
# unused variables, unnecessary comments, redundant repetition... since those are used in docs for illustration.
18+
# If a test breaks in this file, likely the change breaks one of the samples on the docs site
19+
20+
21+
@unittest.skipIf(
22+
os.getenv("UID2_BASE_URL") is None or
23+
os.getenv("UID2_API_KEY") is None or
24+
os.getenv("UID2_SECRET_KEY") is None,
25+
"Environment variables UID2_BASE_URL, UID2_API_KEY, and UID2_SECRET_KEY must be set"
26+
)
27+
class TestDocSampleCode(unittest.TestCase):
28+
29+
# Test data constants
30+
UID2_BASE_URL = os.getenv("UID2_BASE_URL", "")
31+
UID2_API_KEY = os.getenv("UID2_API_KEY", "")
32+
UID2_SECRET_KEY = os.getenv("UID2_SECRET_KEY", "")
33+
34+
# Test email addresses - these should be configured in your test environment
35+
mapped_email = "[email protected]"
36+
mapped_email2 = "[email protected]"
37+
optout_email = "[email protected]"
38+
mapped_phone = "+12345678901"
39+
mapped_phone2 = "+12345678902"
40+
41+
def setUp(self):
42+
# Setup clients used across multiple tests
43+
self.identity_map_v3_client = IdentityMapV3Client(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
44+
self.publisher_client = Uid2PublisherClient(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
45+
self.identity_map_client = IdentityMapClient(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
46+
47+
def test_publisher_basic_usage_example(self):
48+
# Documentation sdk-ref-python.md Line 142: Create an instance of Uid2PublisherClient
49+
client = Uid2PublisherClient(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
50+
51+
# Documentation sdk-ref-python.md Line 147: Generate token from email
52+
token_generate_response = client.generate_token(TokenGenerateInput.from_email("[email protected]").do_not_generate_tokens_for_opted_out())
53+
54+
self.assertIsNotNone(token_generate_response)
55+
56+
def test_publisher_client_server_integration_example(self):
57+
"""Test Publisher client-server integration from documentation"""
58+
client = Uid2PublisherClient(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
59+
token_generate_response = client.generate_token(TokenGenerateInput.from_email("[email protected]").do_not_generate_tokens_for_opted_out())
60+
61+
# Documentation sdk-ref-python.md Line 165: Get identity JSON string
62+
identity_json_string = token_generate_response.get_identity_json_string()
63+
64+
self.assertIsNotNone(identity_json_string)
65+
66+
def test_publisher_server_side_integration_example(self):
67+
"""Test Publisher server-side integration from documentation"""
68+
client = Uid2PublisherClient(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
69+
token_generate_response = client.generate_token(TokenGenerateInput.from_email(self.mapped_email).do_not_generate_tokens_for_opted_out())
70+
71+
# Documentation sdk-ref-python.md Line 176: Store identity JSON string
72+
identity_json_string = token_generate_response.get_identity_json_string()
73+
74+
# Documentation sdk-ref-python.md Line 182: Get identity and advertising token
75+
identity = token_generate_response.get_identity()
76+
if identity:
77+
advertising_token = identity.get_advertising_token()
78+
self.assertIsNotNone(advertising_token)
79+
80+
# Documentation sdk-ref-python.md Line 193: Create IdentityTokens from JSON string
81+
identity = IdentityTokens.from_json_string(identity_json_string)
82+
83+
# Documentation sdk-ref-python.md Line 198: Check if identity can be refreshed
84+
if not identity or not identity.is_refreshable():
85+
pass
86+
87+
# Documentation sdk-ref-python.md Line 203: Check if refresh is needed
88+
if identity and identity.is_due_for_refresh():
89+
# Documentation sdk-ref-python.md Line 208: Refresh the token
90+
token_refresh_response = client.refresh_token(identity)
91+
92+
# Documentation sdk-ref-python.md Line 212: Store new identity JSON string
93+
new_identity_json_string = token_refresh_response.get_identity_json_string()
94+
if new_identity_json_string is None:
95+
# User has opted out - documentation sdk-ref-python.md Line 214
96+
is_optout = token_refresh_response.is_optout()
97+
self.assertTrue(is_optout)
98+
99+
def test_identity_map_v3_basic_usage_example(self):
100+
"""Test IdentityMapV3Client basic usage from documentation sdk-ref-python.md Map DII to Raw UID2s section"""
101+
# Documentation sdk-ref-python.md Line 226: Create IdentityMapV3Client
102+
identity_map_v3_client = IdentityMapV3Client(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
103+
104+
# Documentation sdk-ref-python.md Line 231: Create IdentityMapV3Input with emails
105+
input = IdentityMapV3Input.from_emails(["[email protected]", "[email protected]"])
106+
107+
# Documentation sdk-ref-python.md Line 245: Generate identity map
108+
identity_map_response = identity_map_v3_client.generate_identity_map(input)
109+
110+
# Documentation sdk-ref-python.md Line 249: Get mapped and unmapped results
111+
mapped_identities = identity_map_response.mapped_identities
112+
unmapped_identities = identity_map_response.unmapped_identities
113+
114+
# Verify basic structure
115+
self.assertIsNotNone(mapped_identities)
116+
self.assertIsNotNone(unmapped_identities)
117+
self.assertTrue(len(mapped_identities) + len(unmapped_identities) == 2)
118+
119+
def test_identity_map_v3_multi_identity_type_example(self):
120+
"""Test IdentityMapV3Client with multiple identity types from documentation"""
121+
# Documentation sdk-ref-python.md Line 235: Multi-identity type input
122+
input = IdentityMapV3Input() \
123+
.with_email("[email protected]") \
124+
.with_phone("+12345678901") \
125+
.with_hashed_email("pre_hashed_email") \
126+
.with_hashed_phone("pre_hashed_phone")
127+
128+
response = self.identity_map_v3_client.generate_identity_map(input)
129+
130+
# Verify multi-identity type response
131+
self.assertIsNotNone(response)
132+
self.assertIsNotNone(response.mapped_identities)
133+
self.assertIsNotNone(response.unmapped_identities)
134+
135+
def test_identity_map_v3_response_handling_example(self):
136+
"""Test IdentityMapV3Response handling from documentation"""
137+
input = IdentityMapV3Input.from_emails([self.mapped_email])
138+
response = self.identity_map_v3_client.generate_identity_map(input)
139+
140+
# Documentation sdk-ref-python.md Line 254: Process mapped identity results
141+
mapped_identity = response.mapped_identities.get("[email protected]")
142+
if mapped_identity is not None:
143+
current_uid = mapped_identity.current_raw_uid # Current raw UID2
144+
previous_uid = mapped_identity.previous_raw_uid # Previous raw UID2 (Optional, only available for 90 days after rotation)
145+
refresh_from = mapped_identity.refresh_from # When to refresh this identity
146+
147+
self.assertIsNotNone(current_uid)
148+
self.assertIsNotNone(refresh_from)
149+
else:
150+
unmapped_identity = response.unmapped_identities.get("[email protected]")
151+
if unmapped_identity:
152+
reason = unmapped_identity.reason # OPTOUT, INVALID_IDENTIFIER, or UNKNOWN
153+
self.assertIsNotNone(reason)
154+
155+
def test_identity_map_v3_complete_usage_example(self):
156+
"""Test complete usage example from documentation sdk-ref-python.md Usage Example section"""
157+
158+
# Documentation sdk-ref-python.md Line 272: Example 1: Single identity type
159+
email_input = IdentityMapV3Input.from_emails(["[email protected]", "[email protected]"])
160+
email_response = self.identity_map_v3_client.generate_identity_map(email_input)
161+
162+
# Documentation sdk-ref-python.md Line 276: Process email results
163+
for email, identity in email_response.mapped_identities.items():
164+
print("Email: " + email)
165+
print("Current UID: " + identity.current_raw_uid)
166+
print("Previous UID: " + str(identity.previous_raw_uid))
167+
print("Refresh from: " + str(identity.refresh_from))
168+
169+
for email, identity in email_response.unmapped_identities.items():
170+
unmapped_output = "Unmapped email: " + email + " - Reason: " + str(identity.reason)
171+
self.assertIsNotNone(unmapped_output)
172+
173+
# Documentation sdk-ref-python.md Line 285: Example 2: Mixed identity types
174+
mixed_input = IdentityMapV3Input() \
175+
.with_email("[email protected]") \
176+
.with_phone("+12345678901") \
177+
.with_hashed_email("pre_hashed_email_value") \
178+
.with_hashed_phone("pre_hashed_phone_value")
179+
180+
# Documentation sdk-ref-python.md Line 291: Generate identity map
181+
mixed_response = self.identity_map_v3_client.generate_identity_map(mixed_input)
182+
self.assertIsNotNone(mixed_response)
183+
184+
def test_migration_examples(self):
185+
"""Test migration examples from documentation sdk-ref-python.md Required Changes section"""
186+
187+
# Documentation sdk-ref-python.md Line 322: Change client class
188+
client = IdentityMapV3Client(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
189+
# Documentation sdk-ref-python.md Line 334: Update input construction
190+
input = IdentityMapV3Input.from_emails(["[email protected]"])
191+
192+
# Documentation sdk-ref-python.md Line 337: Mix identity types (new capability)
193+
input = IdentityMapV3Input() \
194+
.with_email("[email protected]") \
195+
.with_phone("+12345678901")
196+
197+
# Documentation sdk-ref-python.md Line 346: Update response handling
198+
response = client.generate_identity_map(input)
199+
mapped = response.mapped_identities.get("[email protected]")
200+
current_uid = mapped.current_raw_uid
201+
previous_uid = mapped.previous_raw_uid
202+
refresh_from = mapped.refresh_from
203+
204+
self.assertIsNotNone(current_uid)
205+
self.assertIsNotNone(refresh_from)
206+
207+
input = IdentityMapV3Input.from_emails([self.optout_email])
208+
response = self.identity_map_v3_client.generate_identity_map(input)
209+
210+
# Documentation sdk-ref-python.md Line 358: Update error handling
211+
unmapped = response.unmapped_identities.get("[email protected]")
212+
if unmapped:
213+
reason = unmapped.reason # Enum - OPTOUT, INVALID_IDENTIFIER, UNKNOWN
214+
raw_reason = unmapped.raw_reason # String version
215+
216+
self.assertIsNotNone(reason)
217+
self.assertIsNotNone(raw_reason)
218+
219+
def test_v2_legacy_identity_map_example(self):
220+
"""Test V2 Identity Map legacy usage from documentation sdk-ref-python.md Previous Version section"""
221+
# Documentation sdk-ref-python.md Line 379: Create V2 IdentityMapClient
222+
client = IdentityMapClient(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
223+
224+
# Documentation sdk-ref-python.md Line 383: Generate identity map with V2 client
225+
identity_map_response = client.generate_identity_map(IdentityMapInput.from_emails(["[email protected]", "[email protected]"]))
226+
227+
# Documentation sdk-ref-python.md Line 390: Get V2 mapped and unmapped results
228+
mapped_identities = identity_map_response.mapped_identities
229+
unmapped_identities = identity_map_response.unmapped_identities
230+
231+
# Documentation sdk-ref-python.md Line 396: V2 response processing
232+
mapped_identity = mapped_identities.get("[email protected]")
233+
if mapped_identity is not None:
234+
raw_uid = mapped_identity.get_raw_uid()
235+
self.assertIsNotNone(raw_uid)
236+
else:
237+
unmapped_identity = unmapped_identities.get("[email protected]")
238+
reason = unmapped_identity.get_reason()
239+
self.assertIsNotNone(reason)
240+
241+
def test_v2_salt_bucket_monitoring_example(self):
242+
"""Test V2 salt bucket monitoring from documentation sdk-ref-python.md Monitor Rotated Salt Buckets section"""
243+
# Documentation sdk-ref-python.md Line 410: Create or reuse IdentityMapClient
244+
client = IdentityMapClient(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
245+
246+
# Documentation sdk-ref-python.md Line 418: Get identity buckets
247+
since_timestamp = '2024-08-18T14:30:15+00:00'
248+
identity_buckets_response = client.get_identity_buckets(datetime.fromisoformat(since_timestamp))
249+
250+
# Documentation sdk-ref-python.md Line 424: Process bucket results
251+
if identity_buckets_response.buckets:
252+
for bucket in identity_buckets_response.buckets:
253+
bucket_id = bucket.get_bucket_id() # example "bucket_id": "a30od4mNRd"
254+
last_updated = bucket.get_last_updated() # example "last_updated" "2024-08-19T22:52:03.109"
255+
self.assertIsNotNone(bucket_id)
256+
self.assertIsNotNone(last_updated)
257+
else:
258+
print("No bucket was returned")
259+
260+
def test_dsp_usage_example(self):
261+
"""Test DSP client usage from documentation sdk-ref-python.md Usage for DSPs section"""
262+
# Documentation sdk-ref-python.md Line 451: Create BidstreamClient
263+
client = BidstreamClient(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
264+
265+
# Documentation sdk-ref-python.md Line 455: Refresh client
266+
client.refresh()
267+
268+
uid_token = "mock_token"
269+
domainOrAppName = "example.com"
270+
271+
# Documentation sdk-ref-python.md Line 464: Decrypt token
272+
decrypted = client.decrypt_token_into_raw_uid(uid_token, domainOrAppName)
273+
# If decryption succeeded, use the raw UID2.
274+
if decrypted.success:
275+
# Use decrypted.uid
276+
used_uid = decrypted.uid
277+
self.assertIsNotNone(used_uid)
278+
else:
279+
# Check decrypted.status for the failure reason.
280+
self.assertIsNotNone(decrypted.status)
281+
282+
def test_sharing_client_usage_example(self):
283+
"""Test Sharing client usage from documentation sdk-ref-python.md Usage for UID2 Sharers section"""
284+
# Documentation sdk-ref-python.md Line 491: Create SharingClient
285+
client = SharingClient(self.UID2_BASE_URL, self.UID2_API_KEY, self.UID2_SECRET_KEY)
286+
287+
# Documentation sdk-ref-python.md Line 495: Refresh client
288+
client.refresh()
289+
290+
raw_uid = "mock_raw_uid"
291+
292+
# Documentation sdk-ref-python.md Line 499: Encrypt raw UID (sender)
293+
encrypted = client.encrypt_raw_uid_into_token(raw_uid)
294+
# If encryption succeeded, send the UID2 token to the receiver.
295+
if encrypted.success:
296+
# Send encrypted.encrypted_data to receiver
297+
sent_data = encrypted.encrypted_data
298+
self.assertIsNotNone(sent_data)
299+
else:
300+
# Check encrypted.status for the failure reason.
301+
self.assertIsNotNone(encrypted.status)
302+
303+
uid_token = "mock_token" # Mock token for testing
304+
305+
# Documentation sdk-ref-python.md Line 508: Decrypt token (receiver)
306+
decrypted = client.decrypt_token_into_raw_uid(uid_token)
307+
# If decryption succeeded, use the raw UID2.
308+
if decrypted.success:
309+
# Use decrypted.uid
310+
used_uid = decrypted.uid
311+
self.assertIsNotNone(used_uid)
312+
else:
313+
# Check decrypted.status for the failure reason.
314+
self.assertIsNotNone(decrypted.status)
315+
316+
317+
if __name__ == '__main__':
318+
unittest.main()

uid2_client/encryption.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def encrypt(uid2, identity_scope, keys, keyset_id=None, **kwargs):
270270
Keyword Args:
271271
now (Datetime): the datettime to use for now. Defaults to utc now
272272
273-
Returns (str): Sharing Token
273+
Returns (EncryptionDataResponse): Sharing Token
274274
275275
"""
276276
now = kwargs.get("now")

uid2_client/encryption_data_response.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ def make_success(encrypted_data):
1515
def make_error(encryption_status):
1616
return EncryptionDataResponse(encryption_status, None)
1717

18+
@property
19+
def success(self):
20+
return self.status == EncryptionStatus.SUCCESS
21+
1822
@property
1923
def encrypted_data(self):
2024
return self._encrypted_data

0 commit comments

Comments
 (0)