feat: add @ocom-verification coverage for member management (#293) - #298
feat: add @ocom-verification coverage for member management (#293)#298aaron-rabinowitz wants to merge 16 commits into
Conversation
… headers to 'execute' cellix funciton
Reviewer's GuideAdds end-to-end and acceptance coverage for community member creation and profile update flows across API and UI, wires GraphQL client to support per-request headers, and integrates event handlers and principal hints to drive member management scenarios. Sequence diagram for member profile update via API using per-request GraphQL headerssequenceDiagram
actor ApiActor
participant UpdateMember as UpdateMemberTask
participant UpdateMemberProfile as UpdateMemberProfileAbility
participant GraphQLClient
participant ApiServer as MemberAPI
ApiActor->>UpdateMember: performAs(details)
UpdateMember->>ApiActor: notes<MemberNotes>().get('lastMemberId')
UpdateMember->>ApiActor: notes<MemberNotes>().get('lastMemberCommunityId')
UpdateMember->>ApiActor: PrincipalMemberIdForCommunity.inCommunity(communityId)
UpdateMember->>UpdateMemberProfile: performAs(actor, { memberId, profile, communityId, principalMemberId })
UpdateMemberProfile->>GraphQLClient: execute(MEMBER_UPDATE_PROFILE_MUTATION, variables, { headers: { 'x-community-id', 'x-member-id' } })
GraphQLClient->>ApiServer: HTTP POST /graphql
ApiServer-->>GraphQLClient: memberUpdateProfile response
GraphQLClient-->>UpdateMemberProfile: { data, errors? }
UpdateMemberProfile-->>UpdateMember: { id, profile }
UpdateMember->>ApiActor: notes<MemberNotes>().set('lastMemberProfileEmail', profile.email)
UpdateMember->>ApiActor: notes<MemberNotes>().set('lastMemberStatus', 'SUCCESS')
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- The global
eventHandlersRegisteredflag increateMockApplicationServicesFactorymay cause cross-test interference or issues under parallel execution; consider scoping event handler registration per test server or using a factory-level guard instead. - You’re repeatedly re-declaring
ExecuteWithRequestOptionsand castingGraphQLClienttoGraphQLClient & { execute: ExecuteWithRequestOptions }; it would be more robust to haveGraphQLClient.executeformally typed to acceptGraphQLExecuteOptionsor expose a reusable type so these casts aren’t needed. - Helpers like
hasGraphqlOperation,selectGraphqlPayload, andgraphqlErrorsare duplicated across several member tasks; consider extracting them into a shared utility to reduce repetition and keep future changes to GraphQL response handling in one place.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The global `eventHandlersRegistered` flag in `createMockApplicationServicesFactory` may cause cross-test interference or issues under parallel execution; consider scoping event handler registration per test server or using a factory-level guard instead.
- You’re repeatedly re-declaring `ExecuteWithRequestOptions` and casting `GraphQLClient` to `GraphQLClient & { execute: ExecuteWithRequestOptions }`; it would be more robust to have `GraphQLClient.execute` formally typed to accept `GraphQLExecuteOptions` or expose a reusable type so these casts aren’t needed.
- Helpers like `hasGraphqlOperation`, `selectGraphqlPayload`, and `graphqlErrors` are duplicated across several member tasks; consider extracting them into a shared utility to reduce repetition and keep future changes to GraphQL response handling in one place.
## Individual Comments
### Comment 1
<location path="packages/ocom-verification/acceptance-api/src/shared/abilities/create-member.ts" line_range="55" />
<code_context>
+ throw new Error('principalMemberId is required to create a member');
+ }
+
+ const response = await graphql.execute(
+ MEMBER_CREATE_MUTATION,
+ {
</code_context>
<issue_to_address>
**issue (bug_risk):** The optional `role` field in `CreateMemberDetails` is never sent to the GraphQL API.
`CreateMemberDetails.role` is passed through `CreateMemberTaskDetails` but never included in the mutation input, so it is effectively ignored. If `role` should affect backend behavior, add it to the GraphQL input and schema handling; if not, remove it from `CreateMemberDetails` and the task to avoid confusing API consumers.
</issue_to_address>
### Comment 2
<location path="packages/ocom-verification/acceptance-api/src/mock-application-services.ts" line_range="23" />
<code_context>
type EndUserUpdateQueueMessage = Awaited<ReturnType<QueueStorageOperations['receiveFromEndUserUpdateQueue']>>;
const communityCreationMessages: RecordedCommunityCreationMessage[] = [];
+let eventHandlersRegistered = false;
function createMockTokenValidation(): TokenValidation {
</code_context>
<issue_to_address>
**issue (bug_risk):** Global `eventHandlersRegistered` flag may cause cross-test coupling and concurrency issues.
Because `eventHandlersRegistered` is module-level, only the first `createMockApplicationServicesFactory` call will register handlers; subsequent factories may skip registration even if they use different `serviceMongoose` instances or state. In parallel tests or multi-tenant environments this shared flag can cause inconsistent, hard-to-debug behavior. Consider scoping registration to the factory (e.g., per `serviceMongoose` or domainDataSource), or providing an explicit idempotent registration API on the event handler module rather than a global flag.
</issue_to_address>
### Comment 3
<location path="packages/ocom-verification/acceptance-ui/src/contexts/community/tasks/create-member.ts" line_range="8-10" />
<code_context>
+import { type Actor, Task } from '@serenity-js/core';
+import type { AcceptanceUiMemberCreatePage } from '../../../shared/page-contracts.ts';
+
+/** Let the form's async `onFinish` callback settle before assertions run. */
+async function flushPendingReactWork(): Promise<void> {
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
+}
</code_context>
<issue_to_address>
**suggestion (testing):** Relying on two `setTimeout(0)` calls to flush React work can be brittle.
This helper waits for `onSave` to complete using two zero-delay timers, which is a timing-based approach and can become flaky if the component does more async work or the test runner’s scheduling changes. If there’s a more explicit signal to await (e.g. `waitFor` on a specific state/DOM change or a promise returned from `onSave`), using that instead would make these acceptance tests more reliable.
Suggested implementation:
```typescript
import { RenderInDom } from '@cellix/serenity-framework/dom/render-in-dom';
import { DomPageAdapter } from '@cellix/serenity-framework/pages/dom';
import { TaskStep } from '@cellix/serenity-framework/serenity';
import { MemberCreatePage } from '@ocom-verification/verification-shared/pages';
import { type Actor, Task } from '@serenity-js/core';
import type { AcceptanceUiMemberCreatePage } from '../../../shared/page-contracts.ts';
/**
* Wait for the member creation to complete using an explicit UI signal
* instead of timing-based `setTimeout(0)` calls.
*/
async function waitForMemberCreationToComplete(
page: AcceptanceUiMemberCreatePage,
memberName: string,
): Promise<void> {
await page.waitForMemberToBeCreated(memberName);
}
export const CreateMember = (memberName: string): Task =>
```
To fully implement this change and remove the brittle timing-based approach:
1. Replace any calls to `flushPendingReactWork()` in this file with:
```ts
await waitForMemberCreationToComplete(page, memberName);
```
right after `await page.clickCreateMember();` and before any assertions that depend on the member having been created.
2. In the `AcceptanceUiMemberCreatePage` contract and its concrete `MemberCreatePage` implementation, add a method such as:
```ts
waitForMemberToBeCreated(memberName: string): Promise<void>;
```
that explicitly waits for a reliable UI signal (e.g. new row appearing in a list, success toast, navigation completion, or a specific DOM state) indicating that `onFinish`/`onSave` has completed.
3. If the existing page objects already expose a more appropriate method (e.g. `waitUntilSaved`, `waitForCreateMemberSuccess`, etc.), adjust `waitForMemberCreationToComplete` to call that method instead of `waitForMemberToBeCreated`, and update the helper’s name/comment to reflect the actual signal being used.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| /** Let the form's async `onFinish` callback settle before assertions run. */ | ||
| async function flushPendingReactWork(): Promise<void> { | ||
| await new Promise<void>((resolve) => setTimeout(resolve, 0)); |
There was a problem hiding this comment.
suggestion (testing): Relying on two setTimeout(0) calls to flush React work can be brittle.
This helper waits for onSave to complete using two zero-delay timers, which is a timing-based approach and can become flaky if the component does more async work or the test runner’s scheduling changes. If there’s a more explicit signal to await (e.g. waitFor on a specific state/DOM change or a promise returned from onSave), using that instead would make these acceptance tests more reliable.
Suggested implementation:
import { RenderInDom } from '@cellix/serenity-framework/dom/render-in-dom';
import { DomPageAdapter } from '@cellix/serenity-framework/pages/dom';
import { TaskStep } from '@cellix/serenity-framework/serenity';
import { MemberCreatePage } from '@ocom-verification/verification-shared/pages';
import { type Actor, Task } from '@serenity-js/core';
import type { AcceptanceUiMemberCreatePage } from '../../../shared/page-contracts.ts';
/**
* Wait for the member creation to complete using an explicit UI signal
* instead of timing-based `setTimeout(0)` calls.
*/
async function waitForMemberCreationToComplete(
page: AcceptanceUiMemberCreatePage,
memberName: string,
): Promise<void> {
await page.waitForMemberToBeCreated(memberName);
}
export const CreateMember = (memberName: string): Task =>To fully implement this change and remove the brittle timing-based approach:
-
Replace any calls to
flushPendingReactWork()in this file with:await waitForMemberCreationToComplete(page, memberName);
right after
await page.clickCreateMember();and before any assertions that depend on the member having been created. -
In the
AcceptanceUiMemberCreatePagecontract and its concreteMemberCreatePageimplementation, add a method such as:waitForMemberToBeCreated(memberName: string): Promise<void>;
that explicitly waits for a reliable UI signal (e.g. new row appearing in a list, success toast, navigation completion, or a specific DOM state) indicating that
onFinish/onSavehas completed. -
If the existing page objects already expose a more appropriate method (e.g.
waitUntilSaved,waitForCreateMemberSuccess, etc.), adjustwaitForMemberCreationToCompleteto call that method instead ofwaitForMemberToBeCreated, and update the helper’s name/comment to reflect the actual signal being used.
…end-users to members, removing members, and validation; refactor existing scenario implementations
|
@sourcery-ai review |
|
Sorry @aaron-rabinowitz, your pull request is larger than the review limit of 150000 diff characters |
|
@sourcery-ai review |
|
Sorry @aaron-rabinowitz, your pull request is larger than the review limit of 150000 diff characters |
noce-nick
left a comment
There was a problem hiding this comment.
May want to consider refactoring some tasks in acceptance-api to leverage interactions as well
| }; | ||
|
|
||
| export const AssignMemberAccount = (communityName: string, displayName: string) => | ||
| Interaction.where(the`#actor assigns end-user account "${displayName}" in "${communityName}" via UI`, async (serenityActor) => { |
There was a problem hiding this comment.
In all of the e2e-tests tasks, you have created Interactions in-line within the task files. Can we extract the Interactions out into their own folder for interactions/ and then use those as needed in tasks/. Will help us avoid duplicate logic for interactions in different task files
There was a problem hiding this comment.
e2e-test tasks were refactored to use reusable interactions inside of interactions/
Summary by Sourcery
Add verification coverage for community member management across API, UI and E2E flows, including member creation and profile update scenarios.
New Features:
Bug Fixes:
Enhancements:
Build:
Tests: