Skip to content

Commit d803a07

Browse files
committed
[MS] Added e2e tests for custom order invoices
1 parent d6584a1 commit d803a07

File tree

7 files changed

+162
-30
lines changed

7 files changed

+162
-30
lines changed

client/src/services/bms/mockApi.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type MockFunction = (...args: any[]) => Promise<BmsResponse>;
4242
function createMockFunction(functionName: string, mockSuccess?: MockFunction, mockError?: MockFunction): MockFunction {
4343
const params = getMockParameters(functionName);
4444

45-
if (params.isMocked) {
45+
if (import.meta.env.PARSEC_APP_BMS_USE_MOCK === 'true' && params.isMocked) {
4646
if (params.shouldFail) {
4747
if (!mockError) {
4848
console.warn(`Function "${functionName}" does not have failure mock, defaulting to default API`);
@@ -70,12 +70,13 @@ function createMockFunction(functionName: string, mockSuccess?: MockFunction, mo
7070
}
7171

7272
function getMockParameters(functionName: string): MockParameters {
73+
const mocksEnabled = import.meta.env.PARSEC_APP_BMS_USE_MOCK === 'true';
7374
const mockFunctionsVariable: string = import.meta.env.PARSEC_APP_BMS_MOCKED_FUNCTIONS ?? '';
7475
const failFunctionsVariable: string = import.meta.env.PARSEC_APP_BMS_FAIL_FUNCTIONS ?? '';
7576
const mockedFunctions = mockFunctionsVariable.split(';');
7677
const failFunctions = failFunctionsVariable.split(';');
7778

78-
if (mockedFunctions.includes(functionName)) {
79+
if (mocksEnabled && mockedFunctions.includes(functionName)) {
7980
console.debug(`Mock call to "${functionName}"`);
8081
return {
8182
isMocked: true,

client/src/views/client-area/BmsLogin.vue

+12-9
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127
</ion-text>
128128
<ion-button
129129
v-show="!loading"
130-
:disabled="!emailInputRef || emailInputRef.validity !== Validity.Valid || !password.length || querying"
130+
:disabled="!validEmail || !password.length || querying"
131131
@click="onLoginClicked"
132132
class="saas-login-button__item"
133133
size="large"
@@ -199,7 +199,7 @@ import { IonButton, IonText, IonButtons, IonFooter, IonIcon, IonSkeletonText } f
199199
import { MsInput, MsPasswordInput, Translatable, Validity, MsSpinner, MsCheckbox } from 'megashark-lib';
200200
import { emailValidator } from '@/common/validators';
201201
import { warning, arrowBack, arrowForward, close } from 'ionicons/icons';
202-
import { onMounted, ref } from 'vue';
202+
import { computed, onMounted, ref } from 'vue';
203203
import { AuthenticationToken, BmsAccessInstance, PersonalInformationResultData } from '@/services/bms';
204204
import CreateOrganizationModalHeader from '@/components/organizations/CreateOrganizationModalHeader.vue';
205205
import { Env } from '@/services/environment';
@@ -226,7 +226,12 @@ const loginError = ref<Translatable>('');
226226
const loading = ref(true);
227227
const storeCredentials = ref<boolean>(false);
228228

229+
const validEmail = computed(() => {
230+
return Boolean(email.value.length > 0 && emailInputRef.value && emailInputRef.value.validity === Validity.Valid);
231+
});
232+
229233
onMounted(async () => {
234+
querying.value = false;
230235
loading.value = true;
231236
if (BmsAccessInstance.get().isLoggedIn()) {
232237
emits('loginSuccess', await BmsAccessInstance.get().getToken(), BmsAccessInstance.get().getPersonalInformation());
@@ -238,13 +243,11 @@ onMounted(async () => {
238243
return;
239244
}
240245

241-
if (emailInputRef.value) {
242-
if (email.value.length > 0) {
243-
await emailInputRef.value.validate(email.value);
244-
await passwordInputRef.value.setFocus();
245-
} else {
246-
await emailInputRef.value.setFocus();
247-
}
246+
if (email.value.length > 0) {
247+
await emailInputRef.value.validate(email.value);
248+
await passwordInputRef.value.setFocus();
249+
} else {
250+
await emailInputRef.value.setFocus();
248251
}
249252
loading.value = false;
250253
});

client/tests/e2e/helpers/bms.ts

+20-9
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ interface MockRouteOptions {
7777
}
7878

7979
interface MockCustomOrderDetailsOverload {
80+
empty?: boolean;
81+
single?: boolean;
8082
created?: DateTime;
8183
amountWithTaxes?: number;
8284
amountWithoutTaxes?: number;
@@ -94,18 +96,26 @@ interface MockCustomOrderDetailsOverload {
9496
storageOrdered?: number;
9597
}
9698

97-
function createCustomOrderInvoices(count: number = 1, overload: MockCustomOrderDetailsOverload = {}): Array<any> {
99+
function createCustomOrderInvoices(overload: MockCustomOrderDetailsOverload = {}): Array<any> {
98100
const invoices: Array<any> = [];
99101

100102
const STATUSES = ['paid', 'draft', 'open', 'uncollectible', 'void'];
101103

102-
for (let i = 1; i < count + 1; i++) {
103-
const licenseStart = overload.licenseStart ? overload.licenseStart : DateTime.now().minus({ months: count + 1 - i });
104-
const licenseEnd = overload.licenseEnd ? overload.licenseEnd : DateTime.now().minus({ months: count - i });
104+
if (overload.empty) {
105+
return invoices;
106+
}
107+
for (let i = 0; i < (overload.single !== undefined ? 1 : 15); i++) {
108+
const licenseStart = overload.licenseStart
109+
? overload.licenseStart
110+
: DateTime.fromObject({ year: 2024 + Math.floor(i / 12), month: (i % 12) + 1, day: 3 });
111+
const licenseEnd = overload.licenseEnd
112+
? overload.licenseEnd
113+
: DateTime.fromObject({ year: 2024 + Math.floor((i + 1) / 12), month: ((i + 1) % 12) + 1, day: 3 });
114+
105115
invoices.push({
106-
id: `custom_order_id${i}`,
107-
created: overload.created ? overload.created.toISO() : DateTime.now().minus({ months: count + 1 - i }),
108-
number: `FACT00${i}`,
116+
id: `custom_order_id${i + 1}`,
117+
created: overload.created ? overload.created.toISO() : licenseStart.toISO(),
118+
number: `FACT00${i + 1}`,
109119
status: overload.status ? overload.status : STATUSES[Math.floor(Math.random() * STATUSES.length)],
110120
amounts: {
111121
total_excl_tax: overload.amountWithoutTaxes ? overload.amountWithoutTaxes.toString() : '42.00',
@@ -594,6 +604,7 @@ async function mockCustomOrderDetails(
594604
overload: MockCustomOrderDetailsOverload = {},
595605
options?: MockRouteOptions,
596606
): Promise<void> {
607+
overload.single = true;
597608
await mockRoute(
598609
page,
599610
// eslint-disable-next-line max-len
@@ -603,7 +614,7 @@ async function mockCustomOrderDetails(
603614
await route.fulfill({
604615
status: 200,
605616
json: {
606-
[DEFAULT_ORGANIZATION_INFORMATION.name]: createCustomOrderInvoices(1, overload)[0],
617+
[DEFAULT_ORGANIZATION_INFORMATION.name]: createCustomOrderInvoices(overload)[0],
607618
},
608619
});
609620
},
@@ -679,7 +690,7 @@ async function mockGetCustomOrderInvoices(
679690
const ret: any = {};
680691
for (const orgId of postData.organization_ids ?? []) {
681692
const parsecId = orgId === '1' ? DEFAULT_ORGANIZATION_INFORMATION.name : `${DEFAULT_ORGANIZATION_INFORMATION.name}-${orgId}`;
682-
ret[parsecId] = createCustomOrderInvoices(12, overload);
693+
ret[parsecId] = createCustomOrderInvoices(overload);
683694
}
684695
return ret;
685696
}

client/tests/e2e/helpers/fixtures.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ export const msTest = debugTest.extend<{
264264
await button.click();
265265
await fillIonInput(home.locator('.input-container').nth(0).locator('ion-input'), DEFAULT_USER_INFORMATION.email);
266266
await fillIonInput(home.locator('.input-container').nth(1).locator('ion-input'), DEFAULT_USER_INFORMATION.password);
267-
await home.locator('.saas-login-button__item').nth(1).click();
267+
await home.locator('.saas-login-button').locator('ion-button').click();
268268
await expect(home).toHaveURL(/.+\/clientArea$/);
269269

270270
// Switch to first org
@@ -297,9 +297,14 @@ export const msTest = debugTest.extend<{
297297
const button = home.locator('.topbar-right').locator('#trigger-customer-area-button');
298298
await expect(button).toHaveText('Customer area');
299299
await button.click();
300-
await fillIonInput(home.locator('.input-container').nth(0).locator('ion-input'), DEFAULT_USER_INFORMATION.email);
301-
await fillIonInput(home.locator('.input-container').nth(1).locator('ion-input'), DEFAULT_USER_INFORMATION.password);
302-
await home.locator('.saas-login-button__item').nth(1).click();
300+
const loginButton = home.locator('.saas-login-button').locator('ion-button');
301+
await expect(loginButton).toHaveAttribute('disabled');
302+
await fillIonInput(home.locator('.saas-login-content').locator('ion-input').nth(0), DEFAULT_USER_INFORMATION.email);
303+
await home.waitForTimeout(100);
304+
await fillIonInput(home.locator('.saas-login-content').locator('ion-input').nth(1), DEFAULT_USER_INFORMATION.password);
305+
await home.waitForTimeout(100);
306+
await expect(loginButton).not.toHaveAttribute('disabled');
307+
await loginButton.click();
303308
await expect(home).toHaveURL(/.+\/clientArea$/);
304309

305310
await use(home);

client/tests/e2e/helpers/utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ export async function clientAreaSwitchOrganization(page: Page, organization: str
200200
const text = await item.textContent();
201201
if (text === organization || (text === 'All organizations' && organization === 'all')) {
202202
await item.click();
203+
break;
203204
}
204205
}
205206
await expect(popover).toBeHidden();

client/tests/e2e/specs/client_area_custom_order_contract.spec.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS
22

33
import { clientAreaSwitchOrganization, expect, MockBms, msTest } from '@tests/e2e/helpers';
4-
import { DateTime } from 'luxon';
54

65
msTest('Test initial status for an org', async ({ clientAreaCustomOrder }) => {
76
const title = clientAreaCustomOrder.locator('.header-content').locator('.header-title');
@@ -26,8 +25,8 @@ msTest('Test initial status for an org', async ({ clientAreaCustomOrder }) => {
2625
await expect(container.locator('.contract-header-title__text')).toHaveText('Contract n°FACT001');
2726

2827
const contract = container.locator('.contract-main');
29-
await expect(contract.locator('.item-content-date__date').nth(0)).toHaveText(DateTime.now().minus({ months: 1 }).toFormat('LLL d, yyyy'));
30-
await expect(contract.locator('.item-content-date__date').nth(1)).toHaveText(DateTime.now().toFormat('LLL d, yyyy'));
28+
await expect(contract.locator('.item-content-date__date').nth(0)).toHaveText('Jan 3, 2024');
29+
await expect(contract.locator('.item-content-date__date').nth(1)).toHaveText('Feb 3, 2024');
3130

3231
await expect(contract.locator('.data-number').nth(0)).toHaveText('32');
3332
await expect(contract.locator('.data-number').nth(1)).toHaveText('50');

client/tests/e2e/specs/client_area_custom_order_invoices.spec.ts

+115-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS
22

3-
import { clientAreaSwitchOrganization, DEFAULT_ORGANIZATION_INFORMATION, expect, msTest } from '@tests/e2e/helpers';
3+
import { clientAreaSwitchOrganization, DEFAULT_ORGANIZATION_INFORMATION, expect, MockBms, msTest } from '@tests/e2e/helpers';
44

55
msTest('Test all orgs', async ({ clientAreaCustomOrder }) => {
66
const title = clientAreaCustomOrder.locator('.header-content').locator('.header-title');
@@ -9,7 +9,13 @@ msTest('Test all orgs', async ({ clientAreaCustomOrder }) => {
99
await expect(title).toHaveText('Invoices');
1010
const content = clientAreaCustomOrder.locator('.client-page-invoices');
1111
const invoices = content.locator('.invoices-year-content-list-item');
12-
await expect(invoices).toHaveCount(24);
12+
await expect(invoices).toHaveCount(30);
13+
await expect(content.locator('.no-invoices')).toBeHidden();
14+
for (const invoice of await invoices.all()) {
15+
await expect(invoice.locator('.invoices-organization')).toHaveText(/^BlackMesa(-2)?$/);
16+
await expect(invoice.locator('.invoices-amount')).toHaveText(/^\d+\.\d{2}$/);
17+
await expect(invoice.locator('.badge-status')).toHaveText(/^To pay|Paid|Void|In progress|Uncollectible$/);
18+
}
1319
});
1420

1521
msTest('Test only one org', async ({ clientAreaCustomOrder }) => {
@@ -21,5 +27,111 @@ msTest('Test only one org', async ({ clientAreaCustomOrder }) => {
2127
await expect(title).toHaveText('Invoices');
2228
const content = clientAreaCustomOrder.locator('.client-page-invoices');
2329
const invoices = content.locator('.invoices-year-content-list-item');
24-
await expect(invoices).toHaveCount(12);
30+
await expect(invoices).toHaveCount(15);
31+
await expect(content.locator('.no-invoices')).toBeHidden();
32+
for (const invoice of await invoices.all()) {
33+
await expect(invoice.locator('.invoices-organization')).toHaveText('BlackMesa');
34+
await expect(invoice.locator('.invoices-amount')).toHaveText(/^\d+\.\d{2}$/);
35+
await expect(invoice.locator('.badge-status')).toHaveText(/^To pay|Paid|Void|In progress|Uncollectible$/);
36+
}
2537
});
38+
39+
msTest('Test filter date', async ({ clientAreaCustomOrder }) => {
40+
const title = clientAreaCustomOrder.locator('.header-content').locator('.header-title');
41+
42+
await clientAreaCustomOrder.locator('.menu-client').locator('.menu-client-list').getByRole('listitem').nth(3).click();
43+
await expect(title).toHaveText('Invoices');
44+
45+
const containers = clientAreaCustomOrder.locator('.invoices-year:visible');
46+
await expect(containers).toHaveCount(2);
47+
await expect(containers.locator('.invoices-year-text')).toHaveText(['2025', '2024']);
48+
49+
const yearFilterButton = clientAreaCustomOrder.locator('.invoices-header-filter').locator('.invoices-header-filter-button').nth(0);
50+
const monthFilterButton = clientAreaCustomOrder.locator('.invoices-header-filter').locator('.invoices-header-filter-button').nth(1);
51+
const popover = clientAreaCustomOrder.locator('.time-filter-popover');
52+
await expect(popover).toBeHidden();
53+
54+
await yearFilterButton.click();
55+
await expect(popover).toBeVisible();
56+
await expect(popover.locator('.time-list-item')).toHaveText(['2025', '2024']);
57+
58+
await popover.locator('.time-list-item').nth(1).click();
59+
await popover.locator('ion-backdrop').click();
60+
await expect(popover).toBeHidden();
61+
62+
await expect(containers).toHaveCount(1);
63+
await expect(containers.locator('.invoices-year-text')).toHaveText('2024');
64+
await expect(containers.locator('.invoices-year-content-list-item:visible')).toHaveCount(24);
65+
66+
await monthFilterButton.click();
67+
await expect(popover).toBeVisible();
68+
await expect(popover.locator('.time-list-item')).toHaveText([
69+
'Jan',
70+
'Feb',
71+
'Mar',
72+
'Apr',
73+
'May',
74+
'Jun',
75+
'Jul',
76+
'Aug',
77+
'Sep',
78+
'Oct',
79+
'Nov',
80+
'Dec',
81+
]);
82+
await popover.locator('.time-list-item').nth(1).click();
83+
await popover.locator('.time-list-item').nth(3).click();
84+
await popover.locator('.time-list-item').nth(4).click();
85+
await popover.locator('.time-list-item').nth(10).click();
86+
await popover.locator('ion-backdrop').click();
87+
await expect(popover).toBeHidden();
88+
await expect(containers.locator('.invoices-year-content-list-item:visible')).toHaveCount(8);
89+
});
90+
91+
msTest('Test no invoices', async ({ clientAreaCustomOrder }) => {
92+
await MockBms.mockGetCustomOrderInvoices(clientAreaCustomOrder, {}, { empty: true });
93+
const title = clientAreaCustomOrder.locator('.header-content').locator('.header-title');
94+
95+
await clientAreaCustomOrder.locator('.menu-client').locator('.menu-client-list').getByRole('listitem').nth(3).click();
96+
await expect(title).toHaveText('Invoices');
97+
const content = clientAreaCustomOrder.locator('.client-page-invoices');
98+
await expect(content.locator('.invoices-year-content-list-item')).toHaveCount(0);
99+
await expect(content.locator('.no-invoices')).toBeVisible();
100+
await expect(content.locator('.no-invoices')).toHaveText("You don't have any invoice yet.");
101+
});
102+
103+
for (const orgMode of ['oneOrg', 'allOrgs']) {
104+
msTest(`List the invoices for ${orgMode} generic error`, async ({ clientAreaCustomOrder }) => {
105+
await MockBms.mockGetCustomOrderInvoices(clientAreaCustomOrder, { POST: { errors: { status: 400 } } });
106+
107+
if (orgMode === 'orgOrg') {
108+
await clientAreaSwitchOrganization(clientAreaCustomOrder, DEFAULT_ORGANIZATION_INFORMATION.name);
109+
}
110+
111+
const title = clientAreaCustomOrder.locator('.header-content').locator('.header-title');
112+
await clientAreaCustomOrder.locator('.menu-client').locator('.menu-client-list').getByRole('listitem').nth(3).click();
113+
await expect(title).toHaveText('Invoices');
114+
const content = clientAreaCustomOrder.locator('.client-page-invoices');
115+
await expect(content.locator('.no-invoices')).toBeHidden();
116+
await expect(content.locator('.invoices-year-content-list-item')).toHaveCount(0);
117+
await expect(clientAreaCustomOrder.locator('.main-content').locator('.form-error')).toHaveText('Could not retrieve your invoices.');
118+
await expect(clientAreaCustomOrder.locator('.main-content').locator('.no-invoices')).toBeHidden();
119+
});
120+
121+
msTest(`List the invoices for ${orgMode} timeout`, async ({ clientAreaCustomOrder }) => {
122+
await MockBms.mockGetCustomOrderInvoices(clientAreaCustomOrder, { POST: { timeout: true } });
123+
124+
if (orgMode === 'orgOrg') {
125+
await clientAreaSwitchOrganization(clientAreaCustomOrder, DEFAULT_ORGANIZATION_INFORMATION.name);
126+
}
127+
128+
const title = clientAreaCustomOrder.locator('.header-content').locator('.header-title');
129+
await clientAreaCustomOrder.locator('.menu-client').locator('.menu-client-list').getByRole('listitem').nth(3).click();
130+
await expect(title).toHaveText('Invoices');
131+
const content = clientAreaCustomOrder.locator('.client-page-invoices');
132+
await expect(content.locator('.no-invoices')).toBeHidden();
133+
await expect(content.locator('.invoices-year-content-list-item')).toHaveCount(0);
134+
await expect(clientAreaCustomOrder.locator('.main-content').locator('.form-error')).toHaveText('Could not retrieve your invoices.');
135+
await expect(clientAreaCustomOrder.locator('.main-content').locator('.no-invoices')).toBeHidden();
136+
});
137+
}

0 commit comments

Comments
 (0)