@crvouga/mockingbird-service-google-ads 1.2.0 → 1.3.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 +7 -1
- package/README.md +91 -19
- package/dist/{chunk-KCDQPGFS.js → chunk-5T6QIJK4.js} +2 -2
- package/dist/{chunk-DDJPMCRI.js → chunk-NU66LXUZ.js} +116 -49
- package/dist/{chunk-DDJPMCRI.js.map → chunk-NU66LXUZ.js.map} +2 -2
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +15 -1
- package/dist/index.js +1 -1
- package/dist/server.d.ts +15 -1
- package/dist/server.js +2 -2
- package/openapi.yaml +28 -0
- package/package.json +1 -1
- /package/dist/{chunk-KCDQPGFS.js.map → chunk-5T6QIJK4.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-google-ads
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.3.0 (2026-10-11)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- resolve reported emulator coverage gaps ([6edc643](https://github.com/crvouga/mockingbird/commit/6edc6438444853a51aca27849c4c8c0449109ea0))
|
|
8
|
+
|
|
9
|
+
## 1.2.0 (2026-10-08)
|
|
4
10
|
|
|
5
11
|
### Features
|
|
6
12
|
|
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ faults, and resets are isolated by namespace.
|
|
|
51
51
|
|
|
52
52
|
| Operation | Route | Behavior |
|
|
53
53
|
| --- | --- | --- |
|
|
54
|
-
| SearchGoogleAds | `POST /v25/customers/{customerId}/googleAds:search` | GAQL selection, filtering, ordering, aggregation, summary rows,
|
|
54
|
+
| SearchGoogleAds | `POST /v25/customers/{customerId}/googleAds:search` | GAQL selection, filtering, ordering, aggregation, summary rows, fixed 10,000-row pages, two-hour page tokens bound to the query, and validate-only requests |
|
|
55
55
|
| SearchStreamGoogleAds | `POST /v25/customers/{customerId}/googleAds:searchStream` | Deterministic JSON batches and optional summary rows |
|
|
56
56
|
| MutateCampaignBudgets | `POST /v25/customers/{customerId}/campaignBudgets:mutate` | Create, update, remove, update masks, validate-only and atomic or partial failure |
|
|
57
57
|
| UploadClickConversions | `POST /v25/customers/{customerId}:uploadClickConversions` | Click identifiers, conversion actions, duplicate detection, job IDs and partial failure |
|
|
@@ -63,6 +63,50 @@ faults, and resets are isolated by namespace.
|
|
|
63
63
|
The exact request and response schemas are in [`openapi.yaml`](openapi.yaml), and the generated
|
|
64
64
|
support matrix is in [`SUPPORT.md`](SUPPORT.md).
|
|
65
65
|
|
|
66
|
+
Campaign budgets are read back with GAQL (`FROM campaign_budget`); uploaded conversions and
|
|
67
|
+
collected events are read through the inspection routes below, and events also through the
|
|
68
|
+
Analytics Data reports. REST rows use protobuf JSON names (`costMicros`, `conversionsValue`,
|
|
69
|
+
`conversionActionCategory`) and int64 values are strings; GAQL field names stay snake_case.
|
|
70
|
+
|
|
71
|
+
Google Ads failures are a `google.rpc.Status` whose `details` hold a `GoogleAdsFailure` with the
|
|
72
|
+
per-operation `errorCode`, `message`, `location` and a `requestId` equal to the `request-id`
|
|
73
|
+
response header. Unparsable JSON and every Analytics Data failure are a bare status with no Ads
|
|
74
|
+
detail and no `request-id` header.
|
|
75
|
+
|
|
76
|
+
## Where Google differs from a common assumption
|
|
77
|
+
|
|
78
|
+
These follow Google's documentation and the pinned `googleapis` v25 protos rather than what a
|
|
79
|
+
client written against an older contract may expect.
|
|
80
|
+
|
|
81
|
+
- **Search page size is fixed.** `Search` returns pages of up to 10,000 rows and a request that
|
|
82
|
+
sets `pageSize` is rejected with `requestError: PAGE_SIZE_NOT_SUPPORTED`. Continue by re-sending
|
|
83
|
+
the identical query with `pageToken`; a changed query is `INVALID_PAGE_TOKEN` and a token unused
|
|
84
|
+
for two hours is `EXPIRED_PAGE_TOKEN`
|
|
85
|
+
([paging guide](https://developers.google.com/google-ads/api/docs/reporting/paging),
|
|
86
|
+
[`SearchGoogleAdsRequest`](https://github.com/googleapis/googleapis/blob/03a91044136a014466d4293eb1fe91f2b02075d2/google/ads/googleads/v25/services/google_ads_service.proto)).
|
|
87
|
+
To page a small fixture, lower the emulator's page with the `searchPageSize` setting; the
|
|
88
|
+
request still cannot choose it.
|
|
89
|
+
- **`/mp/collect` never reports validation errors over HTTP.** It answers an empty `204` for a
|
|
90
|
+
bad secret, an unparsable body or a rejected event alike. Inspect a rejection with
|
|
91
|
+
`POST /debug/mp/collect`, which returns `validationMessages` and stores nothing, or with
|
|
92
|
+
`GET /__admin/request-metadata`, which records `accepted`, `rejected`, `duplicate` and `reasons`
|
|
93
|
+
per request
|
|
94
|
+
([validation guide](https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events)).
|
|
95
|
+
- **Timestamps older than 72 hours are overridden, not rejected,** unless the request sets
|
|
96
|
+
`validation_behavior: "ENFORCE_RECOMMENDATIONS"`
|
|
97
|
+
([sending guide](https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag)).
|
|
98
|
+
Google documents no bound for future timestamps, so the one-minute `futureToleranceMs` default
|
|
99
|
+
is a test policy of this emulator, not a vendor rule; set it to `null` to disable it.
|
|
100
|
+
- **A validate-only budget mutation returns errors only.** A valid preflight answers `{}` with no
|
|
101
|
+
`results` and writes nothing
|
|
102
|
+
([`MutateCampaignBudgetsRequest`](https://github.com/googleapis/googleapis/blob/03a91044136a014466d4293eb1fe91f2b02075d2/google/ads/googleads/v25/services/campaign_budget_service.proto)).
|
|
103
|
+
- **Mutations take no idempotency key.** The duplicate guards are Google's own: a shared budget
|
|
104
|
+
name (`campaignBudgetError: DUPLICATE_NAME`), a conversion `orderId`
|
|
105
|
+
(`ORDER_ID_ALREADY_IN_USE`), a click and conversion time (`CLICK_CONVERSION_ALREADY_EXISTS`),
|
|
106
|
+
and a web `purchase` event's `transaction_id` per user.
|
|
107
|
+
- **`developer-token` is accepted and ignored.** The pinned protos mark the developer-token
|
|
108
|
+
errors as sunset; `login-customer-id` is still checked against the customer's manager.
|
|
109
|
+
|
|
66
110
|
## Controls and failures
|
|
67
111
|
|
|
68
112
|
Service-specific controls are protected by the shared `x-mockingbird-admin-key` guard and live
|
|
@@ -70,21 +114,41 @@ beside the shared health, state, clock, journal, fault, reset, and Timeline endp
|
|
|
70
114
|
|
|
71
115
|
| Control | Purpose |
|
|
72
116
|
| --- | --- |
|
|
73
|
-
| `GET /__admin/events` | Inspect stored Analytics events |
|
|
74
|
-
| `GET /__admin/conversions` | Inspect uploaded conversions |
|
|
117
|
+
| `GET /__admin/events` | Inspect stored Analytics events, oldest first |
|
|
118
|
+
| `GET /__admin/conversions` | Inspect uploaded conversions, oldest first |
|
|
75
119
|
| `GET /__admin/budgets` | Inspect campaign budgets |
|
|
76
|
-
| `GET /__admin/budget-mutations` | Inspect the mutation audit trail |
|
|
120
|
+
| `GET /__admin/budget-mutations` | Inspect the mutation audit trail: request ID, `before` and `after` per write |
|
|
77
121
|
| `GET /__admin/request-metadata` | Inspect redacted request metadata |
|
|
78
|
-
| `POST /__admin/daily-metrics` | Seed synthetic daily Ads metrics |
|
|
122
|
+
| `POST /__admin/daily-metrics` | Seed synthetic daily Ads metrics (`availableAt` delays one row) |
|
|
79
123
|
| `POST /__admin/page-tokens/expire` | Expire all current GAQL page tokens |
|
|
80
|
-
| `GET /__admin/settings` | Read
|
|
124
|
+
| `GET /__admin/settings` | Read `reportingLagMs`, `pageTokenTtlMs`, `futureToleranceMs` and `searchPageSize` |
|
|
81
125
|
| `PUT /__admin/settings` | Update those deterministic test settings |
|
|
82
126
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
127
|
+
| To | Use |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| Advance the reporting date | `POST /__admin/clock {"advance": "1d"}` or `{"set": "<ISO-8601>"}`; GAQL `DURING` ranges and Analytics `today`/`yesterday`/`NdaysAgo` follow it |
|
|
130
|
+
| Delay reporting | `PUT /__admin/settings {"reportingLagMs": n}` hides new events until the clock passes it |
|
|
131
|
+
| Page a small fixture | `PUT /__admin/settings {"searchPageSize": n}` (1 to 10,000) |
|
|
132
|
+
| Add latency | the `slow_response` preset, or `POST /__admin/faults {"latencyMs": n, "count": 1}` |
|
|
133
|
+
| Fail one request | the `server_error` or `network_reset` preset |
|
|
134
|
+
| Throttle | the `quota_exhausted` or `rate_limited` preset; `"count": n` throttles n requests |
|
|
135
|
+
| See what arrived | `GET /__admin/requests` (the journal) and `GET /__admin/request-metadata` |
|
|
136
|
+
|
|
137
|
+
Fault presets are `quota_exhausted`, `rate_limited`, `server_error`, `network_reset`,
|
|
138
|
+
`slow_response`, `ambiguous_budget_write`, `partial_budget_failure`, and
|
|
139
|
+
`partial_conversion_failure`. Each fires once unless `count` says otherwise, and is scoped to the
|
|
140
|
+
calling namespace. The two throttling presets answer `429 RESOURCE_EXHAUSTED` with
|
|
141
|
+
`quotaError: RESOURCE_EXHAUSTED` or `RESOURCE_TEMPORARILY_EXHAUSTED` and the backoff in
|
|
142
|
+
`details.quotaErrorDetails.retryDelay`, before anything is written.
|
|
143
|
+
|
|
144
|
+
`ambiguous_budget_write` commits the next executed budget mutation and then returns `503`, so
|
|
145
|
+
retry logic can be tested against an uncertain outcome. A validate-only preflight executes
|
|
146
|
+
nothing and does not spend it: the fault waits for the real call that follows. Reconcile before
|
|
147
|
+
replaying: the `503` carries a `request-id` header, and `GET /__admin/budget-mutations` lists the
|
|
148
|
+
write under that request ID with the budget's `before` and `after`.
|
|
149
|
+
|
|
150
|
+
Sensitive event and conversion payloads are sealed in state; journals and inspection metadata
|
|
151
|
+
contain only the fields needed for assertions.
|
|
88
152
|
|
|
89
153
|
## API
|
|
90
154
|
|
|
@@ -100,11 +164,19 @@ and the public fixture/runtime option types. The Node `./server` entry exports `
|
|
|
100
164
|
Acceptance tests cover GAQL precision and paging, budget preflight and partial writes, conversion
|
|
101
165
|
deduplication, GA4 event validation and reporting, OAuth scope/expiry behavior, encrypted state,
|
|
102
166
|
namespaces, resets, fault recovery, and the unmodified `google-auth-library` 11.1.0 HTTP signing
|
|
103
|
-
path.
|
|
104
|
-
is detected deterministically.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
167
|
+
path. Runtime tests drive every control above over `/__admin`. Property tests exercise every
|
|
168
|
+
parity-enabled operation and prove that a divergent transport is detected deterministically.
|
|
169
|
+
|
|
170
|
+
`test/consumer.ts` is a port of the raw-fetch traffic described in the original service request.
|
|
171
|
+
It is not the requesting application's client, whose source is not in this repository, so no test
|
|
172
|
+
here runs that client unmodified.
|
|
173
|
+
|
|
174
|
+
## Deliberately not modelled
|
|
175
|
+
|
|
176
|
+
The emulator implements a bounded subset. It does not model the complete Google Ads resource
|
|
177
|
+
graph, every GAQL function, GAQL field-compatibility rules between segments and metrics, every
|
|
178
|
+
Analytics dimension or metric, real Google identity, production quota allocation, billing,
|
|
179
|
+
dashboards, attribution processing, or undocumented backend behavior. Uploaded conversions do not
|
|
180
|
+
feed `metrics.conversions`; seed those with daily metrics. Normal operation is local and does not
|
|
181
|
+
contact Google; only the explicit parity command uses credentials supplied through `.env.local`
|
|
182
|
+
or GitHub Actions secrets.
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createRuntime,
|
|
4
4
|
parseDuration,
|
|
5
5
|
resolveAdminPrefix
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-NU66LXUZ.js";
|
|
7
7
|
|
|
8
8
|
// ../../adapters/node/dist/cli.js
|
|
9
9
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
@@ -817,4 +817,4 @@ export {
|
|
|
817
817
|
createServer2 as createServer,
|
|
818
818
|
serveTarget
|
|
819
819
|
};
|
|
820
|
-
//# sourceMappingURL=chunk-
|
|
820
|
+
//# sourceMappingURL=chunk-5T6QIJK4.js.map
|