@admoai/sdk-scenarios 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +26 -2
- package/package.json +14 -4
- package/scenarios.json +946 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Versioning: a scenario added or an `expect` tightened is a **minor** release; a scenario
|
|
4
|
+
removed, or a `request`/`expect` key renamed (every SDK's interpreter must change), is a
|
|
5
|
+
**major** one. Each SDK vendors one version and its own CI checks the vendored bytes against it.
|
|
6
|
+
|
|
7
|
+
## 1.0.0 — 2026-10-20
|
|
8
|
+
|
|
9
|
+
The 39 scenarios the three SDKs already carried, byte for byte (sha256 `a55f68eb…`):
|
|
10
|
+
L1 L5 L6 L7 L8 · M1 M2 M3 M5 M6 M7 M8 · P1 P2 P3 P6 P8 · V1 V3 V4 V5 V6 V9 · Z1 Z2 · SP1–SP14.
|
|
11
|
+
First release from adhub, the source of truth since admoai/adhub#4158; before it the file was
|
|
12
|
+
mirrored into each SDK repo and held equal by a hash in each and a parity check in adhub.
|
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @admoai/sdk-scenarios
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The scenarios every AdMoai mobile SDK (iOS, Android, Flutter) must pass against a real
|
|
4
|
+
decision-engine: given a request, the outcome a publisher's app observes. One definition,
|
|
5
|
+
executed by a thin interpreter in each SDK, so the three cannot make different claims.
|
|
6
|
+
|
|
7
|
+
This package is **data only**: `scenarios.json` (the manifest, `schemaVersion` inside). It
|
|
8
|
+
carries no URLs, hosts, credentials or tenant identifiers — `check.sh` refuses a release that
|
|
9
|
+
does.
|
|
10
|
+
|
|
11
|
+
## For an SDK
|
|
12
|
+
|
|
13
|
+
Vendor one version and pin it; your CI verifies the vendored bytes against the published
|
|
14
|
+
tarball, and your tests read the vendored file (they never touch the network):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
curl -sSfL "https://registry.npmjs.org/@admoai/sdk-scenarios/-/sdk-scenarios-$VERSION.tgz" \
|
|
18
|
+
| tar -xzO package/scenarios.json | cmp - path/to/vendored/scenarios.json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## For adhub (the source)
|
|
22
|
+
|
|
23
|
+
The source of truth is `sdk/scenarios/` in admoai/adhub. To change a scenario: edit
|
|
24
|
+
`scenarios.json`, bump `version` in `package.json` (minor: added or tightened; major: removed
|
|
25
|
+
or a key renamed), add a `CHANGELOG.md` entry, and run `sdk/scenarios/check.sh`. Merging to
|
|
26
|
+
`main` publishes the new version (`.github/workflows/sdk-scenarios-release.yml`); each SDK then
|
|
27
|
+
moves to it in its own PR. See `docs/guides/sdk-ci.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@admoai/sdk-scenarios",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The cross-SDK decision & tracking scenarios every AdMoai SDK asserts against a real decision-engine. Data only.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"scenarios.json",
|
|
8
|
+
"CHANGELOG.md",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"admoai",
|
|
13
|
+
"sdk",
|
|
14
|
+
"contract-tests"
|
|
15
|
+
]
|
|
16
|
+
}
|
package/scenarios.json
ADDED
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"description": "Shared cross-SDK E2E scenario manifest. ONE definition, executed by a thin interpreter in each SDK, so the three suites cannot drift. Declarative only: given a request, expect an outcome. Sponsored Pin tracking scenarios add `pinTracking`: the point-tracking calls an app makes on the served pins, as data, and the beacons that must reach the engine. Procedural scenarios (log capture, concurrency, retry, TTL waits) stay hand-written per SDK. Mirrored byte-identically into all three repos; a checksum test fails the build if the copies diverge. Every expected value here was verified against a live local decision-engine before being written.",
|
|
4
|
+
"scenarios": [
|
|
5
|
+
{
|
|
6
|
+
"id": "L1",
|
|
7
|
+
"title": "a normal native ad serves with complete, well-formed metadata",
|
|
8
|
+
"request": {
|
|
9
|
+
"placements": [
|
|
10
|
+
{
|
|
11
|
+
"key": "home"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"expect": {
|
|
16
|
+
"outcome": "served",
|
|
17
|
+
"decisions": 1,
|
|
18
|
+
"creative": {
|
|
19
|
+
"requireMetadata": true,
|
|
20
|
+
"journey": false,
|
|
21
|
+
"formatEquals": "native",
|
|
22
|
+
"priorityIn": [
|
|
23
|
+
"standard",
|
|
24
|
+
"sponsorship",
|
|
25
|
+
"house"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "L5",
|
|
32
|
+
"title": "a placement with no eligible ad returns a clean no-ad, not an error",
|
|
33
|
+
"request": {
|
|
34
|
+
"placements": [
|
|
35
|
+
{
|
|
36
|
+
"key": "postroll"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"expect": {
|
|
41
|
+
"outcome": "noAd",
|
|
42
|
+
"decisions": 1
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "L6",
|
|
47
|
+
"title": "the advertiser block is complete (name, legalName, logoUrl)",
|
|
48
|
+
"request": {
|
|
49
|
+
"placements": [
|
|
50
|
+
{
|
|
51
|
+
"key": "home"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"expect": {
|
|
56
|
+
"outcome": "served",
|
|
57
|
+
"creative": {
|
|
58
|
+
"requireAdvertiser": true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "L7",
|
|
64
|
+
"title": "the template block carries a non-empty key",
|
|
65
|
+
"request": {
|
|
66
|
+
"placements": [
|
|
67
|
+
{
|
|
68
|
+
"key": "home"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"expect": {
|
|
73
|
+
"outcome": "served",
|
|
74
|
+
"creative": {
|
|
75
|
+
"requireTemplate": true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "L8",
|
|
81
|
+
"title": "a served native ad exposes an impression URL, a click URL and non-empty contents",
|
|
82
|
+
"request": {
|
|
83
|
+
"placements": [
|
|
84
|
+
{
|
|
85
|
+
"key": "home"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
"expect": {
|
|
90
|
+
"outcome": "served",
|
|
91
|
+
"creative": {
|
|
92
|
+
"impressions": "required",
|
|
93
|
+
"clicksAtLeast": 1,
|
|
94
|
+
"requireContents": true
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "M1",
|
|
100
|
+
"title": "count=2 returns at most two creatives, and they are distinct",
|
|
101
|
+
"request": {
|
|
102
|
+
"placements": [
|
|
103
|
+
{
|
|
104
|
+
"key": "promotions",
|
|
105
|
+
"count": 2
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"expect": {
|
|
110
|
+
"outcome": "served",
|
|
111
|
+
"decisions": 1,
|
|
112
|
+
"creativesAtMost": 2,
|
|
113
|
+
"creativesDistinct": true
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "M2",
|
|
118
|
+
"title": "three placements in one request each get their own correctly-keyed decision",
|
|
119
|
+
"request": {
|
|
120
|
+
"placements": [
|
|
121
|
+
{
|
|
122
|
+
"key": "home"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"key": "promotions"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"key": "search"
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"expect": {
|
|
133
|
+
"outcome": "served",
|
|
134
|
+
"decisions": 3,
|
|
135
|
+
"decisionsMatchRequest": true
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"id": "M3",
|
|
140
|
+
"title": "an advertiserId filter still serves that advertiser's ad",
|
|
141
|
+
"request": {
|
|
142
|
+
"placements": [
|
|
143
|
+
{
|
|
144
|
+
"key": "promotions",
|
|
145
|
+
"advertiserId": "advertiser_01JEPGMJXARVYSJA0CAM9FQ79Y"
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
"expect": {
|
|
150
|
+
"outcome": "served",
|
|
151
|
+
"creative": {
|
|
152
|
+
"requireAdvertiser": true
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"id": "M5",
|
|
158
|
+
"title": "format=native on a mixed placement returns a native ad",
|
|
159
|
+
"request": {
|
|
160
|
+
"placements": [
|
|
161
|
+
{
|
|
162
|
+
"key": "promotions",
|
|
163
|
+
"format": "native"
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
"expect": {
|
|
168
|
+
"outcome": "served",
|
|
169
|
+
"creative": {
|
|
170
|
+
"formatEquals": "native"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"id": "M6",
|
|
176
|
+
"title": "format=video on the same mixed placement returns a video ad instead",
|
|
177
|
+
"request": {
|
|
178
|
+
"placements": [
|
|
179
|
+
{
|
|
180
|
+
"key": "promotions",
|
|
181
|
+
"format": "video"
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
"expect": {
|
|
186
|
+
"outcome": "served",
|
|
187
|
+
"creative": {
|
|
188
|
+
"formatEquals": "video"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"id": "M7",
|
|
194
|
+
"title": "omitting format still serves \u2014 the engine chooses",
|
|
195
|
+
"request": {
|
|
196
|
+
"placements": [
|
|
197
|
+
{
|
|
198
|
+
"key": "promotions"
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
},
|
|
202
|
+
"expect": {
|
|
203
|
+
"outcome": "served"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"id": "M8",
|
|
208
|
+
"title": "a journey-owned placement and a normal placement resolve independently in ONE request",
|
|
209
|
+
"request": {
|
|
210
|
+
"placements": [
|
|
211
|
+
{
|
|
212
|
+
"key": "promotions"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"key": "home"
|
|
216
|
+
}
|
|
217
|
+
],
|
|
218
|
+
"session": "auto",
|
|
219
|
+
"journeyOpt": "in"
|
|
220
|
+
},
|
|
221
|
+
"expect": {
|
|
222
|
+
"outcome": "served",
|
|
223
|
+
"decisions": 2
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"id": "P1",
|
|
228
|
+
"title": "a non-skippable JSON video exposes json delivery and five video events (no skip)",
|
|
229
|
+
"request": {
|
|
230
|
+
"placements": [
|
|
231
|
+
{
|
|
232
|
+
"key": "json_none"
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
},
|
|
236
|
+
"expect": {
|
|
237
|
+
"outcome": "served",
|
|
238
|
+
"creative": {
|
|
239
|
+
"deliveryEquals": "json",
|
|
240
|
+
"formatEquals": "video",
|
|
241
|
+
"videoEventCount": 5,
|
|
242
|
+
"videoEventKeys": [
|
|
243
|
+
"start",
|
|
244
|
+
"first_quartile",
|
|
245
|
+
"midpoint",
|
|
246
|
+
"third_quartile",
|
|
247
|
+
"complete"
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"id": "P2",
|
|
254
|
+
"title": "a vast_tag ad exposes its tag URL and surfaces NO engine-side beacons (no double count)",
|
|
255
|
+
"request": {
|
|
256
|
+
"placements": [
|
|
257
|
+
{
|
|
258
|
+
"key": "vasttag_none"
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
"expect": {
|
|
263
|
+
"outcome": "served",
|
|
264
|
+
"creative": {
|
|
265
|
+
"deliveryEquals": "vast_tag",
|
|
266
|
+
"requireVastTagUrl": true,
|
|
267
|
+
"videoEventCount": 0,
|
|
268
|
+
"impressions": "forbidden"
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"id": "P3",
|
|
274
|
+
"title": "a vast_xml ad exposes base64 that decodes to a VAST document, and no beacons",
|
|
275
|
+
"request": {
|
|
276
|
+
"placements": [
|
|
277
|
+
{
|
|
278
|
+
"key": "vastxml_none"
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
},
|
|
282
|
+
"expect": {
|
|
283
|
+
"outcome": "served",
|
|
284
|
+
"creative": {
|
|
285
|
+
"deliveryEquals": "vast_xml",
|
|
286
|
+
"requireVastXmlDecodes": true,
|
|
287
|
+
"videoEventCount": 0,
|
|
288
|
+
"impressions": "forbidden"
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"id": "P6",
|
|
294
|
+
"title": "a skippable video resolves skippability from ENGINE METADATA, plus a sixth skip event",
|
|
295
|
+
"request": {
|
|
296
|
+
"placements": [
|
|
297
|
+
{
|
|
298
|
+
"key": "json_none_skippable"
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
},
|
|
302
|
+
"expect": {
|
|
303
|
+
"outcome": "served",
|
|
304
|
+
"creative": {
|
|
305
|
+
"metadataIsSkippable": true,
|
|
306
|
+
"skipOffsetSecondsEquals": 5,
|
|
307
|
+
"helperIsSkippable": true,
|
|
308
|
+
"helperSkipOffsetEquals": "5",
|
|
309
|
+
"videoEventCount": 6
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"id": "P8",
|
|
315
|
+
"title": "an end-card video reports its configured endCardMode",
|
|
316
|
+
"request": {
|
|
317
|
+
"placements": [
|
|
318
|
+
{
|
|
319
|
+
"key": "json_native_endcard"
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
},
|
|
323
|
+
"expect": {
|
|
324
|
+
"outcome": "served",
|
|
325
|
+
"creative": {
|
|
326
|
+
"endCardModeEquals": "native_endcard"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"id": "V1",
|
|
332
|
+
"title": "an unknown placement key raises a typed validation error, not a silent no-fill",
|
|
333
|
+
"request": {
|
|
334
|
+
"placements": [
|
|
335
|
+
{
|
|
336
|
+
"key": "definitely_not_a_real_placement"
|
|
337
|
+
}
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
"expect": {
|
|
341
|
+
"outcome": "error",
|
|
342
|
+
"error": {
|
|
343
|
+
"kind": "validation",
|
|
344
|
+
"status": 422,
|
|
345
|
+
"code": 10017
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"id": "V3",
|
|
351
|
+
"title": "an unknown custom-targeting key is rejected by the engine",
|
|
352
|
+
"request": {
|
|
353
|
+
"placements": [
|
|
354
|
+
{
|
|
355
|
+
"key": "home"
|
|
356
|
+
}
|
|
357
|
+
],
|
|
358
|
+
"custom": [
|
|
359
|
+
{
|
|
360
|
+
"key": "not_a_configured_preset",
|
|
361
|
+
"value": "x"
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
},
|
|
365
|
+
"expect": {
|
|
366
|
+
"outcome": "error",
|
|
367
|
+
"error": {
|
|
368
|
+
"kind": "validation",
|
|
369
|
+
"status": 422,
|
|
370
|
+
"code": 10028
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"id": "V4",
|
|
376
|
+
"title": "the SDK deduplicates a repeated custom key, so the engine never sees the duplicate that would fail the whole request",
|
|
377
|
+
"comment": "Sent raw over HTTP this same payload returns 422 code 10031, and the engine aborts validation for the ENTIRE request \u2014 every placement, not just the offending target. All three SDKs dedupe by key (last write wins), so a publisher cannot reach that state through the public API. This scenario is the live proof of that guard: it must SERVE, and both placements must come back.",
|
|
378
|
+
"request": {
|
|
379
|
+
"placements": [
|
|
380
|
+
{
|
|
381
|
+
"key": "home"
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"key": "promotions"
|
|
385
|
+
}
|
|
386
|
+
],
|
|
387
|
+
"custom": [
|
|
388
|
+
{
|
|
389
|
+
"key": "category",
|
|
390
|
+
"value": "sports"
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"key": "category",
|
|
394
|
+
"value": "news"
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
},
|
|
398
|
+
"expect": {
|
|
399
|
+
"outcome": "served",
|
|
400
|
+
"decisions": 2,
|
|
401
|
+
"decisionsMatchRequest": true
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
"id": "V5",
|
|
406
|
+
"title": "a single valid custom target does not break serving",
|
|
407
|
+
"request": {
|
|
408
|
+
"placements": [
|
|
409
|
+
{
|
|
410
|
+
"key": "home"
|
|
411
|
+
}
|
|
412
|
+
],
|
|
413
|
+
"custom": [
|
|
414
|
+
{
|
|
415
|
+
"key": "category",
|
|
416
|
+
"value": "sports"
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
},
|
|
420
|
+
"expect": {
|
|
421
|
+
"outcome": "served"
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
"id": "V6",
|
|
426
|
+
"title": "a count above the documented maximum is rejected",
|
|
427
|
+
"request": {
|
|
428
|
+
"placements": [
|
|
429
|
+
{
|
|
430
|
+
"key": "home",
|
|
431
|
+
"count": 99
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
},
|
|
435
|
+
"expect": {
|
|
436
|
+
"outcome": "error",
|
|
437
|
+
"error": {
|
|
438
|
+
"kind": "validation",
|
|
439
|
+
"status": 422,
|
|
440
|
+
"code": 10016
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"id": "V9",
|
|
446
|
+
"title": "a request with no placements fails locally, before any network call",
|
|
447
|
+
"request": {
|
|
448
|
+
"placements": []
|
|
449
|
+
},
|
|
450
|
+
"expect": {
|
|
451
|
+
"outcome": "error",
|
|
452
|
+
"error": {
|
|
453
|
+
"kind": "local"
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"id": "Z1",
|
|
459
|
+
"title": "API version 2025-01-01 still serves a native ad and never a journey block",
|
|
460
|
+
"request": {
|
|
461
|
+
"placements": [
|
|
462
|
+
{
|
|
463
|
+
"key": "home"
|
|
464
|
+
}
|
|
465
|
+
],
|
|
466
|
+
"apiVersion": "2025-01-01"
|
|
467
|
+
},
|
|
468
|
+
"expect": {
|
|
469
|
+
"outcome": "served",
|
|
470
|
+
"creative": {
|
|
471
|
+
"journey": false,
|
|
472
|
+
"requireMetadata": true
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
"id": "Z2",
|
|
478
|
+
"title": "omitting the version header still serves (engine falls back to the oldest active version)",
|
|
479
|
+
"request": {
|
|
480
|
+
"placements": [
|
|
481
|
+
{
|
|
482
|
+
"key": "home"
|
|
483
|
+
}
|
|
484
|
+
],
|
|
485
|
+
"apiVersion": "none"
|
|
486
|
+
},
|
|
487
|
+
"expect": {
|
|
488
|
+
"outcome": "served",
|
|
489
|
+
"creative": {
|
|
490
|
+
"journey": false
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"id": "SP1",
|
|
496
|
+
"title": "a radius search returns the campaign's own shops, nearest first",
|
|
497
|
+
"request": {
|
|
498
|
+
"apiVersion": "2025-11-01",
|
|
499
|
+
"placements": [
|
|
500
|
+
{
|
|
501
|
+
"key": "poi"
|
|
502
|
+
}
|
|
503
|
+
],
|
|
504
|
+
"distance": {
|
|
505
|
+
"latitude": -33.4175,
|
|
506
|
+
"longitude": -70.6065,
|
|
507
|
+
"radius": 20000
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
"expect": {
|
|
511
|
+
"outcome": "served",
|
|
512
|
+
"creative": {
|
|
513
|
+
"matchedPointsAtLeast": 2,
|
|
514
|
+
"matchedPointsNearestFirst": true,
|
|
515
|
+
"matchedPointsWellFormed": true
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
"id": "SP2",
|
|
521
|
+
"title": "a bounds search returns them too \u2014 the other shape a map can ask with",
|
|
522
|
+
"request": {
|
|
523
|
+
"apiVersion": "2025-11-01",
|
|
524
|
+
"placements": [
|
|
525
|
+
{
|
|
526
|
+
"key": "poi"
|
|
527
|
+
}
|
|
528
|
+
],
|
|
529
|
+
"distance": {
|
|
530
|
+
"latitude": -33.4175,
|
|
531
|
+
"longitude": -70.6065,
|
|
532
|
+
"bounds": {
|
|
533
|
+
"north": -33.3,
|
|
534
|
+
"south": -33.55,
|
|
535
|
+
"east": -70.45,
|
|
536
|
+
"west": -70.65
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
"expect": {
|
|
541
|
+
"outcome": "served",
|
|
542
|
+
"creative": {
|
|
543
|
+
"matchedPointsAtLeast": 2,
|
|
544
|
+
"matchedPointsWellFormed": true
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
"id": "SP3",
|
|
550
|
+
"title": "every returned shop carries its own view, tap and click beacons",
|
|
551
|
+
"request": {
|
|
552
|
+
"apiVersion": "2025-11-01",
|
|
553
|
+
"placements": [
|
|
554
|
+
{
|
|
555
|
+
"key": "poi"
|
|
556
|
+
}
|
|
557
|
+
],
|
|
558
|
+
"distance": {
|
|
559
|
+
"latitude": -33.4175,
|
|
560
|
+
"longitude": -70.6065,
|
|
561
|
+
"radius": 20000
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
"expect": {
|
|
565
|
+
"outcome": "served",
|
|
566
|
+
"creative": {
|
|
567
|
+
"matchedPointsHaveBeacons": true
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
"id": "SP4",
|
|
573
|
+
"title": "every returned shop carries a resolved destination, its own or the creative's",
|
|
574
|
+
"request": {
|
|
575
|
+
"apiVersion": "2025-11-01",
|
|
576
|
+
"placements": [
|
|
577
|
+
{
|
|
578
|
+
"key": "poi"
|
|
579
|
+
}
|
|
580
|
+
],
|
|
581
|
+
"distance": {
|
|
582
|
+
"latitude": -33.4175,
|
|
583
|
+
"longitude": -70.6065,
|
|
584
|
+
"radius": 20000
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
"expect": {
|
|
588
|
+
"outcome": "served",
|
|
589
|
+
"creative": {
|
|
590
|
+
"matchedPointsHaveClickUrl": true
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
"id": "SP5",
|
|
596
|
+
"title": "the request's limit narrows how many shops come back",
|
|
597
|
+
"request": {
|
|
598
|
+
"apiVersion": "2025-11-01",
|
|
599
|
+
"placements": [
|
|
600
|
+
{
|
|
601
|
+
"key": "poi"
|
|
602
|
+
}
|
|
603
|
+
],
|
|
604
|
+
"distance": {
|
|
605
|
+
"latitude": -33.4175,
|
|
606
|
+
"longitude": -70.6065,
|
|
607
|
+
"radius": 20000,
|
|
608
|
+
"limit": 2
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
"expect": {
|
|
612
|
+
"outcome": "served",
|
|
613
|
+
"creative": {
|
|
614
|
+
"matchedPointsEquals": 2
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
"id": "SP6",
|
|
620
|
+
"title": "a tight radius excludes the shops outside it",
|
|
621
|
+
"request": {
|
|
622
|
+
"apiVersion": "2025-11-01",
|
|
623
|
+
"placements": [
|
|
624
|
+
{
|
|
625
|
+
"key": "poi"
|
|
626
|
+
}
|
|
627
|
+
],
|
|
628
|
+
"distance": {
|
|
629
|
+
"latitude": -33.4175,
|
|
630
|
+
"longitude": -70.6065,
|
|
631
|
+
"radius": 1500
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
"expect": {
|
|
635
|
+
"outcome": "served",
|
|
636
|
+
"creative": {
|
|
637
|
+
"matchedPointsEquals": 1
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
"id": "SP7",
|
|
643
|
+
"title": "without a distance search a Sponsored Pin campaign does not serve at all",
|
|
644
|
+
"request": {
|
|
645
|
+
"apiVersion": "2025-11-01",
|
|
646
|
+
"placements": [
|
|
647
|
+
{
|
|
648
|
+
"key": "poi"
|
|
649
|
+
}
|
|
650
|
+
]
|
|
651
|
+
},
|
|
652
|
+
"expect": {
|
|
653
|
+
"outcome": "noAd"
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
"id": "SP8",
|
|
658
|
+
"title": "on the older API version the ad still serves, silently carrying no pins \u2014 the failure an integrator must recognise",
|
|
659
|
+
"request": {
|
|
660
|
+
"apiVersion": "none",
|
|
661
|
+
"placements": [
|
|
662
|
+
{
|
|
663
|
+
"key": "poi"
|
|
664
|
+
}
|
|
665
|
+
],
|
|
666
|
+
"distance": {
|
|
667
|
+
"latitude": -33.4175,
|
|
668
|
+
"longitude": -70.6065,
|
|
669
|
+
"radius": 20000
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
"expect": {
|
|
673
|
+
"outcome": "served",
|
|
674
|
+
"creative": {
|
|
675
|
+
"matchedPointsNone": true
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
"id": "SP9",
|
|
681
|
+
"title": "a map's first render sends every shop's view beacon exactly once",
|
|
682
|
+
"request": {
|
|
683
|
+
"apiVersion": "2025-11-01",
|
|
684
|
+
"placements": [
|
|
685
|
+
{
|
|
686
|
+
"key": "poi"
|
|
687
|
+
}
|
|
688
|
+
],
|
|
689
|
+
"distance": {
|
|
690
|
+
"latitude": -33.4175,
|
|
691
|
+
"longitude": -70.6065,
|
|
692
|
+
"radius": 20000
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
"expect": {
|
|
696
|
+
"outcome": "served",
|
|
697
|
+
"creative": {
|
|
698
|
+
"matchedPointsAtLeast": 1,
|
|
699
|
+
"matchedPointsHaveBeacons": true
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
"pinTracking": {
|
|
703
|
+
"steps": [
|
|
704
|
+
{
|
|
705
|
+
"views": "all"
|
|
706
|
+
}
|
|
707
|
+
],
|
|
708
|
+
"expect": {
|
|
709
|
+
"viewsOncePerResponse": true,
|
|
710
|
+
"engineAcceptsEveryBeacon": true
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
"id": "SP10",
|
|
716
|
+
"title": "re-rendering the same response sends no view beacon again",
|
|
717
|
+
"request": {
|
|
718
|
+
"apiVersion": "2025-11-01",
|
|
719
|
+
"placements": [
|
|
720
|
+
{
|
|
721
|
+
"key": "poi"
|
|
722
|
+
}
|
|
723
|
+
],
|
|
724
|
+
"distance": {
|
|
725
|
+
"latitude": -33.4175,
|
|
726
|
+
"longitude": -70.6065,
|
|
727
|
+
"radius": 20000
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
"expect": {
|
|
731
|
+
"outcome": "served",
|
|
732
|
+
"creative": {
|
|
733
|
+
"matchedPointsAtLeast": 1,
|
|
734
|
+
"matchedPointsHaveBeacons": true
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
"pinTracking": {
|
|
738
|
+
"steps": [
|
|
739
|
+
{
|
|
740
|
+
"views": "all"
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
"views": "all"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"views": "all"
|
|
747
|
+
}
|
|
748
|
+
],
|
|
749
|
+
"expect": {
|
|
750
|
+
"viewsOncePerResponse": true,
|
|
751
|
+
"engineAcceptsEveryBeacon": true
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
"id": "SP11",
|
|
757
|
+
"title": "a render that brings new shops into view sends views only for the new ones",
|
|
758
|
+
"request": {
|
|
759
|
+
"apiVersion": "2025-11-01",
|
|
760
|
+
"placements": [
|
|
761
|
+
{
|
|
762
|
+
"key": "poi"
|
|
763
|
+
}
|
|
764
|
+
],
|
|
765
|
+
"distance": {
|
|
766
|
+
"latitude": -33.4175,
|
|
767
|
+
"longitude": -70.6065,
|
|
768
|
+
"radius": 20000
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
"expect": {
|
|
772
|
+
"outcome": "served",
|
|
773
|
+
"creative": {
|
|
774
|
+
"matchedPointsAtLeast": 4,
|
|
775
|
+
"matchedPointsHaveBeacons": true
|
|
776
|
+
}
|
|
777
|
+
},
|
|
778
|
+
"pinTracking": {
|
|
779
|
+
"steps": [
|
|
780
|
+
{
|
|
781
|
+
"views": {
|
|
782
|
+
"from": 0,
|
|
783
|
+
"to": 2
|
|
784
|
+
}
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
"views": {
|
|
788
|
+
"from": 1,
|
|
789
|
+
"to": 4
|
|
790
|
+
}
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
"views": {
|
|
794
|
+
"from": 0,
|
|
795
|
+
"to": 4
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
],
|
|
799
|
+
"expect": {
|
|
800
|
+
"viewsOncePerResponse": true,
|
|
801
|
+
"engineAcceptsEveryBeacon": true
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
"id": "SP12",
|
|
807
|
+
"title": "a new decision response reports the same shops again, once",
|
|
808
|
+
"request": {
|
|
809
|
+
"apiVersion": "2025-11-01",
|
|
810
|
+
"placements": [
|
|
811
|
+
{
|
|
812
|
+
"key": "poi"
|
|
813
|
+
}
|
|
814
|
+
],
|
|
815
|
+
"distance": {
|
|
816
|
+
"latitude": -33.4175,
|
|
817
|
+
"longitude": -70.6065,
|
|
818
|
+
"radius": 20000
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
"expect": {
|
|
822
|
+
"outcome": "served",
|
|
823
|
+
"creative": {
|
|
824
|
+
"matchedPointsAtLeast": 1,
|
|
825
|
+
"matchedPointsHaveBeacons": true
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
"pinTracking": {
|
|
829
|
+
"steps": [
|
|
830
|
+
{
|
|
831
|
+
"views": "all"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
"newResponse": true
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
"views": "all"
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
"views": "all"
|
|
841
|
+
}
|
|
842
|
+
],
|
|
843
|
+
"expect": {
|
|
844
|
+
"viewsOncePerResponse": true,
|
|
845
|
+
"newResponseHasNewViewUrls": true,
|
|
846
|
+
"engineAcceptsEveryBeacon": true
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
"id": "SP13",
|
|
852
|
+
"title": "single-pin views follow the same once-per-response rule as the bulk call",
|
|
853
|
+
"request": {
|
|
854
|
+
"apiVersion": "2025-11-01",
|
|
855
|
+
"placements": [
|
|
856
|
+
{
|
|
857
|
+
"key": "poi"
|
|
858
|
+
}
|
|
859
|
+
],
|
|
860
|
+
"distance": {
|
|
861
|
+
"latitude": -33.4175,
|
|
862
|
+
"longitude": -70.6065,
|
|
863
|
+
"radius": 20000
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
"expect": {
|
|
867
|
+
"outcome": "served",
|
|
868
|
+
"creative": {
|
|
869
|
+
"matchedPointsAtLeast": 1,
|
|
870
|
+
"matchedPointsHaveBeacons": true
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
"pinTracking": {
|
|
874
|
+
"steps": [
|
|
875
|
+
{
|
|
876
|
+
"view": 0
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
"view": 0
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
"views": "all"
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
"view": 0
|
|
886
|
+
}
|
|
887
|
+
],
|
|
888
|
+
"expect": {
|
|
889
|
+
"viewsOncePerResponse": true,
|
|
890
|
+
"engineAcceptsEveryBeacon": true
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
"id": "SP14",
|
|
896
|
+
"title": "taps and clicks are sent on every call, never de-duplicated",
|
|
897
|
+
"request": {
|
|
898
|
+
"apiVersion": "2025-11-01",
|
|
899
|
+
"placements": [
|
|
900
|
+
{
|
|
901
|
+
"key": "poi"
|
|
902
|
+
}
|
|
903
|
+
],
|
|
904
|
+
"distance": {
|
|
905
|
+
"latitude": -33.4175,
|
|
906
|
+
"longitude": -70.6065,
|
|
907
|
+
"radius": 20000
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
"expect": {
|
|
911
|
+
"outcome": "served",
|
|
912
|
+
"creative": {
|
|
913
|
+
"matchedPointsAtLeast": 1,
|
|
914
|
+
"matchedPointsHaveBeacons": true
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
"pinTracking": {
|
|
918
|
+
"steps": [
|
|
919
|
+
{
|
|
920
|
+
"views": "all"
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
"tap": 0
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
"tap": 0
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
"click": 0
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
"click": 0
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
"views": "all"
|
|
936
|
+
}
|
|
937
|
+
],
|
|
938
|
+
"expect": {
|
|
939
|
+
"viewsOncePerResponse": true,
|
|
940
|
+
"interactionsEveryCall": true,
|
|
941
|
+
"engineAcceptsEveryBeacon": true
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
]
|
|
946
|
+
}
|