Context
PR #1279 added the addMemberToPolicyBinding helper at cmd/configure_gcp.go:402-420 to merge a new IAM member into the project policy returned by cloudresourcemanager.Projects.GetIamPolicy. The function is pure (in-memory, no SDK calls) and has three branches: existing-binding + member-already-present (no change), existing-binding + new-member (append to Members), no-binding (append a fresh Binding).
It is currently uncovered by any unit test, which is the only test the SDK-direct GCP path is amenable to (the surrounding grantGCPIAMRole requires a real Cloud Resource Manager client; there is no provisioner interface there as there is for the Azure SP path).
A regression here silently corrupts a GCP project's IAM policy (members appended twice, or wrong role) on every wizard run — exactly the failure mode feedback_no_silent_fallbacks.md warns against.
Acceptance
A cmd/configure_gcp_test.go table-driven test for addMemberToPolicyBinding covering:
- Idempotent when member already bound — given a policy with
{role: roles/compute.admin, members: [sa@a]} and (member=sa@a, role=roles/compute.admin), returns false, no mutation.
- Appends to an existing role binding — given
{role: roles/compute.admin, members: [sa@a]} and (member=sa@b, role=roles/compute.admin), returns true and the binding now contains both members.
- Creates a new binding for a missing role — given
{role: roles/compute.viewer, members: [sa@a]} and (member=sa@a, role=roles/compute.admin), returns true and a new roles/compute.admin binding is appended; the original viewer binding is untouched.
- Empty policy — given a policy with no bindings, returns
true and a single new binding is created with exactly the requested member.
- Multiple bindings of the same role — guard the assumption that the helper only touches the first matching binding; documents/asserts the intended behaviour either way (the SDK normally folds duplicates, but Cloud Resource Manager v1 does permit them).
No SDK call mocking needed; the function operates on *cloudresourcemanager.Policy directly.
Out of scope
- Mocking
GetIamPolicy / SetIamPolicy to test the round-trip in grantGCPIAMRole. That would require extracting a GCP-side provisioner interface analogous to azureSPProvisioner; track separately if pursued.
Context
PR #1279 added the
addMemberToPolicyBindinghelper atcmd/configure_gcp.go:402-420to merge a new IAM member into the project policy returned bycloudresourcemanager.Projects.GetIamPolicy. The function is pure (in-memory, no SDK calls) and has three branches: existing-binding + member-already-present (no change), existing-binding + new-member (append toMembers), no-binding (append a freshBinding).It is currently uncovered by any unit test, which is the only test the SDK-direct GCP path is amenable to (the surrounding
grantGCPIAMRolerequires a real Cloud Resource Manager client; there is no provisioner interface there as there is for the Azure SP path).A regression here silently corrupts a GCP project's IAM policy (members appended twice, or wrong role) on every wizard run — exactly the failure mode
feedback_no_silent_fallbacks.mdwarns against.Acceptance
A
cmd/configure_gcp_test.gotable-driven test foraddMemberToPolicyBindingcovering:{role: roles/compute.admin, members: [sa@a]}and(member=sa@a, role=roles/compute.admin), returnsfalse, no mutation.{role: roles/compute.admin, members: [sa@a]}and(member=sa@b, role=roles/compute.admin), returnstrueand the binding now contains both members.{role: roles/compute.viewer, members: [sa@a]}and(member=sa@a, role=roles/compute.admin), returnstrueand a newroles/compute.adminbinding is appended; the original viewer binding is untouched.trueand a single new binding is created with exactly the requested member.No SDK call mocking needed; the function operates on
*cloudresourcemanager.Policydirectly.Out of scope
GetIamPolicy/SetIamPolicyto test the round-trip ingrantGCPIAMRole. That would require extracting a GCP-side provisioner interface analogous toazureSPProvisioner; track separately if pursued.