Skip to content

Commit

Permalink
Track business metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
wcalderipe committed Nov 13, 2024
1 parent aec797f commit ec2f539
Show file tree
Hide file tree
Showing 18 changed files with 307 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { secret } from '@narval/nestjs-shared'
import { REQUEST_HEADER_CLIENT_ID, REQUEST_HEADER_CLIENT_SECRET, secret } from '@narval/nestjs-shared'
import { getPublicKey, privateKeyToJwk } from '@narval/signature'
import { ExecutionContext } from '@nestjs/common'
import { mock } from 'jest-mock-extended'
import { generatePrivateKey } from 'viem/accounts'
import { REQUEST_HEADER_CLIENT_ID, REQUEST_HEADER_CLIENT_SECRET } from '../../../../../armory.constant'
import { ClientService } from '../../../../../client/core/service/client.service'
import { Client } from '../../../../../client/core/type/client.type'
import { ApplicationException } from '../../../../../shared/exception/application.exception'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { LoggerModule, LoggerService, NullLoggerService } from '@narval/nestjs-shared'
import {
LoggerModule,
LoggerService,
MetricService,
NullLoggerService,
OTEL_ATTR_CLIENT_ID,
OpenTelemetryModule,
StatefulMetricService
} from '@narval/nestjs-shared'
import {
Action,
AuthorizationRequest,
Expand Down Expand Up @@ -44,6 +52,7 @@ describe(AuthorizationRequestService.name, () => {
let clusterServiceMock: MockProxy<ClusterService>
let priceServiceMock: MockProxy<PriceService>
let feedServiceMock: MockProxy<FeedService>
let statefulMetricService: StatefulMetricService
let service: AuthorizationRequestService

const authzRequest: AuthorizationRequest = generateAuthorizationRequest({
Expand All @@ -65,7 +74,7 @@ describe(AuthorizationRequestService.name, () => {
feedServiceMock = mock<FeedService>()

module = await Test.createTestingModule({
imports: [LoggerModule],
imports: [LoggerModule, OpenTelemetryModule.forTest()],
providers: [
AuthorizationRequestService,
{
Expand Down Expand Up @@ -104,6 +113,23 @@ describe(AuthorizationRequestService.name, () => {
}).compile()

service = module.get<AuthorizationRequestService>(AuthorizationRequestService)
statefulMetricService = module.get(MetricService)
})

describe('create', () => {
it('increments create counter metric', async () => {
await service.create(authzRequest)

expect(statefulMetricService.counters).toEqual([
{
name: 'authorization_request_create_count',
value: 1,
attributes: {
[OTEL_ATTR_CLIENT_ID]: authzRequest.clientId
}
}
])
})
})

describe('approve', () => {
Expand Down Expand Up @@ -234,6 +260,21 @@ describe(AuthorizationRequestService.name, () => {
createdAt: expect.any(Date)
})
})

it('increments evaluation counter metric', async () => {
await service.evaluate(authzRequest)

expect(statefulMetricService.counters).toEqual([
{
name: 'authorization_request_evaluation_count',
value: 1,
attributes: {
[OTEL_ATTR_CLIENT_ID]: authzRequest.clientId,
'domain.authorization_request.status': AuthorizationRequestStatus.PERMITTED
}
}
])
})
})

describe('process', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LoggerService } from '@narval/nestjs-shared'
import { LoggerService, MetricService, OTEL_ATTR_CLIENT_ID } from '@narval/nestjs-shared'
import {
Action,
AuthorizationRequest,
Expand All @@ -9,7 +9,8 @@ import {
JwtString
} from '@narval/policy-engine-shared'
import { Intent, Intents } from '@narval/transaction-request-intent'
import { HttpStatus, Injectable } from '@nestjs/common'
import { HttpStatus, Inject, Injectable } from '@nestjs/common'
import { Counter } from '@opentelemetry/api'
import { v4 as uuid } from 'uuid'
import { AUTHORIZATION_REQUEST_PROCESSING_QUEUE_ATTEMPTS, FIAT_ID_USD } from '../../../armory.constant'
import { FeedService } from '../../../data-feed/core/service/feed.service'
Expand Down Expand Up @@ -40,6 +41,9 @@ const getStatus = (decision: string): AuthorizationRequestStatus => {

@Injectable()
export class AuthorizationRequestService {
private createCounter: Counter
private evaluationCounter: Counter

constructor(
private authzRequestRepository: AuthorizationRequestRepository,
private authzRequestApprovalRepository: AuthorizationRequestApprovalRepository,
Expand All @@ -48,10 +52,16 @@ export class AuthorizationRequestService {
private priceService: PriceService,
private clusterService: ClusterService,
private feedService: FeedService,
private logger: LoggerService
) {}
private logger: LoggerService,
@Inject(MetricService) private metricService: MetricService
) {
this.createCounter = this.metricService.createCounter('authorization_request_create_count')
this.evaluationCounter = this.metricService.createCounter('authorization_request_evaluation_count')
}

async create(input: CreateAuthorizationRequest): Promise<AuthorizationRequest> {
this.createCounter.add(1, { [OTEL_ATTR_CLIENT_ID]: input.clientId })

const now = new Date()

const authzRequest = await this.authzRequestRepository.create({
Expand Down Expand Up @@ -156,6 +166,12 @@ export class AuthorizationRequestService {
})

const status = getStatus(evaluation.decision)

this.evaluationCounter.add(1, {
[OTEL_ATTR_CLIENT_ID]: input.clientId,
'domain.authorization_request.status': status
})

// NOTE: we will track the transfer before we update the status to PERMITTED so that we don't have a brief window where a second transfer can come in before the history is tracked.
// TODO: (@wcalderipe, 01/02/24) Move to the TransferTrackingService.
if (input.request.action === Action.SIGN_TRANSACTION && status === AuthorizationRequestStatus.PERMITTED) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { REQUEST_HEADER_CLIENT_SECRET } from '@narval/nestjs-shared'
import { UseGuards, applyDecorators } from '@nestjs/common'
import { ApiHeader, ApiSecurity } from '@nestjs/swagger'
import { CLIENT_SECRET_SECURITY, REQUEST_HEADER_CLIENT_SECRET } from '../../armory.constant'
import { CLIENT_SECRET_SECURITY } from '../../armory.constant'
import { ClientSecretGuard } from '../guard/client-secret.guard'

export function ApiClientSecretGuard() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { secret } from '@narval/nestjs-shared'
import { REQUEST_HEADER_CLIENT_ID, REQUEST_HEADER_CLIENT_SECRET, secret } from '@narval/nestjs-shared'
import { getPublicKey, privateKeyToJwk } from '@narval/signature'
import { ExecutionContext } from '@nestjs/common'
import { mock } from 'jest-mock-extended'
import { generatePrivateKey } from 'viem/accounts'
import { REQUEST_HEADER_CLIENT_ID, REQUEST_HEADER_CLIENT_SECRET } from '../../../../armory.constant'
import { ClientService } from '../../../../client/core/service/client.service'
import { Client } from '../../../../client/core/type/client.type'
import { ApplicationException } from '../../../exception/application.exception'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { REQUEST_HEADER_CLIENT_ID } from '@narval/nestjs-shared'
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'
import { ApiHeader, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { REQUEST_HEADER_CLIENT_ID } from '../../../../policy-engine.constant'
import { ClientId } from '../../../../shared/decorator/client-id.decorator'
import { EvaluationService } from '../../../core/service/evaluation.service'
import { EvaluationRequestDto } from '../dto/evaluation-request.dto'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { REQUEST_HEADER_CLIENT_ID } from '@narval/nestjs-shared'
import { BadRequestException, createParamDecorator, ExecutionContext } from '@nestjs/common'
import { REQUEST_HEADER_CLIENT_ID } from '../../policy-engine.constant'

export const factory = (_value: unknown, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest()
Expand Down
3 changes: 1 addition & 2 deletions apps/vault/src/vault/__test__/e2e/account.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Permission } from '@narval/armory-sdk'
import { ConfigModule, ConfigService } from '@narval/config-module'
import { EncryptionModuleOptionProvider } from '@narval/encryption-module'
import { LoggerModule, secret } from '@narval/nestjs-shared'
import { LoggerModule, REQUEST_HEADER_CLIENT_ID, secret } from '@narval/nestjs-shared'
import {
Payload,
RsaPublicKey,
Expand All @@ -20,7 +20,6 @@ import { v4 as uuid } from 'uuid'
import { ClientModule } from '../../../client/client.module'
import { ClientService } from '../../../client/core/service/client.service'
import { Config, load } from '../../../main.config'
import { REQUEST_HEADER_CLIENT_ID } from '../../../main.constant'
import { TestPrismaService } from '../../../shared/module/persistence/service/test-prisma.service'
import { getTestRawAesKeyring } from '../../../shared/testing/encryption.testing'
import { Client, Origin } from '../../../shared/type/domain.type'
Expand Down
3 changes: 1 addition & 2 deletions apps/vault/src/vault/__test__/e2e/encryption-key.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Permission } from '@narval/armory-sdk'
import { ConfigModule, ConfigService } from '@narval/config-module'
import { EncryptionModuleOptionProvider } from '@narval/encryption-module'
import { LoggerModule } from '@narval/nestjs-shared'
import { LoggerModule, REQUEST_HEADER_CLIENT_ID } from '@narval/nestjs-shared'
import {
Payload,
SigningAlg,
Expand All @@ -17,7 +17,6 @@ import { v4 as uuid } from 'uuid'
import { ClientModule } from '../../../client/client.module'
import { ClientService } from '../../../client/core/service/client.service'
import { Config, load } from '../../../main.config'
import { REQUEST_HEADER_CLIENT_ID } from '../../../main.constant'
import { TestPrismaService } from '../../../shared/module/persistence/service/test-prisma.service'
import { getTestRawAesKeyring } from '../../../shared/testing/encryption.testing'
import { Client } from '../../../shared/type/domain.type'
Expand Down
3 changes: 1 addition & 2 deletions apps/vault/src/vault/__test__/e2e/sign.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ConfigModule } from '@narval/config-module'
import { EncryptionModuleOptionProvider } from '@narval/encryption-module'
import { LoggerModule } from '@narval/nestjs-shared'
import { LoggerModule, REQUEST_HEADER_CLIENT_ID } from '@narval/nestjs-shared'
import { Action, FIXTURE } from '@narval/policy-engine-shared'
import {
SigningAlg,
Expand All @@ -22,7 +22,6 @@ import { v4 as uuid } from 'uuid'
import { verifyMessage } from 'viem'
import { ClientService } from '../../../client/core/service/client.service'
import { load } from '../../../main.config'
import { REQUEST_HEADER_CLIENT_ID } from '../../../main.constant'
import { KeyValueRepository } from '../../../shared/module/key-value/core/repository/key-value.repository'
import { InMemoryKeyValueRepository } from '../../../shared/module/key-value/persistence/repository/in-memory-key-value.repository'
import { TestPrismaService } from '../../../shared/module/persistence/service/test-prisma.service'
Expand Down
3 changes: 1 addition & 2 deletions apps/vault/src/vault/__test__/e2e/wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Permission, resourceId } from '@narval/armory-sdk'
import { ConfigModule, ConfigService } from '@narval/config-module'
import { EncryptionModuleOptionProvider } from '@narval/encryption-module'
import { LoggerModule, secret } from '@narval/nestjs-shared'
import { LoggerModule, REQUEST_HEADER_CLIENT_ID, secret } from '@narval/nestjs-shared'
import {
Alg,
Curves,
Expand All @@ -27,7 +27,6 @@ import { english } from 'viem/accounts'
import { ClientModule } from '../../../client/client.module'
import { ClientService } from '../../../client/core/service/client.service'
import { Config, load } from '../../../main.config'
import { REQUEST_HEADER_CLIENT_ID } from '../../../main.constant'
import { TestPrismaService } from '../../../shared/module/persistence/service/test-prisma.service'
import { getTestRawAesKeyring } from '../../../shared/testing/encryption.testing'
import { Client, Origin } from '../../../shared/type/domain.type'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { LoggerModule } from '@narval/nestjs-shared'
import {
LoggerModule,
MetricService,
OTEL_ATTR_CLIENT_ID,
OpenTelemetryModule,
StatefulMetricService
} from '@narval/nestjs-shared'
import { Test, TestingModule } from '@nestjs/testing'
import { MockProxy, mock } from 'jest-mock-extended'
import { Origin, PrivateAccount } from '../../../../../shared/type/domain.type'
import { AccountRepository } from '../../../../persistence/repository/account.repository'
import { ImportRepository } from '../../../../persistence/repository/import.repository'
Expand All @@ -9,12 +16,18 @@ import { KeyGenerationService } from '../../key-generation.service'
describe('ImportService', () => {
let importService: ImportService
let accountRepository: AccountRepository
let statefulMetricService: StatefulMetricService
let importRepositoryMock: MockProxy<ImportRepository>

const PRIVATE_KEY = '0x7cfef3303797cbc7515d9ce22ffe849c701b0f2812f999b0847229c47951fca5'
const clientId = 'clientId'
const privateKey = '0x7cfef3303797cbc7515d9ce22ffe849c701b0f2812f999b0847229c47951fca5'
const accountId = 'accountId'

beforeEach(async () => {
importRepositoryMock = mock<ImportRepository>()

const module: TestingModule = await Test.createTestingModule({
imports: [LoggerModule.forTest()],
imports: [LoggerModule.forTest(), OpenTelemetryModule.forTest()],
providers: [
ImportService,
{
Expand All @@ -23,15 +36,15 @@ describe('ImportService', () => {
// mock the methods of AccountRepository that are used in ImportService
// for example:
save: jest.fn().mockResolvedValue({
id: 'accountId',
id: accountId,
address: '0x2c4895215973CbBd778C32c456C074b99daF8Bf1',
privateKey: PRIVATE_KEY
privateKey
})
}
},
{
provide: ImportRepository,
useValue: {}
useValue: importRepositoryMock
},
{
provide: KeyGenerationService,
Expand All @@ -40,16 +53,13 @@ describe('ImportService', () => {
]
}).compile()

importService = module.get<ImportService>(ImportService)
accountRepository = module.get<AccountRepository>(AccountRepository)
importService = module.get(ImportService)
accountRepository = module.get(AccountRepository)
statefulMetricService = module.get(MetricService)
})

describe('importPrivateKey', () => {
it('should import private key and return a account', async () => {
const clientId = 'clientId'
const privateKey = PRIVATE_KEY
const accountId = 'accountId'

it('imports private key and return an account', async () => {
const account: PrivateAccount = await importService.importPrivateKey(clientId, privateKey, accountId)

expect(account).toEqual({
Expand All @@ -66,5 +76,19 @@ describe('ImportService', () => {
address: '0x2c4895215973CbBd778C32c456C074b99daF8Bf1'
})
})

it('increments account import counter metric', async () => {
await importService.importPrivateKey(clientId, privateKey, accountId)

expect(statefulMetricService.counters).toEqual([
{
name: 'account_import_count',
value: 1,
attributes: {
[OTEL_ATTR_CLIENT_ID]: clientId
}
}
])
})
})
})
Loading

0 comments on commit ec2f539

Please sign in to comment.