diff --git a/tests/e2e/cucumber/features/ocm/ocm.feature b/tests/e2e/cucumber/features/ocm/ocm.feature index 62cb628735c..44a0d56c8bc 100644 --- a/tests/e2e/cucumber/features/ocm/ocm.feature +++ b/tests/e2e/cucumber/features/ocm/ocm.feature @@ -13,10 +13,17 @@ Feature: federation management | pathToFile | content | | example1.txt | example text | And "Alice" logs in + And "Alice" opens the "open-cloud-mesh" app + When "Alice" generates invitation token for the federation share And "Alice" logs out Given using "FEDERATED" server And "Admin" creates following user using API | id | | Brian | And "Brian" logs in + And "Brian" opens the "open-cloud-mesh" app + When "Brian" accepts federated share invitation by local user "Alice" + Then "Brian" should see the following federated connections: + | user | email | + | Alice Hansen | alice@example.org | And "Brian" logs out diff --git a/tests/e2e/cucumber/steps/ui/federation.ts b/tests/e2e/cucumber/steps/ui/federation.ts new file mode 100644 index 00000000000..1458607047e --- /dev/null +++ b/tests/e2e/cucumber/steps/ui/federation.ts @@ -0,0 +1,34 @@ +import { Given, When, Then, DataTable } from '@cucumber/cucumber' +import { World } from '../../environment' +import { objects } from '../../../support' +import { expect } from '@playwright/test' +Given( + '{string} generates invitation token for the federation share', + async function (this: World, stepUser: any): Promise { + const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const pageObject = new objects.scienceMesh.Federation({ page }) + const user = this.usersEnvironment.getUser({ key: stepUser }) + await pageObject.generateInvitation(user.id) + } +) + +When( + '{string} accepts federated share invitation by local user {string}', + async function (this: World, stepUser: string, sharer: string): Promise { + const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const pageObject = new objects.scienceMesh.Federation({ page }) + await pageObject.acceptInvitation(sharer) + } +) + +Then( + '{string} should see the following federated connections:', + async function (this: World, stepUser: any, stepTable: DataTable): Promise { + const { page } = this.actorsEnvironment.getActor({ key: stepUser }) + const pageObject = new objects.scienceMesh.Federation({ page }) + for (const info of stepTable.hashes()) { + const isConnectionExist = await pageObject.connectionExists(info) + await expect(isConnectionExist).toBeTruthy() + } + } +) diff --git a/tests/e2e/support/objects/federation/actions.ts b/tests/e2e/support/objects/federation/actions.ts new file mode 100644 index 00000000000..4f7ba124875 --- /dev/null +++ b/tests/e2e/support/objects/federation/actions.ts @@ -0,0 +1,61 @@ +import { expect, Page } from '@playwright/test' +import util from 'util' +import { federatedInvitationCode } from '../../store' + +const generateInvitationButton = + '//button[contains(@aria-label,"Generate invitation link that can be shared with one or more invitees")]' +const generateInvitationActionConfirmButton = '.oc-modal-body-actions-confirm' +const invitationToken = 'span:has-text("%s")' +const InvitationInput = '//input[starts-with(@id, "oc-textinput-")]' +const invitationConnectionRow = + '//div[@id="sciencemesh-connections"]//td[text()="%s"]/parent::tr/td[text()="%s"]' +const institutionOptionDropdown = '.vs__open-indicator' +const acceptInvitationButton = 'button:has(span:has-text("Accept invitation"))' + +export const generateInvitation = async (args: { page: Page; user: string }): Promise => { + const { page, user } = args + await page.locator(generateInvitationButton).click() + let inviteCode = '' + await Promise.all([ + page.waitForResponse(async (resp) => { + if ( + resp.url().endsWith('generate-invite') && + resp.status() === 200 && + resp.request().method() === 'POST' + ) { + const responseBody = await resp.json() + inviteCode = responseBody.token + return true + } + return false + }), + page.locator(generateInvitationActionConfirmButton).click() + ]) + await expect(page.locator(util.format(invitationToken, inviteCode))).toBeVisible() + federatedInvitationCode.set(user, { code: inviteCode }) +} + +export const acceptInvitation = async (args: { page: Page; sharer: string }): Promise => { + const { page, sharer } = args + const invitation = federatedInvitationCode.get(sharer.toLowerCase()) + await page.locator(InvitationInput).fill(invitation.code) + await page.locator(institutionOptionDropdown).click() + await page.getByRole('option', { name: 'first-ocis-instance ocis-server:' }).click() + await Promise.all([ + page.waitForResponse( + (resp) => + resp.url().endsWith('accept-invite') && + resp.status() === 200 && + resp.request().method() === 'POST' + ), + page.locator(acceptInvitationButton).click() + ]) +} + +export const connectionExists = async (args: { page: Page; info }): Promise => { + const { page, info } = args + await expect( + page.locator(util.format(invitationConnectionRow, info.user, info.email)) + ).toBeVisible() + return true +} diff --git a/tests/e2e/support/objects/federation/index.ts b/tests/e2e/support/objects/federation/index.ts new file mode 100644 index 00000000000..3f0ef25a28d --- /dev/null +++ b/tests/e2e/support/objects/federation/index.ts @@ -0,0 +1,20 @@ +import { Page } from '@playwright/test' +import * as po from './actions' + +export class Federation { + #page: Page + + constructor({ page }: { page: Page }) { + this.#page = page + } + async generateInvitation(user: string): Promise { + await po.generateInvitation({ page: this.#page, user }) + } + + async acceptInvitation(sharer: string): Promise { + await po.acceptInvitation({ page: this.#page, sharer }) + } + async connectionExists(info): Promise { + return await po.connectionExists({ page: this.#page, info }) + } +} diff --git a/tests/e2e/support/objects/index.ts b/tests/e2e/support/objects/index.ts index 431215e7076..57db947fd78 100644 --- a/tests/e2e/support/objects/index.ts +++ b/tests/e2e/support/objects/index.ts @@ -4,3 +4,4 @@ export * as runtime from './runtime' export * as account from './account' export * as urlNavigation from './url-navigation' export * as appStore from './app-store' +export * as scienceMesh from './federation' diff --git a/tests/e2e/support/store/index.ts b/tests/e2e/support/store/index.ts index a1e0dff4334..6497a2a3c5e 100644 --- a/tests/e2e/support/store/index.ts +++ b/tests/e2e/support/store/index.ts @@ -5,3 +5,4 @@ export { dummyUserStore, createdUserStore, federatedUserStore } from './user' export { dummyGroupStore, createdGroupStore } from './group' export { userRoleStore } from './role' export { keycloakRealmRoles, keycloakCreatedUser } from './keycloak' +export { federatedInvitationCode } from './invitation' diff --git a/tests/e2e/support/store/invitation.ts b/tests/e2e/support/store/invitation.ts new file mode 100644 index 00000000000..b926ed5f67c --- /dev/null +++ b/tests/e2e/support/store/invitation.ts @@ -0,0 +1,3 @@ +import { FederatedShareInvitation } from '../types' + +export const federatedInvitationCode = new Map() diff --git a/tests/e2e/support/types.ts b/tests/e2e/support/types.ts index 72924ba1993..590f5b62d50 100644 --- a/tests/e2e/support/types.ts +++ b/tests/e2e/support/types.ts @@ -70,3 +70,9 @@ export interface AppRole { displayName: string id: string } + +export interface FederatedShareInvitation { + code: string + description?: string + email?: string +}