Add access review end-to-end tests

Add e2e tests for access review API and connector
operations covering RBAC, tenant isolation, and the
full campaign lifecycle.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
Aurélien Sibiril
2026-04-02 11:51:58 +02:00
parent 61b0559a07
commit a4bf885952
4 changed files with 2048 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,330 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package console_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestConnectorProviderInfos(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
orgID := owner.GetOrganizationID().String()
t.Run("returns provider infos", func(t *testing.T) {
t.Parallel()
const query = `
query($id: ID!) {
node(id: $id) {
... on Organization {
connectorProviderInfos {
provider
displayName
oauthConfigured
apiKeySupported
clientCredentialsSupported
extraSettings {
key
label
required
}
}
}
}
}
`
var result struct {
Node struct {
ConnectorProviderInfos []struct {
Provider string `json:"provider"`
DisplayName string `json:"displayName"`
OauthConfigured bool `json:"oauthConfigured"`
APIKeySupported bool `json:"apiKeySupported"`
ClientCredentialsSupported bool `json:"clientCredentialsSupported"`
ExtraSettings []struct {
Key string `json:"key"`
Label string `json:"label"`
Required bool `json:"required"`
} `json:"extraSettings"`
} `json:"connectorProviderInfos"`
} `json:"node"`
}
err := owner.Execute(query, map[string]any{"id": orgID}, &result)
require.NoError(t, err)
infos := result.Node.ConnectorProviderInfos
assert.NotEmpty(t, infos)
providerNames := make(map[string]bool)
for _, info := range infos {
assert.NotEmpty(t, info.Provider)
assert.NotEmpty(t, info.DisplayName)
assert.NotNil(t, info.ExtraSettings)
providerNames[info.Provider] = true
}
assert.True(t, providerNames["SLACK"], "expected SLACK provider to be present")
assert.True(t, providerNames["HUBSPOT"], "expected HUBSPOT provider to be present")
})
t.Run("viewer can list provider infos", func(t *testing.T) {
t.Parallel()
viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
const query = `
query($id: ID!) {
node(id: $id) {
... on Organization {
connectorProviderInfos {
provider
displayName
}
}
}
}
`
var result struct {
Node struct {
ConnectorProviderInfos []struct {
Provider string `json:"provider"`
DisplayName string `json:"displayName"`
} `json:"connectorProviderInfos"`
} `json:"node"`
}
err := viewer.Execute(query, map[string]any{
"id": viewer.GetOrganizationID().String(),
}, &result)
require.NoError(t, err)
assert.NotEmpty(t, result.Node.ConnectorProviderInfos)
})
}
func TestCreateAPIKeyConnector(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
orgID := owner.GetOrganizationID().String()
const query = `
mutation($input: CreateAPIKeyConnectorInput!) {
createAPIKeyConnector(input: $input) {
connector {
id
provider
}
}
}
`
var result struct {
CreateAPIKeyConnector struct {
Connector struct {
ID string `json:"id"`
Provider string `json:"provider"`
} `json:"connector"`
} `json:"createAPIKeyConnector"`
}
err := owner.Execute(query, map[string]any{
"input": map[string]any{
"organizationId": orgID,
"provider": "BREX",
"apiKey": "test-key-123",
},
}, &result)
require.NoError(t, err)
connector := result.CreateAPIKeyConnector.Connector
assert.NotEmpty(t, connector.ID)
assert.Equal(t, "BREX", connector.Provider)
}
func TestCreateAPIKeyConnectorWithSettings(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
orgID := owner.GetOrganizationID().String()
const query = `
mutation($input: CreateAPIKeyConnectorInput!) {
createAPIKeyConnector(input: $input) {
connector {
id
provider
}
}
}
`
var result struct {
CreateAPIKeyConnector struct {
Connector struct {
ID string `json:"id"`
Provider string `json:"provider"`
} `json:"connector"`
} `json:"createAPIKeyConnector"`
}
err := owner.Execute(query, map[string]any{
"input": map[string]any{
"organizationId": orgID,
"provider": "TALLY",
"apiKey": "test-key",
"tallyOrganizationId": "org-123",
},
}, &result)
require.NoError(t, err)
connector := result.CreateAPIKeyConnector.Connector
assert.NotEmpty(t, connector.ID)
assert.Equal(t, "TALLY", connector.Provider)
}
func TestCreateClientCredentialsConnector(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
orgID := owner.GetOrganizationID().String()
const query = `
mutation($input: CreateClientCredentialsConnectorInput!) {
createClientCredentialsConnector(input: $input) {
connector {
id
provider
}
}
}
`
var result struct {
CreateClientCredentialsConnector struct {
Connector struct {
ID string `json:"id"`
Provider string `json:"provider"`
} `json:"connector"`
} `json:"createClientCredentialsConnector"`
}
err := owner.Execute(query, map[string]any{
"input": map[string]any{
"organizationId": orgID,
"provider": "ONE_PASSWORD",
"clientId": "test-client",
"clientSecret": "test-secret",
"tokenUrl": "https://api.1password.com/v1beta1/users/oauth2/token",
"onePasswordAccountId": "ACC123",
"onePasswordRegion": "US",
},
}, &result)
require.NoError(t, err)
connector := result.CreateClientCredentialsConnector.Connector
assert.NotEmpty(t, connector.ID)
assert.Equal(t, "ONE_PASSWORD", connector.Provider)
}
func TestDeleteConnector(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
orgID := owner.GetOrganizationID().String()
// First, create a connector to delete.
const createQuery = `
mutation($input: CreateAPIKeyConnectorInput!) {
createAPIKeyConnector(input: $input) {
connector {
id
provider
}
}
}
`
var createResult struct {
CreateAPIKeyConnector struct {
Connector struct {
ID string `json:"id"`
Provider string `json:"provider"`
} `json:"connector"`
} `json:"createAPIKeyConnector"`
}
err := owner.Execute(createQuery, map[string]any{
"input": map[string]any{
"organizationId": orgID,
"provider": "BREX",
"apiKey": "key-to-delete",
},
}, &createResult)
require.NoError(t, err)
connectorID := createResult.CreateAPIKeyConnector.Connector.ID
require.NotEmpty(t, connectorID)
// Now delete the connector.
const deleteQuery = `
mutation($input: DeleteConnectorInput!) {
deleteConnector(input: $input) {
deletedConnectorId
}
}
`
var deleteResult struct {
DeleteConnector struct {
DeletedConnectorID string `json:"deletedConnectorId"`
} `json:"deleteConnector"`
}
err = owner.Execute(deleteQuery, map[string]any{
"input": map[string]any{
"connectorId": connectorID,
},
}, &deleteResult)
require.NoError(t, err)
assert.Equal(t, connectorID, deleteResult.DeleteConnector.DeletedConnectorID)
}
func TestCreateAPIKeyConnector_RBAC(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
t.Run("viewer cannot create connector", func(t *testing.T) {
t.Parallel()
_, err := viewer.Do(`
mutation($input: CreateAPIKeyConnectorInput!) {
createAPIKeyConnector(input: $input) {
connector { id }
}
}
`, map[string]any{
"input": map[string]any{
"organizationId": viewer.GetOrganizationID().String(),
"provider": "BREX",
"apiKey": "test-key",
},
})
testutil.RequireForbiddenError(t, err, "viewer should not be able to create connector")
})
}

View File

@@ -204,6 +204,66 @@ const (
}
}`
createAccessSourceMutation = `
mutation CreateAccessSource($input: CreateAccessSourceInput!) {
createAccessSource(input: $input) {
accessSourceEdge { node { id } }
}
}`
updateAccessSourceMutation = `
mutation UpdateAccessSource($input: UpdateAccessSourceInput!) {
updateAccessSource(input: $input) {
accessSource { id }
}
}`
deleteAccessSourceMutation = `
mutation DeleteAccessSource($input: DeleteAccessSourceInput!) {
deleteAccessSource(input: $input) {
deletedAccessSourceId
}
}`
listAccessSourcesQuery = `
query GetAccessSources($id: ID!) {
node(id: $id) {
... on Organization {
accessSources(first: 10) { totalCount }
}
}
}`
createAccessReviewCampaignMutation = `
mutation CreateCampaign($input: CreateAccessReviewCampaignInput!) {
createAccessReviewCampaign(input: $input) {
accessReviewCampaignEdge { node { id } }
}
}`
updateAccessReviewCampaignMutation = `
mutation UpdateCampaign($input: UpdateAccessReviewCampaignInput!) {
updateAccessReviewCampaign(input: $input) {
accessReviewCampaign { id }
}
}`
deleteAccessReviewCampaignMutation = `
mutation DeleteCampaign($input: DeleteAccessReviewCampaignInput!) {
deleteAccessReviewCampaign(input: $input) {
deletedAccessReviewCampaignId
}
}`
listAccessReviewCampaignsQuery = `
query GetCampaigns($id: ID!) {
node(id: $id) {
... on Organization {
accessReviewCampaigns(first: 10) { totalCount }
}
}
}`
updateOrganizationMutation = `
mutation UpdateOrganization($input: UpdateOrganizationInput!) {
updateOrganization(input: $input) {
@@ -245,6 +305,8 @@ func TestRBAC(t *testing.T) {
taskID := factory.NewTask(owner, measureID).WithName("RBAC Test Task").Create()
riskID := factory.NewRisk(owner).WithName("RBAC Test Risk").Create()
vendorID := factory.NewVendor(owner).WithName("RBAC Test Vendor").Create()
accessSourceID := factory.NewAccessSource(owner, owner.GetOrganizationID().String()).WithName("RBAC Test Source").Create()
accessReviewCampaignID := factory.NewAccessReviewCampaign(owner, owner.GetOrganizationID().String()).WithName("RBAC Test Campaign").Create()
tests := []struct {
name string
@@ -996,6 +1058,260 @@ func TestRBAC(t *testing.T) {
},
shouldAllow: true,
},
// Access Source - Create
{
name: "owner can create access source",
role: "owner",
client: owner,
query: createAccessSourceMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessSource")}}
},
shouldAllow: true,
},
{
name: "admin can create access source",
role: "admin",
client: admin,
query: createAccessSourceMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessSource")}}
},
shouldAllow: true,
},
{
name: "viewer cannot create access source",
role: "viewer",
client: viewer,
query: createAccessSourceMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("AccessSource")}}
},
shouldAllow: false,
},
// Access Source - Update
{
name: "owner can update access source",
role: "owner",
client: owner,
query: updateAccessSourceMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"accessSourceId": accessSourceID, "name": factory.SafeName("Updated Source")}}
},
shouldAllow: true,
},
{
name: "admin can update access source",
role: "admin",
client: admin,
query: updateAccessSourceMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"accessSourceId": accessSourceID, "name": factory.SafeName("Updated Source")}}
},
shouldAllow: true,
},
{
name: "viewer cannot update access source",
role: "viewer",
client: viewer,
query: updateAccessSourceMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"accessSourceId": accessSourceID, "name": factory.SafeName("Updated Source")}}
},
shouldAllow: false,
},
// Access Source - Delete
{
name: "owner can delete access source",
role: "owner",
client: owner,
query: deleteAccessSourceMutation,
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}}
},
shouldAllow: true,
},
{
name: "admin can delete access source",
role: "admin",
client: admin,
query: deleteAccessSourceMutation,
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}}
},
shouldAllow: true,
},
{
name: "viewer cannot delete access source",
role: "viewer",
client: viewer,
query: deleteAccessSourceMutation,
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}}
},
shouldAllow: false,
},
// Access Source - List
{
name: "owner can list access sources",
role: "owner",
client: owner,
query: listAccessSourcesQuery,
variables: func() map[string]any {
return map[string]any{"id": owner.GetOrganizationID().String()}
},
shouldAllow: true,
},
{
name: "admin can list access sources",
role: "admin",
client: admin,
query: listAccessSourcesQuery,
variables: func() map[string]any {
return map[string]any{"id": owner.GetOrganizationID().String()}
},
shouldAllow: true,
},
{
name: "viewer can list access sources",
role: "viewer",
client: viewer,
query: listAccessSourcesQuery,
variables: func() map[string]any {
return map[string]any{"id": owner.GetOrganizationID().String()}
},
shouldAllow: true,
},
// Access Review Campaign - Create
{
name: "owner can create access review campaign",
role: "owner",
client: owner,
query: createAccessReviewCampaignMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Campaign")}}
},
shouldAllow: true,
},
{
name: "admin can create access review campaign",
role: "admin",
client: admin,
query: createAccessReviewCampaignMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Campaign")}}
},
shouldAllow: true,
},
{
name: "viewer cannot create access review campaign",
role: "viewer",
client: viewer,
query: createAccessReviewCampaignMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "name": factory.SafeName("Campaign")}}
},
shouldAllow: false,
},
// Access Review Campaign - Update
{
name: "owner can update access review campaign",
role: "owner",
client: owner,
query: updateAccessReviewCampaignMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"accessReviewCampaignId": accessReviewCampaignID, "name": factory.SafeName("Updated Campaign")}}
},
shouldAllow: true,
},
{
name: "admin can update access review campaign",
role: "admin",
client: admin,
query: updateAccessReviewCampaignMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"accessReviewCampaignId": accessReviewCampaignID, "name": factory.SafeName("Updated Campaign")}}
},
shouldAllow: true,
},
{
name: "viewer cannot update access review campaign",
role: "viewer",
client: viewer,
query: updateAccessReviewCampaignMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"accessReviewCampaignId": accessReviewCampaignID, "name": factory.SafeName("Updated Campaign")}}
},
shouldAllow: false,
},
// Access Review Campaign - Delete
{
name: "owner can delete access review campaign",
role: "owner",
client: owner,
query: deleteAccessReviewCampaignMutation,
variables: func() map[string]any {
id := factory.NewAccessReviewCampaign(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create()
return map[string]any{"input": map[string]any{"accessReviewCampaignId": id}}
},
shouldAllow: true,
},
{
name: "admin can delete access review campaign",
role: "admin",
client: admin,
query: deleteAccessReviewCampaignMutation,
variables: func() map[string]any {
id := factory.NewAccessReviewCampaign(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create()
return map[string]any{"input": map[string]any{"accessReviewCampaignId": id}}
},
shouldAllow: true,
},
{
name: "viewer cannot delete access review campaign",
role: "viewer",
client: viewer,
query: deleteAccessReviewCampaignMutation,
variables: func() map[string]any {
id := factory.NewAccessReviewCampaign(owner, owner.GetOrganizationID().String()).WithName(factory.SafeName("ToDelete")).Create()
return map[string]any{"input": map[string]any{"accessReviewCampaignId": id}}
},
shouldAllow: false,
},
// Access Review Campaign - List
{
name: "owner can list access review campaigns",
role: "owner",
client: owner,
query: listAccessReviewCampaignsQuery,
variables: func() map[string]any {
return map[string]any{"id": owner.GetOrganizationID().String()}
},
shouldAllow: true,
},
{
name: "admin can list access review campaigns",
role: "admin",
client: admin,
query: listAccessReviewCampaignsQuery,
variables: func() map[string]any {
return map[string]any{"id": owner.GetOrganizationID().String()}
},
shouldAllow: true,
},
{
name: "viewer can list access review campaigns",
role: "viewer",
client: viewer,
query: listAccessReviewCampaignsQuery,
variables: func() map[string]any {
return map[string]any{"id": owner.GetOrganizationID().String()}
},
shouldAllow: true,
},
{
name: "owner can update organization",
role: "owner",

View File

@@ -984,3 +984,139 @@ func (b *ProcessingActivityBuilder) WithSpecialOrCriminalData(value string) *Pro
func (b *ProcessingActivityBuilder) Create() string {
return CreateProcessingActivity(b.client, b.attrs)
}
func CreateAccessSource(c *testutil.Client, organizationID string, attrs ...Attrs) string {
c.T.Helper()
var a Attrs
if len(attrs) > 0 {
a = attrs[0]
}
const query = `
mutation($input: CreateAccessSourceInput!) {
createAccessSource(input: $input) {
accessSourceEdge {
node { id }
}
}
}
`
input := map[string]any{
"organizationId": organizationID,
"name": a.getString("name", SafeName("AccessSource")),
}
if csvData := a.getStringPtr("csvData"); csvData != nil {
input["csvData"] = *csvData
}
if connectorID := a.getStringPtr("connectorId"); connectorID != nil {
input["connectorId"] = *connectorID
}
var result struct {
CreateAccessSource struct {
AccessSourceEdge struct {
Node struct {
ID string `json:"id"`
} `json:"node"`
} `json:"accessSourceEdge"`
} `json:"createAccessSource"`
}
err := c.Execute(query, map[string]any{"input": input}, &result)
require.NoError(c.T, err, "createAccessSource mutation failed")
return result.CreateAccessSource.AccessSourceEdge.Node.ID
}
type AccessSourceBuilder struct {
client *testutil.Client
organizationID string
attrs Attrs
}
func NewAccessSource(c *testutil.Client, organizationID string) *AccessSourceBuilder {
return &AccessSourceBuilder{client: c, organizationID: organizationID, attrs: Attrs{}}
}
func (b *AccessSourceBuilder) WithName(name string) *AccessSourceBuilder {
b.attrs["name"] = name
return b
}
func (b *AccessSourceBuilder) WithCsvData(csvData string) *AccessSourceBuilder {
b.attrs["csvData"] = csvData
return b
}
func (b *AccessSourceBuilder) Create() string {
return CreateAccessSource(b.client, b.organizationID, b.attrs)
}
func CreateAccessReviewCampaign(c *testutil.Client, organizationID string, attrs ...Attrs) string {
c.T.Helper()
var a Attrs
if len(attrs) > 0 {
a = attrs[0]
}
const query = `
mutation($input: CreateAccessReviewCampaignInput!) {
createAccessReviewCampaign(input: $input) {
accessReviewCampaignEdge {
node { id }
}
}
}
`
input := map[string]any{
"organizationId": organizationID,
"name": a.getString("name", SafeName("Campaign")),
}
if v, ok := a["accessSourceIds"]; ok {
input["accessSourceIds"] = v
}
var result struct {
CreateAccessReviewCampaign struct {
AccessReviewCampaignEdge struct {
Node struct {
ID string `json:"id"`
} `json:"node"`
} `json:"accessReviewCampaignEdge"`
} `json:"createAccessReviewCampaign"`
}
err := c.Execute(query, map[string]any{"input": input}, &result)
require.NoError(c.T, err, "createAccessReviewCampaign mutation failed")
return result.CreateAccessReviewCampaign.AccessReviewCampaignEdge.Node.ID
}
type AccessReviewCampaignBuilder struct {
client *testutil.Client
organizationID string
attrs Attrs
}
func NewAccessReviewCampaign(c *testutil.Client, organizationID string) *AccessReviewCampaignBuilder {
return &AccessReviewCampaignBuilder{client: c, organizationID: organizationID, attrs: Attrs{}}
}
func (b *AccessReviewCampaignBuilder) WithName(name string) *AccessReviewCampaignBuilder {
b.attrs["name"] = name
return b
}
func (b *AccessReviewCampaignBuilder) WithAccessSourceIDs(ids []string) *AccessReviewCampaignBuilder {
b.attrs["accessSourceIds"] = ids
return b
}
func (b *AccessReviewCampaignBuilder) Create() string {
return CreateAccessReviewCampaign(b.client, b.organizationID, b.attrs)
}