Skip to content

Commit

Permalink
Add unit tests for policy builder (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
samteb authored Feb 13, 2024
1 parent 6b13ddb commit 2076ff8
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 90 deletions.
51 changes: 4 additions & 47 deletions apps/authz/src/app/opa/opa.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'
import { loadPolicy } from '@open-policy-agent/opa-wasm'
import { readFileSync, writeFileSync } from 'fs'
import Handlebars from 'handlebars'
import { isEmpty } from 'lodash'
import path from 'path'
import * as R from 'remeda'
import { v4 as uuidv4 } from 'uuid'
import { RegoData, User, UserGroup, WalletGroup } from '../../shared/types/entities.types'
import { Criterion, Policy, Then } from '../../shared/types/policy.type'
import { Policy } from '../../shared/types/policy.type'
import { OpaResult, RegoInput } from '../../shared/types/rego'
import { criterionToString, reasonToString } from '../../shared/utils/opa.utils'
import { AdminRepository } from '../persistence/repository/admin.repository'

type PromiseType<T extends Promise<unknown>> = T extends Promise<infer U> ? U : never
Expand Down Expand Up @@ -41,52 +41,9 @@ export class OpaService implements OnApplicationBootstrap {
}

generateRegoFile(policies: Policy[]): string {
Handlebars.registerHelper('criterion', function (item) {
const criterion: Criterion = item.criterion
const args = item.args
Handlebars.registerHelper('criterion', criterionToString)

if (args === null) {
return `${criterion}`
}

if (!isEmpty(args)) {
if (Array.isArray(args)) {
if (typeof args[0] === 'string') {
return `${criterion}({${args.map((el) => `"${el}"`).join(', ')}})`
}

if (criterion === Criterion.CHECK_APPROVALS) {
return `approvals = ${criterion}([${args.map((el) => JSON.stringify(el)).join(', ')}])`
}

return `${criterion}([${args.map((el) => JSON.stringify(el)).join(', ')}])`
}

return `${criterion}(${JSON.stringify(args)})`
}
})

Handlebars.registerHelper('reason', function (item) {
if (item.then === Then.PERMIT) {
const reason = [
`"type": "${item.then}"`,
`"policyId": "${item.name}"`,
'"approvalsSatisfied": approvals.approvalsSatisfied',
'"approvalsMissing": approvals.approvalsMissing'
]
return `reason = {${reason.join(', ')}}`
}

if (item.then === Then.FORBID) {
const reason = {
type: item.then,
policyId: item.name,
approvalsSatisfied: [],
approvalsMissing: []
}
return `reason = ${JSON.stringify(reason)}`
}
})
Handlebars.registerHelper('reason', reasonToString)

const templateSource = readFileSync('./apps/authz/src/opa/template/template.hbs', 'utf-8')

Expand Down
93 changes: 93 additions & 0 deletions apps/authz/src/shared/__test__/unit/opa.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { EntityType, ValueOperators } from '@narval/authz-shared'
import {
ApprovalsCriterion,
Criterion,
ERC1155TransfersCriterion,
IntentAmountCriterion,
NonceRequiredCriterion,
Policy,
Then,
WalletAddressCriterion
} from '../../types/policy.type'
import { criterionToString, reasonToString } from '../../utils/opa.utils'

describe('criterionToString', () => {
it('returns criterion if args are null', () => {
const item: NonceRequiredCriterion = {
criterion: Criterion.CHECK_NONCE_EXISTS,
args: null
}
expect(criterionToString(item)).toEqual(Criterion.CHECK_NONCE_EXISTS)
})

it('returns criterion if args is an array of strings', () => {
const item: WalletAddressCriterion = {
criterion: Criterion.CHECK_WALLET_ADDRESS,
args: ['0x123', '0x456']
}
expect(criterionToString(item)).toEqual(`${Criterion.CHECK_WALLET_ADDRESS}({"0x123", "0x456"})`)
})

it('returns criterion if args is an array of objects', () => {
const item: ERC1155TransfersCriterion = {
criterion: Criterion.CHECK_ERC1155_TRANSFERS,
args: [{ tokenId: 'eip155:137/erc1155:0x12345/123', operator: ValueOperators.LESS_THAN_OR_EQUAL, value: '5' }]
}
expect(criterionToString(item)).toEqual(
`${Criterion.CHECK_ERC1155_TRANSFERS}([${item.args.map((el) => JSON.stringify(el)).join(', ')}])`
)
})

it('returns criterion if args is an object', () => {
const item: IntentAmountCriterion = {
criterion: Criterion.CHECK_INTENT_AMOUNT,
args: {
currency: '*',
operator: ValueOperators.LESS_THAN_OR_EQUAL,
value: '1000000000000000000'
}
}
expect(criterionToString(item)).toEqual(`${Criterion.CHECK_INTENT_AMOUNT}(${JSON.stringify(item.args)})`)
})

it('returns approvals criterion', () => {
const item: ApprovalsCriterion = {
criterion: Criterion.CHECK_APPROVALS,
args: [
{
approvalCount: 2,
countPrincipal: false,
approvalEntityType: EntityType.User,
entityIds: ['aa@narval.xyz']
}
]
}
expect(criterionToString(item)).toEqual(
`approvals = ${Criterion.CHECK_APPROVALS}([${item.args.map((el) => JSON.stringify(el)).join(', ')}])`
)
})
})

describe('reasonToString', () => {
it('returns reason for PERMIT rules', () => {
const item: Policy = {
then: Then.PERMIT,
name: 'policyId',
when: []
}
expect(reasonToString(item)).toBe(
'reason = {"type":"permit","policyId":"policyId","approvalsSatisfied":approvals.approvalsSatisfied,"approvalsMissing":approvals.approvalsMissing}'
)
})

it('returns reason for FORBID rules', () => {
const item: Policy = {
then: Then.FORBID,
name: 'policyId',
when: []
}
expect(reasonToString(item)).toBe(
'reason = {"type":"forbid","policyId":"policyId","approvalsSatisfied":[],"approvalsMissing":[]}'
)
})
})
Loading

0 comments on commit 2076ff8

Please sign in to comment.