Browse Source

Merge branch 'tests-setup-new-chain' into tests-giza-setup-new-chain

Mokhtar Naamani 3 years ago
parent
commit
c9e63478d7

+ 13 - 13
tests/network-tests/run-tests.sh

@@ -26,17 +26,17 @@ echo "{
 }" > ${DATA_PATH}/initial-balances.json
 
 # Make Alice a member
-echo '
-  [{
-    "member_id":0,
-    "root_account":"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
-    "controller_account":"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
-    "handle":"alice",
-    "avatar_uri":"https://alice.com/avatar.png",
-    "about":"Alice",
-    "registered_at_time":0
-  }]
-' > ${DATA_PATH}/initial-members.json
+# echo '
+#   [{
+#     "member_id":0,
+#     "root_account":"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
+#     "controller_account":"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
+#     "handle":"alice",
+#     "avatar_uri":"https://alice.com/avatar.png",
+#     "about":"Alice",
+#     "registered_at_time":0
+#   }]
+# ' > ${DATA_PATH}/initial-members.json
 
 # Create a chain spec file
 docker run --rm -v ${DATA_PATH}:/data --entrypoint ./chain-spec-builder joystream/node:${RUNTIME} \
@@ -45,8 +45,8 @@ docker run --rm -v ${DATA_PATH}:/data --entrypoint ./chain-spec-builder joystrea
   --sudo-account  5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY \
   --deployment dev \
   --chain-spec-path /data/chain-spec.json \
-  --initial-balances-path /data/initial-balances.json \
-  --initial-members-path /data/initial-members.json
+  --initial-balances-path /data/initial-balances.json
+# --initial-members-path /data/initial-members.json
 
 # Convert the chain spec file to a raw chainspec file
 docker run --rm -v ${DATA_PATH}:/data joystream/node:${RUNTIME} build-spec \

+ 76 - 19
tests/network-tests/src/Api.ts

@@ -36,6 +36,7 @@ import { VideoId } from '@joystream/types/content'
 import { ChannelId } from '@joystream/types/common'
 import { ChannelCategoryMetadata, VideoCategoryMetadata } from '@joystream/metadata-protobuf'
 import { metadataToBytes } from '../../../cli/lib/helpers/serialization'
+import { assert } from 'chai'
 
 export enum WorkingGroups {
   StorageWorkingGroup = 'storageWorkingGroup',
@@ -114,14 +115,18 @@ export class ApiFactory {
     const keys: { key: KeyringPair; id: number }[] = []
     for (let i = 0; i < n; i++) {
       const id = this.keyId++
-      const uri = `${this.miniSecret}//testing//${id}`
-      const key = this.keyring.addFromUri(uri)
+      const key = this.createCustomKeyPair(`${id}`)
       keys.push({ key, id })
       this.addressesToKeyId.set(key.address, id)
     }
     return keys
   }
 
+  public createCustomKeyPair(customPath: string): KeyringPair {
+    const uri = `${this.miniSecret}//testing//${customPath}`
+    return this.keyring.addFromUri(uri)
+  }
+
   public keyGenInfo(): { start: number; final: number } {
     const start = 0
     const final = this.keyId
@@ -167,6 +172,10 @@ export class Api {
     return this.factory.createKeyPairs(n)
   }
 
+  public createCustomKeyPair(path: string): KeyringPair {
+    return this.factory.createCustomKeyPair(path)
+  }
+
   public keyGenInfo(): { start: number; final: number } {
     return this.factory.keyGenInfo()
   }
@@ -200,6 +209,11 @@ export class Api {
     return this.sender.signAndSend(this.api.tx.sudo.sudo(tx), sudo)
   }
 
+  public async makeSudoAsCall(who: string, tx: SubmittableExtrinsic<'promise'>): Promise<ISubmittableResult> {
+    const sudo = await this.api.query.sudo.key()
+    return this.sender.signAndSend(this.api.tx.sudo.sudoAs(who, tx), sudo)
+  }
+
   public createPaidTermId(value: BN): PaidTermId {
     return this.api.createType('PaidTermId', value)
   }
@@ -211,8 +225,18 @@ export class Api {
     )
   }
 
-  public getMemberIds(address: string): Promise<MemberId[]> {
-    return this.api.query.members.memberIdsByControllerAccountId<Vec<MemberId>>(address)
+  // Many calls in the testing framework take an account id instead of a member id when an action
+  // is intended to be in the context of the member. This function is used to do a reverse lookup.
+  // There is an underlying assumption that each member has a unique controller account even
+  // though the runtime does not place that constraint. But for the purpose of the tests we throw
+  // if that condition is found to be false to esnure the tests do not fail. As long as all memberships
+  // are created through the membership fixture this should not happen.
+  public async getMemberId(address: string): Promise<MemberId> {
+    const ids = await this.api.query.members.memberIdsByControllerAccountId<Vec<MemberId>>(address)
+    if (ids.length > 1) {
+      throw new Error('More than one member with same controller account was detected')
+    }
+    return ids[0]
   }
 
   public async getBalance(address: string): Promise<Balance> {
@@ -693,7 +717,7 @@ export class Api {
     description: string,
     runtime: Bytes | string
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(memberId, name, description, stake, runtime),
       account
@@ -707,7 +731,7 @@ export class Api {
     description: string,
     text: string
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createTextProposal(memberId, name, description, stake, text),
       account
@@ -722,7 +746,7 @@ export class Api {
     balance: BN,
     destination: string
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createSpendingProposal(memberId, title, description, stake, balance, destination),
       account
@@ -736,7 +760,7 @@ export class Api {
     stake: BN,
     validatorCount: BN
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createSetValidatorCountProposal(memberId, title, description, stake, validatorCount),
       account
@@ -757,7 +781,7 @@ export class Api {
     minCouncilStake: BN,
     minVotingStake: BN
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createSetElectionParametersProposal(memberId, title, description, stake, {
         announcing_period: announcingPeriod,
@@ -781,7 +805,7 @@ export class Api {
     openingId: OpeningId,
     workingGroup: string
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createBeginReviewWorkingGroupLeaderApplicationsProposal(
         memberId,
@@ -803,7 +827,7 @@ export class Api {
     const councilAccounts = await this.getCouncilAccounts()
     return Promise.all(
       councilAccounts.map(async (account) => {
-        const memberId: MemberId = (await this.getMemberIds(account))[0]
+        const memberId: MemberId = await this.getMemberId(account)
         return this.approveProposal(account, memberId, proposal)
       })
     )
@@ -1218,7 +1242,7 @@ export class Api {
       ),
     })
 
-    const memberId: MemberId = (await this.getMemberIds(leaderOpening.account))[0]
+    const memberId: MemberId = await this.getMemberId(leaderOpening.account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createAddWorkingGroupLeaderOpeningProposal(
         memberId,
@@ -1248,7 +1272,7 @@ export class Api {
     payoutInterval: BN
     workingGroup: string
   }): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(fillOpening.account))[0]
+    const memberId: MemberId = await this.getMemberId(fillOpening.account)
 
     const fillOpeningParameters: FillOpeningParameters = this.api.createType('FillOpeningParameters', {
       opening_id: fillOpening.openingId,
@@ -1283,7 +1307,7 @@ export class Api {
     slash: boolean,
     workingGroup: string
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createTerminateWorkingGroupLeaderRoleProposal(
         memberId,
@@ -1310,7 +1334,7 @@ export class Api {
     rewardAmount: BN,
     workingGroup: string
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createSetWorkingGroupLeaderRewardProposal(
         memberId,
@@ -1334,7 +1358,7 @@ export class Api {
     rewardAmount: BN,
     workingGroup: string
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createDecreaseWorkingGroupLeaderStakeProposal(
         memberId,
@@ -1358,7 +1382,7 @@ export class Api {
     rewardAmount: BN,
     workingGroup: string
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createSlashWorkingGroupLeaderStakeProposal(
         memberId,
@@ -1381,7 +1405,7 @@ export class Api {
     mintCapacity: BN,
     workingGroup: string
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx.proposalsCodex.createSetWorkingGroupMintCapacityProposal(
         memberId,
@@ -1434,7 +1458,7 @@ export class Api {
     text: string,
     module: WorkingGroups
   ): Promise<ISubmittableResult> {
-    const memberId: MemberId = (await this.getMemberIds(account))[0]
+    const memberId: MemberId = await this.getMemberId(account)
     return this.sender.signAndSend(
       this.api.tx[module].applyOnOpening(memberId, openingId, roleAccountAddress, roleStake, applicantStake, text),
       account
@@ -1857,4 +1881,37 @@ export class Api {
       account?.toString()
     )
   }
+
+  async assignWorkerRoleAccount(
+    group: WorkingGroups,
+    workerId: WorkerId,
+    account: string
+  ): Promise<ISubmittableResult> {
+    if (!(await this.isWorker(workerId, group))) {
+      throw new Error('Worker not found')
+    }
+    const worker = await this.getWorkerById(workerId, group)
+
+    const memberController = await this.getMemberControllerAccount(worker.member_id.toNumber())
+    // there cannot be a worker associated with member that does not exist
+    assert(memberController, 'Member controller not found')
+
+    // Expect membercontroller key is already added to keyring
+    // Is is responsibility of caller to ensure this is the case!
+
+    const updateRoleAccountCall = this.api.tx[group].updateRoleAccount(workerId, account)
+    return this.makeSudoAsCall(memberController!, updateRoleAccountCall)
+  }
+
+  async assignWorkerWellknownAccount(group: WorkingGroups, workerId: WorkerId): Promise<ISubmittableResult> {
+    // path to append to base SURI
+    const uri = `worker//${this.getWorkingGroupString(group)}//${workerId.toNumber()}`
+    const account = this.createCustomKeyPair(uri).address
+    return this.assignWorkerRoleAccount(group, workerId, account)
+  }
+
+  async assignCouncil(accounts: string[]): Promise<ISubmittableResult> {
+    const setCouncilCall = this.api.tx.council.setCouncil(accounts)
+    return this.makeSudoCall(setCouncilCall)
+  }
 }

+ 25 - 0
tests/network-tests/src/fixtures/councilAssignment.ts

@@ -0,0 +1,25 @@
+import { assert } from 'chai'
+import { Api } from '../Api'
+import { BaseFixture } from '../Fixture'
+
+export class AssignCouncilFixture extends BaseFixture {
+  private members: string[]
+
+  public constructor(api: Api, members: string[]) {
+    super(api)
+    this.members = members
+  }
+
+  public async execute(): Promise<void> {
+    // Assert no council exists
+    if ((await this.api.getCouncil()).length) {
+      return this.error(new Error('Council assignment fixture expects no council seats to be filled'))
+    }
+
+    await this.api.assignCouncil(this.members)
+
+    // Assert council was set
+    const councilSize = (await this.api.getCouncil()).length
+    assert.equal(councilSize, this.members.length, 'Not Expected council size after assignment')
+  }
+}

+ 6 - 4
tests/network-tests/src/fixtures/membershipModule.ts

@@ -63,10 +63,12 @@ export class BuyMembershipWithInsufficienFundsFixture extends BaseFixture {
   }
 
   async execute(): Promise<void> {
-    // Assertions
-    const membership = await this.api.getMemberIds(this.account)
-
-    assert(membership.length === 0, 'Account must not be associated with a member')
+    try {
+      await this.api.getMemberId(this.account)
+      assert(false, 'Account must not be associated with a member')
+    } catch (err) {
+      // member id not found
+    }
 
     // Fee estimation and transfer
     const membershipFee: BN = await this.api.getMembershipFee(this.paidTerms)

+ 48 - 0
tests/network-tests/src/flows/council/assign.ts

@@ -0,0 +1,48 @@
+import BN from 'bn.js'
+import { PaidTermId } from '@joystream/types/members'
+import { FlowProps } from '../../Flow'
+import { AssignCouncilFixture } from '../../fixtures/councilAssignment'
+import { BuyMembershipHappyCaseFixture } from '../../fixtures/membershipModule'
+import { extendDebug } from '../../Debugger'
+import { FixtureRunner } from '../../Fixture'
+import { Resource } from '../../Resources'
+
+export default function createAssignCouncil(size = 1) {
+  return async function (props: FlowProps): Promise<void> {
+    return assignCouncil(props, size)
+  }
+}
+
+async function assignCouncil({ api, env, lock }: FlowProps, size: number): Promise<void> {
+  const label = 'assignCouncil'
+  const debug = extendDebug(`flow:${label}`)
+
+  debug('Started')
+
+  await lock(Resource.Council)
+
+  // Skip creating council if already elected
+  if ((await api.getCouncil()).length) {
+    return debug('Skipping council setup. A Council is already elected')
+  }
+
+  const councilSize = size || (await api.getCouncilSize()).toNumber()
+
+  debug('Assigning new council of size', councilSize)
+
+  const council = []
+
+  for (let i = 0; i < councilSize; i++) {
+    council.push(api.createCustomKeyPair(`CouncilMember//${i}`).address)
+  }
+
+  const paidTerms: PaidTermId = api.createPaidTermId(new BN(+env.MEMBERSHIP_PAID_TERMS!))
+
+  const createMembersFixture = new BuyMembershipHappyCaseFixture(api, council, paidTerms)
+  await new FixtureRunner(createMembersFixture).run()
+
+  const councilAssignment = new AssignCouncilFixture(api, council)
+  await new FixtureRunner(councilAssignment).run()
+
+  debug('Done')
+}

+ 0 - 0
tests/network-tests/src/giza/createCategories.ts → tests/network-tests/src/giza/createCategoriesFixture.ts


+ 1 - 1
tests/network-tests/src/giza/mockContentFlow.ts

@@ -3,7 +3,7 @@
 import { CreateChannelsAsMemberFixture } from './createChannelsAsMemberFixture'
 import { CreateVideosAsMemberFixture } from './createVideosAsMemberFixture'
 import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
-import { CreateMockCategories } from './createCategories'
+import { CreateMockCategories } from './createCategoriesFixture'
 
 import { FlowProps } from '../Flow'
 import { FixtureRunner } from '../Fixture'

+ 15 - 0
tests/network-tests/src/giza/updateAllWorkerRoleAccountsFlow.ts

@@ -0,0 +1,15 @@
+import { UpdateLeadWorkerAccountsFixture } from './updateWorkerAccountsFixture'
+
+import { FlowProps } from '../Flow'
+import { FixtureRunner } from '../Fixture'
+import { extendDebug } from '../Debugger'
+
+export default async function updateAllWorkerAccounts({ api }: FlowProps): Promise<void> {
+  const debug = extendDebug('flow:updateAllWorkerAccounts')
+  debug('Started')
+
+  const updateAccounts = new UpdateLeadWorkerAccountsFixture(api)
+  await new FixtureRunner(updateAccounts).run()
+
+  debug('Done')
+}

+ 16 - 0
tests/network-tests/src/giza/updateWorkerAccountsFixture.ts

@@ -0,0 +1,16 @@
+import { BaseFixture } from '../Fixture'
+import { WorkingGroups } from '../Api'
+
+export class UpdateLeadWorkerAccountsFixture extends BaseFixture {
+  public async execute(): Promise<void> {
+    const storageLead = await this.api.getLeadWorkerId(WorkingGroups.StorageWorkingGroup)
+    if (storageLead) {
+      await this.api.assignWorkerWellknownAccount(WorkingGroups.StorageWorkingGroup, storageLead)
+    }
+
+    const contentLead = await this.api.getLeadWorkerId(WorkingGroups.ContentWorkingGroup)
+    if (contentLead) {
+      await this.api.assignWorkerWellknownAccount(WorkingGroups.ContentWorkingGroup, contentLead)
+    }
+  }
+}

+ 14 - 8
tests/network-tests/src/scenarios/setup-new-chain.ts

@@ -1,22 +1,28 @@
-import councilSetup from '../flows/council/setup'
+import assignCouncil from '../flows/council/assign'
 import leaderSetup from '../flows/workingGroup/leaderSetup'
 import mockContentFlow from '../giza/mockContentFlow'
+import updateAccountsFlow from '../giza/updateAllWorkerRoleAccountsFlow'
 
 import { scenario } from '../Scenario'
 
 scenario(async ({ job }) => {
-  const council = job('Create Council', councilSetup)
+  const COUNCIL_SIZE = 1
+  job('Create Council', assignCouncil(COUNCIL_SIZE))
 
-  const contentLead = job('Set Content Lead', leaderSetup.contentIfNotSet)
-
-  // Create some mock content in content directory - without assets or any real metadata
-  const mockContent = job('Create Mock Content', mockContentFlow).after(contentLead)
-
-  const otherLeads = job('Set WorkingGroup Leads', [
+  const leads = job('Set WorkingGroup Leads', [
+    leaderSetup.contentIfNotSet,
     leaderSetup.storageIfNotSet,
     leaderSetup.distributionIfNotSet,
     leaderSetup.operationsAlphaIfNotSet,
     leaderSetup.operationsBetaIfNotSet,
     leaderSetup.operationsGammaIfNotSet,
   ])
+
+  const updateWorkerAccounts = job('Update worker accounts', updateAccountsFlow).after(leads)
+
+  // Create some mock content in content directory - without assets or any real metadata
+  job('Create Mock Content', mockContentFlow).after(updateWorkerAccounts)
+
+  // assign members known accounts?
+  // assign council known accounts?
 })

+ 2 - 0
tests/network-tests/test-setup-new-chain.sh

@@ -75,6 +75,8 @@ function cleanup() {
 
 trap cleanup EXIT
 
+sleep 3
+
 # Display runtime version
 yarn workspace api-scripts tsnode-strict src/status.ts | grep Runtime