Fix measure count queries missing category column

CountByRiskID and CountByControlID CTEs did not include m.category in their SELECT
lists, causing "column category does not exist" errors when MeasureFilter applied
category constraints. Added m.category to both CTEs.

Also add comprehensive e2e tests for measure filtering by category at organization,
risk, and control levels.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-03-19 12:11:03 +01:00
parent d6b9280038
commit 2b08dea600
2 changed files with 210 additions and 2 deletions

View File

@@ -1754,6 +1754,212 @@ func TestMeasure_Filtering(t *testing.T) {
})
}
func TestMeasure_FilterByCategory(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
// Create measures with different categories
policyID := factory.NewMeasure(owner).WithName("Category Policy Measure").WithCategory("POLICY").Create()
factory.NewMeasure(owner).WithName("Category Technical Measure").WithCategory("TECHNICAL").Create()
t.Run("filter by category on organization", func(t *testing.T) {
t.Parallel()
const query = `
query($id: ID!, $filter: MeasureFilter) {
node(id: $id) {
... on Organization {
measures(first: 100, filter: $filter) {
edges {
node {
id
category
}
}
totalCount
}
}
}
}
`
var result struct {
Node struct {
Measures struct {
Edges []struct {
Node struct {
ID string `json:"id"`
Category string `json:"category"`
} `json:"node"`
} `json:"edges"`
TotalCount int `json:"totalCount"`
} `json:"measures"`
} `json:"node"`
}
err := owner.Execute(query, map[string]any{
"id": owner.GetOrganizationID().String(),
"filter": map[string]any{"category": "POLICY"},
}, &result)
require.NoError(t, err)
assert.GreaterOrEqual(t, result.Node.Measures.TotalCount, 1)
for _, edge := range result.Node.Measures.Edges {
assert.Equal(t, "POLICY", edge.Node.Category)
}
found := false
for _, edge := range result.Node.Measures.Edges {
if edge.Node.ID == policyID {
found = true
break
}
}
assert.True(t, found, "Expected to find POLICY measure in filtered results")
})
t.Run("filter by category on risk", func(t *testing.T) {
t.Parallel()
riskID := factory.NewRisk(owner).WithName("Category Filter Risk").Create()
policyMeasureID := factory.NewMeasure(owner).WithName("Risk Policy Measure").WithCategory("POLICY").Create()
techMeasureID := factory.NewMeasure(owner).WithName("Risk Technical Measure").WithCategory("TECHNICAL").Create()
// Link both measures to the risk
const linkQuery = `
mutation($input: CreateRiskMeasureMappingInput!) {
createRiskMeasureMapping(input: $input) {
riskEdge { node { id } }
}
}
`
for _, mID := range []string{policyMeasureID, techMeasureID} {
_, err := owner.Do(linkQuery, map[string]any{
"input": map[string]any{
"riskId": riskID,
"measureId": mID,
},
})
require.NoError(t, err)
}
var err error
const query = `
query($id: ID!, $filter: MeasureFilter) {
node(id: $id) {
... on Risk {
measures(first: 100, filter: $filter) {
edges {
node {
id
category
}
}
totalCount
}
}
}
}
`
var result struct {
Node struct {
Measures struct {
Edges []struct {
Node struct {
ID string `json:"id"`
Category string `json:"category"`
} `json:"node"`
} `json:"edges"`
TotalCount int `json:"totalCount"`
} `json:"measures"`
} `json:"node"`
}
err = owner.Execute(query, map[string]any{
"id": riskID,
"filter": map[string]any{"category": "POLICY"},
}, &result)
require.NoError(t, err)
assert.Equal(t, 1, result.Node.Measures.TotalCount)
assert.Equal(t, policyMeasureID, result.Node.Measures.Edges[0].Node.ID)
assert.Equal(t, "POLICY", result.Node.Measures.Edges[0].Node.Category)
})
t.Run("filter by category on control", func(t *testing.T) {
t.Parallel()
frameworkID := factory.NewFramework(owner).WithName("Category Filter Framework").Create()
controlID := factory.NewControl(owner, frameworkID).WithName("Category Filter Control").Create()
policyMeasureID := factory.NewMeasure(owner).WithName("Control Policy Measure").WithCategory("POLICY").Create()
techMeasureID := factory.NewMeasure(owner).WithName("Control Technical Measure").WithCategory("TECHNICAL").Create()
// Link both measures to the control
const linkQuery = `
mutation($input: CreateControlMeasureMappingInput!) {
createControlMeasureMapping(input: $input) {
controlEdge { node { id } }
}
}
`
for _, mID := range []string{policyMeasureID, techMeasureID} {
_, err := owner.Do(linkQuery, map[string]any{
"input": map[string]any{
"controlId": controlID,
"measureId": mID,
},
})
require.NoError(t, err)
}
const query = `
query($id: ID!, $filter: MeasureFilter) {
node(id: $id) {
... on Control {
measures(first: 100, filter: $filter) {
edges {
node {
id
category
}
}
totalCount
}
}
}
}
`
var result struct {
Node struct {
Measures struct {
Edges []struct {
Node struct {
ID string `json:"id"`
Category string `json:"category"`
} `json:"node"`
} `json:"edges"`
TotalCount int `json:"totalCount"`
} `json:"measures"`
} `json:"node"`
}
err := owner.Execute(query, map[string]any{
"id": controlID,
"filter": map[string]any{"category": "POLICY"},
}, &result)
require.NoError(t, err)
assert.Equal(t, 1, result.Node.Measures.TotalCount)
assert.Equal(t, policyMeasureID, result.Node.Measures.Edges[0].Node.ID)
assert.Equal(t, "POLICY", result.Node.Measures.Edges[0].Node.Category)
})
}
func TestMeasure_TenantIsolation(t *testing.T) {
t.Parallel()

View File

@@ -84,7 +84,8 @@ WITH msrs AS (
m.id,
m.tenant_id,
m.search_vector,
m.state
m.state,
m.category
FROM
measures m
INNER JOIN
@@ -195,7 +196,8 @@ WITH mtgtns AS (
m.id,
m.tenant_id,
m.search_vector,
m.state
m.state,
m.category
FROM
measures m
INNER JOIN