From dbe87d6592a8384ce8c4104ca032db15cb6e0a08 Mon Sep 17 00:00:00 2001 From: Tair Sabirgaliev Date: Fri, 10 Feb 2017 05:41:49 +0600 Subject: [PATCH 1/5] angular2-product-app: simplify KeycloakHttp --- .../src/main/webapp/app/keycloak.http.ts | 107 +++--------------- 1 file changed, 15 insertions(+), 92 deletions(-) diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts index 028f256a90..8c0958cff8 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts @@ -9,52 +9,10 @@ import {Observable} from 'rxjs/Rx'; */ @Injectable() export class KeycloakHttp extends Http { - constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private _keycloakService:KeycloakService) { + constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private _keycloakService: KeycloakService) { super(_backend, _defaultOptions); } - private setToken(options: RequestOptionsArgs) { - - if (options == null || KeycloakService.auth == null || KeycloakService.auth.authz == null || KeycloakService.auth.authz.token == null) { - console.log("Need a token, but no token is available, not setting bearer token."); - return; - } - - options.headers.set('Authorization', 'Bearer ' + KeycloakService.auth.authz.token); - } - - private configureRequest(f:Function, url:string | Request, options:RequestOptionsArgs, body?: any):Observable { - let tokenPromise:Promise = this._keycloakService.getToken(); - let tokenObservable:Observable = Observable.fromPromise(tokenPromise); - let tokenUpdateObservable:Observable = Observable.create((observer) => { - if (options == null) { - let headers = new Headers(); - options = new RequestOptions({ headers: headers }); - } - - this.setToken(options); - observer.next(); - observer.complete(); - }); - let requestObservable:Observable = Observable.create((observer) => { - let result; - if (body) { - result = f.apply(this, [url, body, options]); - } else { - result = f.apply(this, [url, options]); - } - - result.subscribe((response) => { - observer.next(response); - observer.complete(); - }, (err) => observer.error(err)); - }); - - return >Observable - .merge(tokenObservable, tokenUpdateObservable, requestObservable, 1) // Insure no concurrency in the merged Observables - .filter((response) => response instanceof Response); - } - /** * Performs any type of http request. First argument is required, and can either be a url or * a {@link Request} instance. If the first argument is a url, an optional {@link RequestOptions} @@ -62,55 +20,20 @@ export class KeycloakHttp extends Http { * of {@link BaseRequestOptions} before performing the request. */ request(url: string | Request, options?: RequestOptionsArgs): Observable { - return this.configureRequest(super.request, url, options); + const tokenPromise: Promise = this._keycloakService.getToken(); + const tokenObservable: Observable = Observable.fromPromise(tokenPromise); + + if (typeof url === 'string') { + return tokenObservable.map(token => { + const authOptions = new RequestOptions({headers: new Headers({'Authorization': 'Bearer ' + token})}); + return new RequestOptions().merge(options).merge(authOptions); + }).concatMap(opts => super.request(url, opts)); + } else if (url instanceof Request) { + return tokenObservable.map(token => { + url.headers.set('Authorization', 'Bearer ' + token); + return url; + }).concatMap(request => super.request(request)); + } } - /** - * Performs a request with `get` http method. - */ - get(url: string, options?: RequestOptionsArgs): Observable { - return this.configureRequest(super.get, url, options); - } - - /** - * Performs a request with `post` http method. - */ - post(url: string, body: any, options?: RequestOptionsArgs): Observable { - return this.configureRequest(super.post, url, options, body); - } - - /** - * Performs a request with `put` http method. - */ - put(url: string, body: any, options?: RequestOptionsArgs): Observable { - return this.configureRequest(super.put, url, options, body); - } - - /** - * Performs a request with `delete` http method. - */ - delete(url: string, options?: RequestOptionsArgs): Observable { - return this.configureRequest(super.delete, url, options); - } - - /** - * Performs a request with `patch` http method. - */ - patch(url: string, body: any, options?: RequestOptionsArgs): Observable { - return this.configureRequest(super.patch, url, options, body); - } - - /** - * Performs a request with `head` http method. - */ - head(url: string, options?: RequestOptionsArgs): Observable { - return this.configureRequest(super.head, url, options); - } - - /** - * Performs a request with `options` http method. - */ - options(url: string, options?: RequestOptionsArgs): Observable { - return this.configureRequest(super.options, url, options); - } } From 5a64d674042c22daa5bb33fa78fe918a953860af Mon Sep 17 00:00:00 2001 From: Tair Sabirgaliev Date: Thu, 23 Feb 2017 11:05:53 +0600 Subject: [PATCH 2/5] Angular 2 PoC based on angular-cli generated project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - use `frontend-maven-plugin` to pull node/npm automatically - tie `npm install` to relevant lifecycle (`mvn wildfly:deploy` automatically builds and includes build products into WAR) - extern keycloak and product database base url, use `--env=war` to build a war colocated with keycloak and database app Issues/Todos: - when testing with `ng serve` will have to disable browser web security (product-database doesn’t allow cross-origin requests) - no unit / e2e / arquillian tests for now - `src/main/frontend/node_modules` is included in distribution build, this is a portability debt --- .../angular2-product-app/pom.xml | 48 ++++++++ .../src/main/frontend/.angular-cli.json | 57 +++++++++ .../src/main/frontend/.editorconfig | 13 ++ .../src/main/frontend/.gitignore | 41 +++++++ .../src/main/frontend/README.md | 27 ++++ .../src/main/frontend/e2e/app.e2e-spec.ts | 14 +++ .../src/main/frontend/e2e/app.po.ts | 11 ++ .../src/main/frontend/e2e/tsconfig.json | 19 +++ .../src/main/frontend/karma.conf.js | 45 +++++++ .../src/main/frontend/package.json | 46 +++++++ .../src/main/frontend/protractor.conf.js | 31 +++++ .../main/frontend/src/app/app.component.css | 0 .../main/frontend/src/app/app.component.html | 20 +++ .../frontend/src/app/app.component.spec.ts | 32 +++++ .../main/frontend/src/app/app.component.ts | 30 +++++ .../src/main/frontend/src/app/app.module.ts | 32 +++++ .../src/app/keycloak/keycloak.http.ts | 32 +++++ .../src/app/keycloak/keycloak.service.ts | 60 +++++++++ .../src/main/frontend/src/assets/.gitkeep | 0 .../src/environments/environment.prod.ts | 5 + .../frontend/src/environments/environment.ts | 10 ++ .../src/environments/environment.war.ts | 5 + .../src/main/frontend/src/favicon.ico | Bin 0 -> 5430 bytes .../src/main/frontend/src/index.html | 15 +++ .../src/main/frontend/src/main.ts | 14 +++ .../src/main/frontend/src/polyfills.ts | 68 ++++++++++ .../src/main/frontend/src/styles.css | 1 + .../src/main/frontend/src/test.ts | 32 +++++ .../src/main/frontend/src/tsconfig.json | 21 ++++ .../src/main/frontend/tslint.json | 116 ++++++++++++++++++ .../src/main/webapp/.gitignore | 4 - .../src/main/webapp/app/app.component.ts | 47 ------- .../src/main/webapp/app/app.module.ts | 32 ----- .../src/main/webapp/app/keycloak.http.ts | 39 ------ .../src/main/webapp/app/keycloak.service.ts | 48 -------- .../src/main/webapp/app/main.ts | 10 -- .../src/main/webapp/index.html | 24 ---- .../src/main/webapp/keycloak.json | 7 -- .../src/main/webapp/package.json | 37 ------ .../src/main/webapp/systemjs.config.js | 43 ------- .../src/main/webapp/tsconfig.json | 12 -- .../src/main/webapp/typings.json | 7 -- 42 files changed, 845 insertions(+), 310 deletions(-) create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/.angular-cli.json create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/.editorconfig create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/.gitignore create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/README.md create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/e2e/tsconfig.json create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/karma.conf.js create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/package.json create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/protractor.conf.js create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.css create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.service.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/assets/.gitkeep create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.prod.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.war.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/favicon.ico create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/index.html create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/main.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/polyfills.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/styles.css create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/test.ts create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/tsconfig.json create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/tslint.json delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/.gitignore delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.service.ts delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/index.html delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/package.json delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json delete mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/typings.json diff --git a/examples/demo-template/angular2-product-app/pom.xml b/examples/demo-template/angular2-product-app/pom.xml index f8fe80f603..54854b8e49 100644 --- a/examples/demo-template/angular2-product-app/pom.xml +++ b/examples/demo-template/angular2-product-app/pom.xml @@ -48,6 +48,54 @@ false + + org.apache.maven.plugins + maven-war-plugin + + + + + src/main/frontend/dist + + + + + + com.github.eirslett + frontend-maven-plugin + 1.3 + + v6.10.0 + src/main/frontend + target + + + + install node and npm + + install-node-and-npm + + + + npm install + + npm + + + install + + + + npm run build + + npm + + + run ng -- build --base-href /angular2-product/ --env=war + + + + diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/.angular-cli.json b/examples/demo-template/angular2-product-app/src/main/frontend/.angular-cli.json new file mode 100644 index 0000000000..1d5d927a6c --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/.angular-cli.json @@ -0,0 +1,57 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "version": "1.0.0-beta.32.3", + "name": "angular2-product-app" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.json", + "prefix": "app", + "styles": [ + "styles.css" + ], + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "war": "environments/environment.war.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "files": "src/**/*.ts", + "project": "src/tsconfig.json" + }, + { + "files": "e2e/**/*.ts", + "project": "e2e/tsconfig.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "css", + "component": {} + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/.editorconfig b/examples/demo-template/angular2-product-app/src/main/frontend/.editorconfig new file mode 100644 index 0000000000..6e87a003da --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/.gitignore b/examples/demo-template/angular2-product-app/src/main/frontend/.gitignore new file mode 100644 index 0000000000..8ce8738573 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/.gitignore @@ -0,0 +1,41 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage/* +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +#System Files +.DS_Store +Thumbs.db diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/README.md b/examples/demo-template/angular2-product-app/src/main/frontend/README.md new file mode 100644 index 0000000000..c7a52ab193 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/README.md @@ -0,0 +1,27 @@ +# Angular2ProductApp + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-beta.32.3. + +## Development server +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +Before running the tests make sure you are serving the app via `ng serve`. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts new file mode 100644 index 0000000000..52708dcd45 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { Angular2ProductAppPage } from './app.po'; + +describe('angular2-product-app App', () => { + let page: Angular2ProductAppPage; + + beforeEach(() => { + page = new Angular2ProductAppPage(); + }); + + it('should display message saying app works', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('app works!'); + }); +}); diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts new file mode 100644 index 0000000000..8e7b6e95cd --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, element, by } from 'protractor'; + +export class Angular2ProductAppPage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/tsconfig.json b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/tsconfig.json new file mode 100644 index 0000000000..94da47a242 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "declaration": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016" + ], + "module": "commonjs", + "moduleResolution": "node", + "outDir": "../dist/out-tsc-e2e", + "sourceMap": true, + "target": "es6", + "typeRoots": [ + "../node_modules/@types" + ] + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/karma.conf.js b/examples/demo-template/angular2-product-app/src/main/frontend/karma.conf.js new file mode 100644 index 0000000000..b0052c5231 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/karma.conf.js @@ -0,0 +1,45 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/0.13/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular/cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma') + ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + files: [ + { pattern: './src/test.ts', watched: false } + ], + preprocessors: { + './src/test.ts': ['@angular/cli'] + }, + mime: { + 'text/x-typescript': ['ts','tsx'] + }, + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + angularCli: { + config: './.angular-cli.json', + environment: 'dev' + }, + reporters: config.angularCli && config.angularCli.codeCoverage + ? ['progress', 'coverage-istanbul'] + : ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/package.json b/examples/demo-template/angular2-product-app/src/main/frontend/package.json new file mode 100644 index 0000000000..3508122b17 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/package.json @@ -0,0 +1,46 @@ +{ + "name": "angular2-product-app", + "version": "0.0.0", + "license": "MIT", + "angular-cli": {}, + "scripts": { + "ng": "ng", + "start": "ng serve", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/common": "^2.4.0", + "@angular/compiler": "^2.4.0", + "@angular/core": "^2.4.0", + "@angular/forms": "^2.4.0", + "@angular/http": "^2.4.0", + "@angular/platform-browser": "^2.4.0", + "@angular/platform-browser-dynamic": "^2.4.0", + "@angular/router": "^3.4.0", + "core-js": "^2.4.1", + "rxjs": "^5.1.0", + "zone.js": "^0.7.6" + }, + "devDependencies": { + "@angular/cli": "1.0.0-beta.32.3", + "@angular/compiler-cli": "^2.4.0", + "@types/jasmine": "2.5.38", + "@types/node": "~6.0.60", + "codelyzer": "~2.0.0-beta.4", + "jasmine-core": "~2.5.2", + "jasmine-spec-reporter": "~3.2.0", + "karma": "~1.4.1", + "karma-chrome-launcher": "~2.0.0", + "karma-cli": "~1.0.1", + "karma-jasmine": "~1.1.0", + "karma-jasmine-html-reporter": "^0.2.2", + "karma-coverage-istanbul-reporter": "^0.2.0", + "protractor": "~5.1.0", + "ts-node": "~2.0.0", + "tslint": "~4.4.2", + "typescript": "~2.0.0" + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/protractor.conf.js b/examples/demo-template/angular2-product-app/src/main/frontend/protractor.conf.js new file mode 100644 index 0000000000..c819669c0a --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/protractor.conf.js @@ -0,0 +1,31 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +/*global jasmine */ +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + beforeLaunch: function() { + require('ts-node').register({ + project: 'e2e' + }); + }, + onPrepare() { + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.css b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html new file mode 100644 index 0000000000..5556cf9223 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html @@ -0,0 +1,20 @@ +
+
+

Angular2 Product (Beta)

+

Products

+ + + + + + + + + + + + + +
Product Listing
{{p}}
+
+
diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts new file mode 100644 index 0000000000..13c632d676 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + }); + TestBed.compileComponents(); + }); + + it('should create the app', async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + })); + + it(`should have as title 'app works!'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app works!'); + })); + + it('should render title in a h1 tag', async(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('app works!'); + })); +}); diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts new file mode 100644 index 0000000000..73d116b8fd --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts @@ -0,0 +1,30 @@ +import { Component } from '@angular/core'; + +import {Http, Headers, RequestOptions, Response} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import {KeycloakService} from './keycloak/keycloak.service'; + +import { environment } from '../environments/environment'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent { + products: string[] = []; + + constructor(private http: Http, private kc: KeycloakService) {} + + logout() { + this.kc.logout(); + } + + reloadData() { + this.http.get(environment.serviceBaseUrl + '/products') + .map(res => res.json()) + .subscribe(prods => this.products = prods, + error => console.log(error)); + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts new file mode 100644 index 0000000000..baad699116 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts @@ -0,0 +1,32 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import {HttpModule, Http, XHRBackend, RequestOptions} from '@angular/http'; +import {KeycloakService} from './keycloak/keycloak.service'; +import {KeycloakHttp} from './keycloak/keycloak.http'; +import { AppComponent } from './app.component'; + +export function keycloakHttpFactory(backend: XHRBackend, defaultOptions: RequestOptions, keycloakService: KeycloakService) { + return new KeycloakHttp(backend, defaultOptions, keycloakService); +} + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + FormsModule, + HttpModule + ], + providers: [ + KeycloakService, + { + provide: Http, + useFactory: keycloakHttpFactory, + deps: [XHRBackend, RequestOptions, KeycloakService] + } + ], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts new file mode 100644 index 0000000000..c908965bf5 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts @@ -0,0 +1,32 @@ +import {Injectable} from '@angular/core'; +import {Http, Request, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response, Headers} from '@angular/http'; + +import {KeycloakService} from './keycloak.service'; +import {Observable} from 'rxjs/Rx'; + +/** + * This provides a wrapper over the ng2 Http class that insures tokens are refreshed on each request. + */ +@Injectable() +export class KeycloakHttp extends Http { + constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private _keycloakService: KeycloakService) { + super(_backend, _defaultOptions); + } + + request(url: string | Request, options?: RequestOptionsArgs): Observable { + const tokenPromise: Promise = this._keycloakService.getToken(); + const tokenObservable: Observable = Observable.fromPromise(tokenPromise); + + if (typeof url === 'string') { + return tokenObservable.map(token => { + const authOptions = new RequestOptions({headers: new Headers({'Authorization': 'Bearer ' + token})}); + return new RequestOptions().merge(options).merge(authOptions); + }).concatMap(opts => super.request(url, opts)); + } else if (url instanceof Request) { + return tokenObservable.map(token => { + url.headers.set('Authorization', 'Bearer ' + token); + return url; + }).concatMap(request => super.request(request)); + } + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.service.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.service.ts new file mode 100644 index 0000000000..9ecf4469ad --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.service.ts @@ -0,0 +1,60 @@ +import {Injectable} from '@angular/core'; + +import { environment } from '../../environments/environment'; + +declare var Keycloak: any; + +@Injectable() +export class KeycloakService { + static auth: any = {}; + + static init(): Promise { + const keycloakAuth: any = Keycloak({ + url: environment.keykloakBaseUrl, + realm: 'demo', + clientId: 'angular2-product', + }); + + KeycloakService.auth.loggedIn = false; + + return new Promise((resolve, reject) => { + keycloakAuth.init({ onLoad: 'login-required' }) + .success(() => { + KeycloakService.auth.loggedIn = true; + KeycloakService.auth.authz = keycloakAuth; + KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + + '/realms/demo/protocol/openid-connect/logout?redirect_uri=' + + document.baseURI; + resolve(); + }) + .error(() => { + reject(); + }); + }); + } + + logout() { + console.log('*** LOGOUT'); + KeycloakService.auth.loggedIn = false; + KeycloakService.auth.authz = null; + + window.location.href = KeycloakService.auth.logoutUrl; + } + + getToken(): Promise { + return new Promise((resolve, reject) => { + if (KeycloakService.auth.authz.token) { + KeycloakService.auth.authz + .updateToken(5) + .success(() => { + resolve(KeycloakService.auth.authz.token); + }) + .error(() => { + reject('Failed to refresh token'); + }); + } else { + reject('Not loggen in'); + } + }); + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/assets/.gitkeep b/examples/demo-template/angular2-product-app/src/main/frontend/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.prod.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.prod.ts new file mode 100644 index 0000000000..57a8df3cec --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.prod.ts @@ -0,0 +1,5 @@ +export const environment = { + production: true, + keykloakBaseUrl: 'http://localhost:8080/auth', + serviceBaseUrl: 'http://localhost:8080/database' +}; diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.ts new file mode 100644 index 0000000000..f93bdd5dd7 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.ts @@ -0,0 +1,10 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false, + keykloakBaseUrl: 'http://localhost:8080/auth', + serviceBaseUrl: 'http://localhost:8080/database' +}; diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.war.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.war.ts new file mode 100644 index 0000000000..6bb464d425 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/environments/environment.war.ts @@ -0,0 +1,5 @@ +export const environment = { + production: false, + keykloakBaseUrl: '/auth', + serviceBaseUrl: '/database' +}; diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/favicon.ico b/examples/demo-template/angular2-product-app/src/main/frontend/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8081c7ceaf2be08bf59010158c586170d9d2d517 GIT binary patch literal 5430 zcmc(je{54#6vvCoAI3i*G5%$U7!sA3wtMZ$fH6V9C`=eXGJb@R1%(I_{vnZtpD{6n z5Pl{DmxzBDbrB>}`90e12m8T*36WoeDLA&SD_hw{H^wM!cl_RWcVA!I+x87ee975; z@4kD^=bYPn&pmG@(+JZ`rqQEKxW<}RzhW}I!|ulN=fmjVi@x{p$cC`)5$a!)X&U+blKNvN5tg=uLvuLnuqRM;Yc*swiexsoh#XPNu{9F#c`G zQLe{yWA(Y6(;>y|-efAy11k<09(@Oo1B2@0`PtZSkqK&${ zgEY}`W@t{%?9u5rF?}Y7OL{338l*JY#P!%MVQY@oqnItpZ}?s z!r?*kwuR{A@jg2Chlf0^{q*>8n5Ir~YWf*wmsh7B5&EpHfd5@xVaj&gqsdui^spyL zB|kUoblGoO7G(MuKTfa9?pGH0@QP^b#!lM1yHWLh*2iq#`C1TdrnO-d#?Oh@XV2HK zKA{`eo{--^K&MW66Lgsktfvn#cCAc*(}qsfhrvOjMGLE?`dHVipu1J3Kgr%g?cNa8 z)pkmC8DGH~fG+dlrp(5^-QBeEvkOvv#q7MBVLtm2oD^$lJZx--_=K&Ttd=-krx(Bb zcEoKJda@S!%%@`P-##$>*u%T*mh+QjV@)Qa=Mk1?#zLk+M4tIt%}wagT{5J%!tXAE;r{@=bb%nNVxvI+C+$t?!VJ@0d@HIyMJTI{vEw0Ul ze(ha!e&qANbTL1ZneNl45t=#Ot??C0MHjjgY8%*mGisN|S6%g3;Hlx#fMNcL<87MW zZ>6moo1YD?P!fJ#Jb(4)_cc50X5n0KoDYfdPoL^iV`k&o{LPyaoqMqk92wVM#_O0l z09$(A-D+gVIlq4TA&{1T@BsUH`Bm=r#l$Z51J-U&F32+hfUP-iLo=jg7Xmy+WLq6_tWv&`wDlz#`&)Jp~iQf zZP)tu>}pIIJKuw+$&t}GQuqMd%Z>0?t%&BM&Wo^4P^Y z)c6h^f2R>X8*}q|bblAF?@;%?2>$y+cMQbN{X$)^R>vtNq_5AB|0N5U*d^T?X9{xQnJYeU{ zoZL#obI;~Pp95f1`%X3D$Mh*4^?O?IT~7HqlWguezmg?Ybq|7>qQ(@pPHbE9V?f|( z+0xo!#m@Np9PljsyxBY-UA*{U*la#8Wz2sO|48_-5t8%_!n?S$zlGe+NA%?vmxjS- zHE5O3ZarU=X}$7>;Okp(UWXJxI%G_J-@IH;%5#Rt$(WUX?6*Ux!IRd$dLP6+SmPn= z8zjm4jGjN772R{FGkXwcNv8GBcZI#@Y2m{RNF_w8(Z%^A*!bS*!}s6sh*NnURytky humW;*g7R+&|Ledvc- + + + + Angular2ProductApp + + + + + + + + Loading... + + diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/main.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/main.ts new file mode 100644 index 0000000000..8bd5ab58e1 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/main.ts @@ -0,0 +1,14 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; +import { KeycloakService } from './app/keycloak/keycloak.service'; + +if (environment.production) { + enableProdMode(); +} + +KeycloakService.init() + .then(() => platformBrowserDynamic().bootstrapModule(AppModule)) + .catch(e => window.location.reload()); diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/polyfills.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/polyfills.ts new file mode 100644 index 0000000000..53bdaf1b86 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/polyfills.ts @@ -0,0 +1,68 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/styles.css b/examples/demo-template/angular2-product-app/src/main/frontend/src/styles.css new file mode 100644 index 0000000000..90d4ee0072 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/test.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/test.ts new file mode 100644 index 0000000000..9bf72267e9 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/tsconfig.json b/examples/demo-template/angular2-product-app/src/main/frontend/src/tsconfig.json new file mode 100644 index 0000000000..40fe5c0575 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "baseUrl": "", + "declaration": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016", + "dom" + ], + "mapRoot": "./", + "module": "es2015", + "moduleResolution": "node", + "outDir": "../dist/out-tsc", + "sourceMap": true, + "target": "es5", + "typeRoots": [ + "../node_modules/@types" + ] + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/tslint.json b/examples/demo-template/angular2-product-app/src/main/frontend/tslint.json new file mode 100644 index 0000000000..9113f1368b --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/tslint.json @@ -0,0 +1,116 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "import-blacklist": [true, "rxjs"], + "import-spacing": true, + "indent": [ + true, + "spaces" + ], + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [true, "ignore-params"], + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + + "directive-selector": [true, "attribute", "app", "camelCase"], + "component-selector": [true, "element", "app", "kebab-case"], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true, + "no-access-missing-member": true, + "templates-use-public": true, + "invoke-injectable": true + } +} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/.gitignore b/examples/demo-template/angular2-product-app/src/main/webapp/.gitignore deleted file mode 100644 index 1c790facbc..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -app/*.js -app/*.js.map -node_modules -typings diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts deleted file mode 100644 index cd8ba04a1f..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import {Component} from "@angular/core"; -import {Http, Headers, RequestOptions, Response} from "@angular/http"; -import {Observable} from "rxjs/Observable"; -import "rxjs/add/operator/map"; -import {KeycloakService} from "./keycloak.service"; - -@Component({ - selector: 'my-app', - template: ` -
-
-

Angular2 Product (Beta)

-

Products

- - - - - - - - - - - - - -
Product Listing
{{p}}
-
-
-` -}) -export class AppComponent { - products: string[] = []; - - constructor(private http: Http, private kc: KeycloakService) {} - - logout() { - this.kc.logout(); - } - - reloadData() { - this.http.get('/database/products') - .map(res => res.json()) - .subscribe(prods => this.products = prods, - error => console.log(error)); - } -} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts deleted file mode 100644 index fd6075fbd9..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {NgModule} from "@angular/core"; -import {BrowserModule} from "@angular/platform-browser"; -import {HttpModule, Http, XHRBackend, RequestOptions} from '@angular/http'; -import {KeycloakService} from "./keycloak.service"; -import {AppComponent} from "./app.component"; -import {KeycloakHttp} from "./keycloak.http"; - -@NgModule({ - imports: [ - BrowserModule, - HttpModule - ], - declarations: [ - AppComponent - ], - providers: [ - KeycloakService, - - { - provide: Http, - useFactory: - ( - backend: XHRBackend, - defaultOptions: RequestOptions, - keycloakService: KeycloakService - ) => new KeycloakHttp(backend, defaultOptions, keycloakService), - deps: [XHRBackend, RequestOptions, KeycloakService] - } - ], - bootstrap: [ AppComponent ] -}) -export class AppModule {} \ No newline at end of file diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts deleted file mode 100644 index 8c0958cff8..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {Injectable} from "@angular/core"; -import {Http, Request, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response, Headers} from "@angular/http"; - -import {KeycloakService} from "./keycloak.service"; -import {Observable} from 'rxjs/Rx'; - -/** - * This provides a wrapper over the ng2 Http class that insures tokens are refreshed on each request. - */ -@Injectable() -export class KeycloakHttp extends Http { - constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private _keycloakService: KeycloakService) { - super(_backend, _defaultOptions); - } - - /** - * Performs any type of http request. First argument is required, and can either be a url or - * a {@link Request} instance. If the first argument is a url, an optional {@link RequestOptions} - * object can be provided as the 2nd argument. The options object will be merged with the values - * of {@link BaseRequestOptions} before performing the request. - */ - request(url: string | Request, options?: RequestOptionsArgs): Observable { - const tokenPromise: Promise = this._keycloakService.getToken(); - const tokenObservable: Observable = Observable.fromPromise(tokenPromise); - - if (typeof url === 'string') { - return tokenObservable.map(token => { - const authOptions = new RequestOptions({headers: new Headers({'Authorization': 'Bearer ' + token})}); - return new RequestOptions().merge(options).merge(authOptions); - }).concatMap(opts => super.request(url, opts)); - } else if (url instanceof Request) { - return tokenObservable.map(token => { - url.headers.set('Authorization', 'Bearer ' + token); - return url; - }).concatMap(request => super.request(request)); - } - } - -} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.service.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.service.ts deleted file mode 100644 index aba14a61c2..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.service.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {Injectable} from "@angular/core"; - -declare var Keycloak: any; - -@Injectable() -export class KeycloakService { - static auth: any = {}; - - static init(): Promise { - let keycloakAuth: any = new Keycloak('keycloak.json'); - KeycloakService.auth.loggedIn = false; - - return new Promise((resolve, reject) => { - keycloakAuth.init({ onLoad: 'login-required' }) - .success(() => { - KeycloakService.auth.loggedIn = true; - KeycloakService.auth.authz = keycloakAuth; - KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/demo/protocol/openid-connect/logout?redirect_uri=/angular2-product/index.html"; - resolve(); - }) - .error(() => { - reject(); - }); - }); - } - - logout() { - console.log('*** LOGOUT'); - KeycloakService.auth.loggedIn = false; - KeycloakService.auth.authz = null; - - window.location.href = KeycloakService.auth.logoutUrl; - } - - getToken(): Promise { - return new Promise((resolve, reject) => { - if (KeycloakService.auth.authz.token) { - KeycloakService.auth.authz.updateToken(5) - .success(() => { - resolve(KeycloakService.auth.authz.token); - }) - .error(() => { - reject('Failed to refresh token'); - }); - } - }); - } -} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts deleted file mode 100644 index 352f12b694..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {platformBrowserDynamic} from "@angular/platform-browser-dynamic"; -import {AppModule} from "./app.module"; -import {KeycloakService} from "./keycloak.service"; - -KeycloakService.init() - .then(() => { - const platform = platformBrowserDynamic(); - platform.bootstrapModule(AppModule); - }) - .catch(() => window.location.reload()); diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/index.html b/examples/demo-template/angular2-product-app/src/main/webapp/index.html deleted file mode 100644 index 1edeb56477..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - Angular 2 QuickStart - - - - - - - - - - - - - - - - Loading... - - diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json b/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json deleted file mode 100644 index 87a2ad64ef..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "realm": "demo", - "auth-server-url": "/auth", - "ssl-required": "external", - "resource": "angular2-product", - "public-client": true -} \ No newline at end of file diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/package.json b/examples/demo-template/angular2-product-app/src/main/webapp/package.json deleted file mode 100644 index 5bd783b03e..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "angular2-product-app", - "version": "1.0.0", - "scripts": { - "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ", - "lite": "lite-server", - "postinstall": "typings install", - "tsc": "tsc", - "tsc:w": "tsc -w", - "typings": "typings" - }, - "license": "ISC", - "dependencies": { - "@angular/common": "2.0.0", - "@angular/compiler": "2.0.0", - "@angular/core": "2.0.0", - "@angular/forms": "2.0.0", - "@angular/http": "2.0.0", - "@angular/platform-browser": "2.0.0", - "@angular/platform-browser-dynamic": "2.0.0", - "@angular/router": "3.0.0", - "@angular/upgrade": "2.0.0", - "angular2-in-memory-web-api": "0.0.20", - "bootstrap": "^3.3.6", - "core-js": "^2.4.1", - "reflect-metadata": "^0.1.3", - "rxjs": "5.0.0-beta.12", - "systemjs": "0.19.27", - "zone.js": "^0.6.21" - }, - "devDependencies": { - "concurrently": "^2.2.0", - "lite-server": "^2.2.2", - "typescript": "^2.0.2", - "typings": "^1.3.2" - } -} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js b/examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js deleted file mode 100644 index de199e68ce..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * System configuration for Angular 2 samples - * Adjust as necessary for your application needs. - */ -(function (global) { - System.config({ - paths: { - // paths serve as alias - 'npm:': 'node_modules/' - }, - // map tells the System loader where to look for things - map: { - // our app is within the app folder - app: 'app', - // angular bundles - '@angular/core': 'npm:@angular/core/bundles/core.umd.js', - '@angular/common': 'npm:@angular/common/bundles/common.umd.js', - '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', - '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', - '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', - '@angular/http': 'npm:@angular/http/bundles/http.umd.js', - '@angular/router': 'npm:@angular/router/bundles/router.umd.js', - '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', - // other libraries - 'rxjs': 'npm:rxjs', - 'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api', - }, - // packages tells the System loader how to load when no filename and/or no extension - packages: { - app: { - main: './main.js', - defaultExtension: 'js' - }, - rxjs: { - defaultExtension: 'js' - }, - 'angular2-in-memory-web-api': { - main: './index.js', - defaultExtension: 'js' - } - } - }); -})(this); diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json b/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json deleted file mode 100644 index e6a6eac11d..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "moduleResolution": "node", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "removeComments": false, - "noImplicitAny": false - } -} diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/typings.json b/examples/demo-template/angular2-product-app/src/main/webapp/typings.json deleted file mode 100644 index 7da31ca0af..0000000000 --- a/examples/demo-template/angular2-product-app/src/main/webapp/typings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "globalDependencies": { - "core-js": "registry:dt/core-js#0.0.0+20160725163759", - "jasmine": "registry:dt/jasmine#2.2.0+20160621224255", - "node": "registry:dt/node#6.0.0+20160909174046" - } -} From 26edcdf6bc8ce9474790ada1cf4e30bf2e0ae5b7 Mon Sep 17 00:00:00 2001 From: Tair Sabirgaliev Date: Fri, 24 Feb 2017 06:38:09 +0600 Subject: [PATCH 3/5] proper license and `ng test` --- .../src/main/frontend/package.json | 2 +- .../main/frontend/src/app/app.component.html | 4 +- .../frontend/src/app/app.component.spec.ts | 40 +++++++++++++++++-- .../main/frontend/src/app/app.component.ts | 2 + 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/package.json b/examples/demo-template/angular2-product-app/src/main/frontend/package.json index 3508122b17..91223e5e80 100644 --- a/examples/demo-template/angular2-product-app/src/main/frontend/package.json +++ b/examples/demo-template/angular2-product-app/src/main/frontend/package.json @@ -1,7 +1,7 @@ { "name": "angular2-product-app", "version": "0.0.0", - "license": "MIT", + "license": "Apache-2.0", "angular-cli": {}, "scripts": { "ng": "ng", diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html index 5556cf9223..163d8db9b4 100644 --- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.html @@ -1,9 +1,9 @@
-

Angular2 Product (Beta)

+

{{title}}

Products

- + diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts index 13c632d676..f5fc6c1dea 100644 --- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.spec.ts @@ -1,9 +1,32 @@ import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; +import { KeycloakService } from './keycloak/keycloak.service'; +import { + HttpModule, + XHRBackend, + ResponseOptions, + Response, + RequestMethod +} from '@angular/http'; +import { + MockBackend, + MockConnection +} from '@angular/http/testing/mock_backend'; + describe('AppComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + { + provide: XHRBackend, + useClass: MockBackend + }, + { + provide: KeycloakService + } + ], declarations: [ AppComponent ], @@ -17,16 +40,27 @@ describe('AppComponent', () => { expect(app).toBeTruthy(); })); - it(`should have as title 'app works!'`, async(() => { + it(`should have as title 'Angular2 Product'`, async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; - expect(app.title).toEqual('app works!'); + expect(app.title).toEqual('Angular2 Product'); })); it('should render title in a h1 tag', async(() => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('app works!'); + expect(compiled.querySelector('h1').textContent).toContain('Angular2 Product'); + })); + + it('should render product list', async(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.componentInstance.products = ['iphone', 'ipad', 'ipod']; + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('table thead tr th').textContent).toContain('Product Listing'); + expect(compiled.querySelectorAll('table tbody tr td')[0].textContent).toContain('iphone'); + expect(compiled.querySelectorAll('table tbody tr td')[1].textContent).toContain('ipad'); + expect(compiled.querySelectorAll('table tbody tr td')[2].textContent).toContain('ipod'); })); }); diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts index 73d116b8fd..28b9b98202 100644 --- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.component.ts @@ -13,6 +13,8 @@ import { environment } from '../environments/environment'; styleUrls: ['./app.component.css'] }) export class AppComponent { + title = 'Angular2 Product'; + products: string[] = []; constructor(private http: Http, private kc: KeycloakService) {} From 6b808d78e677db70dfc4acfb5f9b605d41ab7dcc Mon Sep 17 00:00:00 2001 From: Tair Sabirgaliev Date: Fri, 24 Feb 2017 14:13:09 +0600 Subject: [PATCH 4/5] refactor and implement unit and e2e tests --- .../src/main/frontend/e2e/app.e2e-spec.ts | 10 +++- .../src/main/frontend/e2e/app.po.ts | 19 +++++++- .../src/main/frontend/src/app/app.module.ts | 16 ++----- .../src/app/keycloak/keycloak.http.spec.ts | 47 +++++++++++++++++++ .../src/app/keycloak/keycloak.http.ts | 12 ++++- 5 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.spec.ts diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts index 52708dcd45..83246f10dd 100644 --- a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts +++ b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.e2e-spec.ts @@ -7,8 +7,14 @@ describe('angular2-product-app App', () => { page = new Angular2ProductAppPage(); }); - it('should display message saying app works', () => { + it('should display message saying Angular2 Product', () => { page.navigateTo(); - expect(page.getParagraphText()).toEqual('app works!'); + expect(page.getParagraphText()).toEqual('Angular2 Product'); + }); + + it('should load Products', () => { + page.navigateTo(); + const products = page.loadProducts(); + ['iphone', 'ipad', 'ipod'].forEach(e => expect(products).toContain(e)); }); }); diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts index 8e7b6e95cd..8ebb121c33 100644 --- a/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts +++ b/examples/demo-template/angular2-product-app/src/main/frontend/e2e/app.po.ts @@ -2,10 +2,27 @@ import { browser, element, by } from 'protractor'; export class Angular2ProductAppPage { navigateTo() { - return browser.get('/'); + browser.ignoreSynchronization = true; + browser.get('/'); + browser.getCurrentUrl().then(url => { + if (url.includes('/auth/realms/demo')) { + element(by.id('username')).sendKeys('bburke@redhat.com'); + element(by.id('password')).sendKeys('password'); + element(by.id('kc-login')).click(); + } + browser.ignoreSynchronization = false; + + }); } getParagraphText() { return element(by.css('app-root h1')).getText(); } + + loadProducts() { + const click = element(by.id('reload-data')).click(); + browser.wait(click, 2000, 'Products should load within 2 seconds'); + return element.all(by.css('table.table td')).getText(); + } + } diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts index baad699116..99a60f9171 100644 --- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/app.module.ts @@ -1,15 +1,11 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import {HttpModule, Http, XHRBackend, RequestOptions} from '@angular/http'; -import {KeycloakService} from './keycloak/keycloak.service'; -import {KeycloakHttp} from './keycloak/keycloak.http'; +import { HttpModule } from '@angular/http'; +import { KeycloakService } from './keycloak/keycloak.service'; +import { KeycloakHttp, KEYCLOAK_HTTP_PROVIDER } from './keycloak/keycloak.http'; import { AppComponent } from './app.component'; -export function keycloakHttpFactory(backend: XHRBackend, defaultOptions: RequestOptions, keycloakService: KeycloakService) { - return new KeycloakHttp(backend, defaultOptions, keycloakService); -} - @NgModule({ declarations: [ AppComponent @@ -21,11 +17,7 @@ export function keycloakHttpFactory(backend: XHRBackend, defaultOptions: Request ], providers: [ KeycloakService, - { - provide: Http, - useFactory: keycloakHttpFactory, - deps: [XHRBackend, RequestOptions, KeycloakService] - } + KEYCLOAK_HTTP_PROVIDER ], bootstrap: [AppComponent] }) diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.spec.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.spec.ts new file mode 100644 index 0000000000..7ea175e076 --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.spec.ts @@ -0,0 +1,47 @@ +import {Injectable, ReflectiveInjector} from '@angular/core'; +import {async, fakeAsync, tick} from '@angular/core/testing'; +import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http'; +import {Response, ResponseOptions} from '@angular/http'; +import {MockBackend, MockConnection} from '@angular/http/testing'; + +import { KeycloakHttp, KEYCLOAK_HTTP_PROVIDER, keycloakHttpFactory } from './keycloak.http'; +import { KeycloakService } from './keycloak.service'; + +@Injectable() +class MockKeycloakService extends KeycloakService { + getToken(): Promise { + return Promise.resolve('hello'); + } +} + +describe('KeycloakHttp', () => { + + let injector: ReflectiveInjector; + let backend: MockBackend; + let lastConnection: MockConnection; + let http: Http; + + beforeEach(() => { + injector = ReflectiveInjector.resolveAndCreate([ + {provide: ConnectionBackend, useClass: MockBackend}, + {provide: RequestOptions, useClass: BaseRequestOptions}, + {provide: KeycloakService, useClass: MockKeycloakService}, + { + provide: Http, + useFactory: keycloakHttpFactory, + deps: [ConnectionBackend, RequestOptions, KeycloakService] + } + ]); + http = injector.get(Http); + backend = injector.get(ConnectionBackend) as MockBackend; + backend.connections.subscribe((c: MockConnection) => lastConnection = c); + }); + + it('should set Authorization header', fakeAsync(() => { + http.get('foo').subscribe(r => console.log(r)); + tick(); + expect(lastConnection).toBeDefined('no http service connection at all?'); + expect(lastConnection.request.headers.get('Authorization')).toBe('Bearer hello'); + })); + +}); diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts index c908965bf5..07887bdd01 100644 --- a/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts +++ b/examples/demo-template/angular2-product-app/src/main/frontend/src/app/keycloak/keycloak.http.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {Http, Request, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response, Headers} from '@angular/http'; +import {Http, Request, XHRBackend, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response, Headers} from '@angular/http'; import {KeycloakService} from './keycloak.service'; import {Observable} from 'rxjs/Rx'; @@ -30,3 +30,13 @@ export class KeycloakHttp extends Http { } } } + +export function keycloakHttpFactory(backend: XHRBackend, defaultOptions: RequestOptions, keycloakService: KeycloakService) { + return new KeycloakHttp(backend, defaultOptions, keycloakService); +} + +export const KEYCLOAK_HTTP_PROVIDER = { + provide: Http, + useFactory: keycloakHttpFactory, + deps: [XHRBackend, RequestOptions, KeycloakService] +}; From 6a809a20091c8bcea2959cf4e8cf7aa4d49586fb Mon Sep 17 00:00:00 2001 From: Tair Sabirgaliev Date: Fri, 24 Feb 2017 15:16:06 +0600 Subject: [PATCH 5/5] update `readme` --- .../src/main/frontend/README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/demo-template/angular2-product-app/src/main/frontend/README.md b/examples/demo-template/angular2-product-app/src/main/frontend/README.md index c7a52ab193..f0520d74ee 100644 --- a/examples/demo-template/angular2-product-app/src/main/frontend/README.md +++ b/examples/demo-template/angular2-product-app/src/main/frontend/README.md @@ -1,10 +1,19 @@ # Angular2ProductApp -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-beta.32.3. +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-beta.32.3. Keycloak integration is based on Angular ## Development server Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. +This application depends on `database-service` application API deployed at `http://localhost:8080/database-service` so you will have to make sure it allows cross-origin requests. It can be enabled for `database-service` in it's `keycloak.json`: + + { + "realm" : "demo", + "resource" : "database-service", + ... + "enable-cors": true + } + ## Code scaffolding Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`. @@ -20,7 +29,7 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github. ## Running end-to-end tests Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). -Before running the tests make sure you are serving the app via `ng serve`. +Before running the tests make sure you are serving the app via `ng serve` and Keycloak and `database-service` is up and running at `http://localhost:8080`. ## Further help