Skip to content

Commit

Permalink
Feat/e2e tests (#85)
Browse files Browse the repository at this point in the history
* test: add tests for login and credential issuance

Signed-off-by: Mirko Mollik <[email protected]>

* add tests for verification

Signed-off-by: Mirko Mollik <[email protected]>

* fix: backend tests

Signed-off-by: Mirko Mollik <[email protected]>

---------

Signed-off-by: Mirko Mollik <[email protected]>
  • Loading branch information
cre8 authored Jul 15, 2024
1 parent be38dbf commit 6c492af
Show file tree
Hide file tree
Showing 37 changed files with 633 additions and 246 deletions.
2 changes: 2 additions & 0 deletions apps/holder-app-e2e/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# If set, the tests will not spin up dependencies but use the running ones (faster during test development)
NO_CONTAINER=true
7 changes: 5 additions & 2 deletions apps/holder-app-e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { defineConfig, devices } from '@playwright/test';
import { nxE2EPreset } from '@nx/playwright/preset';
import { config } from 'dotenv';

import { workspaceRoot } from '@nx/devkit';
config();

// For CI, you may want to set BASE_URL to the deployed application.
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
Expand All @@ -10,20 +11,22 @@ const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
...nxE2EPreset(__filename, { testDir: './src' }),
workers: 1,
//globalSetup: require.resolve('./global-setup'),
//globalTeardown: require.resolve('./global-teardown'),
retries: process.env['CI'] ? 2 : 0,
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
baseURL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
permissions: ['camera', 'clipboard-read', 'clipboard-write'],
},
/* Run your local dev server before starting the tests */
projects: [
Expand Down
3 changes: 0 additions & 3 deletions apps/holder-app-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
"targets": {
"e2e-ci": {
"dependsOn": ["holder-app:container"]
},
"e2e": {
"dependsOn": ["holder-app:container"]
}
}
}
138 changes: 138 additions & 0 deletions apps/holder-app-e2e/src/credentials.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { faker } from '@faker-js/faker';
import { test, expect, Page } from '@playwright/test';
import { register } from './helpers';
import {
Keycloak,
HolderBackend,
HolderFrontend,
IssuerBackend,
VerifierBackend,
} from '@credhub/testing';
import axios from 'axios';

export const username = faker.internet.email();
export const password = faker.internet.password();
export let hostname: string;
let keycloak: Keycloak;
let backend: HolderBackend;
let frontend: HolderFrontend;
let issuerBackend: IssuerBackend;
let verifierBackend: VerifierBackend;
let page: Page;

test.beforeAll(async ({ browser }) => {
if (process.env['NO_CONTAINER']) {
hostname = 'http://localhost:4200';
} else {
keycloak = await Keycloak.init();
backend = await HolderBackend.init(keycloak);
frontend = await HolderFrontend.init(backend);
issuerBackend = await IssuerBackend.init(keycloak);
verifierBackend = await VerifierBackend.init(keycloak);
hostname = `http://localhost:${frontend.instance.getMappedPort(80)}`;
}

page = await browser.newPage();
await register(page, hostname, username, password);
});

test.afterAll(async () => {
if (process.env['NO_CONTAINER']) {
return;
}
await verifierBackend.stop();
await issuerBackend.stop();
await keycloak.stop();
await backend.stop();
await frontend.stop();
});

function getToken() {
const keycloakUrl = 'http://localhost:8080';
const realm = 'wallet';
const clientId = 'relying-party';
const clientSecret = 'hA0mbfpKl8wdMrUxr2EjKtL5SGsKFW5D';
const tokenUrl = `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`;
const params = new URLSearchParams();
params.append('client_id', clientId);
params.append('client_secret', clientSecret);
params.append('grant_type', 'client_credentials');

return axios
.post(tokenUrl, params, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
.then((response) => response.data.access_token as string);
}

async function getAxiosInstance(port: number) {
if (process.env['NO_CONTAINER']) {
const token = await getToken();
const host = 'localhost';
return axios.create({
baseURL: `http://${host}:${port}`,
headers: {
Authorization: `Bearer ${token}`,
},
});
} else {
return issuerBackend.getAxiosInstance();
}
}

async function receiveCredential(pin = false) {
const axios = await getAxiosInstance(3001);
const response = await axios.post(`/sessions`, {
credentialSubject: {
prename: 'Max',
surname: 'Mustermann',
},
credentialId: 'Identity',
pin,
});
const uri = response.data.uri;
const userPin = response.data.userPin;
await page.evaluate(`navigator.clipboard.writeText("${uri}")`);
await page.goto(`${hostname}/scan`);
const menu = await page.waitForSelector('#menu');
await menu.click();
const inserButton = await page.waitForSelector('#insert');
await inserButton.click();
if (userPin) {
const el = await page.waitForSelector('#pin-field');
await el.fill(userPin);
await page.click('#send');
}
const acceptButton = await page.waitForSelector('#accept');
await acceptButton.click();
await page.waitForSelector('#credential');
}

test('issuance without pin', async () => {
// get credential uri and copy it to clipboard
await receiveCredential();
expect(true).toBeTruthy();
});

test('issuance with pin', async () => {
// get credential uri and copy it to clipboard
await receiveCredential(true);
expect(true).toBeTruthy();
});

test('verify credential', async () => {
await receiveCredential();
const credentialId = 'Identity';
const axios = await getAxiosInstance(3002);
const response = await axios.post(`/siop/${credentialId}`);
const uri = response.data.uri;
await page.evaluate(`navigator.clipboard.writeText("${uri}")`);
await page.goto(`${hostname}/scan`);
await page.waitForSelector('#menu').then((menu) => menu.click());
await page.waitForSelector('#insert').then((button) => button.click());
await page.waitForSelector('#match');
await page.click('mat-list-option');
await page.click('#send');
await page.waitForSelector('#success');
expect(true).toBeTruthy();
});
41 changes: 41 additions & 0 deletions apps/holder-app-e2e/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Page } from '@playwright/test';

export async function register(
page: Page,
hostname: string,
username: string,
password: string
) {
await page.goto(hostname);

//click on the button
await page.click('#login');

await page.click('text=Register');

//fill the form
await page.fill('input[name=email]', username);
await page.fill('input[name=password]', password);
await page.fill('input[name=password-confirm]', password);
await page.click('input[type=submit]');

await page.waitForSelector('text=Credentials');
}

export async function login(page: Page, username: string, password: string) {
//click on the button
await page.click('#login');

//login into keycloak
await page.fill('input[name=username]', username);
await page.fill('input[name=password]', password);
await page.click('id=kc-login');

await page.waitForSelector('text=Credentials');
}

export async function logout(page: Page, hostname: string) {
await page.goto(`${hostname}/settings`);
await page.click('id=logout');
await page.waitForSelector('text=Login');
}
Loading

0 comments on commit 6c492af

Please sign in to comment.