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 ()
0 commit comments