Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.

Commit 4f47754

Browse files
thinkallaboutsemyers
authored andcommitted
No public description
PiperOrigin-RevId: 735751140 Change-Id: Ifa795164478679905c7f71b1c34a4dcb93fde9f7
1 parent 7b5ba2f commit 4f47754

2 files changed

Lines changed: 175 additions & 9 deletions

File tree

adservices_cli/ad_selection.py

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
"""Command for interacting with Protected Audience Ad Selection CLI Commands."""
1616

1717
import base64
18+
import gzip
1819
import json
1920

2021
from google.protobuf.json_format import MessageToJson
22+
from google.protobuf.message import DecodeError
2123

2224
import adb
2325
import bidding_auction_servers_pb2
@@ -30,6 +32,7 @@
3032
_CONSENTED_DEBUG_COMMAND_ENABLE_EXPIRY_IN_HOURS = "--expires-in-hours"
3133
_CONSENTED_DEBUG_COMMAND_DISABLE = "disable"
3234
_CONSENTED_DEBUG_COMMAND_VIEW = "view"
35+
_FAILURE_TEMPLATE = "Failed to parse output: %s"
3336
_GET_AD_SELECTION_DATA_COMMAND = "get-ad-selection-data"
3437
_VIEW_AUCTION_RESULT_COMMAND = "view-auction-result"
3538
_ARG_BUYER = "--buyer"
@@ -118,11 +121,14 @@ def disable_consented_debug(self) -> str:
118121
)
119122
)
120123

121-
def get_ad_selection_data(self, buyer: str) -> str:
122-
"""Prints the JSON formatted input for GetBids request to BuyerFrontEnd service.
124+
def get_ad_selection_data(self, buyer: str = "") -> str:
125+
"""Prints the JSON formatted input for usage with secure_invoke.
123126
124127
Args:
125-
buyer: AdTech buyer.
128+
buyer: AdTech buyer used to generate the payload to a BuyerFrontend
129+
GetBids API. If omitted, then an "raw" SelectAdRequest will be returned
130+
for usage with an SellerFrontend SelectAds API. Note that this only
131+
works with secure_invoke.
126132
127133
Returns:
128134
Textual output of get_ad_selection__data command.
@@ -132,18 +138,97 @@ def get_ad_selection_data(self, buyer: str) -> str:
132138
_COMMAND_PREFIX,
133139
_GET_AD_SELECTION_DATA_COMMAND,
134140
"",
135-
{_ARG_BUYER: buyer},
141+
{_ARG_BUYER: buyer} if buyer else {},
136142
)
137143
)
138144
try:
139-
proto_json = json.loads(command_output)
145+
proto_json = json.loads(command_output.replace("\n", ""))
140146
base64_decoded_str = base64.b64decode(proto_json.get("output_proto"))
141-
return MessageToJson(
142-
bidding_auction_servers_pb2.GetBidsRequest.GetBidsRawRequest.FromString(
147+
if buyer:
148+
print("Querying for buyer: " + buyer)
149+
try:
150+
message = bidding_auction_servers_pb2.GetBidsRequest.GetBidsRawRequest.FromString(
143151
base64_decoded_str
144152
)
145-
)
146-
except ValueError:
153+
return MessageToJson(message)
154+
except DecodeError as e:
155+
return _FAILURE_TEMPLATE % e
156+
else:
157+
print("Querying for seller")
158+
try:
159+
auction_input = (
160+
bidding_auction_servers_pb2.ProtectedAuctionInput.FromString(
161+
base64_decoded_str
162+
)
163+
)
164+
except DecodeError as e:
165+
return _FAILURE_TEMPLATE % e
166+
decompressed_buyer_inputs = {}
167+
for buyer, compressed_buyer_input in auction_input.buyer_input.items():
168+
buyer_input_bytes = gzip.decompress(compressed_buyer_input)
169+
buyer_input = bidding_auction_servers_pb2.BuyerInput.FromString(
170+
buyer_input_bytes
171+
)
172+
interest_groups = []
173+
for interest_group in buyer_input.interest_groups:
174+
interest_groups.append({
175+
"name": interest_group.name,
176+
"origin": interest_group.origin,
177+
"bidding_signals_keys": list(
178+
interest_group.bidding_signals_keys
179+
),
180+
"ad_render_ids": list(interest_group.ad_render_ids),
181+
"component_ads": list(interest_group.component_ads),
182+
"user_bidding_signals": interest_group.user_bidding_signals,
183+
})
184+
raw_buyer_input = {
185+
"interest_groups": interest_groups,
186+
}
187+
if buyer_input.protected_app_signals.app_install_signals:
188+
raw_buyer_input["protected_app_signals"] = {
189+
"app_install_signals": base64.b64encode(
190+
buyer_input.protected_app_signals.app_install_signals
191+
or "{}"
192+
).decode("utf-8"),
193+
"encoding_version": (
194+
buyer_input.protected_app_signals.encoding_version
195+
),
196+
}
197+
decompressed_buyer_inputs[buyer] = raw_buyer_input
198+
empty_per_buyer_config = {}
199+
for buyer in decompressed_buyer_inputs:
200+
empty_per_buyer_config[buyer] = {
201+
"buyer_signals": "Replace-With-Buyer-Signals",
202+
"auction_signals": "Replace-With-Auction-Signals",
203+
}
204+
return json.dumps({
205+
"auction_config": {
206+
"seller_signals": "Replace-With-Seller-Signals",
207+
"auction_signals": "Replace-With-Auction-Signals",
208+
"buyer_list": list(decompressed_buyer_inputs.keys()),
209+
"seller": "Replace-With-Seller",
210+
"per_buyer_config": empty_per_buyer_config,
211+
},
212+
"client_type": "CLIENT_TYPE_ANDROID",
213+
"raw_protected_audience_input": {
214+
"raw_buyer_input": decompressed_buyer_inputs,
215+
"publisher_name": auction_input.publisher_name,
216+
"enable_debug_reporting": auction_input.enable_debug_reporting,
217+
"generation_id": auction_input.generation_id,
218+
"consented_debug_config": {
219+
"is_consented": (
220+
auction_input.consented_debug_config.is_consented
221+
),
222+
"token": auction_input.consented_debug_config.token,
223+
"is_debug_info_in_response": (
224+
auction_input.consented_debug_config.is_debug_info_in_response
225+
),
226+
},
227+
},
228+
})
229+
230+
except ValueError as e:
231+
print("Failed to parse output: %s" % e)
147232
return command_output
148233

149234
def view_auction_result(self, ad_selection_id: str) -> str:

adservices_cli/ad_selection_test.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,20 @@
3535
)
3636

3737
_GET_AD_SELECTION_DATA_SHELL_CMD_RESPONSE = '{"output_proto":"EvgBCngKCXdpbm5pbmdDQRIFc2hvZXMSIGJhLWJ1eWVyLTVqeXk1dWxhZ3EtdWMuYS5ydW4uYXBwEgRrZXkxEgRrZXkyGgExKgJ7fUIvY29tLmV4YW1wbGUuYWRzZXJ2aWNlcy5zYW1wbGVzLmZsZWRnZS5zYW1wbGVhcHAKfAoNc2hpcnRzX3NlcnZlchIFc2hvZXMSIGJhLWJ1eWVyLTVqeXk1dWxhZ3EtdWMuYS5ydW4uYXBwEgRrZXkxEgRrZXkyGgEyKgJ7fUIvY29tLmV4YW1wbGUuYWRzZXJ2aWNlcy5zYW1wbGVzLmZsZWRnZS5zYW1wbGVhcHAaAnt9IgJ7fSpAUGxhY2Vob2xkZXItU2hvdWxkLU1hdGNoLVdpdGgtU2VsbGVyLU9yaWdpbi1Eb21haW4tSW4tU0ZFLUNvbmZpZzIuUGxhY2Vob2xkZXItU2hvdWxkLU1hdGNoLVdpdGgtQXBwLVBhY2thZ2UtTmFtZTgBSg4IARIKMTIzNDU2Nzg5MFICCgBYAWJKUGxhY2Vob2xkZXItU2hvdWxkLU1hdGNoLVdpdGgtVG9wLUxldmVsLVNlbGxlci1PcmlnaW4tRG9tYWluLUluLVNGRS1Db25maWdoAHAB"}'
38+
_GET_AD_SELECTION_DATA_SHELL_CMD_RESPONSE_FOR_SELLER = '{"output_proto":"CooBCgtleGFtcGxlLmNvbRJ7H4sIAAAAAAAA\\/+NK5uIsz8zLy8xLd3YUYi3OyE8tFuJOrUjMLchJ\n1UvOzxViyU6tNASTRlKMhlpM1bVO+kBxPZiaxJTi1KKyzOTUYr1isEixXlpOakp6KpSbWFDAlc7F\nW5yRWVRSHA9Sm1pE2CIjsiwCAAj\\/PHbOAAAAEi5QbGFjZWhvbGRlci1TaG91bGQtTWF0Y2gtV2l0aC1BcHAtUGFja2FnZS1OYW1lGAEiFC01NjU2OTEzNzcwNjQ1MjI1NDIzKgAwAVAB"}'
39+
_GET_AD_SELECTION_DATA_SHELL_CMD_RESPONSE_FOR_SELLER_EMPTY = '{"output_proto":"Ei5QbGFjZWhvbGRlci1TaG91bGQtTWF0Y2gtV2l0aC1BcHAtUGFja2FnZS1OYW1lGAEiFC00NzU2\nMjI2NjQ2NTEwODU1MjUzKgAwAVAB\n"}'
40+
_GET_AD_SELECTION_DATA_SHELL_CMD_INVALID_PROTO_RESPONSE = '{"output_proto":"EvgBCngKCXdpbm5pbmdDQRIFc2hvZXMSIGJhLWJ1eWVyLTVqeXk1dWxhZ3EtdWMuYS5ydW4uYXBwEgRrZXkxEgRrZXkyGgExKgJ7fUIvY29tLmV4YW1wbGUuYWRzZXJ2aWNlcy5zYW1wbGVzLmZsZWRnZS5zYW1wbGVhcA=="}'
3841

3942

4043
_GET_AD_SELECTION_DATA_EXPECTED_RESPONSE = json.loads(
4144
'{"buyerInput":{"interestGroups":[{"name":"winningCA","biddingSignalsKeys":["shoes","ba-buyer-5jyy5ulagq-uc.a.run.app","key1","key2"],"adRenderIds":["1"],"userBiddingSignals":"{}","origin":"com.example.adservices.samples.fledge.sampleapp"},{"name":"shirts_server","biddingSignalsKeys":["shoes","ba-buyer-5jyy5ulagq-uc.a.run.app","key1","key2"],"adRenderIds":["2"],"userBiddingSignals":"{}","origin":"com.example.adservices.samples.fledge.sampleapp"}]},"auctionSignals":"{}","buyerSignals":"{}","seller":"Placeholder-Should-Match-With-Seller-Origin-Domain-In-SFE-Config","publisherName":"Placeholder-Should-Match-With-App-Package-Name","enableDebugReporting":true,"consentedDebugConfig":{"isConsented":true,"token":"1234567890"},"protectedAppSignalsBuyerInput":{"protectedAppSignals":{}},"clientType":"CLIENT_TYPE_ANDROID","topLevelSeller":"Placeholder-Should-Match-With-Top-Level-Seller-Origin-Domain-In-SFE-Config","buyerKvExperimentGroupId":0,"enableUnlimitedEgress":true}'
4245
)
46+
_GET_AD_SELECTION_DATA_EXPECTED_RESPONSE_FOR_SELLER = json.loads(
47+
'{"auction_config":{"seller_signals":"Replace-With-Seller-Signals","auction_signals":"Replace-With-Auction-Signals","buyer_list":["example.com"],"seller":"Replace-With-Seller","per_buyer_config":{"example.com":{"buyer_signals":"Replace-With-Buyer-Signals","auction_signals":"Replace-With-Auction-Signals"}}},"client_type":"CLIENT_TYPE_ANDROID","raw_protected_audience_input":{"raw_buyer_input":{"example.com":{"interest_groups":[{"name":"winningCA","origin":"com.example.adservices.samples.fledge.sampleapp","bidding_signals_keys":["shoes","example.com","key1","key2"],"ad_render_ids":["1"],"component_ads":[],"user_bidding_signals":"{}"},{"name":"shirts_server","origin":"com.example.adservices.samples.fledge.sampleapp","bidding_signals_keys":["shoes","example.com","key1","key2"],"ad_render_ids":["2"],"component_ads":[],"user_bidding_signals":"{}"}]}},"publisher_name":"Placeholder-Should-Match-With-App-Package-Name","enable_debug_reporting":true,"generation_id":"-5656913770645225423","consented_debug_config":{"is_consented":false,"token":"","is_debug_info_in_response":false}}}'
48+
)
49+
_GET_AD_SELECTION_DATA_EXPECTED_RESPONSE_FOR_SELLER_EMPTY = json.loads(
50+
'{"client_type":"CLIENT_TYPE_ANDROID","auction_config":{"seller_signals":"Replace-With-Seller-Signals","auction_signals":"Replace-With-Auction-Signals","buyer_list":[],"seller":"Replace-With-Seller","per_buyer_config":{}},"raw_protected_audience_input":{"raw_buyer_input":{},"publisher_name":"Placeholder-Should-Match-With-App-Package-Name","enable_debug_reporting":true,"generation_id":"-4756226646510855253","consented_debug_config":{"is_consented":false,"token":"","is_debug_info_in_response":false}}}'
51+
)
4352

4453
_GET_AD_SELECTION_DATA_SHELL_CMD_NO_DATA_RESPONSE = (
4554
"could not find data for buyer: test-buyer"
@@ -154,6 +163,78 @@ def test_get_ad_selection_data_happy_path(self):
154163
json_output = json.loads(output)
155164
self.assertEqual(json_output, _GET_AD_SELECTION_DATA_EXPECTED_RESPONSE)
156165

166+
def test_get_ad_selection_data_happy_path_for_seller(self):
167+
self.adb.set_shell_outputs(
168+
[_GET_AD_SELECTION_DATA_SHELL_CMD_RESPONSE_FOR_SELLER]
169+
)
170+
171+
output = self.ad_selection.get_ad_selection_data()
172+
173+
self.assertContainsSubset(
174+
utilities.split_adb_command(ad_selection._COMMAND_PREFIX)
175+
+ [
176+
ad_selection._GET_AD_SELECTION_DATA_COMMAND,
177+
],
178+
self.adb.shell_calls[0],
179+
)
180+
print("output: ")
181+
print(output)
182+
json_output = json.loads(output)
183+
self.assertEqual(
184+
json_output, _GET_AD_SELECTION_DATA_EXPECTED_RESPONSE_FOR_SELLER
185+
)
186+
187+
def test_get_ad_selection_data_happy_path_for_seller_empty(self):
188+
self.adb.set_shell_outputs(
189+
[_GET_AD_SELECTION_DATA_SHELL_CMD_RESPONSE_FOR_SELLER_EMPTY]
190+
)
191+
192+
output = self.ad_selection.get_ad_selection_data()
193+
194+
self.assertContainsSubset(
195+
utilities.split_adb_command(ad_selection._COMMAND_PREFIX)
196+
+ [
197+
ad_selection._GET_AD_SELECTION_DATA_COMMAND,
198+
],
199+
self.adb.shell_calls[0],
200+
)
201+
json_output = json.loads(output)
202+
self.assertEqual(
203+
json_output, _GET_AD_SELECTION_DATA_EXPECTED_RESPONSE_FOR_SELLER_EMPTY
204+
)
205+
206+
def test_get_ad_selection_data_invalid_proto(self):
207+
self.adb.set_shell_outputs(
208+
[_GET_AD_SELECTION_DATA_SHELL_CMD_INVALID_PROTO_RESPONSE]
209+
)
210+
211+
output = self.ad_selection.get_ad_selection_data(buyer="test-buyer")
212+
213+
self.assertStartsWith(output, ad_selection._FAILURE_TEMPLATE % "")
214+
self.assertContainsSubset(
215+
utilities.split_adb_command(ad_selection._COMMAND_PREFIX)
216+
+ [
217+
ad_selection._GET_AD_SELECTION_DATA_COMMAND,
218+
],
219+
self.adb.shell_calls[0],
220+
)
221+
222+
def test_get_ad_selection_data_invalid_proto_seller(self):
223+
self.adb.set_shell_outputs(
224+
[_GET_AD_SELECTION_DATA_SHELL_CMD_INVALID_PROTO_RESPONSE]
225+
)
226+
227+
output = self.ad_selection.get_ad_selection_data()
228+
229+
self.assertStartsWith(output, ad_selection._FAILURE_TEMPLATE % "")
230+
self.assertContainsSubset(
231+
utilities.split_adb_command(ad_selection._COMMAND_PREFIX)
232+
+ [
233+
ad_selection._GET_AD_SELECTION_DATA_COMMAND,
234+
],
235+
self.adb.shell_calls[0],
236+
)
237+
157238
def test_get_ad_selection_data_no_data_for_buyer(self):
158239
self.adb.set_shell_outputs(
159240
[_GET_AD_SELECTION_DATA_SHELL_CMD_NO_DATA_RESPONSE]

0 commit comments

Comments
 (0)