diff --git a/e2e/console/continual_improvement_test.go b/e2e/console/continual_improvement_test.go deleted file mode 100644 index e5f0b94ab..000000000 --- a/e2e/console/continual_improvement_test.go +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright (c) 2025 Probo Inc . -// -// 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 ( - "fmt" - "testing" - "time" - - "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 TestContinualImprovement_Create(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - - query := ` - mutation CreateContinualImprovement($input: CreateContinualImprovementInput!) { - createContinualImprovement(input: $input) { - continualImprovementEdge { - node { - id - referenceId - description - source - status - priority - } - } - } - } - ` - - var result struct { - CreateContinualImprovement struct { - ContinualImprovementEdge struct { - Node struct { - ID string `json:"id"` - ReferenceID string `json:"referenceId"` - Description string `json:"description"` - Source string `json:"source"` - Status string `json:"status"` - Priority string `json:"priority"` - } `json:"node"` - } `json:"continualImprovementEdge"` - } `json:"createContinualImprovement"` - } - - err := owner.Execute(query, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("CI-%d", time.Now().UnixNano()), - "description": "Improve security training program", - "source": "Internal Audit", - "ownerId": profileID, - "status": "OPEN", - "priority": "HIGH", - }, - }, &result) - require.NoError(t, err) - - ci := result.CreateContinualImprovement.ContinualImprovementEdge.Node - assert.NotEmpty(t, ci.ID) - assert.Equal(t, "Improve security training program", ci.Description) - assert.Equal(t, "Internal Audit", ci.Source) - assert.Equal(t, "OPEN", ci.Status) - assert.Equal(t, "HIGH", ci.Priority) -} - -func TestContinualImprovement_Update(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - - createQuery := ` - mutation CreateContinualImprovement($input: CreateContinualImprovementInput!) { - createContinualImprovement(input: $input) { - continualImprovementEdge { - node { - id - } - } - } - } - ` - - var createResult struct { - CreateContinualImprovement struct { - ContinualImprovementEdge struct { - Node struct { - ID string `json:"id"` - } `json:"node"` - } `json:"continualImprovementEdge"` - } `json:"createContinualImprovement"` - } - - err := owner.Execute(createQuery, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("CI-UPDATE-%d", time.Now().UnixNano()), - "description": "Original description", - "ownerId": profileID, - "status": "OPEN", - "priority": "LOW", - }, - }, &createResult) - require.NoError(t, err) - ciID := createResult.CreateContinualImprovement.ContinualImprovementEdge.Node.ID - - query := ` - mutation UpdateContinualImprovement($input: UpdateContinualImprovementInput!) { - updateContinualImprovement(input: $input) { - continualImprovement { - id - description - status - priority - } - } - } - ` - - var result struct { - UpdateContinualImprovement struct { - ContinualImprovement struct { - ID string `json:"id"` - Description string `json:"description"` - Status string `json:"status"` - Priority string `json:"priority"` - } `json:"continualImprovement"` - } `json:"updateContinualImprovement"` - } - - err = owner.Execute(query, map[string]any{ - "input": map[string]any{ - "id": ciID, - "description": "Updated description", - "status": "IN_PROGRESS", - "priority": "HIGH", - }, - }, &result) - require.NoError(t, err) - - assert.Equal(t, ciID, result.UpdateContinualImprovement.ContinualImprovement.ID) - assert.Equal(t, "Updated description", result.UpdateContinualImprovement.ContinualImprovement.Description) - assert.Equal(t, "IN_PROGRESS", result.UpdateContinualImprovement.ContinualImprovement.Status) - assert.Equal(t, "HIGH", result.UpdateContinualImprovement.ContinualImprovement.Priority) -} - -func TestContinualImprovement_Delete(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - - createQuery := ` - mutation CreateContinualImprovement($input: CreateContinualImprovementInput!) { - createContinualImprovement(input: $input) { - continualImprovementEdge { - node { - id - } - } - } - } - ` - - var createResult struct { - CreateContinualImprovement struct { - ContinualImprovementEdge struct { - Node struct { - ID string `json:"id"` - } `json:"node"` - } `json:"continualImprovementEdge"` - } `json:"createContinualImprovement"` - } - - err := owner.Execute(createQuery, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("CI-DELETE-%d", time.Now().UnixNano()), - "ownerId": profileID, - "status": "OPEN", - "priority": "LOW", - }, - }, &createResult) - require.NoError(t, err) - ciID := createResult.CreateContinualImprovement.ContinualImprovementEdge.Node.ID - - query := ` - mutation DeleteContinualImprovement($input: DeleteContinualImprovementInput!) { - deleteContinualImprovement(input: $input) { - deletedContinualImprovementId - } - } - ` - - var result struct { - DeleteContinualImprovement struct { - DeletedContinualImprovementID string `json:"deletedContinualImprovementId"` - } `json:"deleteContinualImprovement"` - } - - err = owner.Execute(query, map[string]any{ - "input": map[string]any{ - "continualImprovementId": ciID, - }, - }, &result) - require.NoError(t, err) - assert.Equal(t, ciID, result.DeleteContinualImprovement.DeletedContinualImprovementID) -} - -func TestContinualImprovement_List(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - - createQuery := ` - mutation CreateContinualImprovement($input: CreateContinualImprovementInput!) { - createContinualImprovement(input: $input) { - continualImprovementEdge { - node { - id - } - } - } - } - ` - - for i := range 3 { - _, err := owner.Do(createQuery, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("CI-LIST-%d-%d", i, time.Now().UnixNano()), - "description": fmt.Sprintf("Improvement %d", i), - "ownerId": profileID, - "status": "OPEN", - "priority": "MEDIUM", - }, - }) - require.NoError(t, err) - } - - query := ` - query GetContinualImprovements($id: ID!) { - node(id: $id) { - ... on Organization { - continualImprovements(first: 10) { - edges { - node { - id - referenceId - status - priority - } - } - totalCount - } - } - } - } - ` - - var result struct { - Node struct { - ContinualImprovements struct { - Edges []struct { - Node struct { - ID string `json:"id"` - ReferenceID string `json:"referenceId"` - Status string `json:"status"` - Priority string `json:"priority"` - } `json:"node"` - } `json:"edges"` - TotalCount int `json:"totalCount"` - } `json:"continualImprovements"` - } `json:"node"` - } - - err := owner.Execute(query, map[string]any{ - "id": owner.GetOrganizationID().String(), - }, &result) - require.NoError(t, err) - assert.GreaterOrEqual(t, result.Node.ContinualImprovements.TotalCount, 3) -} - -func TestContinualImprovement_StatusAndPriorityValues(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - - t.Run("status values", func(t *testing.T) { - statuses := []string{"OPEN", "IN_PROGRESS", "CLOSED"} - - for _, status := range statuses { - t.Run(status, func(t *testing.T) { - query := ` - mutation CreateContinualImprovement($input: CreateContinualImprovementInput!) { - createContinualImprovement(input: $input) { - continualImprovementEdge { - node { - id - status - } - } - } - } - ` - - var result struct { - CreateContinualImprovement struct { - ContinualImprovementEdge struct { - Node struct { - ID string `json:"id"` - Status string `json:"status"` - } `json:"node"` - } `json:"continualImprovementEdge"` - } `json:"createContinualImprovement"` - } - - err := owner.Execute(query, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("CI-STATUS-%s-%d", status, time.Now().UnixNano()), - "ownerId": profileID, - "status": status, - "priority": "LOW", - }, - }, &result) - require.NoError(t, err) - assert.Equal(t, status, result.CreateContinualImprovement.ContinualImprovementEdge.Node.Status) - }) - } - }) - - t.Run("priority values", func(t *testing.T) { - priorities := []string{"LOW", "MEDIUM", "HIGH"} - - for _, priority := range priorities { - t.Run(priority, func(t *testing.T) { - query := ` - mutation CreateContinualImprovement($input: CreateContinualImprovementInput!) { - createContinualImprovement(input: $input) { - continualImprovementEdge { - node { - id - priority - } - } - } - } - ` - - var result struct { - CreateContinualImprovement struct { - ContinualImprovementEdge struct { - Node struct { - ID string `json:"id"` - Priority string `json:"priority"` - } `json:"node"` - } `json:"continualImprovementEdge"` - } `json:"createContinualImprovement"` - } - - err := owner.Execute(query, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("CI-PRIORITY-%s-%d", priority, time.Now().UnixNano()), - "ownerId": profileID, - "status": "OPEN", - "priority": priority, - }, - }, &result) - require.NoError(t, err) - assert.Equal(t, priority, result.CreateContinualImprovement.ContinualImprovementEdge.Node.Priority) - }) - } - }) -} diff --git a/e2e/console/finding_test.go b/e2e/console/finding_test.go new file mode 100644 index 000000000..813e7bd67 --- /dev/null +++ b/e2e/console/finding_test.go @@ -0,0 +1,836 @@ +// Copyright (c) 2025-2026 Probo Inc . +// +// 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 ( + "fmt" + "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 TestFinding_CreateNonconformity(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + profileID := factory.CreateUser(owner) + + query := ` + mutation CreateFinding($input: CreateFindingInput!) { + createFinding(input: $input) { + findingEdge { + node { + id + kind + referenceId + description + rootCause + correctiveAction + status + priority + } + } + } + } + ` + + var result struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + Kind string `json:"kind"` + ReferenceID string `json:"referenceId"` + Description string `json:"description"` + RootCause string `json:"rootCause"` + CorrectiveAction string `json:"correctiveAction"` + Status string `json:"status"` + Priority string `json:"priority"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + err := owner.Execute(query, map[string]any{ + "input": map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": "NONCONFORMITY", + "description": "Unauthorized access detected", + "rootCause": "Insufficient access controls", + "correctiveAction": "Implement MFA", + "ownerId": profileID, + "status": "OPEN", + "priority": "HIGH", + }, + }, &result) + require.NoError(t, err) + + node := result.CreateFinding.FindingEdge.Node + assert.NotEmpty(t, node.ID) + assert.Equal(t, "NONCONFORMITY", node.Kind) + assert.Equal(t, "Unauthorized access detected", node.Description) + assert.Equal(t, "Insufficient access controls", node.RootCause) + assert.Equal(t, "Implement MFA", node.CorrectiveAction) + assert.Equal(t, "OPEN", node.Status) + assert.Equal(t, "HIGH", node.Priority) +} + +func TestFinding_CreateObservation(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + profileID := factory.CreateUser(owner) + + query := ` + mutation CreateFinding($input: CreateFindingInput!) { + createFinding(input: $input) { + findingEdge { + node { + id + kind + description + source + status + priority + } + } + } + } + ` + + var result struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + Kind string `json:"kind"` + Description string `json:"description"` + Source string `json:"source"` + Status string `json:"status"` + Priority string `json:"priority"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + err := owner.Execute(query, map[string]any{ + "input": map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": "OBSERVATION", + "description": "Improve security training program", + "source": "Internal Audit", + "ownerId": profileID, + "status": "OPEN", + "priority": "MEDIUM", + }, + }, &result) + require.NoError(t, err) + + node := result.CreateFinding.FindingEdge.Node + assert.NotEmpty(t, node.ID) + assert.Equal(t, "OBSERVATION", node.Kind) + assert.Equal(t, "Improve security training program", node.Description) + assert.Equal(t, "Internal Audit", node.Source) + assert.Equal(t, "OPEN", node.Status) + assert.Equal(t, "MEDIUM", node.Priority) +} + +func TestFinding_Update(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + profileID := factory.CreateUser(owner) + + createQuery := ` + mutation CreateFinding($input: CreateFindingInput!) { + createFinding(input: $input) { + findingEdge { + node { + id + } + } + } + } + ` + + var createResult struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + err := owner.Execute(createQuery, map[string]any{ + "input": map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": "NONCONFORMITY", + "description": "Original description", + "ownerId": profileID, + "status": "OPEN", + "priority": "LOW", + }, + }, &createResult) + require.NoError(t, err) + findingID := createResult.CreateFinding.FindingEdge.Node.ID + + query := ` + mutation UpdateFinding($input: UpdateFindingInput!) { + updateFinding(input: $input) { + finding { + id + description + rootCause + correctiveAction + status + priority + } + } + } + ` + + var result struct { + UpdateFinding struct { + Finding struct { + ID string `json:"id"` + Description string `json:"description"` + RootCause string `json:"rootCause"` + CorrectiveAction string `json:"correctiveAction"` + Status string `json:"status"` + Priority string `json:"priority"` + } `json:"finding"` + } `json:"updateFinding"` + } + + err = owner.Execute(query, map[string]any{ + "input": map[string]any{ + "id": findingID, + "description": "Updated description", + "rootCause": "Updated root cause", + "correctiveAction": "New corrective action", + "status": "IN_PROGRESS", + "priority": "HIGH", + }, + }, &result) + require.NoError(t, err) + + f := result.UpdateFinding.Finding + assert.Equal(t, findingID, f.ID) + assert.Equal(t, "Updated description", f.Description) + assert.Equal(t, "Updated root cause", f.RootCause) + assert.Equal(t, "New corrective action", f.CorrectiveAction) + assert.Equal(t, "IN_PROGRESS", f.Status) + assert.Equal(t, "HIGH", f.Priority) +} + +func TestFinding_Delete(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + profileID := factory.CreateUser(owner) + + createQuery := ` + mutation CreateFinding($input: CreateFindingInput!) { + createFinding(input: $input) { + findingEdge { + node { + id + } + } + } + } + ` + + var createResult struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + err := owner.Execute(createQuery, map[string]any{ + "input": map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": "OBSERVATION", + "ownerId": profileID, + "status": "OPEN", + "priority": "LOW", + }, + }, &createResult) + require.NoError(t, err) + findingID := createResult.CreateFinding.FindingEdge.Node.ID + + query := ` + mutation DeleteFinding($input: DeleteFindingInput!) { + deleteFinding(input: $input) { + deletedFindingId + } + } + ` + + var result struct { + DeleteFinding struct { + DeletedFindingID string `json:"deletedFindingId"` + } `json:"deleteFinding"` + } + + err = owner.Execute(query, map[string]any{ + "input": map[string]any{ + "findingId": findingID, + }, + }, &result) + require.NoError(t, err) + assert.Equal(t, findingID, result.DeleteFinding.DeletedFindingID) +} + +func TestFinding_List(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + profileID := factory.CreateUser(owner) + + createQuery := ` + mutation CreateFinding($input: CreateFindingInput!) { + createFinding(input: $input) { + findingEdge { + node { + id + } + } + } + } + ` + + for i := range 3 { + var createResult struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + err := owner.Execute(createQuery, map[string]any{ + "input": map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": "NONCONFORMITY", + "description": fmt.Sprintf("Finding %d", i), + "ownerId": profileID, + "status": "OPEN", + "priority": "MEDIUM", + }, + }, &createResult) + require.NoError(t, err) + } + + query := ` + query GetFindings($id: ID!) { + node(id: $id) { + ... on Organization { + findings(first: 10) { + edges { + node { + id + kind + referenceId + status + priority + } + } + totalCount + } + } + } + } + ` + + var result struct { + Node struct { + Findings struct { + Edges []struct { + Node struct { + ID string `json:"id"` + Kind string `json:"kind"` + ReferenceID string `json:"referenceId"` + Status string `json:"status"` + Priority string `json:"priority"` + } `json:"node"` + } `json:"edges"` + TotalCount int `json:"totalCount"` + } `json:"findings"` + } `json:"node"` + } + + err := owner.Execute(query, map[string]any{ + "id": owner.GetOrganizationID().String(), + }, &result) + require.NoError(t, err) + assert.GreaterOrEqual(t, result.Node.Findings.TotalCount, 3) +} + +func TestFinding_ListWithKindFilter(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + profileID := factory.CreateUser(owner) + + createQuery := ` + mutation CreateFinding($input: CreateFindingInput!) { + createFinding(input: $input) { + findingEdge { + node { + id + } + } + } + } + ` + + kinds := []string{"NONCONFORMITY", "OBSERVATION", "EXCEPTION"} + for _, kind := range kinds { + var createResult struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + err := owner.Execute(createQuery, map[string]any{ + "input": map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": kind, + "ownerId": profileID, + "status": "OPEN", + "priority": "LOW", + }, + }, &createResult) + require.NoError(t, err) + } + + query := ` + query GetFindings($id: ID!, $filter: FindingFilter) { + node(id: $id) { + ... on Organization { + findings(first: 10, filter: $filter) { + edges { + node { + id + kind + } + } + totalCount + } + } + } + } + ` + + var result struct { + Node struct { + Findings struct { + Edges []struct { + Node struct { + ID string `json:"id"` + Kind string `json:"kind"` + } `json:"node"` + } `json:"edges"` + TotalCount int `json:"totalCount"` + } `json:"findings"` + } `json:"node"` + } + + err := owner.Execute(query, map[string]any{ + "id": owner.GetOrganizationID().String(), + "filter": map[string]any{"kind": "OBSERVATION"}, + }, &result) + require.NoError(t, err) + assert.GreaterOrEqual(t, result.Node.Findings.TotalCount, 1) + + for _, edge := range result.Node.Findings.Edges { + assert.Equal(t, "OBSERVATION", edge.Node.Kind) + } +} + +func TestFinding_CreateAuditMapping(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + profileID := factory.CreateUser(owner) + frameworkID := factory.CreateFramework(owner) + auditID := factory.CreateAudit(owner, frameworkID) + + // Create a finding first + createQuery := ` + mutation CreateFinding($input: CreateFindingInput!) { + createFinding(input: $input) { + findingEdge { + node { + id + } + } + } + } + ` + + var createResult struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + err := owner.Execute(createQuery, map[string]any{ + "input": map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": "NONCONFORMITY", + "ownerId": profileID, + "status": "OPEN", + "priority": "HIGH", + }, + }, &createResult) + require.NoError(t, err) + findingID := createResult.CreateFinding.FindingEdge.Node.ID + + // Link finding to audit + linkQuery := ` + mutation CreateFindingAuditMapping($input: CreateFindingAuditMappingInput!) { + createFindingAuditMapping(input: $input) { + findingEdge { + node { + id + } + } + auditEdge { + node { + id + } + } + } + } + ` + + var linkResult struct { + CreateFindingAuditMapping struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"findingEdge"` + AuditEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"auditEdge"` + } `json:"createFindingAuditMapping"` + } + + err = owner.Execute(linkQuery, map[string]any{ + "input": map[string]any{ + "findingId": findingID, + "auditId": auditID, + "referenceId": "MinNC/001", + }, + }, &linkResult) + require.NoError(t, err) + assert.Equal(t, findingID, linkResult.CreateFindingAuditMapping.FindingEdge.Node.ID) + assert.Equal(t, auditID, linkResult.CreateFindingAuditMapping.AuditEdge.Node.ID) + + // Verify audits appear on finding + auditsQuery := ` + query GetFinding($id: ID!) { + node(id: $id) { + ... on Finding { + audits(first: 10) { + edges { + node { + id + } + } + totalCount + } + } + } + } + ` + + var auditsResult struct { + Node struct { + Audits struct { + Edges []struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"edges"` + TotalCount int `json:"totalCount"` + } `json:"audits"` + } `json:"node"` + } + + err = owner.Execute(auditsQuery, map[string]any{ + "id": findingID, + }, &auditsResult) + require.NoError(t, err) + assert.Equal(t, 1, auditsResult.Node.Audits.TotalCount) + assert.Equal(t, auditID, auditsResult.Node.Audits.Edges[0].Node.ID) +} + +func TestFinding_DeleteAuditMapping(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + profileID := factory.CreateUser(owner) + frameworkID := factory.CreateFramework(owner) + auditID := factory.CreateAudit(owner, frameworkID) + + // Create a finding + createQuery := ` + mutation CreateFinding($input: CreateFindingInput!) { + createFinding(input: $input) { + findingEdge { + node { + id + } + } + } + } + ` + + var createResult struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + err := owner.Execute(createQuery, map[string]any{ + "input": map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": "OBSERVATION", + "ownerId": profileID, + "status": "OPEN", + "priority": "LOW", + }, + }, &createResult) + require.NoError(t, err) + findingID := createResult.CreateFinding.FindingEdge.Node.ID + + // Link finding to audit + linkQuery := ` + mutation CreateFindingAuditMapping($input: CreateFindingAuditMappingInput!) { + createFindingAuditMapping(input: $input) { + findingEdge { + node { id } + } + auditEdge { + node { id } + } + } + } + ` + + var linkResult struct { + CreateFindingAuditMapping struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"findingEdge"` + AuditEdge struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"auditEdge"` + } `json:"createFindingAuditMapping"` + } + + err = owner.Execute(linkQuery, map[string]any{ + "input": map[string]any{ + "findingId": findingID, + "auditId": auditID, + "referenceId": "MajNC/001", + }, + }, &linkResult) + require.NoError(t, err) + + // Delete the mapping + unlinkQuery := ` + mutation DeleteFindingAuditMapping($input: DeleteFindingAuditMappingInput!) { + deleteFindingAuditMapping(input: $input) { + deletedFindingId + deletedAuditId + } + } + ` + + var unlinkResult struct { + DeleteFindingAuditMapping struct { + DeletedFindingID string `json:"deletedFindingId"` + DeletedAuditID string `json:"deletedAuditId"` + } `json:"deleteFindingAuditMapping"` + } + + err = owner.Execute(unlinkQuery, map[string]any{ + "input": map[string]any{ + "findingId": findingID, + "auditId": auditID, + }, + }, &unlinkResult) + require.NoError(t, err) + assert.Equal(t, findingID, unlinkResult.DeleteFindingAuditMapping.DeletedFindingID) + assert.Equal(t, auditID, unlinkResult.DeleteFindingAuditMapping.DeletedAuditID) + + // Verify no audits on finding + auditsQuery := ` + query GetFinding($id: ID!) { + node(id: $id) { + ... on Finding { + audits(first: 10) { + totalCount + } + } + } + } + ` + + var auditsResult struct { + Node struct { + Audits struct { + TotalCount int `json:"totalCount"` + } `json:"audits"` + } `json:"node"` + } + + err = owner.Execute(auditsQuery, map[string]any{ + "id": findingID, + }, &auditsResult) + require.NoError(t, err) + assert.Equal(t, 0, auditsResult.Node.Audits.TotalCount) +} + +func TestFinding_StatusAndPriorityValues(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + profileID := factory.CreateUser(owner) + + createQuery := ` + mutation CreateFinding($input: CreateFindingInput!) { + createFinding(input: $input) { + findingEdge { + node { + id + status + priority + } + } + } + } + ` + + t.Run( + "status values", + func(t *testing.T) { + t.Parallel() + riskID := factory.CreateRisk(owner) + statuses := []string{"OPEN", "IN_PROGRESS", "CLOSED", "RISK_ACCEPTED", "MITIGATED", "FALSE_POSITIVE"} + + for _, status := range statuses { + t.Run( + status, + func(t *testing.T) { + t.Parallel() + var result struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + Status string `json:"status"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + input := map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": "NONCONFORMITY", + "ownerId": profileID, + "status": status, + "priority": "LOW", + } + + if status == "RISK_ACCEPTED" { + input["riskId"] = riskID + } + + err := owner.Execute(createQuery, map[string]any{ + "input": input, + }, &result) + require.NoError(t, err) + assert.Equal(t, status, result.CreateFinding.FindingEdge.Node.Status) + }, + ) + } + }, + ) + + t.Run( + "priority values", + func(t *testing.T) { + t.Parallel() + priorities := []string{"LOW", "MEDIUM", "HIGH"} + + for _, priority := range priorities { + t.Run( + priority, + func(t *testing.T) { + t.Parallel() + var result struct { + CreateFinding struct { + FindingEdge struct { + Node struct { + ID string `json:"id"` + Priority string `json:"priority"` + } `json:"node"` + } `json:"findingEdge"` + } `json:"createFinding"` + } + + err := owner.Execute(createQuery, map[string]any{ + "input": map[string]any{ + "organizationId": owner.GetOrganizationID().String(), + "kind": "OBSERVATION", + "ownerId": profileID, + "status": "OPEN", + "priority": priority, + }, + }, &result) + require.NoError(t, err) + assert.Equal(t, priority, result.CreateFinding.FindingEdge.Node.Priority) + }, + ) + } + }, + ) +} diff --git a/e2e/console/nonconformity_test.go b/e2e/console/nonconformity_test.go deleted file mode 100644 index dfa52573f..000000000 --- a/e2e/console/nonconformity_test.go +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright (c) 2025 Probo Inc . -// -// 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 ( - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.probo.inc/probo/e2e/internal/factory" - "go.probo.inc/probo/e2e/internal/testutil" -) - -// createAuditForNC creates a framework and audit for nonconformity testing -func createAuditForNC(t *testing.T, owner *testutil.Client, name string) string { - t.Helper() - - // Create a framework first - createFrameworkQuery := ` - mutation CreateFramework($input: CreateFrameworkInput!) { - createFramework(input: $input) { - frameworkEdge { - node { - id - } - } - } - } - ` - - var createFrameworkResult struct { - CreateFramework struct { - FrameworkEdge struct { - Node struct { - ID string `json:"id"` - } `json:"node"` - } `json:"frameworkEdge"` - } `json:"createFramework"` - } - - err := owner.Execute(createFrameworkQuery, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "name": "Framework for " + name, - }, - }, &createFrameworkResult) - require.NoError(t, err) - frameworkID := createFrameworkResult.CreateFramework.FrameworkEdge.Node.ID - - // Create an audit - createAuditQuery := ` - mutation CreateAudit($input: CreateAuditInput!) { - createAudit(input: $input) { - auditEdge { - node { - id - } - } - } - } - ` - - var createAuditResult struct { - CreateAudit struct { - AuditEdge struct { - Node struct { - ID string `json:"id"` - } `json:"node"` - } `json:"auditEdge"` - } `json:"createAudit"` - } - - err = owner.Execute(createAuditQuery, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "frameworkId": frameworkID, - "name": name, - "state": "NOT_STARTED", - }, - }, &createAuditResult) - require.NoError(t, err) - - return createAuditResult.CreateAudit.AuditEdge.Node.ID -} - -func TestNonconformity_Create(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - auditID := createAuditForNC(t, owner, "NC Test Audit") - - query := ` - mutation CreateNonconformity($input: CreateNonconformityInput!) { - createNonconformity(input: $input) { - nonconformityEdge { - node { - id - referenceId - description - rootCause - correctiveAction - status - } - } - } - } - ` - - var result struct { - CreateNonconformity struct { - NonconformityEdge struct { - Node struct { - ID string `json:"id"` - ReferenceID string `json:"referenceId"` - Description string `json:"description"` - RootCause string `json:"rootCause"` - CorrectiveAction string `json:"correctiveAction"` - Status string `json:"status"` - } `json:"node"` - } `json:"nonconformityEdge"` - } `json:"createNonconformity"` - } - - err := owner.Execute(query, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("NC-%d", time.Now().UnixNano()), - "description": "Unauthorized access detected", - "auditId": auditID, - "rootCause": "Insufficient access controls", - "correctiveAction": "Implement MFA", - "ownerId": profileID, - "status": "OPEN", - }, - }, &result) - require.NoError(t, err) - - nc := result.CreateNonconformity.NonconformityEdge.Node - assert.NotEmpty(t, nc.ID) - assert.Equal(t, "Unauthorized access detected", nc.Description) - assert.Equal(t, "Insufficient access controls", nc.RootCause) - assert.Equal(t, "OPEN", nc.Status) -} - -func TestNonconformity_Update(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - auditID := createAuditForNC(t, owner, "NC Update Test Audit") - - // Create a nonconformity to update - createQuery := ` - mutation CreateNonconformity($input: CreateNonconformityInput!) { - createNonconformity(input: $input) { - nonconformityEdge { - node { - id - } - } - } - } - ` - - var createResult struct { - CreateNonconformity struct { - NonconformityEdge struct { - Node struct { - ID string `json:"id"` - } `json:"node"` - } `json:"nonconformityEdge"` - } `json:"createNonconformity"` - } - - err := owner.Execute(createQuery, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("NC-UPDATE-%d", time.Now().UnixNano()), - "auditId": auditID, - "rootCause": "Original root cause", - "ownerId": profileID, - "status": "OPEN", - }, - }, &createResult) - require.NoError(t, err) - ncID := createResult.CreateNonconformity.NonconformityEdge.Node.ID - - query := ` - mutation UpdateNonconformity($input: UpdateNonconformityInput!) { - updateNonconformity(input: $input) { - nonconformity { - id - rootCause - correctiveAction - status - } - } - } - ` - - var result struct { - UpdateNonconformity struct { - Nonconformity struct { - ID string `json:"id"` - RootCause string `json:"rootCause"` - CorrectiveAction string `json:"correctiveAction"` - Status string `json:"status"` - } `json:"nonconformity"` - } `json:"updateNonconformity"` - } - - err = owner.Execute(query, map[string]any{ - "input": map[string]any{ - "id": ncID, - "rootCause": "Updated root cause", - "correctiveAction": "New corrective action", - "status": "IN_PROGRESS", - }, - }, &result) - require.NoError(t, err) - - nc := result.UpdateNonconformity.Nonconformity - assert.Equal(t, ncID, nc.ID) - assert.Equal(t, "Updated root cause", nc.RootCause) - assert.Equal(t, "New corrective action", nc.CorrectiveAction) - assert.Equal(t, "IN_PROGRESS", nc.Status) -} - -func TestNonconformity_Delete(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - auditID := createAuditForNC(t, owner, "NC Delete Test Audit") - - // Create a nonconformity to delete - createQuery := ` - mutation CreateNonconformity($input: CreateNonconformityInput!) { - createNonconformity(input: $input) { - nonconformityEdge { - node { - id - } - } - } - } - ` - - var createResult struct { - CreateNonconformity struct { - NonconformityEdge struct { - Node struct { - ID string `json:"id"` - } `json:"node"` - } `json:"nonconformityEdge"` - } `json:"createNonconformity"` - } - - err := owner.Execute(createQuery, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("NC-DELETE-%d", time.Now().UnixNano()), - "auditId": auditID, - "rootCause": "Test root cause", - "ownerId": profileID, - "status": "OPEN", - }, - }, &createResult) - require.NoError(t, err) - ncID := createResult.CreateNonconformity.NonconformityEdge.Node.ID - - deleteQuery := ` - mutation DeleteNonconformity($input: DeleteNonconformityInput!) { - deleteNonconformity(input: $input) { - deletedNonconformityId - } - } - ` - - var deleteResult struct { - DeleteNonconformity struct { - DeletedNonconformityID string `json:"deletedNonconformityId"` - } `json:"deleteNonconformity"` - } - - err = owner.Execute(deleteQuery, map[string]any{ - "input": map[string]any{ - "nonconformityId": ncID, - }, - }, &deleteResult) - require.NoError(t, err) - assert.Equal(t, ncID, deleteResult.DeleteNonconformity.DeletedNonconformityID) -} - -func TestNonconformity_List(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - auditID := createAuditForNC(t, owner, "NC List Test Audit") - - // Create multiple nonconformities - createQuery := ` - mutation CreateNonconformity($input: CreateNonconformityInput!) { - createNonconformity(input: $input) { - nonconformityEdge { - node { - id - } - } - } - } - ` - - for i := range 3 { - var createResult struct { - CreateNonconformity struct { - NonconformityEdge struct { - Node struct { - ID string `json:"id"` - } `json:"node"` - } `json:"nonconformityEdge"` - } `json:"createNonconformity"` - } - - err := owner.Execute(createQuery, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("NC-LIST-%d-%d", i, time.Now().UnixNano()), - "auditId": auditID, - "rootCause": fmt.Sprintf("Root cause %d", i), - "ownerId": profileID, - "status": "OPEN", - }, - }, &createResult) - require.NoError(t, err) - } - - query := ` - query GetNonconformities($id: ID!) { - node(id: $id) { - ... on Organization { - nonconformities(first: 10) { - edges { - node { - id - referenceId - status - } - } - totalCount - } - } - } - } - ` - - var result struct { - Node struct { - Nonconformities struct { - Edges []struct { - Node struct { - ID string `json:"id"` - ReferenceID string `json:"referenceId"` - Status string `json:"status"` - } `json:"node"` - } `json:"edges"` - TotalCount int `json:"totalCount"` - } `json:"nonconformities"` - } `json:"node"` - } - - err := owner.Execute(query, map[string]any{ - "id": owner.GetOrganizationID().String(), - }, &result) - require.NoError(t, err) - assert.GreaterOrEqual(t, result.Node.Nonconformities.TotalCount, 3) -} - -func TestNonconformity_StatusValues(t *testing.T) { - t.Parallel() - owner := testutil.NewClient(t, testutil.RoleOwner) - profileID := factory.CreateUser(owner) - auditID := createAuditForNC(t, owner, "NC Status Test Audit") - - statuses := []string{"OPEN", "IN_PROGRESS", "CLOSED"} - - for _, status := range statuses { - t.Run(status, func(t *testing.T) { - query := ` - mutation CreateNonconformity($input: CreateNonconformityInput!) { - createNonconformity(input: $input) { - nonconformityEdge { - node { - id - status - } - } - } - } - ` - - var result struct { - CreateNonconformity struct { - NonconformityEdge struct { - Node struct { - ID string `json:"id"` - Status string `json:"status"` - } `json:"node"` - } `json:"nonconformityEdge"` - } `json:"createNonconformity"` - } - - err := owner.Execute(query, map[string]any{ - "input": map[string]any{ - "organizationId": owner.GetOrganizationID().String(), - "referenceId": fmt.Sprintf("NC-STATUS-%s-%d", status, time.Now().UnixNano()), - "auditId": auditID, - "rootCause": "Test root cause", - "ownerId": profileID, - "status": status, - }, - }, &result) - require.NoError(t, err) - assert.Equal(t, status, result.CreateNonconformity.NonconformityEdge.Node.Status) - }) - } -}