Files
probo/e2e/console/risk_assessment_test.go
Sacha Al Himdani b9262b5150 Add risk assessment system
Introduce a hierarchical risk assessment model with six entity types:

- Risk Assessment: top-level container scoped to an organization
- Risk Assessment Scope: sub-container for scoping threat modeling
  exercises within an assessment
- Risk Assessment Node: DFD elements typed as ENTITY, BOUNDARY,
  ASSET, or DATA within a scope
- Risk Assessment Process: directed data flows between two nodes
- Risk Assessment Threat: descriptive threats attached to a process
  with a free-text category (e.g. Confidentiality, Integrity)
- Risk Scenario: thin join linking a threat to a risk from the
  register, carrying only a name and description

Risk scoring (likelihood, impact, treatment) remains on the existing
Risk entity. Threats are purely descriptive. Risk Scenarios connect
the threat model to the risk register without duplicating scores.

Backend: migration with PG enum for node types, coredata structs,
service layer with full CRUD and validation, GraphQL schema with
18 mutations and paginated connections, authorization actions and
policies, and base_resolvers.go Node dispatch for all entity types.

Frontend: Risk Assessments list page with create dialog, detail page
showing scopes as cards with nodes/processes/threats tables, inline
create/edit/delete actions on all entities, and a Scenarios tab on
the Risk detail page linking threats to risks. Existing RiskGraph.ts
hook file removed in favor of colocated queries in page files.

E2E tests cover CRUD for all entity types, RBAC, and tenant
isolation.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-19 21:44:27 +02:00

763 lines
23 KiB
Go

// 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/factory"
"go.probo.inc/probo/e2e/internal/testutil"
)
func TestRiskAssessment_Create(t *testing.T) {
t.Parallel()
t.Run("with required fields", func(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
var result struct {
CreateRiskAssessment struct {
RiskAssessmentEdge struct {
Node struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"node"`
} `json:"riskAssessmentEdge"`
} `json:"createRiskAssessment"`
}
err := owner.Execute(`
mutation($input: CreateRiskAssessmentInput!) {
createRiskAssessment(input: $input) {
riskAssessmentEdge { node { id name } }
}
}
`, map[string]any{
"input": map[string]any{
"organizationId": owner.GetOrganizationID().String(),
"name": "Platform Threat Model",
},
}, &result)
require.NoError(t, err)
assert.NotEmpty(t, result.CreateRiskAssessment.RiskAssessmentEdge.Node.ID)
assert.Equal(t, "Platform Threat Model", result.CreateRiskAssessment.RiskAssessmentEdge.Node.Name)
})
}
func TestRiskAssessment_Delete(t *testing.T) {
t.Parallel()
t.Run("cascades to scopes", func(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
_, err := owner.Do(`
mutation($input: DeleteRiskAssessmentInput!) {
deleteRiskAssessment(input: $input) { deletedRiskAssessmentId }
}
`, map[string]any{"input": map[string]any{"riskAssessmentId": raID}})
require.NoError(t, err)
var result struct {
Node *struct {
ID string `json:"id"`
} `json:"node"`
}
err = owner.Execute(`query($id: ID!) { node(id: $id) { ... on RiskAssessmentScope { id } } }`,
map[string]any{"id": scopeID}, &result)
testutil.AssertNodeNotAccessible(t, err, result.Node == nil, "RiskAssessmentScope")
})
}
func TestRiskAssessmentScope_CRUD(t *testing.T) {
t.Parallel()
t.Run("create and list via assessment", func(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
factory.CreateRiskAssessmentScope(owner, raID, factory.Attrs{"name": "API scope"})
factory.CreateRiskAssessmentScope(owner, raID, factory.Attrs{"name": "Infra scope"})
var result struct {
Node struct {
Scopes struct {
TotalCount int `json:"totalCount"`
Edges []struct {
Node struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"node"`
} `json:"edges"`
} `json:"scopes"`
} `json:"node"`
}
err := owner.Execute(`
query($id: ID!) {
node(id: $id) {
... on RiskAssessment {
scopes(first: 10) {
totalCount
edges { node { id name } }
}
}
}
}
`, map[string]any{"id": raID}, &result)
require.NoError(t, err)
assert.Equal(t, 2, result.Node.Scopes.TotalCount)
assert.Len(t, result.Node.Scopes.Edges, 2)
})
}
func TestRiskAssessmentNode_Create(t *testing.T) {
t.Parallel()
for _, nodeType := range []string{"ENTITY", "BOUNDARY", "ASSET", "DATA"} {
t.Run("nodeType="+nodeType, func(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
var result struct {
CreateRiskAssessmentNode struct {
RiskAssessmentNodeEdge struct {
Node struct {
ID string `json:"id"`
NodeType string `json:"nodeType"`
} `json:"node"`
} `json:"riskAssessmentNodeEdge"`
} `json:"createRiskAssessmentNode"`
}
err := owner.Execute(`
mutation($input: CreateRiskAssessmentNodeInput!) {
createRiskAssessmentNode(input: $input) {
riskAssessmentNodeEdge { node { id nodeType } }
}
}
`, map[string]any{
"input": map[string]any{
"riskAssessmentScopeId": scopeID,
"nodeType": nodeType,
"name": "Node-" + nodeType,
},
}, &result)
require.NoError(t, err)
assert.Equal(t, nodeType, result.CreateRiskAssessmentNode.RiskAssessmentNodeEdge.Node.NodeType)
})
}
}
func TestRiskAssessmentProcess_Create(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
src := factory.CreateRiskAssessmentNode(owner, scopeID, factory.Attrs{"nodeType": "ENTITY"})
dst := factory.CreateRiskAssessmentNode(owner, scopeID, factory.Attrs{"nodeType": "ASSET"})
var result struct {
CreateRiskAssessmentProcess struct {
RiskAssessmentProcessEdge struct {
Node struct {
ID string `json:"id"`
SourceNodeID string `json:"sourceNodeId"`
TargetNodeID string `json:"targetNodeId"`
Name string `json:"name"`
} `json:"node"`
} `json:"riskAssessmentProcessEdge"`
} `json:"createRiskAssessmentProcess"`
}
err := owner.Execute(`
mutation($input: CreateRiskAssessmentProcessInput!) {
createRiskAssessmentProcess(input: $input) {
riskAssessmentProcessEdge { node { id sourceNodeId targetNodeId name } }
}
}
`, map[string]any{
"input": map[string]any{
"riskAssessmentScopeId": scopeID,
"sourceNodeId": src,
"targetNodeId": dst,
"name": "User → API",
},
}, &result)
require.NoError(t, err)
assert.Equal(t, src, result.CreateRiskAssessmentProcess.RiskAssessmentProcessEdge.Node.SourceNodeID)
assert.Equal(t, dst, result.CreateRiskAssessmentProcess.RiskAssessmentProcessEdge.Node.TargetNodeID)
}
func TestRiskAssessmentThreat_Create(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
src := factory.CreateRiskAssessmentNode(owner, scopeID)
dst := factory.CreateRiskAssessmentNode(owner, scopeID)
processID := factory.CreateRiskAssessmentProcess(owner, scopeID, src, dst)
var result struct {
CreateRiskAssessmentThreat struct {
RiskAssessmentThreatEdge struct {
Node struct {
ID string `json:"id"`
ProcessID string `json:"processId"`
Category string `json:"category"`
} `json:"node"`
} `json:"riskAssessmentThreatEdge"`
} `json:"createRiskAssessmentThreat"`
}
err := owner.Execute(`
mutation($input: CreateRiskAssessmentThreatInput!) {
createRiskAssessmentThreat(input: $input) {
riskAssessmentThreatEdge { node { id processId category } }
}
}
`, map[string]any{
"input": map[string]any{
"riskAssessmentScopeId": scopeID,
"processId": processID,
"name": "SQL injection",
"category": "Confidentiality",
},
}, &result)
require.NoError(t, err)
assert.Equal(t, processID, result.CreateRiskAssessmentThreat.RiskAssessmentThreatEdge.Node.ProcessID)
assert.Equal(t, "Confidentiality", result.CreateRiskAssessmentThreat.RiskAssessmentThreatEdge.Node.Category)
}
func TestRiskAssessmentScenario_Create(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
var result struct {
CreateRiskAssessmentScenario struct {
RiskAssessmentScenarioEdge struct {
Node struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"node"`
} `json:"riskAssessmentScenarioEdge"`
} `json:"createRiskAssessmentScenario"`
}
err := owner.Execute(`
mutation($input: CreateRiskAssessmentScenarioInput!) {
createRiskAssessmentScenario(input: $input) {
riskAssessmentScenarioEdge { node { id name } }
}
}
`, map[string]any{
"input": map[string]any{
"riskAssessmentScopeId": scopeID,
"name": "SQL injection impacts data breach risk",
},
}, &result)
require.NoError(t, err)
assert.NotEmpty(t, result.CreateRiskAssessmentScenario.RiskAssessmentScenarioEdge.Node.ID)
assert.Equal(t, "SQL injection impacts data breach risk", result.CreateRiskAssessmentScenario.RiskAssessmentScenarioEdge.Node.Name)
}
func TestRiskAssessmentScenario_ListViaRisk(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
riskID := factory.CreateRisk(owner)
s1 := factory.CreateRiskAssessmentScenario(owner, scopeID, factory.Attrs{"name": "S1"})
s2 := factory.CreateRiskAssessmentScenario(owner, scopeID, factory.Attrs{"name": "S2"})
factory.LinkRiskAssessmentScenarioRisk(owner, s1, riskID)
factory.LinkRiskAssessmentScenarioRisk(owner, s2, riskID)
var result struct {
Node struct {
Scenarios struct {
TotalCount int `json:"totalCount"`
Edges []struct {
Node struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"node"`
} `json:"edges"`
} `json:"scenarios"`
} `json:"node"`
}
err := owner.Execute(`
query($id: ID!) {
node(id: $id) {
... on Risk {
scenarios(first: 10) {
totalCount
edges { node { id name } }
}
}
}
}
`, map[string]any{"id": riskID}, &result)
require.NoError(t, err)
assert.Equal(t, 2, result.Node.Scenarios.TotalCount)
assert.Len(t, result.Node.Scenarios.Edges, 2)
}
func TestRiskAssessmentScenario_ListViaScope(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
factory.CreateRiskAssessmentScenario(owner, scopeID, factory.Attrs{"name": "Scenario A"})
factory.CreateRiskAssessmentScenario(owner, scopeID, factory.Attrs{"name": "Scenario B"})
var result struct {
Node struct {
Scenarios struct {
TotalCount int `json:"totalCount"`
Edges []struct {
Node struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"node"`
} `json:"edges"`
} `json:"scenarios"`
} `json:"node"`
}
err := owner.Execute(`
query($id: ID!) {
node(id: $id) {
... on RiskAssessmentScope {
scenarios(first: 10) {
totalCount
edges { node { id name } }
}
}
}
}
`, map[string]any{"id": scopeID}, &result)
require.NoError(t, err)
assert.Equal(t, 2, result.Node.Scenarios.TotalCount)
assert.Len(t, result.Node.Scenarios.Edges, 2)
}
func TestRiskAssessment_Update(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner, factory.Attrs{"name": "Original"})
var result struct {
UpdateRiskAssessment struct {
RiskAssessment struct {
ID string `json:"id"`
Name string `json:"name"`
Description *string `json:"description"`
} `json:"riskAssessment"`
} `json:"updateRiskAssessment"`
}
err := owner.Execute(`
mutation($input: UpdateRiskAssessmentInput!) {
updateRiskAssessment(input: $input) {
riskAssessment { id name description }
}
}
`, map[string]any{
"input": map[string]any{
"id": raID,
"name": "Updated",
"description": "New description",
},
}, &result)
require.NoError(t, err)
assert.Equal(t, "Updated", result.UpdateRiskAssessment.RiskAssessment.Name)
require.NotNil(t, result.UpdateRiskAssessment.RiskAssessment.Description)
assert.Equal(t, "New description", *result.UpdateRiskAssessment.RiskAssessment.Description)
}
func TestRiskAssessmentScope_Update(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID, factory.Attrs{"name": "Original"})
var result struct {
UpdateRiskAssessmentScope struct {
RiskAssessmentScope struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"riskAssessmentScope"`
} `json:"updateRiskAssessmentScope"`
}
err := owner.Execute(`
mutation($input: UpdateRiskAssessmentScopeInput!) {
updateRiskAssessmentScope(input: $input) {
riskAssessmentScope { id name }
}
}
`, map[string]any{
"input": map[string]any{
"id": scopeID,
"name": "Updated scope",
},
}, &result)
require.NoError(t, err)
assert.Equal(t, "Updated scope", result.UpdateRiskAssessmentScope.RiskAssessmentScope.Name)
}
func TestRiskAssessmentNode_Update(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
nodeID := factory.CreateRiskAssessmentNode(owner, scopeID, factory.Attrs{"nodeType": "ENTITY", "name": "Original"})
var result struct {
UpdateRiskAssessmentNode struct {
RiskAssessmentNode struct {
ID string `json:"id"`
Name string `json:"name"`
NodeType string `json:"nodeType"`
} `json:"riskAssessmentNode"`
} `json:"updateRiskAssessmentNode"`
}
err := owner.Execute(`
mutation($input: UpdateRiskAssessmentNodeInput!) {
updateRiskAssessmentNode(input: $input) {
riskAssessmentNode { id name nodeType }
}
}
`, map[string]any{
"input": map[string]any{
"id": nodeID,
"name": "Updated node",
"nodeType": "BOUNDARY",
},
}, &result)
require.NoError(t, err)
assert.Equal(t, "Updated node", result.UpdateRiskAssessmentNode.RiskAssessmentNode.Name)
assert.Equal(t, "BOUNDARY", result.UpdateRiskAssessmentNode.RiskAssessmentNode.NodeType)
}
func TestRiskAssessmentProcess_Update(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
src := factory.CreateRiskAssessmentNode(owner, scopeID, factory.Attrs{"nodeType": "ENTITY"})
dst := factory.CreateRiskAssessmentNode(owner, scopeID, factory.Attrs{"nodeType": "ASSET"})
processID := factory.CreateRiskAssessmentProcess(owner, scopeID, src, dst)
var result struct {
UpdateRiskAssessmentProcess struct {
RiskAssessmentProcess struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"riskAssessmentProcess"`
} `json:"updateRiskAssessmentProcess"`
}
err := owner.Execute(`
mutation($input: UpdateRiskAssessmentProcessInput!) {
updateRiskAssessmentProcess(input: $input) {
riskAssessmentProcess { id name }
}
}
`, map[string]any{
"input": map[string]any{
"id": processID,
"name": "Updated process",
},
}, &result)
require.NoError(t, err)
assert.Equal(t, "Updated process", result.UpdateRiskAssessmentProcess.RiskAssessmentProcess.Name)
}
func TestRiskAssessmentThreat_Update(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
src := factory.CreateRiskAssessmentNode(owner, scopeID)
dst := factory.CreateRiskAssessmentNode(owner, scopeID)
processID := factory.CreateRiskAssessmentProcess(owner, scopeID, src, dst)
threatID := factory.CreateRiskAssessmentThreat(owner, scopeID, processID, factory.Attrs{"name": "Original", "category": "Confidentiality"})
var result struct {
UpdateRiskAssessmentThreat struct {
RiskAssessmentThreat struct {
ID string `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
} `json:"riskAssessmentThreat"`
} `json:"updateRiskAssessmentThreat"`
}
err := owner.Execute(`
mutation($input: UpdateRiskAssessmentThreatInput!) {
updateRiskAssessmentThreat(input: $input) {
riskAssessmentThreat { id name category }
}
}
`, map[string]any{
"input": map[string]any{
"id": threatID,
"name": "Updated threat",
"category": "Integrity",
},
}, &result)
require.NoError(t, err)
assert.Equal(t, "Updated threat", result.UpdateRiskAssessmentThreat.RiskAssessmentThreat.Name)
assert.Equal(t, "Integrity", result.UpdateRiskAssessmentThreat.RiskAssessmentThreat.Category)
}
func TestRiskAssessmentScenario_Update(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
scenarioID := factory.CreateRiskAssessmentScenario(owner, scopeID, factory.Attrs{"name": "Original"})
var result struct {
UpdateRiskAssessmentScenario struct {
RiskAssessmentScenario struct {
ID string `json:"id"`
Name string `json:"name"`
Description *string `json:"description"`
} `json:"riskAssessmentScenario"`
} `json:"updateRiskAssessmentScenario"`
}
err := owner.Execute(`
mutation($input: UpdateRiskAssessmentScenarioInput!) {
updateRiskAssessmentScenario(input: $input) {
riskAssessmentScenario { id name description }
}
}
`, map[string]any{
"input": map[string]any{
"id": scenarioID,
"name": "Updated scenario",
"description": "Scenario desc",
},
}, &result)
require.NoError(t, err)
assert.Equal(t, "Updated scenario", result.UpdateRiskAssessmentScenario.RiskAssessmentScenario.Name)
require.NotNil(t, result.UpdateRiskAssessmentScenario.RiskAssessmentScenario.Description)
assert.Equal(t, "Scenario desc", *result.UpdateRiskAssessmentScenario.RiskAssessmentScenario.Description)
}
func TestRiskAssessmentScenario_LinkUnlinkThreat(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
src := factory.CreateRiskAssessmentNode(owner, scopeID)
dst := factory.CreateRiskAssessmentNode(owner, scopeID)
processID := factory.CreateRiskAssessmentProcess(owner, scopeID, src, dst)
threatID := factory.CreateRiskAssessmentThreat(owner, scopeID, processID)
scenarioID := factory.CreateRiskAssessmentScenario(owner, scopeID)
factory.LinkRiskAssessmentScenarioThreat(owner, scenarioID, threatID)
var result struct {
Node struct {
Threats struct {
TotalCount int `json:"totalCount"`
} `json:"threats"`
} `json:"node"`
}
err := owner.Execute(`
query($id: ID!) {
node(id: $id) {
... on RiskAssessmentScenario {
threats(first: 10) { totalCount }
}
}
}
`, map[string]any{"id": scenarioID}, &result)
require.NoError(t, err)
assert.Equal(t, 1, result.Node.Threats.TotalCount)
_, err = owner.Do(`
mutation($input: UnlinkRiskAssessmentScenarioThreatInput!) {
unlinkRiskAssessmentScenarioThreat(input: $input) { riskAssessmentScenario { id } }
}
`, map[string]any{
"input": map[string]any{
"riskAssessmentScenarioId": scenarioID,
"threatId": threatID,
},
})
require.NoError(t, err)
err = owner.Execute(`
query($id: ID!) {
node(id: $id) {
... on RiskAssessmentScenario {
threats(first: 10) { totalCount }
}
}
}
`, map[string]any{"id": scenarioID}, &result)
require.NoError(t, err)
assert.Equal(t, 0, result.Node.Threats.TotalCount)
}
func TestRiskAssessmentScenario_LinkUnlinkRisk(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner)
scopeID := factory.CreateRiskAssessmentScope(owner, raID)
riskID := factory.CreateRisk(owner)
scenarioID := factory.CreateRiskAssessmentScenario(owner, scopeID)
factory.LinkRiskAssessmentScenarioRisk(owner, scenarioID, riskID)
var result struct {
Node struct {
Risks struct {
TotalCount int `json:"totalCount"`
} `json:"risks"`
} `json:"node"`
}
err := owner.Execute(`
query($id: ID!) {
node(id: $id) {
... on RiskAssessmentScenario {
risks(first: 10) { totalCount }
}
}
}
`, map[string]any{"id": scenarioID}, &result)
require.NoError(t, err)
assert.Equal(t, 1, result.Node.Risks.TotalCount)
_, err = owner.Do(`
mutation($input: UnlinkRiskAssessmentScenarioRiskInput!) {
unlinkRiskAssessmentScenarioRisk(input: $input) { riskAssessmentScenario { id } }
}
`, map[string]any{
"input": map[string]any{
"riskAssessmentScenarioId": scenarioID,
"riskId": riskID,
},
})
require.NoError(t, err)
err = owner.Execute(`
query($id: ID!) {
node(id: $id) {
... on RiskAssessmentScenario {
risks(first: 10) { totalCount }
}
}
}
`, map[string]any{"id": scenarioID}, &result)
require.NoError(t, err)
assert.Equal(t, 0, result.Node.Risks.TotalCount)
}
func TestRiskAssessment_RBAC(t *testing.T) {
t.Parallel()
t.Run("viewer cannot create", func(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
_, err := viewer.Do(`
mutation($input: CreateRiskAssessmentInput!) {
createRiskAssessment(input: $input) { riskAssessmentEdge { node { id } } }
}
`, map[string]any{
"input": map[string]any{
"organizationId": viewer.GetOrganizationID().String(),
"name": "test",
},
})
testutil.RequireForbiddenError(t, err, "viewer cannot create risk assessment")
})
t.Run("viewer can read", func(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
raID := factory.CreateRiskAssessment(owner, factory.Attrs{"name": "Visible"})
var result struct {
Node struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"node"`
}
err := viewer.Execute(`
query($id: ID!) { node(id: $id) { ... on RiskAssessment { id name } } }
`, map[string]any{"id": raID}, &result)
require.NoError(t, err)
assert.Equal(t, "Visible", result.Node.Name)
})
}
func TestRiskAssessment_TenantIsolation(t *testing.T) {
t.Parallel()
owner1 := testutil.NewClient(t, testutil.RoleOwner)
owner2 := testutil.NewClient(t, testutil.RoleOwner)
raID := factory.CreateRiskAssessment(owner1)
var result struct {
Node *struct {
ID string `json:"id"`
} `json:"node"`
}
err := owner2.Execute(`
query($id: ID!) { node(id: $id) { ... on RiskAssessment { id } } }
`, map[string]any{"id": raID}, &result)
testutil.AssertNodeNotAccessible(t, err, result.Node == nil, "RiskAssessment")
}