Introduce access-review source snapshot and normalize naming
Decouple each campaign from the live access-review sources it was started with by introducing a per-campaign source snapshot table (access_review_campaign_sources). The snapshot captures the source name, category, and connector at start time, so a review remains coherent even after the underlying source is edited or deleted. Fetch tracking becomes an append-only log (access_review_campaign_source_fetch_attempts) that preserves every attempt with its own status and error rather than overwriting a single row. Rename the shared access-review tables and enums to use a consistent access_review_ prefix throughout: access_entries → access_review_entries access_sources → access_review_sources access_source_category → access_review_source_category access_entry_* → access_review_entry_* The same rename propagates to every coredata type, service, GraphQL schema, MCP specification, CLI command, frontend component, and e2e test. The accessreview package gains dedicated actions.go and policies.go files for its own IAM policy set, mirroring the agentrun package pattern. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -26,7 +26,7 @@ import (
|
||||
|
||||
const testCsvData = "email,full_name,role,job_title,is_admin,active,mfa_status,auth_method,last_login,account_created_at,external_id\njane@example.com,Jane Smith,admin,CTO,true,true,ENABLED,SSO,2026-01-15T00:00:00Z,2024-06-01T00:00:00Z,ext-jane"
|
||||
|
||||
func TestAccessSource_Create(t *testing.T) {
|
||||
func TestAccessReviewSource_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
@@ -35,9 +35,9 @@ func TestAccessSource_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const query = `
|
||||
mutation($input: CreateAccessSourceInput!) {
|
||||
createAccessSource(input: $input) {
|
||||
accessSourceEdge {
|
||||
mutation($input: CreateAccessReviewSourceInput!) {
|
||||
createAccessReviewSource(input: $input) {
|
||||
accessReviewSourceEdge {
|
||||
node {
|
||||
id
|
||||
name
|
||||
@@ -50,16 +50,16 @@ func TestAccessSource_Create(t *testing.T) {
|
||||
`
|
||||
|
||||
var result struct {
|
||||
CreateAccessSource struct {
|
||||
AccessSourceEdge struct {
|
||||
CreateAccessReviewSource struct {
|
||||
AccessReviewSourceEdge struct {
|
||||
Node struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
} `json:"node"`
|
||||
} `json:"accessSourceEdge"`
|
||||
} `json:"createAccessSource"`
|
||||
} `json:"accessReviewSourceEdge"`
|
||||
} `json:"createAccessReviewSource"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
@@ -70,7 +70,7 @@ func TestAccessSource_Create(t *testing.T) {
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
node := result.CreateAccessSource.AccessSourceEdge.Node
|
||||
node := result.CreateAccessReviewSource.AccessReviewSourceEdge.Node
|
||||
assert.NotEmpty(t, node.ID)
|
||||
assert.Equal(t, "Slack", node.Name)
|
||||
assert.NotEmpty(t, node.CreatedAt)
|
||||
@@ -80,9 +80,9 @@ func TestAccessSource_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const query = `
|
||||
mutation($input: CreateAccessSourceInput!) {
|
||||
createAccessSource(input: $input) {
|
||||
accessSourceEdge {
|
||||
mutation($input: CreateAccessReviewSourceInput!) {
|
||||
createAccessReviewSource(input: $input) {
|
||||
accessReviewSourceEdge {
|
||||
node {
|
||||
id
|
||||
name
|
||||
@@ -94,15 +94,15 @@ func TestAccessSource_Create(t *testing.T) {
|
||||
`
|
||||
|
||||
var result struct {
|
||||
CreateAccessSource struct {
|
||||
AccessSourceEdge struct {
|
||||
CreateAccessReviewSource struct {
|
||||
AccessReviewSourceEdge struct {
|
||||
Node struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CsvData *string `json:"csvData"`
|
||||
} `json:"node"`
|
||||
} `json:"accessSourceEdge"`
|
||||
} `json:"createAccessSource"`
|
||||
} `json:"accessReviewSourceEdge"`
|
||||
} `json:"createAccessReviewSource"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
@@ -114,7 +114,7 @@ func TestAccessSource_Create(t *testing.T) {
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
node := result.CreateAccessSource.AccessSourceEdge.Node
|
||||
node := result.CreateAccessReviewSource.AccessReviewSourceEdge.Node
|
||||
assert.NotEmpty(t, node.ID)
|
||||
assert.Equal(t, "CSV Import", node.Name)
|
||||
require.NotNil(t, node.CsvData)
|
||||
@@ -122,18 +122,18 @@ func TestAccessSource_Create(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccessSource_Update(t *testing.T) {
|
||||
func TestAccessReviewSource_Update(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
sourceID := factory.NewAccessSource(owner, orgID).
|
||||
sourceID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("Original Source").
|
||||
Create()
|
||||
|
||||
const query = `
|
||||
mutation($input: UpdateAccessSourceInput!) {
|
||||
updateAccessSource(input: $input) {
|
||||
accessSource {
|
||||
mutation($input: UpdateAccessReviewSourceInput!) {
|
||||
updateAccessReviewSource(input: $input) {
|
||||
accessReviewSource {
|
||||
id
|
||||
name
|
||||
}
|
||||
@@ -142,71 +142,71 @@ func TestAccessSource_Update(t *testing.T) {
|
||||
`
|
||||
|
||||
var result struct {
|
||||
UpdateAccessSource struct {
|
||||
AccessSource struct {
|
||||
UpdateAccessReviewSource struct {
|
||||
AccessReviewSource struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"accessSource"`
|
||||
} `json:"updateAccessSource"`
|
||||
} `json:"accessReviewSource"`
|
||||
} `json:"updateAccessReviewSource"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"accessSourceId": sourceID,
|
||||
"name": "Updated Source",
|
||||
"accessReviewSourceId": sourceID,
|
||||
"name": "Updated Source",
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, sourceID, result.UpdateAccessSource.AccessSource.ID)
|
||||
assert.Equal(t, "Updated Source", result.UpdateAccessSource.AccessSource.Name)
|
||||
assert.Equal(t, sourceID, result.UpdateAccessReviewSource.AccessReviewSource.ID)
|
||||
assert.Equal(t, "Updated Source", result.UpdateAccessReviewSource.AccessReviewSource.Name)
|
||||
}
|
||||
|
||||
func TestAccessSource_Delete(t *testing.T) {
|
||||
func TestAccessReviewSource_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
sourceID := factory.NewAccessSource(owner, orgID).
|
||||
sourceID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("Source to Delete").
|
||||
Create()
|
||||
|
||||
const query = `
|
||||
mutation($input: DeleteAccessSourceInput!) {
|
||||
deleteAccessSource(input: $input) {
|
||||
deletedAccessSourceId
|
||||
mutation($input: DeleteAccessReviewSourceInput!) {
|
||||
deleteAccessReviewSource(input: $input) {
|
||||
deletedAccessReviewSourceId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
DeleteAccessSource struct {
|
||||
DeletedAccessSourceID string `json:"deletedAccessSourceId"`
|
||||
} `json:"deleteAccessSource"`
|
||||
DeleteAccessReviewSource struct {
|
||||
DeletedAccessReviewSourceID string `json:"deletedAccessReviewSourceId"`
|
||||
} `json:"deleteAccessReviewSource"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"accessSourceId": sourceID,
|
||||
"accessReviewSourceId": sourceID,
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, sourceID, result.DeleteAccessSource.DeletedAccessSourceID)
|
||||
assert.Equal(t, sourceID, result.DeleteAccessReviewSource.DeletedAccessReviewSourceID)
|
||||
}
|
||||
|
||||
func TestAccessSource_List(t *testing.T) {
|
||||
func TestAccessReviewSource_List(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
|
||||
for _, name := range []string{"Slack", "GitHub", "Google Workspace"} {
|
||||
factory.NewAccessSource(owner, orgID).WithName(name).Create()
|
||||
factory.NewAccessReviewSource(owner, orgID).WithName(name).Create()
|
||||
}
|
||||
|
||||
const query = `
|
||||
query($id: ID!) {
|
||||
node(id: $id) {
|
||||
... on Organization {
|
||||
accessSources(first: 10) {
|
||||
accessReviewSources(first: 10) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
@@ -222,7 +222,7 @@ func TestAccessSource_List(t *testing.T) {
|
||||
|
||||
var result struct {
|
||||
Node struct {
|
||||
AccessSources struct {
|
||||
AccessReviewSources struct {
|
||||
Edges []struct {
|
||||
Node struct {
|
||||
ID string `json:"id"`
|
||||
@@ -230,13 +230,13 @@ func TestAccessSource_List(t *testing.T) {
|
||||
} `json:"node"`
|
||||
} `json:"edges"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
} `json:"accessSources"`
|
||||
} `json:"accessReviewSources"`
|
||||
} `json:"node"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{"id": orgID}, &result)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, result.Node.AccessSources.TotalCount, 3)
|
||||
assert.GreaterOrEqual(t, result.Node.AccessReviewSources.TotalCount, 3)
|
||||
}
|
||||
|
||||
func TestAccessReviewCampaign_Create(t *testing.T) {
|
||||
@@ -295,10 +295,10 @@ func TestAccessReviewCampaign_Create(t *testing.T) {
|
||||
t.Run("with access sources", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source1ID := factory.NewAccessSource(owner, orgID).
|
||||
source1ID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("Slack Source").
|
||||
Create()
|
||||
source2ID := factory.NewAccessSource(owner, orgID).
|
||||
source2ID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("GitHub Source").
|
||||
Create()
|
||||
|
||||
@@ -309,7 +309,7 @@ func TestAccessReviewCampaign_Create(t *testing.T) {
|
||||
node {
|
||||
id
|
||||
name
|
||||
scopeSources {
|
||||
sources {
|
||||
id
|
||||
name
|
||||
}
|
||||
@@ -323,12 +323,12 @@ func TestAccessReviewCampaign_Create(t *testing.T) {
|
||||
CreateAccessReviewCampaign struct {
|
||||
AccessReviewCampaignEdge struct {
|
||||
Node struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ScopeSources []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CampaignSources []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"scopeSources"`
|
||||
} `json:"sources"`
|
||||
} `json:"node"`
|
||||
} `json:"accessReviewCampaignEdge"`
|
||||
} `json:"createAccessReviewCampaign"`
|
||||
@@ -336,9 +336,9 @@ func TestAccessReviewCampaign_Create(t *testing.T) {
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": orgID,
|
||||
"name": "Campaign with Sources",
|
||||
"accessSourceIds": []string{source1ID, source2ID},
|
||||
"organizationId": orgID,
|
||||
"name": "Campaign with Sources",
|
||||
"accessReviewSourceIds": []string{source1ID, source2ID},
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
@@ -346,7 +346,7 @@ func TestAccessReviewCampaign_Create(t *testing.T) {
|
||||
node := result.CreateAccessReviewCampaign.AccessReviewCampaignEdge.Node
|
||||
assert.NotEmpty(t, node.ID)
|
||||
assert.Equal(t, "Campaign with Sources", node.Name)
|
||||
assert.Len(t, node.ScopeSources, 2)
|
||||
assert.Len(t, node.CampaignSources, 2)
|
||||
})
|
||||
|
||||
t.Run("with framework controls", func(t *testing.T) {
|
||||
@@ -478,13 +478,13 @@ func TestAccessReviewCampaign_DeleteRemovesFromListAndNode(t *testing.T) {
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
|
||||
sourceID := factory.NewAccessSource(owner, orgID).
|
||||
sourceID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("Source for Delete").
|
||||
WithCsvData(testCsvData).
|
||||
Create()
|
||||
campaignID := factory.NewAccessReviewCampaign(owner, orgID).
|
||||
WithName("Campaign to Cascade Delete").
|
||||
WithAccessSourceIDs([]string{sourceID}).
|
||||
WithAccessReviewSourceIDs([]string{sourceID}).
|
||||
Create()
|
||||
|
||||
const deleteMutation = `
|
||||
@@ -676,14 +676,14 @@ func TestAccessReviewCampaign_StartWithCsvSource(t *testing.T) {
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
|
||||
sourceID := factory.NewAccessSource(owner, orgID).
|
||||
sourceID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("CSV Test Source").
|
||||
WithCsvData(testCsvData).
|
||||
Create()
|
||||
|
||||
campaignID := factory.NewAccessReviewCampaign(owner, orgID).
|
||||
WithName("CSV Campaign").
|
||||
WithAccessSourceIDs([]string{sourceID}).
|
||||
WithAccessReviewSourceIDs([]string{sourceID}).
|
||||
Create()
|
||||
|
||||
const query = `
|
||||
@@ -721,12 +721,12 @@ func TestAccessReviewCampaign_StartWithCsvSource(t *testing.T) {
|
||||
assert.NotNil(t, campaign.StartedAt)
|
||||
}
|
||||
|
||||
func TestAccessReviewCampaign_AddAndRemoveScopeSource(t *testing.T) {
|
||||
func TestAccessReviewCampaign_AddAndRemoveCampaignSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
|
||||
sourceID := factory.NewAccessSource(owner, orgID).
|
||||
sourceID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("Scope Source").
|
||||
Create()
|
||||
|
||||
@@ -736,13 +736,16 @@ func TestAccessReviewCampaign_AddAndRemoveScopeSource(t *testing.T) {
|
||||
|
||||
t.Run("add scope source", func(t *testing.T) {
|
||||
const query = `
|
||||
mutation($input: AddAccessReviewCampaignScopeSourceInput!) {
|
||||
addAccessReviewCampaignScopeSource(input: $input) {
|
||||
mutation($input: AddAccessReviewCampaignSourceInput!) {
|
||||
addAccessReviewCampaignSource(input: $input) {
|
||||
accessReviewCampaign {
|
||||
id
|
||||
scopeSources {
|
||||
sources {
|
||||
id
|
||||
name
|
||||
source {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -750,38 +753,45 @@ func TestAccessReviewCampaign_AddAndRemoveScopeSource(t *testing.T) {
|
||||
`
|
||||
|
||||
var result struct {
|
||||
AddAccessReviewCampaignScopeSource struct {
|
||||
AddAccessReviewCampaignSource struct {
|
||||
AccessReviewCampaign struct {
|
||||
ID string `json:"id"`
|
||||
ScopeSources []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"scopeSources"`
|
||||
ID string `json:"id"`
|
||||
CampaignSources []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Source *struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"source"`
|
||||
} `json:"sources"`
|
||||
} `json:"accessReviewCampaign"`
|
||||
} `json:"addAccessReviewCampaignScopeSource"`
|
||||
} `json:"addAccessReviewCampaignSource"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"accessReviewCampaignId": campaignID,
|
||||
"accessSourceId": sourceID,
|
||||
"accessReviewSourceId": sourceID,
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
campaign := result.AddAccessReviewCampaignScopeSource.AccessReviewCampaign
|
||||
campaign := result.AddAccessReviewCampaignSource.AccessReviewCampaign
|
||||
assert.Equal(t, campaignID, campaign.ID)
|
||||
assert.Len(t, campaign.ScopeSources, 1)
|
||||
assert.Equal(t, sourceID, campaign.ScopeSources[0].ID)
|
||||
assert.Len(t, campaign.CampaignSources, 1)
|
||||
// The scope source id is now a per-campaign snapshot id, distinct from
|
||||
// the live source id, which is exposed via the source link.
|
||||
assert.NotEqual(t, sourceID, campaign.CampaignSources[0].ID)
|
||||
require.NotNil(t, campaign.CampaignSources[0].Source)
|
||||
assert.Equal(t, sourceID, campaign.CampaignSources[0].Source.ID)
|
||||
})
|
||||
|
||||
t.Run("remove scope source", func(t *testing.T) {
|
||||
const query = `
|
||||
mutation($input: RemoveAccessReviewCampaignScopeSourceInput!) {
|
||||
removeAccessReviewCampaignScopeSource(input: $input) {
|
||||
mutation($input: RemoveAccessReviewCampaignSourceInput!) {
|
||||
removeAccessReviewCampaignSource(input: $input) {
|
||||
accessReviewCampaign {
|
||||
id
|
||||
scopeSources {
|
||||
sources {
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -790,27 +800,27 @@ func TestAccessReviewCampaign_AddAndRemoveScopeSource(t *testing.T) {
|
||||
`
|
||||
|
||||
var result struct {
|
||||
RemoveAccessReviewCampaignScopeSource struct {
|
||||
RemoveAccessReviewCampaignSource struct {
|
||||
AccessReviewCampaign struct {
|
||||
ID string `json:"id"`
|
||||
ScopeSources []struct {
|
||||
ID string `json:"id"`
|
||||
CampaignSources []struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"scopeSources"`
|
||||
} `json:"sources"`
|
||||
} `json:"accessReviewCampaign"`
|
||||
} `json:"removeAccessReviewCampaignScopeSource"`
|
||||
} `json:"removeAccessReviewCampaignSource"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"accessReviewCampaignId": campaignID,
|
||||
"accessSourceId": sourceID,
|
||||
"accessReviewSourceId": sourceID,
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
campaign := result.RemoveAccessReviewCampaignScopeSource.AccessReviewCampaign
|
||||
campaign := result.RemoveAccessReviewCampaignSource.AccessReviewCampaign
|
||||
assert.Equal(t, campaignID, campaign.ID)
|
||||
assert.Empty(t, campaign.ScopeSources)
|
||||
assert.Empty(t, campaign.CampaignSources)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -819,14 +829,14 @@ func TestAccessReviewCampaign_Cancel(t *testing.T) {
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
|
||||
sourceID := factory.NewAccessSource(owner, orgID).
|
||||
sourceID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("Cancel Test Source").
|
||||
WithCsvData(testCsvData).
|
||||
Create()
|
||||
|
||||
campaignID := factory.NewAccessReviewCampaign(owner, orgID).
|
||||
WithName("Campaign to Cancel").
|
||||
WithAccessSourceIDs([]string{sourceID}).
|
||||
WithAccessReviewSourceIDs([]string{sourceID}).
|
||||
Create()
|
||||
|
||||
// Start the campaign first
|
||||
@@ -971,7 +981,7 @@ func TestAccessReviewCampaign_FullLifecycle(t *testing.T) {
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
|
||||
// Step 1: Create a CSV source with test data
|
||||
sourceID := factory.NewAccessSource(owner, orgID).
|
||||
sourceID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("Lifecycle Test Source").
|
||||
WithCsvData(testCsvData).
|
||||
Create()
|
||||
@@ -986,7 +996,7 @@ func TestAccessReviewCampaign_FullLifecycle(t *testing.T) {
|
||||
name
|
||||
description
|
||||
status
|
||||
scopeSources {
|
||||
sources {
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -999,13 +1009,13 @@ func TestAccessReviewCampaign_FullLifecycle(t *testing.T) {
|
||||
CreateAccessReviewCampaign struct {
|
||||
AccessReviewCampaignEdge struct {
|
||||
Node struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
ScopeSources []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
CampaignSources []struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"scopeSources"`
|
||||
} `json:"sources"`
|
||||
} `json:"node"`
|
||||
} `json:"accessReviewCampaignEdge"`
|
||||
} `json:"createAccessReviewCampaign"`
|
||||
@@ -1013,10 +1023,10 @@ func TestAccessReviewCampaign_FullLifecycle(t *testing.T) {
|
||||
|
||||
err := owner.Execute(createQuery, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": orgID,
|
||||
"name": "Full Lifecycle Campaign",
|
||||
"description": "Testing the full lifecycle",
|
||||
"accessSourceIds": []string{sourceID},
|
||||
"organizationId": orgID,
|
||||
"name": "Full Lifecycle Campaign",
|
||||
"description": "Testing the full lifecycle",
|
||||
"accessReviewSourceIds": []string{sourceID},
|
||||
},
|
||||
}, &createResult)
|
||||
require.NoError(t, err)
|
||||
@@ -1025,7 +1035,7 @@ func TestAccessReviewCampaign_FullLifecycle(t *testing.T) {
|
||||
campaignID := campaignNode.ID
|
||||
assert.Equal(t, "DRAFT", campaignNode.Status)
|
||||
assert.Equal(t, "Testing the full lifecycle", campaignNode.Description)
|
||||
assert.Len(t, campaignNode.ScopeSources, 1)
|
||||
assert.Len(t, campaignNode.CampaignSources, 1)
|
||||
|
||||
// Step 3: Start the campaign (triggers worker to fetch CSV data)
|
||||
const startQuery = `
|
||||
@@ -1144,8 +1154,8 @@ func TestAccessReviewCampaign_FullLifecycle(t *testing.T) {
|
||||
|
||||
// Step 5: Record decisions on all entries
|
||||
const recordDecisionQuery = `
|
||||
mutation($input: RecordAccessEntryDecisionInput!) {
|
||||
recordAccessEntryDecision(input: $input) {
|
||||
mutation($input: RecordAccessReviewEntryDecisionInput!) {
|
||||
recordAccessReviewEntryDecision(input: $input) {
|
||||
accessEntry {
|
||||
id
|
||||
decision
|
||||
@@ -1162,8 +1172,8 @@ func TestAccessReviewCampaign_FullLifecycle(t *testing.T) {
|
||||
|
||||
for _, edge := range campaignResult.Node.Entries.Edges {
|
||||
var decisionResult struct {
|
||||
RecordAccessEntryDecision struct {
|
||||
AccessEntry struct {
|
||||
RecordAccessReviewEntryDecision struct {
|
||||
AccessReviewEntry struct {
|
||||
ID string `json:"id"`
|
||||
Decision string `json:"decision"`
|
||||
DecidedAt *string `json:"decidedAt"`
|
||||
@@ -1172,18 +1182,18 @@ func TestAccessReviewCampaign_FullLifecycle(t *testing.T) {
|
||||
Decision string `json:"decision"`
|
||||
} `json:"decisionHistory"`
|
||||
} `json:"accessEntry"`
|
||||
} `json:"recordAccessEntryDecision"`
|
||||
} `json:"recordAccessReviewEntryDecision"`
|
||||
}
|
||||
|
||||
err = owner.Execute(recordDecisionQuery, map[string]any{
|
||||
"input": map[string]any{
|
||||
"accessEntryId": edge.Node.ID,
|
||||
"decision": "APPROVED",
|
||||
"accessReviewEntryId": edge.Node.ID,
|
||||
"decision": "APPROVED",
|
||||
},
|
||||
}, &decisionResult)
|
||||
require.NoError(t, err)
|
||||
|
||||
entry := decisionResult.RecordAccessEntryDecision.AccessEntry
|
||||
entry := decisionResult.RecordAccessReviewEntryDecision.AccessReviewEntry
|
||||
assert.Equal(t, "APPROVED", entry.Decision)
|
||||
assert.NotNil(t, entry.DecidedAt)
|
||||
|
||||
@@ -1232,14 +1242,14 @@ func TestAccessReviewCampaign_CloseRequiresAllDecisions(t *testing.T) {
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
orgID := owner.GetOrganizationID().String()
|
||||
|
||||
sourceID := factory.NewAccessSource(owner, orgID).
|
||||
sourceID := factory.NewAccessReviewSource(owner, orgID).
|
||||
WithName("Close Guard Source").
|
||||
WithCsvData(testCsvData).
|
||||
Create()
|
||||
|
||||
campaignID := factory.NewAccessReviewCampaign(owner, orgID).
|
||||
WithName("Close Guard Campaign").
|
||||
WithAccessSourceIDs([]string{sourceID}).
|
||||
WithAccessReviewSourceIDs([]string{sourceID}).
|
||||
Create()
|
||||
|
||||
// Start the campaign
|
||||
@@ -1334,9 +1344,9 @@ func TestAccessReview_TenantIsolation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const query = `
|
||||
mutation($input: CreateAccessSourceInput!) {
|
||||
createAccessSource(input: $input) {
|
||||
accessSourceEdge {
|
||||
mutation($input: CreateAccessReviewSourceInput!) {
|
||||
createAccessReviewSource(input: $input) {
|
||||
accessReviewSourceEdge {
|
||||
node { id }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,32 +204,32 @@ const (
|
||||
}
|
||||
}`
|
||||
|
||||
createAccessSourceMutation = `
|
||||
mutation CreateAccessSource($input: CreateAccessSourceInput!) {
|
||||
createAccessSource(input: $input) {
|
||||
accessSourceEdge { node { id } }
|
||||
createAccessReviewSourceMutation = `
|
||||
mutation CreateAccessReviewSource($input: CreateAccessReviewSourceInput!) {
|
||||
createAccessReviewSource(input: $input) {
|
||||
accessReviewSourceEdge { node { id } }
|
||||
}
|
||||
}`
|
||||
|
||||
updateAccessSourceMutation = `
|
||||
mutation UpdateAccessSource($input: UpdateAccessSourceInput!) {
|
||||
updateAccessSource(input: $input) {
|
||||
accessSource { id }
|
||||
updateAccessReviewSourceMutation = `
|
||||
mutation UpdateAccessReviewSource($input: UpdateAccessReviewSourceInput!) {
|
||||
updateAccessReviewSource(input: $input) {
|
||||
accessReviewSource { id }
|
||||
}
|
||||
}`
|
||||
|
||||
deleteAccessSourceMutation = `
|
||||
mutation DeleteAccessSource($input: DeleteAccessSourceInput!) {
|
||||
deleteAccessSource(input: $input) {
|
||||
deletedAccessSourceId
|
||||
deleteAccessReviewSourceMutation = `
|
||||
mutation DeleteAccessReviewSource($input: DeleteAccessReviewSourceInput!) {
|
||||
deleteAccessReviewSource(input: $input) {
|
||||
deletedAccessReviewSourceId
|
||||
}
|
||||
}`
|
||||
|
||||
listAccessSourcesQuery = `
|
||||
query GetAccessSources($id: ID!) {
|
||||
listAccessReviewSourcesQuery = `
|
||||
query GetAccessReviewSources($id: ID!) {
|
||||
node(id: $id) {
|
||||
... on Organization {
|
||||
accessSources(first: 10) { totalCount }
|
||||
accessReviewSources(first: 10) { totalCount }
|
||||
}
|
||||
}
|
||||
}`
|
||||
@@ -305,7 +305,7 @@ func TestRBAC(t *testing.T) {
|
||||
taskID := factory.NewTask(owner, measureID).WithName("RBAC Test Task").Create()
|
||||
riskID := factory.NewRisk(owner).WithName("RBAC Test Risk").Create()
|
||||
thirdPartyID := factory.NewThirdParty(owner).WithName("RBAC Test ThirdParty").Create()
|
||||
accessSourceID := factory.NewAccessSource(owner, owner.GetOrganizationID().String()).WithName("RBAC Test Source").Create()
|
||||
accessReviewSourceID := factory.NewAccessReviewSource(owner, owner.GetOrganizationID().String()).WithName("RBAC Test Source").Create()
|
||||
accessReviewCampaignID := factory.NewAccessReviewCampaign(owner, owner.GetOrganizationID().String()).WithName("RBAC Test Campaign").Create()
|
||||
|
||||
tests := []struct {
|
||||
@@ -1063,9 +1063,9 @@ func TestRBAC(t *testing.T) {
|
||||
name: "owner can create access source",
|
||||
role: "owner",
|
||||
client: owner,
|
||||
query: createAccessSourceMutation,
|
||||
query: createAccessReviewSourceMutation,
|
||||
variables: func() map[string]any {
|
||||
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessSource")}}
|
||||
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessReviewSource")}}
|
||||
},
|
||||
shouldAllow: true,
|
||||
},
|
||||
@@ -1073,9 +1073,9 @@ func TestRBAC(t *testing.T) {
|
||||
name: "admin can create access source",
|
||||
role: "admin",
|
||||
client: admin,
|
||||
query: createAccessSourceMutation,
|
||||
query: createAccessReviewSourceMutation,
|
||||
variables: func() map[string]any {
|
||||
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessSource")}}
|
||||
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessReviewSource")}}
|
||||
},
|
||||
shouldAllow: true,
|
||||
},
|
||||
@@ -1083,9 +1083,9 @@ func TestRBAC(t *testing.T) {
|
||||
name: "viewer cannot create access source",
|
||||
role: "viewer",
|
||||
client: viewer,
|
||||
query: createAccessSourceMutation,
|
||||
query: createAccessReviewSourceMutation,
|
||||
variables: func() map[string]any {
|
||||
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessSource")}}
|
||||
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessReviewSource")}}
|
||||
},
|
||||
shouldAllow: false,
|
||||
},
|
||||
@@ -1094,9 +1094,9 @@ func TestRBAC(t *testing.T) {
|
||||
name: "owner can update access source",
|
||||
role: "owner",
|
||||
client: owner,
|
||||
query: updateAccessSourceMutation,
|
||||
query: updateAccessReviewSourceMutation,
|
||||
variables: func() map[string]any {
|
||||
return map[string]any{"input": map[string]any{"accessSourceId": accessSourceID, "name": factory.SafeName("Updated Source")}}
|
||||
return map[string]any{"input": map[string]any{"accessReviewSourceId": accessReviewSourceID, "name": factory.SafeName("Updated Source")}}
|
||||
},
|
||||
shouldAllow: true,
|
||||
},
|
||||
@@ -1104,9 +1104,9 @@ func TestRBAC(t *testing.T) {
|
||||
name: "admin can update access source",
|
||||
role: "admin",
|
||||
client: admin,
|
||||
query: updateAccessSourceMutation,
|
||||
query: updateAccessReviewSourceMutation,
|
||||
variables: func() map[string]any {
|
||||
return map[string]any{"input": map[string]any{"accessSourceId": accessSourceID, "name": factory.SafeName("Updated Source")}}
|
||||
return map[string]any{"input": map[string]any{"accessReviewSourceId": accessReviewSourceID, "name": factory.SafeName("Updated Source")}}
|
||||
},
|
||||
shouldAllow: true,
|
||||
},
|
||||
@@ -1114,9 +1114,9 @@ func TestRBAC(t *testing.T) {
|
||||
name: "viewer cannot update access source",
|
||||
role: "viewer",
|
||||
client: viewer,
|
||||
query: updateAccessSourceMutation,
|
||||
query: updateAccessReviewSourceMutation,
|
||||
variables: func() map[string]any {
|
||||
return map[string]any{"input": map[string]any{"accessSourceId": accessSourceID, "name": factory.SafeName("Updated Source")}}
|
||||
return map[string]any{"input": map[string]any{"accessReviewSourceId": accessReviewSourceID, "name": factory.SafeName("Updated Source")}}
|
||||
},
|
||||
shouldAllow: false,
|
||||
},
|
||||
@@ -1125,10 +1125,10 @@ func TestRBAC(t *testing.T) {
|
||||
name: "owner can delete access source",
|
||||
role: "owner",
|
||||
client: owner,
|
||||
query: deleteAccessSourceMutation,
|
||||
query: deleteAccessReviewSourceMutation,
|
||||
variables: func() map[string]any {
|
||||
id := factory.NewAccessSource(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create()
|
||||
return map[string]any{"input": map[string]any{"accessSourceId": id}}
|
||||
id := factory.NewAccessReviewSource(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create()
|
||||
return map[string]any{"input": map[string]any{"accessReviewSourceId": id}}
|
||||
},
|
||||
shouldAllow: true,
|
||||
},
|
||||
@@ -1136,10 +1136,10 @@ func TestRBAC(t *testing.T) {
|
||||
name: "admin can delete access source",
|
||||
role: "admin",
|
||||
client: admin,
|
||||
query: deleteAccessSourceMutation,
|
||||
query: deleteAccessReviewSourceMutation,
|
||||
variables: func() map[string]any {
|
||||
id := factory.NewAccessSource(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create()
|
||||
return map[string]any{"input": map[string]any{"accessSourceId": id}}
|
||||
id := factory.NewAccessReviewSource(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create()
|
||||
return map[string]any{"input": map[string]any{"accessReviewSourceId": id}}
|
||||
},
|
||||
shouldAllow: true,
|
||||
},
|
||||
@@ -1147,10 +1147,10 @@ func TestRBAC(t *testing.T) {
|
||||
name: "viewer cannot delete access source",
|
||||
role: "viewer",
|
||||
client: viewer,
|
||||
query: deleteAccessSourceMutation,
|
||||
query: deleteAccessReviewSourceMutation,
|
||||
variables: func() map[string]any {
|
||||
id := factory.NewAccessSource(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create()
|
||||
return map[string]any{"input": map[string]any{"accessSourceId": id}}
|
||||
id := factory.NewAccessReviewSource(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create()
|
||||
return map[string]any{"input": map[string]any{"accessReviewSourceId": id}}
|
||||
},
|
||||
shouldAllow: false,
|
||||
},
|
||||
@@ -1159,7 +1159,7 @@ func TestRBAC(t *testing.T) {
|
||||
name: "owner can list access sources",
|
||||
role: "owner",
|
||||
client: owner,
|
||||
query: listAccessSourcesQuery,
|
||||
query: listAccessReviewSourcesQuery,
|
||||
variables: func() map[string]any {
|
||||
return map[string]any{"id": owner.GetOrganizationID().String()}
|
||||
},
|
||||
@@ -1169,7 +1169,7 @@ func TestRBAC(t *testing.T) {
|
||||
name: "admin can list access sources",
|
||||
role: "admin",
|
||||
client: admin,
|
||||
query: listAccessSourcesQuery,
|
||||
query: listAccessReviewSourcesQuery,
|
||||
variables: func() map[string]any {
|
||||
return map[string]any{"id": owner.GetOrganizationID().String()}
|
||||
},
|
||||
@@ -1179,7 +1179,7 @@ func TestRBAC(t *testing.T) {
|
||||
name: "viewer can list access sources",
|
||||
role: "viewer",
|
||||
client: viewer,
|
||||
query: listAccessSourcesQuery,
|
||||
query: listAccessReviewSourcesQuery,
|
||||
variables: func() map[string]any {
|
||||
return map[string]any{"id": owner.GetOrganizationID().String()}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user