Three defects, all found reviewing the rebased branch. The connection probe hit /v1alpha/accounts, which any analytics.readonly grant can call, while the driver's first request is the account's accessBindings — that additionally needs Administrator on the account and the manage.users.readonly scope. An Editor connecting, or a user declining the second scope on Google's granular consent screen, probed green and then 403'd on every campaign fetch, leaving the source permanently "Connected" with no rows. The probe now targets the same accessBindings collection the driver reads. A single unreadable property aborted the whole account. A property the token cannot see, or one deleted between the list and the read, threw away every binding already collected; 49 of 50 readable properties are still worth reviewing, so 403 and 404 now skip that property. Anything else still fails the fetch. Fan-out errors named no resource: the account call, the property list and each per-property call all returned the same "unexpected status" string, so a 403 on one subproperty out of forty was unattributable. Errors now carry the account or property ID. The cassette gains a subproperty parented to another property (only reachable through the ancestor filter, so it pins the hierarchy walk that the filter change claimed) and a property returning 403. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
156 lines
5.4 KiB
YAML
156 lines
5.4 KiB
YAML
---
|
|
# Hand-authored cassette for the GA4 Admin API v1alpha (Bearer token stripped by
|
|
# the recorder). The driver lists account-level accessBindings, then the
|
|
# properties beneath the account, then each property's accessBindings, merging a
|
|
# user's roles across levels by email. alice appears at both levels (admin +
|
|
# viewer) to exercise the merge; carol appears only at the property level; dave
|
|
# appears only on properties/99999, a subproperty parented to another property
|
|
# rather than to the account, so the ancestor filter's hierarchy walk is
|
|
# covered. properties/55555 returns 403 to cover a property the token cannot
|
|
# read being skipped instead of failing the whole account. The AccessBinding
|
|
# shape (name, user, roles[]) and property list shape mirror the live API.
|
|
version: 2
|
|
interactions:
|
|
- id: 0
|
|
request:
|
|
proto: HTTP/1.1
|
|
proto_major: 1
|
|
proto_minor: 1
|
|
content_length: 0
|
|
host: analyticsadmin.googleapis.com
|
|
form:
|
|
pageSize:
|
|
- "200"
|
|
headers:
|
|
Accept:
|
|
- application/json
|
|
url: https://analyticsadmin.googleapis.com/v1alpha/accounts/123456/accessBindings?pageSize=200
|
|
method: GET
|
|
response:
|
|
proto: HTTP/2.0
|
|
proto_major: 2
|
|
proto_minor: 0
|
|
content_length: -1
|
|
uncompressed: true
|
|
body: '{"accessBindings":[{"name":"accounts/123456/accessBindings/abc123","user":"alice@example.com","roles":["predefinedRoles/admin"]},{"name":"accounts/123456/accessBindings/def456","user":"bob@example.com","roles":["predefinedRoles/analyst"]}]}'
|
|
headers:
|
|
Content-Type:
|
|
- application/json
|
|
status: 200 OK
|
|
code: 200
|
|
duration: 100ms
|
|
- id: 1
|
|
request:
|
|
proto: HTTP/1.1
|
|
proto_major: 1
|
|
proto_minor: 1
|
|
content_length: 0
|
|
host: analyticsadmin.googleapis.com
|
|
form:
|
|
filter:
|
|
- ancestor:accounts/123456
|
|
pageSize:
|
|
- "200"
|
|
headers:
|
|
Accept:
|
|
- application/json
|
|
url: https://analyticsadmin.googleapis.com/v1alpha/properties?filter=ancestor%3Aaccounts%2F123456&pageSize=200
|
|
method: GET
|
|
response:
|
|
proto: HTTP/2.0
|
|
proto_major: 2
|
|
proto_minor: 0
|
|
content_length: -1
|
|
uncompressed: true
|
|
body: '{"properties":[{"name":"properties/67890","displayName":"Acme Website","parent":"accounts/123456"},{"name":"properties/99999","displayName":"Acme Rollup","parent":"properties/67890"},{"name":"properties/55555","displayName":"Acme Restricted","parent":"accounts/123456"}]}'
|
|
headers:
|
|
Content-Type:
|
|
- application/json
|
|
status: 200 OK
|
|
code: 200
|
|
duration: 100ms
|
|
- id: 2
|
|
request:
|
|
proto: HTTP/1.1
|
|
proto_major: 1
|
|
proto_minor: 1
|
|
content_length: 0
|
|
host: analyticsadmin.googleapis.com
|
|
form:
|
|
pageSize:
|
|
- "200"
|
|
headers:
|
|
Accept:
|
|
- application/json
|
|
url: https://analyticsadmin.googleapis.com/v1alpha/properties/67890/accessBindings?pageSize=200
|
|
method: GET
|
|
response:
|
|
proto: HTTP/2.0
|
|
proto_major: 2
|
|
proto_minor: 0
|
|
content_length: -1
|
|
uncompressed: true
|
|
body: '{"accessBindings":[{"name":"properties/67890/accessBindings/ghi789","user":"alice@example.com","roles":["predefinedRoles/viewer"]},{"name":"properties/67890/accessBindings/jkl012","user":"carol@example.com","roles":["predefinedRoles/analyst"]}]}'
|
|
headers:
|
|
Content-Type:
|
|
- application/json
|
|
status: 200 OK
|
|
code: 200
|
|
duration: 100ms
|
|
- id: 3
|
|
request:
|
|
proto: HTTP/1.1
|
|
proto_major: 1
|
|
proto_minor: 1
|
|
content_length: 0
|
|
host: analyticsadmin.googleapis.com
|
|
form:
|
|
pageSize:
|
|
- "200"
|
|
headers:
|
|
Accept:
|
|
- application/json
|
|
url: https://analyticsadmin.googleapis.com/v1alpha/properties/99999/accessBindings?pageSize=200
|
|
method: GET
|
|
response:
|
|
proto: HTTP/2.0
|
|
proto_major: 2
|
|
proto_minor: 0
|
|
content_length: -1
|
|
uncompressed: true
|
|
body: '{"accessBindings":[{"name":"properties/99999/accessBindings/mno345","user":"dave@example.com","roles":["predefinedRoles/analyst"]}]}'
|
|
headers:
|
|
Content-Type:
|
|
- application/json
|
|
status: 200 OK
|
|
code: 200
|
|
duration: 100ms
|
|
- id: 4
|
|
request:
|
|
proto: HTTP/1.1
|
|
proto_major: 1
|
|
proto_minor: 1
|
|
content_length: 0
|
|
host: analyticsadmin.googleapis.com
|
|
form:
|
|
pageSize:
|
|
- "200"
|
|
headers:
|
|
Accept:
|
|
- application/json
|
|
url: https://analyticsadmin.googleapis.com/v1alpha/properties/55555/accessBindings?pageSize=200
|
|
method: GET
|
|
response:
|
|
proto: HTTP/2.0
|
|
proto_major: 2
|
|
proto_minor: 0
|
|
content_length: -1
|
|
uncompressed: true
|
|
body: '{"error":{"code":403,"message":"User does not have sufficient permissions for this property.","status":"PERMISSION_DENIED"}}'
|
|
headers:
|
|
Content-Type:
|
|
- application/json
|
|
status: 403 Forbidden
|
|
code: 403
|
|
duration: 100ms
|