Skip to content

Commit

Permalink
Feature/add extension e2e tests (#1587)
Browse files Browse the repository at this point in the history
* Converted extensions query to a subscription

* Added e2e tests for extensions

* Removed permission check from the old extension call and added it to the new subscription
  • Loading branch information
cohansen authored Jan 7, 2025
1 parent a2bed7f commit bd8d540
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 35 deletions.
67 changes: 67 additions & 0 deletions e2e-tests/fixtures/Extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { APIRequestContext, Page } from '@playwright/test';
import { getUserCookieValue } from '../utilities/helpers';

export class Extension {
async createExtension(page: Page, request: APIRequestContext, extensionName: string): Promise<number | undefined> {
const cookie = getUserCookieValue(await page.context().cookies());

if (cookie !== undefined) {
const response = await request.post('http://localhost:8080/v1/graphql/', {
data: JSON.stringify({
query: `
mutation InsertExtension {
insert_extensions(objects: {
label: "${extensionName}",
description: "Description for ${extensionName}",
url: "http://localhost:8080",
extension_roles: {
data: {
role: aerie_admin
}
}
}) {
returning {
id
}
}
}`,
}),
headers: {
Authorization: `Bearer ${cookie}`,
'Content-Type': 'application/json',
},
});

const data = (await response.json()).data.insert_extensions.returning;

return data[0].id as number;
}
}

async deleteExtension(page: Page, request: APIRequestContext, id: number): Promise<void> {
const cookie = getUserCookieValue(await page.context().cookies());

if (cookie !== undefined) {
await request.post('http://localhost:8080/v1/graphql/', {
data: JSON.stringify({
query: `
mutation DeleteExtension {
delete_extensions(where: {
id: {
_eq: ${id}
}
}) {
returning {
id
}
}
}`,
}),
headers: {
Authorization: `Bearer ${cookie}`,
'Content-Type': 'application/json',
},
});
}
}
}
4 changes: 4 additions & 0 deletions e2e-tests/fixtures/Plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export class Plan {
navButtonConstraintsMenu: Locator;
navButtonExpansion: Locator;
navButtonExpansionMenu: Locator;
navButtonExtension: Locator;
navButtonExtensionMenu: Locator;
navButtonScheduling: Locator;
navButtonSchedulingMenu: Locator;
navButtonSimulation: Locator;
Expand Down Expand Up @@ -513,6 +515,8 @@ export class Plan {
this.navButtonActivityCheckingMenu = this.navButtonActivityChecking.getByRole('menu');
this.navButtonExpansion = page.locator(`.nav-button:has-text("Expansion")`);
this.navButtonExpansionMenu = this.navButtonExpansion.getByRole('menu');
this.navButtonExtension = page.locator(`.nav-button:has-text("Extensions")`);
this.navButtonExtensionMenu = this.navButtonExtension.getByRole('menu');
this.navButtonConstraints = page.locator(`.nav-button:has-text("Constraints")`);
this.navButtonConstraintsMenu = this.navButtonConstraints.getByRole('menu');
this.navButtonScheduling = page.locator(`.nav-button:has-text("Scheduling")`);
Expand Down
84 changes: 84 additions & 0 deletions e2e-tests/tests/extension.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import test, { BrowserContext, expect, Page } from '@playwright/test';
import { adjectives, animals, colors, uniqueNamesGenerator } from 'unique-names-generator';
import { Constraints } from '../fixtures/Constraints';
import { Extension } from '../fixtures/Extension';
import { Models } from '../fixtures/Models';
import { Plan } from '../fixtures/Plan';
import { Plans } from '../fixtures/Plans';
import { SchedulingConditions } from '../fixtures/SchedulingConditions';
import { SchedulingGoals } from '../fixtures/SchedulingGoals';

let extension: Extension;
let extensionName: string;
let extensionId: number | undefined;
let constraints: Constraints;
let context: BrowserContext;
let models: Models;
let page: Page;
let plan: Plan;
let plans: Plans;
let schedulingConditions: SchedulingConditions;
let schedulingGoals: SchedulingGoals;

test.beforeAll(async ({ baseURL, browser, request }) => {
context = await browser.newContext();
page = await context.newPage();

extension = new Extension();
extensionName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] });
extensionId = await extension.createExtension(page, request, extensionName);

models = new Models(page);
plans = new Plans(page, models);
constraints = new Constraints(page);
schedulingConditions = new SchedulingConditions(page);
schedulingGoals = new SchedulingGoals(page);
plan = new Plan(page, plans, constraints, schedulingGoals, schedulingConditions);

await models.goto();
await models.createModel(baseURL);
await plans.goto();
await plans.createPlan();
await plan.goto();
});

test.afterAll(async () => {
await plan.deleteAllActivities();
await plans.goto();
await plans.deletePlan();
await models.goto();
await models.deleteModel();
await page.close();
await context.close();
});

test.describe.serial('Extensions', () => {
test(`Hovering on 'Extensions' in the top navigation bar should show the extension menu`, async () => {
await expect(plan.navButtonExtensionMenu).not.toBeVisible();
plan.navButtonExtension.hover();
await expect(plan.navButtonExtensionMenu).toBeVisible();
plan.planTitle.hover();
await expect(plan.navButtonExtensionMenu).not.toBeVisible();
});

test(`The extension that we created before the tests should be in the extension menu`, async () => {
plan.navButtonExtension.hover();
await expect(plan.navButtonExtensionMenu).toBeVisible();
await expect(plan.navButtonExtensionMenu.getByRole('menuitem', { name: extensionName })).toBeVisible();
});

test(`Clicking the extension should invoke the http call`, async () => {
plan.navButtonExtension.hover();
const extensionRequest = page.waitForRequest('http://localhost:3000/extensions');
plan.navButtonExtensionMenu.getByRole('menuitem', { name: extensionName }).click();
expect((await (await extensionRequest).response())?.ok).toBeTruthy();
});

test(`Delete an extension`, async ({ page, request }) => {
if (extensionId !== undefined) {
await extension.deleteExtension(page, request, extensionId);
await expect(plan.navButtonExtensionMenu).not.toBeVisible();
await expect(plan.navButtonExtension).not.toBeVisible();
}
});
});
5 changes: 5 additions & 0 deletions e2e-tests/tests/plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ test.describe.serial('Plan', () => {
await expect(plan.navButtonSchedulingMenu).not.toBeVisible();
});

test(`By default the extension menu should not show because there are no extensions`, async () => {
await expect(plan.navButtonExtension).not.toBeVisible();
await expect(plan.navButtonExtensionMenu).not.toBeVisible();
});

test(`Changing to a new plan should clear the selected activity`, async ({ baseURL }) => {
await plan.showPanel(PanelNames.TIMELINE_ITEMS);

Expand Down
11 changes: 11 additions & 0 deletions e2e-tests/utilities/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Cookie } from '@playwright/test';

export function getUserCookieValue(cookies: Cookie[]): string | undefined {
for (const cookie of cookies) {
if (cookie.name === 'user') {
return JSON.parse(atob(cookie.value)).token;
}
}

return undefined;
}
3 changes: 2 additions & 1 deletion src/routes/plans/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
simulationDatasetErrors,
} from '../../../stores/errors';
import { planExpansionStatus, resetExpansionStores, selectedExpansionSetId } from '../../../stores/expansion';
import { extensions } from '../../../stores/extensions';
import {
activityTypes,
initialPlan,
Expand Down Expand Up @@ -791,7 +792,7 @@
</svelte:fragment>
</PlanNavButton>
<ExtensionMenu
extensions={data.extensions}
extensions={$extensions}
title={!compactNavMode ? 'Extensions' : ''}
user={data.user}
on:callExtension={onCallExtension}
Expand Down
2 changes: 0 additions & 2 deletions src/routes/plans/[id]/+page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,8 @@ export const load: PageLoad = async ({ parent, params, url }) => {
initialPlan.model.view,
);
const initialPlanSnapshotId = getSearchParameterNumber(SearchParameters.SNAPSHOT_ID, url.searchParams);
const extensions = await effects.getExtensions(user);

return {
extensions,
initialActivityTypes,
initialPlan,
initialPlanSnapshotId,
Expand Down
5 changes: 5 additions & 0 deletions src/stores/extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Extension } from '../types/extension';
import gql from '../utilities/gql';
import { gqlSubscribable } from './subscribable';

export const extensions = gqlSubscribable<Extension[]>(gql.SUB_EXTENSIONS, {}, [], null);
15 changes: 0 additions & 15 deletions src/utilities/effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3627,21 +3627,6 @@ const effects = {
}
},

async getExtensions(user: User | null): Promise<Extension[]> {
try {
const data = await reqHasura<Extension[]>(gql.GET_EXTENSIONS, {}, user);
const { extensions = [] } = data;
if (extensions != null) {
return extensions;
} else {
throw Error('Unable to retrieve extensions');
}
} catch (e) {
catchError(e as Error);
return [];
}
},

async getExternalEventTypes(plan_id: number, user: User | null): Promise<ExternalEventType[]> {
try {
const sourceData = await reqHasura<
Expand Down
32 changes: 16 additions & 16 deletions src/utilities/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1460,22 +1460,6 @@ const gql = {
}
`,

GET_EXTENSIONS: `#graphql
query GetExtensions {
${Queries.EXTENSIONS} {
description
extension_roles {
extension_id
role
}
id
label
updated_at
url
}
}
`,

GET_EXTERNAL_EVENTS: `#graphql
query GetExternalEvents(
$sourceKey: String!,
Expand Down Expand Up @@ -2467,6 +2451,22 @@ const gql = {
}
`,

SUB_EXTENSIONS: `#graphql
subscription SubExtensions {
${Queries.EXTENSIONS} {
description
extension_roles {
extension_id
role
}
id
label
updated_at
url
}
}
`,

SUB_EXTERNAL_EVENT_TYPES: `#graphql
subscription SubExternalEventTypes {
models: ${Queries.EXTERNAL_EVENT_TYPES}(order_by: { name: asc }) {
Expand Down
2 changes: 1 addition & 1 deletion src/utilities/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,6 @@ const queryPermissions: Record<GQLKeys, (user: User | null, ...args: any[]) => b
return isUserAdmin(user) || getPermission([Queries.SEQUENCE_TO_SIMULATED_ACTIVITY], user);
},
GET_EXPANSION_SEQUENCE_SEQ_JSON: () => true,
GET_EXTENSIONS: () => true,
GET_EXTERNAL_EVENTS: () => true,
GET_EXTERNAL_EVENT_TYPE_BY_SOURCE: () => true,
GET_MODELS: () => true,
Expand Down Expand Up @@ -858,6 +857,7 @@ const queryPermissions: Record<GQLKeys, (user: User | null, ...args: any[]) => b
SUB_EXPANSION_SETS: (user: User | null): boolean => {
return isUserAdmin(user) || getPermission([Queries.EXPANSION_SETS], user);
},
SUB_EXTENSIONS: () => true,
SUB_EXTERNAL_EVENT_TYPES: () => true,
SUB_EXTERNAL_SOURCE: () => true,
SUB_EXTERNAL_SOURCES: () => true,
Expand Down

0 comments on commit bd8d540

Please sign in to comment.