Browse Source

Merge pull request #3324 from Lezek123/olympia-qn-MemberVerificationStatusUpdated-fix

Fix MemberVerificationStatusUpdated event handler
Mokhtar Naamani 3 years ago
parent
commit
b96db65665

+ 8 - 6
query-node/mappings/src/membership.ts

@@ -5,7 +5,7 @@ import { EventContext, StoreContext, DatabaseManager, SubstrateEvent } from '@jo
 import { Members } from '../generated/types'
 import { MemberId, BuyMembershipParameters, InviteMembershipParameters } from '@joystream/types/augment/all'
 import { MembershipMetadata } from '@joystream/metadata-protobuf'
-import { bytesToString, deserializeMetadata, genericEventFields } from './common'
+import { bytesToString, deserializeMetadata, genericEventFields, getWorker } from './common'
 import {
   Membership,
   MembershipEntryMethod,
@@ -265,12 +265,13 @@ export async function members_MemberAccountsUpdated({ store, event }: EventConte
   await store.save<MemberAccountsUpdatedEvent>(memberAccountsUpdatedEvent)
 }
 
-export async function members_MemberVerificationStatusUpdated(
-  store: DatabaseManager,
-  event: SubstrateEvent
-): Promise<void> {
-  const [memberId, verificationStatus] = new Members.MemberVerificationStatusUpdatedEvent(event).params
+export async function members_MemberVerificationStatusUpdated({
+  store,
+  event,
+}: EventContext & StoreContext): Promise<void> {
+  const [memberId, verificationStatus, workerId] = new Members.MemberVerificationStatusUpdatedEvent(event).params
   const member = await getMemberById(store, memberId)
+  const worker = await getWorker(store, 'membershipWorkingGroup', workerId)
   const eventTime = new Date(event.blockTimestamp)
 
   member.isVerified = verificationStatus.valueOf()
@@ -282,6 +283,7 @@ export async function members_MemberVerificationStatusUpdated(
     ...genericEventFields(event),
     member: member,
     isVerified: member.isVerified,
+    worker,
   })
 
   await store.save<MemberVerificationStatusUpdatedEvent>(memberVerificationStatusUpdatedEvent)

+ 14 - 0
tests/network-tests/src/QueryNodeApi.ts

@@ -311,6 +311,10 @@ import {
   GetOwnedNftByVideoId,
   GetOwnedNftByVideoIdQuery,
   GetOwnedNftByVideoIdQueryVariables,
+  MemberVerificationStatusUpdatedEventFieldsFragment,
+  GetMemberVerificationStatusUpdatedEventsByEventIdsQuery,
+  GetMemberVerificationStatusUpdatedEventsByEventIdsQueryVariables,
+  GetMemberVerificationStatusUpdatedEventsByEventIds,
 } from './graphql/generated/queries'
 import { Maybe } from './graphql/generated/schema'
 import { OperationDefinitionNode } from 'graphql'
@@ -1119,4 +1123,14 @@ export class QueryNodeApi {
       'ownedNfts'
     )
   }
+
+  public async getMembershipVerificationStatusUpdatedEvents(
+    events: EventDetails[]
+  ): Promise<MemberVerificationStatusUpdatedEventFieldsFragment[]> {
+    const eventIds = events.map((e) => this.getQueryNodeEventId(e.blockNumber, e.indexInBlock))
+    return this.multipleEntitiesQuery<
+      GetMemberVerificationStatusUpdatedEventsByEventIdsQuery,
+      GetMemberVerificationStatusUpdatedEventsByEventIdsQueryVariables
+    >(GetMemberVerificationStatusUpdatedEventsByEventIds, { eventIds }, 'memberVerificationStatusUpdatedEvents')
+  }
 }

+ 77 - 0
tests/network-tests/src/fixtures/membership/UpdateVerificationStatusFixture.ts

@@ -0,0 +1,77 @@
+import { Api } from '../../Api'
+import { assert } from 'chai'
+import { QueryNodeApi } from '../../QueryNodeApi'
+import { SubmittableExtrinsic } from '@polkadot/api/types'
+import { StandardizedFixture } from '../../Fixture'
+import { EventDetails } from '../../types'
+import {
+  MembershipFieldsFragment,
+  MemberVerificationStatusUpdatedEventFieldsFragment,
+} from '../../graphql/generated/queries'
+import { WorkerId, Worker } from '@joystream/types/working-group'
+import { ISubmittableResult } from '@polkadot/types/types'
+import { MemberId } from '@joystream/types/common'
+
+export type UpdateVerificationStatusDetails = {
+  memberId: MemberId
+  isVerified: boolean
+}
+
+export class UpdateVerificationStatusFixture extends StandardizedFixture {
+  private updates: UpdateVerificationStatusDetails[]
+  private membershipWgLead!: [WorkerId, Worker]
+
+  public constructor(api: Api, query: QueryNodeApi, updates: UpdateVerificationStatusDetails[]) {
+    super(api, query)
+    this.updates = updates
+  }
+
+  private async loadMembershipLead(): Promise<void> {
+    this.membershipWgLead = await this.api.getLeader('membershipWorkingGroup')
+  }
+
+  protected async getExtrinsics(): Promise<SubmittableExtrinsic<'promise'>[]> {
+    return this.updates.map((u) =>
+      this.api.tx.members.updateProfileVerification(this.membershipWgLead[0], u.memberId, u.isVerified)
+    )
+  }
+
+  protected async getSignerAccountOrAccounts(): Promise<string> {
+    return this.membershipWgLead[1].role_account_id.toString()
+  }
+
+  protected assertQueryNodeEventIsValid(qEvent: MemberVerificationStatusUpdatedEventFieldsFragment, i: number): void {
+    const update = this.updates[i]
+    assert.equal(qEvent.isVerified, update.isVerified)
+    assert.equal(qEvent.worker.id, `membershipWorkingGroup-${this.membershipWgLead[0].toString()}`)
+  }
+
+  protected async getEventFromResult(result: ISubmittableResult): Promise<EventDetails> {
+    return this.api.getEventDetails(result, 'members', 'MemberVerificationStatusUpdated')
+  }
+
+  private assertStatusUpdateSuccesful(qMembers: MembershipFieldsFragment[]) {
+    this.updates.forEach(({ memberId, isVerified }) => {
+      const qMember = qMembers.find((m) => m.id === memberId.toString())
+      if (!qMember) {
+        throw new Error('Query node: Membership not found!')
+      }
+      assert.equal(qMember.isVerified, isVerified)
+    })
+  }
+
+  async execute(): Promise<void> {
+    await this.loadMembershipLead()
+    await super.execute()
+  }
+
+  async runQueryNodeChecks(): Promise<void> {
+    await super.runQueryNodeChecks()
+    await this.query.tryQueryWithTimeout(
+      () => this.query.getMembersByIds(this.updates.map((u) => u.memberId)),
+      (qMembers) => this.assertStatusUpdateSuccesful(qMembers)
+    )
+    const qEvents = await this.query.getMembershipVerificationStatusUpdatedEvents(this.events)
+    await this.assertQueryNodeEventsAreValid(qEvents)
+  }
+}

+ 33 - 0
tests/network-tests/src/flows/membership/updateVerificationStatus.ts

@@ -0,0 +1,33 @@
+import { FlowProps } from '../../Flow'
+import { BuyMembershipHappyCaseFixture } from '../../fixtures/membership'
+
+import { extendDebug } from '../../Debugger'
+import { FixtureRunner } from '../../Fixture'
+import {
+  UpdateVerificationStatusDetails,
+  UpdateVerificationStatusFixture,
+} from '../../fixtures/membership/UpdateVerificationStatusFixture'
+
+export default async function updatingVerificationStatus({ api, query }: FlowProps): Promise<void> {
+  const debug = extendDebug('flow:updating-member-verification-status')
+  debug('Started')
+  api.enableDebugTxLogs()
+
+  const accounts = (await api.createKeyPairs(2)).map(({ key }) => key.address)
+  const buyMembershipsFixture = new BuyMembershipHappyCaseFixture(api, query, accounts)
+  await new FixtureRunner(buyMembershipsFixture).runWithQueryNodeChecks()
+
+  const updates1: UpdateVerificationStatusDetails[] = buyMembershipsFixture
+    .getCreatedMembers()
+    .map((memberId) => ({ memberId, isVerified: true }))
+  const updateStatusFixture1 = new UpdateVerificationStatusFixture(api, query, updates1)
+  await new FixtureRunner(updateStatusFixture1).runWithQueryNodeChecks()
+
+  const updates2: UpdateVerificationStatusDetails[] = buyMembershipsFixture
+    .getCreatedMembers()
+    .map((memberId) => ({ memberId, isVerified: false }))
+  const updateStatusFixture2 = new UpdateVerificationStatusFixture(api, query, updates2)
+  await new FixtureRunner(updateStatusFixture2).runWithQueryNodeChecks()
+
+  debug('Done')
+}

+ 41 - 0
tests/network-tests/src/graphql/generated/queries.ts

@@ -790,6 +790,25 @@ export type GetInitialInvitationCountUpdatedEventsByEventIdQuery = {
   initialInvitationCountUpdatedEvents: Array<InitialInvitationCountUpdatedEventFieldsFragment>
 }
 
+export type MemberVerificationStatusUpdatedEventFieldsFragment = {
+  id: string
+  createdAt: any
+  inBlock: number
+  network: Types.Network
+  inExtrinsic?: Types.Maybe<string>
+  indexInBlock: number
+  isVerified: boolean
+  worker: { id: string }
+}
+
+export type GetMemberVerificationStatusUpdatedEventsByEventIdsQueryVariables = Types.Exact<{
+  eventIds: Array<Types.Scalars['ID']> | Types.Scalars['ID']
+}>
+
+export type GetMemberVerificationStatusUpdatedEventsByEventIdsQuery = {
+  memberVerificationStatusUpdatedEvents: Array<MemberVerificationStatusUpdatedEventFieldsFragment>
+}
+
 type ProposalStatusFields_ProposalStatusDeciding_Fragment = {
   __typename: 'ProposalStatusDeciding'
   proposalStatusUpdatedEvent?: Types.Maybe<{
@@ -2791,6 +2810,20 @@ export const InitialInvitationCountUpdatedEventFields = gql`
     newInitialInvitationCount
   }
 `
+export const MemberVerificationStatusUpdatedEventFields = gql`
+  fragment MemberVerificationStatusUpdatedEventFields on MemberVerificationStatusUpdatedEvent {
+    id
+    createdAt
+    inBlock
+    network
+    inExtrinsic
+    indexInBlock
+    worker {
+      id
+    }
+    isVerified
+  }
+`
 export const ApplicationFormQuestionFields = gql`
   fragment ApplicationFormQuestionFields on ApplicationFormQuestion {
     question
@@ -4189,6 +4222,14 @@ export const GetInitialInvitationCountUpdatedEventsByEventId = gql`
   }
   ${InitialInvitationCountUpdatedEventFields}
 `
+export const GetMemberVerificationStatusUpdatedEventsByEventIds = gql`
+  query getMemberVerificationStatusUpdatedEventsByEventIds($eventIds: [ID!]!) {
+    memberVerificationStatusUpdatedEvents(where: { id_in: $eventIds }) {
+      ...MemberVerificationStatusUpdatedEventFields
+    }
+  }
+  ${MemberVerificationStatusUpdatedEventFields}
+`
 export const GetProposalsByIds = gql`
   query getProposalsByIds($ids: [ID!]) {
     proposals(where: { id_in: $ids }) {

+ 19 - 0
tests/network-tests/src/graphql/queries/membershipEvents.graphql

@@ -238,3 +238,22 @@ query getInitialInvitationCountUpdatedEventsByEventId($eventId: ID!) {
     ...InitialInvitationCountUpdatedEventFields
   }
 }
+
+fragment MemberVerificationStatusUpdatedEventFields on MemberVerificationStatusUpdatedEvent {
+  id
+  createdAt
+  inBlock
+  network
+  inExtrinsic
+  indexInBlock
+  worker {
+    id
+  }
+  isVerified
+}
+
+query getMemberVerificationStatusUpdatedEventsByEventIds($eventIds: [ID!]!) {
+  memberVerificationStatusUpdatedEvents(where: { id_in: $eventIds }) {
+    ...MemberVerificationStatusUpdatedEventFields
+  }
+}

+ 4 - 0
tests/network-tests/src/scenarios/full.ts

@@ -32,6 +32,7 @@ import createChannel from '../flows/clis/createChannel'
 import { scenario } from '../Scenario'
 import activeVideoCounters from '../flows/content/activeVideoCounters'
 import nftAuctionAndOffers from '../flows/content/nftAuctionAndOffers'
+import updatingVerificationStatus from '../flows/membership/updateVerificationStatus'
 
 scenario('Full', async ({ job, env }) => {
   // Runtime upgrade should always be first job
@@ -80,6 +81,9 @@ scenario('Full', async ({ job, env }) => {
   job('worker actions', workerActions).requires(sudoHireLead)
   job('group budget', groupBudget).requires(sudoHireLead)
 
+  // Memberships (depending on hired lead)
+  job('updating member verification status', updatingVerificationStatus).after(sudoHireLead)
+
   // Forum:
   job('forum categories', categories).requires(sudoHireLead)
   job('forum threads', threads).requires(sudoHireLead)

+ 6 - 0
tests/network-tests/src/scenarios/memberships.ts

@@ -6,9 +6,14 @@ import transferringInvites from '../flows/membership/transferringInvites'
 import managingStakingAccounts from '../flows/membership/managingStakingAccounts'
 import membershipSystem from '../flows/membership/membershipSystem'
 import { scenario } from '../Scenario'
+import updatingVerificationStatus from '../flows/membership/updateVerificationStatus'
+import leadOpening from '../flows/working-groups/leadOpening'
 
 scenario('Memberships', async ({ job }) => {
   const membershipSystemJob = job('membership system', membershipSystem)
+  const sudoHireLead = job('sudo lead opening', leadOpening(true, ['membershipWorkingGroup'])).after(
+    membershipSystemJob
+  )
   // All other job should be executed after, otherwise changing membershipPrice etc. may break them
   job('creating members', creatingMemberships).after(membershipSystemJob)
   job('updating member profile', updatingMemberProfile).after(membershipSystemJob)
@@ -16,4 +21,5 @@ scenario('Memberships', async ({ job }) => {
   job('inviting members', invitingMebers).after(membershipSystemJob)
   job('transferring invites', transferringInvites).after(membershipSystemJob)
   job('managing staking accounts', managingStakingAccounts).after(membershipSystemJob)
+  job('updating member verification status', updatingVerificationStatus).after(sudoHireLead)
 })