diff --git a/packages/jellyfish-testing/__tests__/testingcontainer.test.ts b/packages/jellyfish-testing/__tests__/testingcontainer.test.ts new file mode 100644 index 0000000000..ae4f8408f2 --- /dev/null +++ b/packages/jellyfish-testing/__tests__/testingcontainer.test.ts @@ -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 + + 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() + }) +}) diff --git a/packages/jellyfish-testing/src/anchor.ts b/packages/jellyfish-testing/src/anchor.ts index 5ab28cd994..51e39d666e 100644 --- a/packages/jellyfish-testing/src/anchor.ts +++ b/packages/jellyfish-testing/src/anchor.ts @@ -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 ) { } diff --git a/packages/jellyfish-testing/src/fixture.ts b/packages/jellyfish-testing/src/fixture.ts index 392fe0d05a..0f0e6988b2 100644 --- a/packages/jellyfish-testing/src/fixture.ts +++ b/packages/jellyfish-testing/src/fixture.ts @@ -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 ) { } diff --git a/packages/jellyfish-testing/src/icxorderbook.ts b/packages/jellyfish-testing/src/icxorderbook.ts index 4abed02d71..5900639eaf 100644 --- a/packages/jellyfish-testing/src/icxorderbook.ts +++ b/packages/jellyfish-testing/src/icxorderbook.ts @@ -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 @@ -13,7 +14,7 @@ export class TestingICX { public DEX_DFI_PER_BTC_RATE: BigNumber constructor ( - private readonly testing: Testing + private readonly testing: Testing ) { this.accountDFI = '' this.accountBTC = '' diff --git a/packages/jellyfish-testing/src/testing.ts b/packages/jellyfish-testing/src/testing.ts index 6a1261aed6..f3880d2686 100644 --- a/packages/jellyfish-testing/src/testing.ts +++ b/packages/jellyfish-testing/src/testing.ts @@ -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 = (index: number) => Container + +export class Testing { + readonly fixture: TestingFixture + readonly icxorderbook: TestingICX + readonly token: TestingToken + readonly poolpair: TestingPoolPair + readonly rawtx: TestingRawTx + readonly misc: TestingMisc private readonly addresses: Record = {} private constructor ( - public readonly container: MasterNodeRegTestContainer, - public readonly rpc: TestingJsonRpcClient + public readonly container: Container, + public readonly rpc: TestingJsonRpcClient ) { + this.fixture = new TestingFixture(this as Testing) + this.icxorderbook = new TestingICX(this as Testing) + 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 { - await this.container.generate(n) + await (this.container as MasterNodeRegTestContainer).generate(n) } async address (key: number | string): Promise { @@ -54,44 +63,51 @@ export class Testing { return addresses } - static create (container: MasterNodeRegTestContainer): Testing { + static create (container: Container): Testing { const rpc = new TestingJsonRpcClient(container) - return new Testing(container, rpc) + return new Testing(container, rpc) } } -export class TestingGroup { - public readonly anchor = new TestingGroupAnchor(this) +export class TestingGroup { + readonly anchor: TestingGroupAnchor private constructor ( public readonly group: ContainerGroup, - public readonly testings: Testing[] + public readonly testings: Array> ) { + this.anchor = new TestingGroupAnchor(this as TestingGroup) } /** * @param {number} n of testing container to create * @param {(index: number) => MasterNodeRegTestContainer} [init=MasterNodeRegTestContainer] */ - static create ( + static create ( n: number, - init = (index: number) => new MasterNodeRegTestContainer(RegTestFoundationKeys[index]) - ): TestingGroup { - const containers: MasterNodeRegTestContainer[] = [] - const testings: Testing[] = [] + init: TestingGroupInit = (index: number): Container => { + return new MasterNodeRegTestContainer(RegTestFoundationKeys[index]) as Container + } + ): TestingGroup { + const containers: Container[] = [] + const testings: Array> = [] 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) testings.push(testing) } const group = new ContainerGroup(containers) + return new TestingGroup(group, testings) + } + + static createFrom (group: ContainerGroup, testings: Array>): TestingGroup { return new TestingGroup(group, testings) } - get (index: number): Testing { + get (index: number): Testing { return this.testings[index] } @@ -99,12 +115,19 @@ export class TestingGroup { return this.testings.length } - async add (container: MasterNodeRegTestContainer): Promise { + async add (container: Container): Promise { await this.group.add(container) + const testing = Testing.create(container) this.testings.push(testing) } + async addOther (container: TestingContainer): Promise { + // 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 { return await this.group.start(opts) } @@ -117,7 +140,7 @@ export class TestingGroup { return await this.group.link() } - async exec (runner: (testing: Testing) => Promise): Promise { + async exec (runner: (testing: Testing) => Promise): Promise { for (let i = 0; i < this.testings.length; i += 1) { await runner(this.testings[i]) } @@ -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 extends JsonRpcClient { + constructor (public readonly container: Container) { super('resolved in fetch') } diff --git a/packages/jellyfish-testing/src/testingwrapper.ts b/packages/jellyfish-testing/src/testingwrapper.ts new file mode 100644 index 0000000000..d124cc09c6 --- /dev/null +++ b/packages/jellyfish-testing/src/testingwrapper.ts @@ -0,0 +1,38 @@ +import { RegTestFoundationKeys } from '@defichain/jellyfish-network' +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { Testing, TestingGroup, TestingContainer } from '.' + +export type InitDeFiContainerFn = (index: number) => Container + +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class TestingWrapper { + static create (): Testing + static create (n: 0 | 1): Testing + static create (n: number): TestingGroup + static create (n: 0 | 1, init: InitDeFiContainerFn): Testing + static create (n: number, init: InitDeFiContainerFn): TestingGroup + + static create ( + n?: number, + init: InitDeFiContainerFn = (index: number): Container => { + return new MasterNodeRegTestContainer(RegTestFoundationKeys[index]) as Container + } + ): Testing | TestingGroup { + if (n === undefined || n <= 1) { + return Testing.create(init(0)) + } + + return TestingGroup.create(n, init) + } + + // static group (testings: Testing[]): TestingGroup { + // const containers: TestingContainer[] = [] + + // testings.forEach(testing => { + // containers.push(testing.container) + // }) + + // const group = new ContainerGroup(containers) + // return TestingGroup.createFrom(group, testings) + // } +}