Skip to content

Commit 3ebed43

Browse files
authored
Merge pull request #16 from cloudgraphdev/feature/CG-1246
feat: Extend rule engine to include missing checks when the resource …
2 parents 887c514 + 0d14267 commit 3ebed43

File tree

10 files changed

+88
-15
lines changed

10 files changed

+88
-15
lines changed

src/rules-engine/data-processors/dgraph-data-processor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,12 @@ export default class DgraphDataProcessor implements DataProcessor {
145145

146146
private prepareEntitiesMutations(findings: RuleFinding[]): RuleFinding[] {
147147
// Group Findings by schema type
148-
const { manual: manualData = [], ...processedRules } = groupBy(
148+
const { manual: manualData = [], missing: missingData = [], ...processedRules } = groupBy(
149149
findings,
150150
'typename'
151151
)
152152

153-
const mutations = [...manualData]
153+
const mutations = [...manualData, ...missingData]
154154
for (const findingType in processedRules) {
155155
if (!isEmpty(findingType)) {
156156
// Group Findings by resource
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import cuid from 'cuid'
2+
import { ResourceData, Result, Rule, RuleFinding } from '../types'
3+
import { RuleEvaluator } from './rule-evaluator'
4+
5+
export default abstract class BaseEvaluator<K extends Rule> implements RuleEvaluator<K>
6+
{
7+
abstract canEvaluate(rule: K): boolean
8+
9+
abstract evaluateSingleResource(rule: K, data?: ResourceData): Promise<RuleFinding>
10+
11+
async evaluateMissingResource({ id, severity, resource }: Rule): Promise<RuleFinding> {
12+
return {
13+
id: `${id}/missing/${cuid()}`,
14+
resourceId: resource?.replace('[*]', ''),
15+
result: Result.MISSING,
16+
typename: 'missing',
17+
rule: {
18+
id,
19+
severity,
20+
},
21+
} as RuleFinding
22+
}
23+
}

src/rules-engine/evaluators/js-evaluator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import {
66
RuleFinding,
77
Result,
88
} from '../types'
9-
import { RuleEvaluator } from './rule-evaluator'
9+
import BaseEvaluator from './base-evaluator'
1010

11-
export default class JsEvaluator implements RuleEvaluator<JsRule> {
11+
export default class JsEvaluator extends BaseEvaluator<JsRule> {
1212
canEvaluate(rule: Rule | JsRule): boolean {
1313
return 'check' in rule
1414
}

src/rules-engine/evaluators/json-evaluator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import {
1212
} from '../types'
1313
import AdditionalOperators from '../operators'
1414
import ComparisonOperators from '../operators/comparison'
15-
import { RuleEvaluator } from './rule-evaluator'
15+
import BaseEvaluator from './base-evaluator'
1616

17-
export default class JsonEvaluator implements RuleEvaluator<JsonRule> {
17+
export default class JsonEvaluator extends BaseEvaluator<JsonRule> {
1818
canEvaluate(rule: JsonRule): boolean {
1919
return 'conditions' in rule
2020
}

src/rules-engine/evaluators/manual-evaluator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { JsonRule, Result, Rule, RuleFinding } from '../types'
2-
import { RuleEvaluator } from './rule-evaluator'
2+
import BaseEvaluator from './base-evaluator'
33

4-
export default class ManualEvaluator implements RuleEvaluator<JsonRule> {
4+
export default class ManualEvaluator extends BaseEvaluator<JsonRule> {
55
canEvaluate(rule: Rule): boolean {
66
return !('gql' in rule) && !('conditions' in rule) && !('resource' in rule)
77
}

src/rules-engine/evaluators/rule-evaluator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ import { ResourceData, Rule, RuleFinding } from '../types'
33
export interface RuleEvaluator<K extends Rule> {
44
canEvaluate: (rule: K) => boolean
55
evaluateSingleResource: (rule: K, data?: ResourceData) => Promise<RuleFinding>
6+
evaluateMissingResource: (rule: K) => Promise<RuleFinding>
67
}

src/rules-engine/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,20 @@ export default class RulesProvider implements Engine {
9191
const resourcePaths = data ? jsonpath.nodes(data, rule.resource) : []
9292
const evaluator = this.getRuleEvaluator(rule)
9393

94+
if (!evaluator) {
95+
return []
96+
}
97+
9498
if (isEmpty(data) && evaluator instanceof ManualEvaluator) {
9599
// Returned Manual Rule Response
96100
res.push(await evaluator.evaluateSingleResource(rule))
97101
return res
98102
}
99103

100-
if (!evaluator) {
101-
return []
104+
if (isEmpty(resourcePaths)) {
105+
// Returned Missing Resource Rule Response
106+
res.push(await evaluator.evaluateMissingResource(rule))
107+
return res
102108
}
103109

104110
// @NOTE: here we can evaluate things such as Data being empty (may imply rule to pass)

tests/evaluators/js-evaluator.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,25 @@ describe('JsEvaluator', () => {
6666
)
6767
expect(passedRule.result).toEqual(Result.PASS)
6868
})
69+
70+
test('should return missing as result', async () => {
71+
const finding = await evaluator.evaluateMissingResource(
72+
{ id: cuid() } as never
73+
)
74+
expect(finding.result).toEqual(Result.MISSING)
75+
})
76+
77+
test('should contain "missing" at the id', async () => {
78+
const finding = await evaluator.evaluateMissingResource(
79+
{ id: cuid() } as any
80+
)
81+
expect(finding.id.includes('missing')).toEqual(true)
82+
})
83+
84+
test('should return the resource path at the resourceId', async () => {
85+
const finding = await evaluator.evaluateMissingResource(
86+
{ id: cuid(), resource: 'resourcePath[*]'} as any
87+
)
88+
expect(finding.resourceId).toEqual('resourcePath')
89+
})
6990
})

tests/evaluators/json-evaluator.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,4 +596,25 @@ describe('JsonEvaluator', () => {
596596

597597
expect(finding.result).toBe(Result.FAIL)
598598
})
599+
600+
test('should return missing as result', async () => {
601+
const finding = await evaluator.evaluateMissingResource(
602+
{ id: cuid() } as never
603+
)
604+
expect(finding.result).toEqual(Result.MISSING)
605+
})
606+
607+
test('should contain "missing" at the id', async () => {
608+
const finding = await evaluator.evaluateMissingResource(
609+
{ id: cuid() } as any
610+
)
611+
expect(finding.id.includes('missing')).toEqual(true)
612+
})
613+
614+
test('should return the resource path at the resourceId', async () => {
615+
const finding = await evaluator.evaluateMissingResource(
616+
{ id: cuid(), resource: 'resourcePath[*]'} as any
617+
)
618+
expect(finding.resourceId).toEqual('resourcePath')
619+
})
599620
})

tests/rules-engine.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import RulesProvider from '../src/rules-engine'
2-
import { Engine, Rule } from '../src/rules-engine/types'
2+
import { Engine, Result, Rule } from '../src/rules-engine/types'
33
import ManualEvaluatorMock from './evaluators/manual-evaluator.test'
44
import JSONEvaluatordMock from './evaluators/json-evaluator.test'
55
import DgraphDataProcessor from '../src/rules-engine/data-processors/dgraph-data-processor'
6+
import { RuleFinding } from '../src'
67

78
const typenameToFieldMap = {
89
resourceA: 'querySchemaA',
@@ -50,17 +51,17 @@ describe('RulesEngine', () => {
5051
)
5152
})
5253

53-
it('Should return an empty array processing a rule with no data', async () => {
54+
it('Should return a missing finding type processing a rule with no data', async () => {
5455
const data = {}
5556

5657
const processedRule = await rulesEngine.processRule(
5758
JSONEvaluatordMock.jsonRule,
5859
data
59-
)
60+
) as RuleFinding[]
6061

6162
expect(processedRule).toBeDefined()
62-
expect(processedRule instanceof Array).toBeTruthy()
63-
expect(processedRule.length).toBe(0)
63+
expect(processedRule.length).toBe(1)
64+
expect(processedRule[0].result).toBe(Result.MISSING)
6465
})
6566

6667
it('Should return empty mutations array given an empty findings array', () => {

0 commit comments

Comments
 (0)