Skip to content

Commit

Permalink
test: protocol plugins & manager (#138)
Browse files Browse the repository at this point in the history
- Integration tests for protocol plugin data
- Integration test workflow (Ci)
- Unit tests for protocol plugins & BaseProtocolPlugin class
- Unit tests for ProtocolManager
  • Loading branch information
zerotucks authored Apr 2, 2024
1 parent be04e71 commit f0cc7a7
Show file tree
Hide file tree
Showing 37 changed files with 1,168 additions and 339 deletions.
52 changes: 52 additions & 0 deletions .github/workflows/build-integration-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: CI
on:
pull_request:
types: [opened, synchronize]

jobs:
build:
name: Build and Integration Test
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 2

- name: Cache turbo build setup
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- uses: pnpm/action-setup@v2.0.1
with:
version: 8.14.1

- name: Setup Node.js environment
uses: actions/setup-node@v3
with:
node-version: 20
cache: 'pnpm'

- name: Install dependencies
run: pnpm install

- name: Prebuild
run: pnpm prebuild

- name: Build
run: pnpm build

- name: Test
run: pnpm test:integration
env:
ONE_INCH_API_KEY: ${{ secrets.ONE_INCH_API_KEY }}
ONE_INCH_API_VERSION: ${{ secrets.ONE_INCH_API_VERSION }}
ONE_INCH_API_URL: ${{ secrets.ONE_INCH_API_URL }}
ONE_INCH_ALLOWED_SWAP_PROTOCOLS: ${{ secrets.ONE_INCH_ALLOWED_SWAP_PROTOCOLS }}
ONE_INCH_SWAP_CHAIN_IDS: ${{ secrets.ONE_INCH_SWAP_CHAIN_IDS }}
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: Build Pull Request
name: CI
on:
pull_request:
types: [opened, synchronize]

jobs:
build:
name: Build and Test
name: Build and Unit Test
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"lint": "turbo run lint --cache-dir=.turbo",
"lint:fix": "turbo run lint:fix",
"test": "turbo run test --cache-dir=.turbo",
"test:integration": "turbo run test:integration --cache-dir=.turbo",
"check-circular": "turbo run check-circular --cache-dir=.turbo",
"cicheck": "turbo run cicheck --cache-dir=.turbo && pnpm format",
"sst:dev": "sst dev",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ describe('Order Planner Service', () => {
provider: undefined as unknown as PublicClient,
tokenService: undefined as unknown as ITokenService,
priceService: undefined as unknown as IPriceService,
contractProvider: undefined as unknown as IContractProvider,
},
})

Expand Down
144 changes: 144 additions & 0 deletions sdk/protocol-manager-service/tests/ProtocolManager.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { IProtocolManager, IProtocolManagerContext } from '@summerfi/protocol-manager-common'
import {
IProtocolPluginsRegistry,
IProtocolPlugin,
IProtocolPluginContext,
} from '@summerfi/protocol-plugins-common'
import { ProtocolName } from '@summerfi/sdk-common/protocols'
import { ChainInfo } from '@summerfi/sdk-common/common'
import { createPublicClient, http, PublicClient } from 'viem'
import { mainnet } from 'viem/chains'
import { ProtocolManager } from '../src'
import {
TokenService,
PriceService,
ProtocolPluginsRegistry,
} from '@summerfi/protocol-plugins/implementation'

describe('Protocol Manager', () => {
let ctx: IProtocolManagerContext
let pluginsRegistry: IProtocolPluginsRegistry
let protocolManager: IProtocolManager
let mockPlugins: Partial<Record<ProtocolName, ProtocolPluginConstructor>>

beforeEach(async () => {
ctx = await createProtocolManagerContext()
mockPlugins = {
[ProtocolName.Spark]: MockPlugin as unknown as ProtocolPluginConstructor,
[ProtocolName.Maker]: MockPlugin as unknown as ProtocolPluginConstructor,
}
pluginsRegistry = new ProtocolPluginsRegistry({
plugins: mockPlugins,
context: ctx,
})
protocolManager = new ProtocolManager({ pluginsRegistry })
})

it('should throw an error when getPool is called with an unsupported protocol', async () => {
await expect(
protocolManager.getPool({ protocol: { name: 'unsupportedProtocol' } } as any),
).rejects.toThrow('Invalid pool ID: {"protocol":{"name":"unsupportedProtocol"}}')
})

it('should throw an error when getPool is called for a chain that is not supported by the plugin', async () => {
const unsupportedChainId = 'unsupportedChain'
ctx.provider.getChainId = jest.fn().mockResolvedValue(unsupportedChainId)
await expect(
protocolManager.getPool({ protocol: { name: ProtocolName.Spark } } as any),
).rejects.toThrow(`Invalid pool ID: {"protocol":{"name":"Spark"}}`)
})

it('should retrieve the pool using the correct plugin and chain ID', async () => {
const ctx = await createProtocolManagerContext()

class TestMockPlugin extends MockPlugin {
constructor(params: {
protocolName: ProtocolName
context: IProtocolPluginContext
__overrides?: { schema?: any; supportedChains?: any[] }
}) {
super(params)
this.protocolName = ProtocolName.Spark
this.getPool = jest.fn().mockResolvedValue('mockPoolData')
}
}

const mockPlugins = {
[ProtocolName.Spark]: TestMockPlugin as unknown as ProtocolPluginConstructor,
}

const pluginsRegistry = new ProtocolPluginsRegistry({
plugins: mockPlugins,
context: ctx,
})

protocolManager = new ProtocolManager({ pluginsRegistry })

const chainId = 'supportedChain'
const poolId = {
protocol: {
name: ProtocolName.Spark,
chainInfo: ChainInfo.createFrom({ chainId: 1, name: 'Ethereum' }),
},
}

ctx.provider.getChainId = jest.fn().mockResolvedValue(chainId)

const pool = await protocolManager.getPool(poolId as any)
expect(pool).toBe('mockPoolData')
})

it('should throw an error when getPosition is called as it is not implemented', () => {
expect(() => protocolManager.getPosition()).toThrow('Not implemented')
})
})

export type ProtocolPluginConstructor = new (params: {
context: IProtocolPluginContext
}) => IProtocolPlugin

class MockPlugin implements IProtocolPlugin {
protocolName: ProtocolName
schema: any
supportedChains: any[]
stepBuilders: object
readonly context: IProtocolPluginContext

constructor(params: {
protocolName: ProtocolName
context: IProtocolPluginContext
__overrides?: { schema?: any; supportedChains?: any[] }
}) {
this.protocolName = params.protocolName
this.context = params.context
this.schema = params.__overrides?.schema ?? {}
this.supportedChains = params.__overrides?.supportedChains ?? []
this.stepBuilders = {}
}

getPool = jest.fn()
getPosition = jest.fn()
// @ts-ignore
isPoolId = jest.fn()
validatePoolId = jest.fn()
getActionBuilder = jest.fn()
ctx = () => this.context
}

async function createProtocolManagerContext(): Promise<IProtocolManagerContext> {
const RPC_URL = process.env['MAINNET_RPC_URL'] || ''
const provider: PublicClient = createPublicClient({
batch: {
multicall: true,
},
chain: mainnet,
transport: http(RPC_URL),
})

return {
provider,
tokenService: new TokenService(),
priceService: new PriceService(provider),
}
}
127 changes: 0 additions & 127 deletions sdk/protocol-manager-service/tests/tests.spec.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface IProtocolPlugin {
stepBuilders: Partial<ActionBuildersMap>
context: IProtocolPluginContext

isPoolId: (candidate: unknown) => candidate is IPoolId
isPoolId(candidate: unknown): candidate is IPoolId
validatePoolId(candidate: unknown): asserts candidate is IPoolId

getPool: (poolId: IPoolId) => Promise<IPool>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { PublicClient } from 'viem'
import { IPriceService } from './IPriceService'
import { ITokenService } from './ITokenService'
import { IContractProvider } from './IContractProvider'
// import { IContractProvider } from './IContractProvider'

export interface IProtocolPluginContext {
provider: PublicClient
tokenService: ITokenService
priceService: IPriceService
contractProvider: IContractProvider
}
1 change: 1 addition & 0 deletions sdk/protocol-plugins/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ require('dotenv').config({ path: '../.env' })
module.exports = {
...sharedConfig(compilerOptions),
silent: false,
coveragePathIgnorePatterns: ['/tests/utils/', '/tests/mocks/'],
}
Loading

0 comments on commit f0cc7a7

Please sign in to comment.