Skip to content

Commit

Permalink
Feat/coverage (#74)
Browse files Browse the repository at this point in the history
* feat: add testcontainers to project

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

* tmp

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

* remove coverage

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

* fix: add tests for app

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

* fix: move test deps to a dedicated module

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

* fix: fix project file

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

* fix: testing package

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

* fix tests

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

* fix: demo app

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

* fix: run e2e in the cloud

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

* fix: install browsers

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

* fix: testing in ci

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

* build keycloak in the ci step

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

* add docker login

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

* move keycloak image to cd

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

* add testcontainer token

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

* remove testcontainers run

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

* write token directly into the ci

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

* fix: add domain name

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

* fix: add logging to the test

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

* fix: just run the e2e test

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

* remove tc

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

* add verbose option

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

* call it with npx

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

* set start timeout instead of healthcheck

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

* use another port

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

* set logger on keycloak level

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

* fix: build keycloak

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

* fix: ci

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

* debug failing test

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

* add container step

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

* login into registry

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

* fix typo

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

* fix: tests

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

* remove logging in error

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

* fix: catch error

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

* ci

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

---------

Signed-off-by: Mirko Mollik <[email protected]>
  • Loading branch information
cre8 authored Jul 3, 2024
1 parent 72d1cf9 commit aabfd35
Show file tree
Hide file tree
Showing 73 changed files with 1,646 additions and 203 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,9 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# build the docker image for keycloak
- name: Build Keycloak Docker Image
run: cd deploys/keycloak && docker-compose build keycloak && docker-compose push keycloak

- name: Build and push images
run: INPUT_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} INPUT_PUSH=true pnpm exec nx affected -t container
25 changes: 23 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,26 @@ jobs:
- name: Run license check
run: npx license-checker --onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;0BSD"

- name: Lint, test, build, and container
run: INPUT_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} pnpm exec nx affected -t lint test container
- name: Install Playwright Browsers
run: npx playwright install --with-deps

- name: Build keycloak
run: cd deploys/keycloak && docker-compose build keycloak

- name: Add entry to /etc/hosts
run: echo "127.0.0.1 host.testcontainers.internal" | sudo tee -a /etc/hosts

- name: Lint, test, build, e2e
run: INPUT_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} pnpm exec nx affected -t lint test e2e-ci
# comment out since the current e2e tests do not produce any artifacts
# - name: Upload coverage
# uses: codecov/codecov-action@v4
# with:
# token: ${{ secrets.CODECOV_TOKEN }}

- name: Upload playwright results
uses: actions/upload-artifact@v2
with:
name: playwright-results
path: dist/.playwright
retention-days: 30
5 changes: 1 addition & 4 deletions apps/demo-e2e/src/example.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { test, expect } from '@playwright/test';

test('has title', async ({ page }) => {
await page.goto('/');

// Expect h1 to contain a substring.
expect(await page.locator('h1').innerText()).toContain('Welcome');
expect(true).toBeTruthy();
});
8 changes: 0 additions & 8 deletions apps/demo/public/assets/config.json

This file was deleted.

7 changes: 7 additions & 0 deletions apps/demo/public/assets/issuer-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"backendUrlPP": "http://localhost:3001",
"credentialId": "Identity",
"oidcUrl": "http://host.docker.internal:8080/realms/wallet",
"oidcClientId": "relying-party",
"oidcClientSecret": "hA0mbfpKl8wdMrUxr2EjKtL5SGsKFW5D"
}
7 changes: 7 additions & 0 deletions apps/demo/public/assets/verifier-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"backendUrl": "http://localhost:3002",
"credentialId": "Identity",
"oidcUrl": "http://host.docker.internal:8080/realms/wallet",
"oidcClientId": "relying-party",
"oidcClientSecret": "hA0mbfpKl8wdMrUxr2EjKtL5SGsKFW5D"
}
37 changes: 20 additions & 17 deletions apps/demo/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,14 @@ import { provideAnimationsAsync } from '@angular/platform-browser/animations/asy
import {
Configuration as IssuerConfiguration,
ApiModule as IssuerApiModule,
IssuerConfig,
IssuerConfigService,
} from '@credhub/issuer-shared';
import {
Configuration as VerifierConfiguration,
ApiModule as VerifierApiModule,
VerifierConfig,
VerifierConfigService,
} from '@credhub/verifier-shared';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { ConfigBasic, ConfigService } from '@credhub/relying-party-frontend';

class Config extends ConfigBasic {
issuerUrl!: string;
verifierUrl!: string;
credentialId!: string;
}

export const appConfig: ApplicationConfig = {
providers: [
Expand All @@ -36,19 +29,29 @@ export const appConfig: ApplicationConfig = {
provide: APP_INITIALIZER,
// in case we add two different configServices, we could extend them. So we also do not have to pass the ConfigType to it
useFactory: (
configService: ConfigService<Config>,
configService: IssuerConfigService,
httpClient: HttpClient
) => configService.appConfigLoader(httpClient),
deps: [IssuerConfigService, HttpClient],
multi: true,
},
{
provide: APP_INITIALIZER,
// in case we add two different configServices, we could extend them. So we also do not have to pass the ConfigType to it
useFactory: (
configService: VerifierConfigService,
httpClient: HttpClient
) => configService.appConfigLoader(httpClient),
deps: [ConfigService, HttpClient],
deps: [VerifierConfigService, HttpClient],
multi: true,
},
importProvidersFrom(IssuerApiModule, VerifierApiModule),
{
provide: IssuerConfiguration,
deps: [ConfigService],
useFactory: (configService: ConfigService<IssuerConfig>) => {
deps: [IssuerConfigService],
useFactory: (configService: IssuerConfigService) => {
return new IssuerConfiguration({
basePath: configService.getConfig('issuerUrl'),
basePath: configService.getConfig('backendUrl'),
credentials: {
oauth2: () => configService.getToken(),
},
Expand All @@ -57,10 +60,10 @@ export const appConfig: ApplicationConfig = {
},
{
provide: VerifierConfiguration,
deps: [ConfigService],
useFactory: (configService: ConfigService<VerifierConfig>) => {
deps: [VerifierConfigService],
useFactory: (configService: VerifierConfigService) => {
return new VerifierConfiguration({
basePath: configService.getConfig('verifierUrl'),
basePath: configService.getConfig('backendUrl'),
credentials: {
oauth2: () => configService.getToken(),
},
Expand Down
12 changes: 4 additions & 8 deletions apps/holder-app-e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,30 @@ const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
*/
export default defineConfig({
...nxE2EPreset(__filename, { testDir: './src' }),
workers: 1,
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',
},
/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm exec nx serve holder-app',
url: 'http://localhost:4200',
reuseExistingServer: !process.env.CI,
cwd: workspaceRoot,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
/* {
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
}, */

// Uncomment for mobile browsers support
/* {
Expand Down
9 changes: 8 additions & 1 deletion apps/holder-app-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,12 @@
"sourceRoot": "apps/holder-app-e2e/src",
"implicitDependencies": ["holder-app"],
"// targets": "to see all targets run: nx show project holder-app-e2e --web",
"targets": {}
"targets": {
"e2e-ci": {
"dependsOn": ["holder-app:container"]
},
"e2e": {
"dependsOn": ["holder-app:container"]
}
}
}
8 changes: 0 additions & 8 deletions apps/holder-app-e2e/src/example.spec.ts

This file was deleted.

141 changes: 141 additions & 0 deletions apps/holder-app-e2e/src/login.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {
Keycloak,
HolderBackend,
KeycloakGlobalThis,
BackendGlobalThis,
HolderFrontend,
} from '@credhub/testing';
import { test, expect } from '@playwright/test';

const username = '[email protected]';
const password = 'mirko';
let keycloak: Keycloak;
let backend: HolderBackend;
let frontend: HolderFrontend;
let hostname: string;

test.beforeAll(async () => {
//start keycloak
keycloak = await Keycloak.init();
(globalThis as KeycloakGlobalThis).keycloak = keycloak;

//start backend
backend = await HolderBackend.init();
(globalThis as BackendGlobalThis).backend = backend;

//start frontend
frontend = await HolderFrontend.init();

hostname = `http://localhost:${frontend.instance.getMappedPort(80)}`;

const testUserEmail = '[email protected]';
const testUserPassword = 'password';
// create a new user
await keycloak.createUser(
`http://localhost:${keycloak.instance.getMappedPort(8080)}`,
'wallet',
testUserEmail,
testUserPassword
);
});

test.afterAll(async () => {
await frontend.stop();
await backend.stop();
await keycloak.stop();
});

test('register', async ({ page }) => {
await page.goto(hostname);

//click on the button
await page.click('text=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');
expect(true).toBeTruthy();
});

test('login', async ({ page }) => {
await page.goto(hostname);

//click on the button
await page.click('text=Login');

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

//wait for the password input field
// await page.waitForSelector('input[name=password]');
// await page.click('text=Sign In');

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

expect(true).toBeTruthy();
});

test('logout', async ({ page }) => {
await page.goto(hostname);

//click on the button
await page.click('text=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');
await page.goto(`${hostname}/settings`);

await page.click('id=logout');

await page.waitForSelector('text=Login');
//expect to see the login button
expect(true).toBeTruthy();
});

//TODO: does not work yet
// test('delete account', async ({ page }) => {
// await page.goto('http://localhost:4200');

// page.on('dialog', async (dialog) => dialog.accept());

// //click on the button
// await page.click('text=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');
// await page.goto('http://localhost:4200/settings');

// await page.waitForSelector('id=delete-account');

// await page.click('id=delete-account');

// await page.waitForSelector('text=Login');

// //click on the button
// await page.click('text=Login');

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

// //Invalid username or password. should be seen as an error
// await page.waitForSelector('text=Invalid username or password.');
// expect(true).toBeTruthy();
// });
8 changes: 7 additions & 1 deletion apps/holder-app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
FROM docker.io/nginx:stable-alpine

# Copy application files and the startup script with permissions
COPY dist/apps/holder-app/* /usr/share/nginx/html/
COPY --chmod=755 apps/holder-app/startup.sh /usr/local/bin/startup.sh

# Combine RUN instructions to reduce layers and improve readability
RUN echo "server {" > /etc/nginx/conf.d/default.conf && \
echo " listen 80;" >> /etc/nginx/conf.d/default.conf && \
echo " location / {" >> /etc/nginx/conf.d/default.conf && \
Expand All @@ -8,5 +13,6 @@ RUN echo "server {" > /etc/nginx/conf.d/default.conf && \
echo " try_files \$uri \$uri/ /index.html =404;" >> /etc/nginx/conf.d/default.conf && \
echo " }" >> /etc/nginx/conf.d/default.conf && \
echo "}" >> /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
CMD ["/usr/local/bin/startup.sh"]
8 changes: 1 addition & 7 deletions apps/holder-app/src/app/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export class AuthService implements AuthServiceInterface {
]).pipe(map((values) => values.every((b) => b)));

private navigateToLoginPage() {
// TODO: Remember current URL
this.router.navigateByUrl('/login');
}

Expand Down Expand Up @@ -120,9 +119,7 @@ export class AuthService implements AuthServiceInterface {
.subscribe(() => this.oauthService.loadUserProfile());

this.oauthService.events
.pipe(
filter((e) => ['session_terminated', 'session_error'].includes(e.type))
)
.pipe(filter((e) => ['session_terminated'].includes(e.type)))
.subscribe(() => this.navigateToLoginPage());

this.oauthService.setupAutomaticSilentRefresh();
Expand Down Expand Up @@ -208,9 +205,6 @@ export class AuthService implements AuthServiceInterface {
}

public login(targetUrl?: string) {
//TODO: check if config has to be loaded here or in the constructor.
// Note: before version 9.1.0 of the library you needed to
// call encodeURIComponent on the argument to the method.
this.oauthService.initLoginFlow(targetUrl || this.router.url);
}

Expand Down
Loading

0 comments on commit aabfd35

Please sign in to comment.