Refactor access review campaign source API

Expose campaign sources as first-class nodes, paginate fetch attempts
instead of denormalized status fields, and bind entries to their
campaign snapshot. Update GraphQL, MCP, CLI, console, and e2e coverage
to match.

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-06-15 14:42:58 +02:00
parent 82c9800677
commit eed6bf579d
25 changed files with 1078 additions and 692 deletions

View File

@@ -824,6 +824,182 @@ func TestAccessReviewCampaign_AddAndRemoveCampaignSource(t *testing.T) {
})
}
func TestAccessReviewCampaignSource_Node(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
orgID := owner.GetOrganizationID().String()
sourceID := factory.NewAccessReviewSource(owner, orgID).
WithName("Node Query Source").
WithCsvData(testCsvData).
Create()
campaignID := factory.NewAccessReviewCampaign(owner, orgID).
WithName("Node Query Campaign").
WithAccessReviewSourceIDs([]string{sourceID}).
Create()
const campaignQuery = `
query($id: ID!) {
node(id: $id) {
... on AccessReviewCampaign {
sources {
id
name
}
}
}
}
`
var campaignResult struct {
Node struct {
Sources []struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"sources"`
} `json:"node"`
}
err := owner.Execute(campaignQuery, map[string]any{"id": campaignID}, &campaignResult)
require.NoError(t, err)
require.Len(t, campaignResult.Node.Sources, 1)
campaignSourceID := campaignResult.Node.Sources[0].ID
const nodeQuery = `
query($id: ID!) {
node(id: $id) {
__typename
... on AccessReviewCampaignSource {
id
campaign {
id
}
name
entries(first: 10) {
totalCount
}
}
}
}
`
var nodeResult struct {
Node struct {
Typename string `json:"__typename"`
ID string `json:"id"`
Campaign struct {
ID string `json:"id"`
} `json:"campaign"`
Name string `json:"name"`
Entries struct {
TotalCount int `json:"totalCount"`
} `json:"entries"`
} `json:"node"`
}
err = owner.Execute(nodeQuery, map[string]any{"id": campaignSourceID}, &nodeResult)
require.NoError(t, err)
assert.Equal(t, "AccessReviewCampaignSource", nodeResult.Node.Typename)
assert.Equal(t, campaignSourceID, nodeResult.Node.ID)
assert.Equal(t, campaignID, nodeResult.Node.Campaign.ID)
assert.Equal(t, "Node Query Source", nodeResult.Node.Name)
assert.Greater(t, nodeResult.Node.Entries.TotalCount, 0)
}
func TestAccessReviewCampaignSource_NameSurvivesSourceDeletion(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
orgID := owner.GetOrganizationID().String()
const snapshotName = "Archived Snapshot Source"
sourceID := factory.NewAccessReviewSource(owner, orgID).
WithName(snapshotName).
WithCsvData(testCsvData).
Create()
campaignID := factory.NewAccessReviewCampaign(owner, orgID).
WithName("Archival Campaign").
WithAccessReviewSourceIDs([]string{sourceID}).
Create()
const sourcesQuery = `
query($id: ID!) {
node(id: $id) {
... on AccessReviewCampaign {
sources {
id
name
sourceId
source {
id
}
}
}
}
}
`
var before struct {
Node struct {
Sources []struct {
ID string `json:"id"`
Name string `json:"name"`
SourceID *string `json:"sourceId"`
Source *struct {
ID string `json:"id"`
} `json:"source"`
} `json:"sources"`
} `json:"node"`
}
err := owner.Execute(sourcesQuery, map[string]any{"id": campaignID}, &before)
require.NoError(t, err)
require.Len(t, before.Node.Sources, 1)
assert.Equal(t, snapshotName, before.Node.Sources[0].Name)
require.NotNil(t, before.Node.Sources[0].SourceID)
assert.Equal(t, sourceID, *before.Node.Sources[0].SourceID)
require.NotNil(t, before.Node.Sources[0].Source)
assert.Equal(t, sourceID, before.Node.Sources[0].Source.ID)
const deleteQuery = `
mutation($input: DeleteAccessReviewSourceInput!) {
deleteAccessReviewSource(input: $input) {
deletedAccessReviewSourceId
}
}
`
err = owner.Execute(deleteQuery, map[string]any{
"input": map[string]any{
"accessReviewSourceId": sourceID,
},
}, nil)
require.NoError(t, err)
var after struct {
Node struct {
Sources []struct {
ID string `json:"id"`
Name string `json:"name"`
SourceID *string `json:"sourceId"`
Source *struct {
ID string `json:"id"`
} `json:"source"`
} `json:"sources"`
} `json:"node"`
}
err = owner.Execute(sourcesQuery, map[string]any{"id": campaignID}, &after)
require.NoError(t, err)
require.Len(t, after.Node.Sources, 1)
assert.Equal(t, before.Node.Sources[0].ID, after.Node.Sources[0].ID)
assert.Equal(t, snapshotName, after.Node.Sources[0].Name)
assert.Nil(t, after.Node.Sources[0].SourceID)
assert.Nil(t, after.Node.Sources[0].Source)
}
func TestAccessReviewCampaign_Cancel(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)