Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jellyfish-testing): Add TestingWrapper as an abstraction layer approach 2 #1114

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions packages/jellyfish-testing/__tests__/testingcontainer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { RegTestFoundationKeys } from '@defichain/jellyfish-network'
import { Testing, TestingContainer } from '@defichain/jellyfish-testing'
import { MasterNodeRegTestContainer, RegTestContainer, StartFlags } from '@defichain/testcontainers'
import { TestingWrapper } from '../src/testingwrapper'

describe('create a single test container using testwrapper', () => {
let testingRef: Testing<TestingContainer>

afterEach(async () => {
await testingRef.container.stop()
})

it('should be able to create and call single regtest masternode container', async () => {
const testing = TestingWrapper.create()
testingRef = testing
await testing.container.start()

// call rpc
let blockHeight = await testing.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(0)
await testing.generate(1)
blockHeight = await testing.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(1)
})

it('should be able to create regtest non masternode container', async () => {
const testing = TestingWrapper.create(1, () => new RegTestContainer())
testingRef = testing
await testing.container.start()

// call rpc
const blockHeight = await testing.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(0)

const addr = await testing.generateAddress()
const promise = testing.container.call('generatetoaddress', [1, addr, 1])

await expect(promise).rejects.toThrow('Error: I am not masternode operator')
})

it('should be able to override start option', async () => {
const testing = TestingWrapper.create()
testingRef = testing
const startFlags: StartFlags[] = [{ name: 'fortcanninghillheight', value: 11 }]
await testing.container.start({ startFlags })

const { softforks } = await testing.rpc.blockchain.getBlockchainInfo()
// eslint-disable-next-line @typescript-eslint/dot-notation
expect(softforks['fortcanninghill'].height).toStrictEqual(11)
})

it('should create 1 testing instance even if 0 is passed in as a param to TestingWrapper.create()', async () => {
const testing = TestingWrapper.create(0, () => new MasterNodeRegTestContainer(RegTestFoundationKeys[0]))
testingRef = testing
await testing.container.start()

// call rpc
const blockHeight = await testing.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(0)
})
})

describe('create multiple test container using TestingWrapper', () => {
it('should create a masternode group and test sync block and able to add and sync non masternode container', async () => {
const tGroup = TestingWrapper.create(2)
await tGroup.start()

const alice = tGroup.get(0)
let blockHeight = await alice.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(0)

const bob = tGroup.get(1)
blockHeight = await bob.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(0)

await alice.generate(10)
await tGroup.waitForSync()

blockHeight = await alice.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(10)
blockHeight = await bob.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(10)

let blockHashAlice = await alice.rpc.blockchain.getBestBlockHash()
let blockHashBob = await alice.rpc.blockchain.getBestBlockHash()
expect(blockHashAlice).toStrictEqual(blockHashBob)

await bob.generate(10)
await tGroup.waitForSync()

blockHeight = await alice.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(20)
blockHeight = await bob.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(20)

blockHashAlice = await alice.rpc.blockchain.getBestBlockHash()
blockHashBob = await alice.rpc.blockchain.getBestBlockHash()
expect(blockHashAlice).toStrictEqual(blockHashBob)

// create a non masternode RegTestTesting
const nonMasternodeTesting = TestingWrapper.create(1, () => new RegTestContainer())
await nonMasternodeTesting.container.start()

// add to group
await tGroup.addOther(nonMasternodeTesting.container)

await tGroup.waitForSync()
blockHeight = await nonMasternodeTesting.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(20)
const blockHashNonMasternode = await nonMasternodeTesting.rpc.blockchain.getBestBlockHash()
expect(blockHashNonMasternode).toStrictEqual(blockHashAlice)

await tGroup.stop()
})

it('should create a non masternode group and add a masternode container and then generate and sync', async () => {
const tGroup = TestingWrapper.create(2, () => new RegTestContainer())
await tGroup.start()

const alice = tGroup.get(0)
let blockHeight = await alice.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(0)

const bob = tGroup.get(1)
blockHeight = await bob.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(0)

// create a masternode container
const mnTesting = TestingWrapper.create()
await mnTesting.container.start()

// add to group
await tGroup.addOther(mnTesting.container)
await tGroup.waitForSync()

const mnMike = mnTesting

blockHeight = await mnMike.rpc.blockchain.getBlockCount()
expect(blockHeight).toStrictEqual(0)

// mnMike generates 10 blocks
await mnMike.generate(10)
await tGroup.waitForSync()

const mnMikeBlockHeight = await mnMike.rpc.blockchain.getBlockCount()
const mnMikeBlockHash = await mnMike.rpc.blockchain.getBestBlockHash()

const nonMNAliceBlockHeight = await alice.rpc.blockchain.getBlockCount()
const nonMNAliceBlockHash = await alice.rpc.blockchain.getBestBlockHash()

expect(mnMikeBlockHeight).toStrictEqual(nonMNAliceBlockHeight)
expect(mnMikeBlockHash).toStrictEqual(nonMNAliceBlockHash)

await tGroup.stop()
})

it.skip('should be able to create a TestingGroup from separate masternode and non masternode containers', async () => {
const masternodeTesting = TestingWrapper.create()
await masternodeTesting.container.start()
const nonmasternodeTesting = TestingWrapper.create(1, () => new RegTestContainer())
await nonmasternodeTesting.container.start()

const tGroup = TestingWrapper.group([masternodeTesting, nonmasternodeTesting])
await tGroup.start()

// access containers from tGroup
const mnTestingAlice = tGroup.get(0)
const NonMNTestingBob = tGroup.get(1)

await mnTestingAlice.generate(1)
await tGroup.waitForSync()

const masternodeBestHash = await mnTestingAlice.rpc.blockchain.getBestBlockHash()
const nonMasternodeBestHash = await NonMNTestingBob.rpc.blockchain.getBestBlockHash()
expect(masternodeBestHash).toStrictEqual(nonMasternodeBestHash)

await tGroup.stop()
})
})
3 changes: 2 additions & 1 deletion packages/jellyfish-testing/src/anchor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { TestingGroup } from './testing'

export class TestingGroupAnchor {
constructor (
private readonly testingGroup: TestingGroup
private readonly testingGroup: TestingGroup<MasterNodeRegTestContainer>
) {
}

Expand Down
3 changes: 2 additions & 1 deletion packages/jellyfish-testing/src/fixture.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Testing } from './index'
import { poolpair } from '@defichain/jellyfish-api-core'
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'

/**
* TestingFixture setup complex fixtures for jellyfish testing.
*/
export class TestingFixture {
constructor (
private readonly testing: Testing
private readonly testing: Testing<MasterNodeRegTestContainer>
) {
}

Expand Down
3 changes: 2 additions & 1 deletion packages/jellyfish-testing/src/icxorderbook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Testing } from './index'
import BigNumber from 'bignumber.js'
import { icxorderbook } from '@defichain/jellyfish-api-core'
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'

export class TestingICX {
public symbolDFI: string
Expand All @@ -13,7 +14,7 @@ export class TestingICX {
public DEX_DFI_PER_BTC_RATE: BigNumber

constructor (
private readonly testing: Testing
private readonly testing: Testing<MasterNodeRegTestContainer>
) {
this.accountDFI = ''
this.accountBTC = ''
Expand Down
77 changes: 50 additions & 27 deletions packages/jellyfish-testing/src/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,37 @@ import { TestingRawTx } from './rawtx'
import { TestingICX } from './icxorderbook'
import { TestingMisc } from './misc'
import { TestingGroupAnchor } from './anchor'
import { ContainerGroup, MasterNodeRegTestContainer, StartOptions } from '@defichain/testcontainers'
import { ContainerGroup, MasterNodeRegTestContainer, RegTestContainer, StartOptions } from '@defichain/testcontainers'
import { RegTestFoundationKeys } from '@defichain/jellyfish-network'
import { JsonRpcClient } from '@defichain/jellyfish-api-jsonrpc'

export class Testing {
public readonly fixture = new TestingFixture(this)
public readonly token = new TestingToken(this.container, this.rpc)
public readonly poolpair = new TestingPoolPair(this.container, this.rpc)
public readonly rawtx = new TestingRawTx(this.container, this.rpc)
public readonly icxorderbook = new TestingICX(this)
public readonly misc = new TestingMisc(this.container, this.rpc)
export type TestingContainer = MasterNodeRegTestContainer | RegTestContainer
export type TestingGroupInit<Container> = (index: number) => Container

export class Testing<Container extends TestingContainer = MasterNodeRegTestContainer> {
readonly fixture: TestingFixture
readonly icxorderbook: TestingICX
readonly token: TestingToken
readonly poolpair: TestingPoolPair
readonly rawtx: TestingRawTx
readonly misc: TestingMisc

private readonly addresses: Record<string, string> = {}

private constructor (
public readonly container: MasterNodeRegTestContainer,
public readonly rpc: TestingJsonRpcClient
public readonly container: Container,
public readonly rpc: TestingJsonRpcClient<Container>
) {
this.fixture = new TestingFixture(this as Testing<MasterNodeRegTestContainer>)
this.icxorderbook = new TestingICX(this as Testing<MasterNodeRegTestContainer>)
this.token = new TestingToken(this.container as MasterNodeRegTestContainer, this.rpc)
this.poolpair = new TestingPoolPair(this.container as MasterNodeRegTestContainer, this.rpc)
this.rawtx = new TestingRawTx(this.container as MasterNodeRegTestContainer, this.rpc)
this.misc = new TestingMisc(this.container as MasterNodeRegTestContainer, this.rpc)
}

async generate (n: number): Promise<void> {
await this.container.generate(n)
await (this.container as MasterNodeRegTestContainer).generate(n)
}

async address (key: number | string): Promise<string> {
Expand All @@ -54,57 +63,71 @@ export class Testing {
return addresses
}

static create (container: MasterNodeRegTestContainer): Testing {
static create<Container extends TestingContainer> (container: Container): Testing<Container> {
const rpc = new TestingJsonRpcClient(container)
return new Testing(container, rpc)
return new Testing<Container>(container, rpc)
}
}

export class TestingGroup {
public readonly anchor = new TestingGroupAnchor(this)
export class TestingGroup<Container extends TestingContainer = MasterNodeRegTestContainer> {
readonly anchor: TestingGroupAnchor

private constructor (
public readonly group: ContainerGroup,
public readonly testings: Testing[]
public readonly testings: Array<Testing<Container>>
) {
this.anchor = new TestingGroupAnchor(this as TestingGroup<MasterNodeRegTestContainer>)
}

/**
* @param {number} n of testing container to create
* @param {(index: number) => MasterNodeRegTestContainer} [init=MasterNodeRegTestContainer]
*/
static create (
static create<Container extends TestingContainer = MasterNodeRegTestContainer> (
n: number,
init = (index: number) => new MasterNodeRegTestContainer(RegTestFoundationKeys[index])
): TestingGroup {
const containers: MasterNodeRegTestContainer[] = []
const testings: Testing[] = []
init: TestingGroupInit<Container> = (index: number): Container => {
return new MasterNodeRegTestContainer(RegTestFoundationKeys[index]) as Container
}
): TestingGroup<Container> {
const containers: Container[] = []
const testings: Array<Testing<Container>> = []
for (let i = 0; i < n; i += 1) {
const container = init(i)
containers.push(container)

const testing = Testing.create(container)
const testing = Testing.create<Container>(container)
testings.push(testing)
}

const group = new ContainerGroup(containers)
return new TestingGroup<Container>(group, testings)
}

static createFrom (group: ContainerGroup, testings: Array<Testing<TestingContainer>>): TestingGroup<TestingContainer> {
return new TestingGroup(group, testings)
}

get (index: number): Testing {
get (index: number): Testing<Container> {
return this.testings[index]
}

length (): number {
return this.testings.length
}

async add (container: MasterNodeRegTestContainer): Promise<void> {
async add (container: Container): Promise<void> {
await this.group.add(container)

const testing = Testing.create(container)
this.testings.push(testing)
}

async addOther (container: TestingContainer): Promise<void> {
// only add the container to the group. no testing will be stored since
// it's not possible to store other type with already narrowed generic type.
await this.group.add(container)
}

async start (opts?: StartOptions): Promise<void> {
return await this.group.start(opts)
}
Expand All @@ -117,7 +140,7 @@ export class TestingGroup {
return await this.group.link()
}

async exec (runner: (testing: Testing) => Promise<void>): Promise<void> {
async exec (runner: (testing: Testing<Container>) => Promise<void>): Promise<void> {
for (let i = 0; i < this.testings.length; i += 1) {
await runner(this.testings[i])
}
Expand All @@ -136,8 +159,8 @@ export class TestingGroup {
/**
* JsonRpcClient with dynamic url resolved from MasterNodeRegTestContainer.
*/
class TestingJsonRpcClient extends JsonRpcClient {
constructor (public readonly container: MasterNodeRegTestContainer) {
class TestingJsonRpcClient<Container extends TestingContainer> extends JsonRpcClient {
constructor (public readonly container: Container) {
super('resolved in fetch')
}

Expand Down
Loading