Cover access-review campaign delete with e2e test
Lock the backend contract the console relies on after deleting a campaign: the deleted GID must be gone from the organization's accessReviewCampaigns connection and node(id:) must return NOT_FOUND. The frontend caches both queries and would crash again if either contract slipped (e.g. a stale row, a wrong error code, or a missing cascade on scope sources). Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
@@ -466,6 +466,103 @@ func TestAccessReviewCampaign_Delete(t *testing.T) {
|
|||||||
assert.Equal(t, campaignID, result.DeleteAccessReviewCampaign.DeletedAccessReviewCampaignID)
|
assert.Equal(t, campaignID, result.DeleteAccessReviewCampaign.DeletedAccessReviewCampaignID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAccessReviewCampaign_DeleteRemovesFromListAndNode guards the contract
|
||||||
|
// the console frontend relies on after deleting a campaign: the campaign must
|
||||||
|
// disappear from the organization's `accessReviewCampaigns` connection, and a
|
||||||
|
// `node(id:)` lookup on the deleted GID must surface a NOT_FOUND error rather
|
||||||
|
// than partial data. Without this contract the cached Relay query in the
|
||||||
|
// access-reviews tab would render edges pointing to a vanished record and
|
||||||
|
// crash with "Unexpected error :(".
|
||||||
|
func TestAccessReviewCampaign_DeleteRemovesFromListAndNode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||||
|
orgID := owner.GetOrganizationID().String()
|
||||||
|
|
||||||
|
sourceID := factory.NewAccessSource(owner, orgID).
|
||||||
|
WithName("Source for Delete").
|
||||||
|
WithCsvData(testCsvData).
|
||||||
|
Create()
|
||||||
|
campaignID := factory.NewAccessReviewCampaign(owner, orgID).
|
||||||
|
WithName("Campaign to Cascade Delete").
|
||||||
|
WithAccessSourceIDs([]string{sourceID}).
|
||||||
|
Create()
|
||||||
|
|
||||||
|
const deleteMutation = `
|
||||||
|
mutation($input: DeleteAccessReviewCampaignInput!) {
|
||||||
|
deleteAccessReviewCampaign(input: $input) {
|
||||||
|
deletedAccessReviewCampaignId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var deleteResult struct {
|
||||||
|
DeleteAccessReviewCampaign struct {
|
||||||
|
DeletedAccessReviewCampaignID string `json:"deletedAccessReviewCampaignId"`
|
||||||
|
} `json:"deleteAccessReviewCampaign"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := owner.Execute(deleteMutation, map[string]any{
|
||||||
|
"input": map[string]any{
|
||||||
|
"accessReviewCampaignId": campaignID,
|
||||||
|
},
|
||||||
|
}, &deleteResult)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, campaignID, deleteResult.DeleteAccessReviewCampaign.DeletedAccessReviewCampaignID)
|
||||||
|
|
||||||
|
// The campaign must no longer appear in the organization's campaign
|
||||||
|
// connection -- mirrors the AccessReviewCampaignsTabQuery the FE fires
|
||||||
|
// when the user navigates back to the access-reviews tab.
|
||||||
|
const listQuery = `
|
||||||
|
query($id: ID!) {
|
||||||
|
node(id: $id) {
|
||||||
|
... on Organization {
|
||||||
|
accessReviewCampaigns(first: 50) {
|
||||||
|
edges {
|
||||||
|
node { id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var listResult struct {
|
||||||
|
Node struct {
|
||||||
|
AccessReviewCampaigns struct {
|
||||||
|
Edges []struct {
|
||||||
|
Node struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"node"`
|
||||||
|
} `json:"edges"`
|
||||||
|
} `json:"accessReviewCampaigns"`
|
||||||
|
} `json:"node"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = owner.Execute(listQuery, map[string]any{"id": orgID}, &listResult)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, edge := range listResult.Node.AccessReviewCampaigns.Edges {
|
||||||
|
assert.NotEqual(t, campaignID, edge.Node.ID, "deleted campaign must not appear in the connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolving the deleted GID via `node(id:)` must error with NOT_FOUND so
|
||||||
|
// the cached Relay store can't keep serving a tombstoned record.
|
||||||
|
const nodeQuery = `
|
||||||
|
query($id: ID!) {
|
||||||
|
node(id: $id) {
|
||||||
|
... on AccessReviewCampaign {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
_, err = owner.Do(nodeQuery, map[string]any{"id": campaignID})
|
||||||
|
var gqlErrors testutil.GraphQLErrors
|
||||||
|
require.ErrorAs(t, err, &gqlErrors)
|
||||||
|
require.Len(t, gqlErrors, 1)
|
||||||
|
assert.Equal(t, "NOT_FOUND", gqlErrors[0].Code())
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccessReviewCampaign_List(t *testing.T) {
|
func TestAccessReviewCampaign_List(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||||
|
|||||||
Reference in New Issue
Block a user