From c28ca94966886739b181f6fdb5ad504ffe51d4db Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Thu, 19 Sep 2024 10:42:30 -0300 Subject: [PATCH 01/28] test: add tests for the VueOAuth2KeycloakModulesFactory --- .../VueOAuth2KeycloakModulesFactoryTest.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java new file mode 100644 index 00000000000..ebde8b24fb4 --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java @@ -0,0 +1,59 @@ +package tech.jhipster.lite.generator.client.vue.security.oauth2_keycloak.domain; + +import static tech.jhipster.lite.module.infrastructure.secondary.JHipsterModulesAssertions.*; + +import org.junit.jupiter.api.Test; +import tech.jhipster.lite.TestFileUtils; +import tech.jhipster.lite.UnitTest; +import tech.jhipster.lite.module.domain.JHipsterModule; +import tech.jhipster.lite.module.domain.JHipsterModulesFixture; +import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties; + +@UnitTest +class VueOAuth2KeycloakModulesFactoryTest { + + private static final VueOAuth2KeycloakModulesFactory factory = new VueOAuth2KeycloakModulesFactory(); + + @Test + void shouldBuildVueOAuth2KeycloakModule() { + JHipsterModuleProperties properties = JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()) + .projectBaseName("jhipster") + .basePackage("tech.jhipster.jhlitest") + .build(); + + JHipsterModule module = factory.buildModule(properties); + + //@formatter:off + assertThatModuleWithFiles(module, packageJsonFile()) + .hasFile("package.json") + .containing(nodeDependency("keycloak-js")) + .and() + .hasFiles("src/main/webapp/app/auth/application/AuthProvider.ts") + .hasFiles("src/main/webapp/app/auth/domain/AuthRepository.ts") + .hasFiles("src/main/webapp/app/auth/domain/AuthenticatedUser.ts") + .hasFiles("src/main/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts") + .hasFiles("src/main/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts") + .hasFile("src/main/webapp/app/main.ts") + .containing(""" + import { provideForAuth } from '@/auth/application/AuthProvider'; + import { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; + import axios from 'axios'; + import Keycloak from 'keycloak-js'; + // jhipster-needle-main-ts-import\ + """ + ) + .containing(""" + const keycloakHttp = new KeycloakHttp( + new Keycloak({ + url: 'http://localhost:9080', + realm: 'jhipster', + clientId: 'web_app', + }), + + provideForAuth(keycloakHttp); + // jhipster-needle-main-ts-provider\ + """ + ); + //@formatter:on + } +} From 0d9879c80f09e7ce2188a474b3e8721325692820 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Thu, 19 Sep 2024 11:15:12 -0300 Subject: [PATCH 02/28] feat: add keycloak-js dependency to common package.json --- src/main/resources/generator/dependencies/common/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/generator/dependencies/common/package.json b/src/main/resources/generator/dependencies/common/package.json index 8ef810c6703..298b2567e17 100644 --- a/src/main/resources/generator/dependencies/common/package.json +++ b/src/main/resources/generator/dependencies/common/package.json @@ -6,7 +6,8 @@ "dependencies": { "i18next": "23.15.1", "i18next-browser-languagedetector": "8.0.0", - "i18next-http-backend": "2.6.1" + "i18next-http-backend": "2.6.1", + "keycloak-js": "25.0.5" }, "devDependencies": { "@babel/cli": "7.25.6", From 5206565d4ef5a6fb5a1e27a64ba2e62eeddd08fc Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Thu, 19 Sep 2024 11:39:10 -0300 Subject: [PATCH 03/28] feat: add template files to be used by voue-oauth2-keycloak module --- .../auth/application/AuthProvider.ts.mustache | 11 ++++ .../auth/domain/AuthRepository.ts.mustache | 9 ++++ .../auth/domain/AuthenticatedUser.ts.mustache | 8 +++ .../KeycloakAuthRepository.ts.mustache | 53 +++++++++++++++++++ .../secondary/KeycloakHttp.ts.mustache | 49 +++++++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 src/main/resources/generator/client/vue/webapp/app/auth/application/AuthProvider.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthenticatedUser.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/application/AuthProvider.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/application/AuthProvider.ts.mustache new file mode 100644 index 00000000000..44ef1a3c80c --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/auth/application/AuthProvider.ts.mustache @@ -0,0 +1,11 @@ +import { key } from 'piqure'; +import { provide} from '@/injections'; +import type { AuthRepository } from '@/auth/domain/AuthRepository'; +import { KeycloakAuthRepository } from '@/auth/infrastructure/secondary/KeycloakAuthRepository'; +import { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; + +export const AUTH_REPOSITORY = key('AuthRepository'); + +export const provideForAuth = (keycloakHttp: KeycloakHttp): void => { + provide(AUTH_REPOSITORY, new KeycloakAuthRepository(keycloakHttp)); +}; diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache new file mode 100644 index 00000000000..a98fb14437f --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache @@ -0,0 +1,9 @@ +import type {AuthenticatedUser} from "@/auth/domain/AuthenticatedUser"; + +export interface AuthRepository { + currentUser(): Promise; + login(): Promise; + logout(): Promise; + authenticated(): Promise; + refreshToken(): Promise; +} diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthenticatedUser.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthenticatedUser.ts.mustache new file mode 100644 index 00000000000..c23945f5bdf --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthenticatedUser.ts.mustache @@ -0,0 +1,8 @@ +type AuthenticatedUserName = string; +type AuthenticatedUserToken = string; + +export interface AuthenticatedUser { + isAuthenticated: boolean; + username: AuthenticatedUserName; + token: AuthenticatedUserToken; +} diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache new file mode 100644 index 00000000000..6644a20f9df --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache @@ -0,0 +1,53 @@ +import type { AuthRepository } from '@/auth/domain/AuthRepository'; +import type { AuthenticatedUser } from '@/auth/domain/AuthenticatedUser'; +import { KeycloakHttp } from './KeycloakHttp'; + +export class KeycloakAuthRepository implements AuthRepository { + constructor(private keycloakHttp: KeycloakHttp) {} + + async currentUser(): Promise { + try { + return await this.keycloakHttp.currentUser(); + } catch (error) { + console.error('Authentication failed', error); + return { isAuthenticated: false, username: '', token: '' }; + } + } + + async login(): Promise { + try { + await this.keycloakHttp.login(); + } catch (error) { + console.error('Login failed', error); + throw error; + } + } + + async logout(): Promise { + try { + await this.keycloakHttp.logout(); + return true; + } catch (error) { + console.error('Logout failed', error); + return false; + } + } + + async authenticated(): Promise { + try { + return await this.keycloakHttp.authenticated(); + } catch (error) { + console.error('isAuthenticated check failed', error); + return false; + } + } + + async refreshToken(): Promise { + try { + return await this.keycloakHttp.refreshToken(); + } catch (error) { + console.error('Token refresh failed', error); + return ''; + } + } +} diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache new file mode 100644 index 00000000000..2b419ec65d2 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache @@ -0,0 +1,49 @@ +import Keycloak from 'keycloak-js'; +import type { AuthenticatedUser } from '@/auth/domain/AuthenticatedUser'; + +export class KeycloakHttp { + private initialized: boolean = false; + + constructor(private readonly keycloak: Keycloak) {} + + private async ensureInitialized(): Promise { + if (!this.initialized) { + await this.keycloak.init({ onLoad: 'check-sso', checkLoginIframe: false }); + this.initialized = true; + } + } + + async currentUser(): Promise { + await this.ensureInitialized(); + if (this.keycloak.authenticated) { + return { + isAuthenticated: true, + username: this.keycloak.tokenParsed?.preferred_username || '', + token: this.keycloak.token || '', + }; + } else { + return { isAuthenticated: false, username: '', token: '' }; + } + } + + async login(): Promise { + await this.ensureInitialized(); + return this.keycloak.login(); + } + + async logout(): Promise { + await this.ensureInitialized(); + return this.keycloak.logout(); + } + + async authenticated(): Promise { + await this.ensureInitialized(); + return !!this.keycloak.token; + } + + async refreshToken(): Promise { + await this.ensureInitialized(); + await this.keycloak.updateToken(5); + return this.keycloak.token || ''; + } +} From afb1725df82207c01198c3ffe986ac20602e1598 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Thu, 19 Sep 2024 13:13:56 -0300 Subject: [PATCH 04/28] test: fix block indentation --- .../VueOAuth2KeycloakModulesFactoryTest.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java index ebde8b24fb4..1d470e4303e 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java @@ -43,16 +43,17 @@ void shouldBuildVueOAuth2KeycloakModule() { """ ) .containing(""" - const keycloakHttp = new KeycloakHttp( - new Keycloak({ - url: 'http://localhost:9080', - realm: 'jhipster', - clientId: 'web_app', - }), - - provideForAuth(keycloakHttp); - // jhipster-needle-main-ts-provider\ - """ + const keycloakHttp = new KeycloakHttp( + new Keycloak({ + url: 'http://localhost:9080', + realm: 'jhipster', + clientId: 'web_app', + }), + ); + + provideForAuth(keycloakHttp); + // jhipster-needle-main-ts-provider\ + """ ); //@formatter:on } From 6e63bd0f685d7afc1c521e99c0c7d62a338377d6 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Thu, 19 Sep 2024 13:22:59 -0300 Subject: [PATCH 05/28] test: update vue main.ts.template file and add it to the VueOAuth2KeycloakModulesFactory test environment --- .../domain/VueOAuth2KeycloakModulesFactoryTest.java | 6 +++++- src/test/resources/projects/vue/main.ts.template | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java index 1d470e4303e..39cc8d16d95 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java @@ -24,7 +24,7 @@ void shouldBuildVueOAuth2KeycloakModule() { JHipsterModule module = factory.buildModule(properties); //@formatter:off - assertThatModuleWithFiles(module, packageJsonFile()) + assertThatModuleWithFiles(module, packageJsonFile(), mainFile()) .hasFile("package.json") .containing(nodeDependency("keycloak-js")) .and() @@ -57,4 +57,8 @@ void shouldBuildVueOAuth2KeycloakModule() { ); //@formatter:on } + + private static ModuleFile mainFile() { + return file("src/test/resources/projects/vue/main.ts.template", "src/main/webapp/app/main.ts"); + } } diff --git a/src/test/resources/projects/vue/main.ts.template b/src/test/resources/projects/vue/main.ts.template index 0f5ea875b13..1ab21867124 100644 --- a/src/test/resources/projects/vue/main.ts.template +++ b/src/test/resources/projects/vue/main.ts.template @@ -1,7 +1,9 @@ import { createApp } from 'vue'; -import App from './common/primary/app/App.vue'; +import AppVue from './AppVue.vue'; +import router from './router'; // jhipster-needle-main-ts-import -const app = createApp(App); +const app = createApp(AppVue); +app.use(router); // jhipster-needle-main-ts-provider app.mount('#app'); From e7c8e8bc18e3fc21ecc18ceeda6eb640d5878266 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Thu, 19 Sep 2024 13:24:04 -0300 Subject: [PATCH 06/28] feat: add VueOAuth2KeycloakModulesFactory --- .../VueOAuth2KeycloakModulesFactory.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java new file mode 100644 index 00000000000..35883a47ec0 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java @@ -0,0 +1,71 @@ +package tech.jhipster.lite.generator.client.vue.security.oauth2_keycloak.domain; + +import static tech.jhipster.lite.module.domain.JHipsterModule.*; +import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.*; + +import tech.jhipster.lite.module.domain.Indentation; +import tech.jhipster.lite.module.domain.JHipsterModule; +import tech.jhipster.lite.module.domain.file.JHipsterDestination; +import tech.jhipster.lite.module.domain.file.JHipsterSource; +import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties; +import tech.jhipster.lite.shared.error.domain.Assert; + +public class VueOAuth2KeycloakModulesFactory { + + private static final JHipsterSource SOURCE = from("client/vue"); + private static final JHipsterSource APP_SOURCE = from("client/vue/webapp/app"); + + private static final JHipsterDestination MAIN_DESTINATION = to("src/main/webapp/app"); + + public JHipsterModule buildModule(JHipsterModuleProperties properties) { + Assert.notNull("properties", properties); + + Indentation indentation = properties.indentation(); + + //@formatter:off + return moduleBuilder(properties) + .packageJson() + .addDependency(packageName("keycloak-js"), COMMON) + .and() + .files() + .batch(APP_SOURCE.append("auth"), MAIN_DESTINATION.append("auth")) + .addTemplate("application/AuthProvider.ts") + .addTemplate("domain/AuthRepository.ts") + .addTemplate("domain/AuthenticatedUser.ts") + .addTemplate("infrastructure/secondary/KeycloakAuthRepository.ts") + .addTemplate("infrastructure/secondary/KeycloakHttp.ts") + .and() + .and() + .mandatoryReplacements() + .in(path("src/main/webapp/app/main.ts")) + .add(lineBeforeText("// jhipster-needle-main-ts-import"), + """ + import { provideForAuth } from '@/auth/application/AuthProvider'; + import { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; + import axios from 'axios'; + import Keycloak from 'keycloak-js';\ + """ + ) + .add(lineBeforeText("// jhipster-needle-main-ts-provider"), + """ + const keycloakHttp = new KeycloakHttp( + %snew Keycloak({ + %surl: 'http://localhost:9080', + %srealm: 'jhipster', + %sclientId: 'web_app', + %s}), + ); + + provideForAuth(keycloakHttp);\ + """.formatted(indentation.spaces(), + indentation.times(2), + indentation.times(2), + indentation.times(2), + indentation.spaces()) + ) + .and() + .and() + .build(); + //@formatter:on + } +} From ff4f9afa924a8b6bb6de0a2b03ed2d70def76641 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Thu, 19 Sep 2024 14:29:44 -0300 Subject: [PATCH 07/28] fix: sonar issues at KeycloakHttp.ts.mustache --- .../auth/infrastructure/secondary/KeycloakHttp.ts.mustache | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache index 2b419ec65d2..ae69bce783c 100644 --- a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache @@ -18,8 +18,8 @@ export class KeycloakHttp { if (this.keycloak.authenticated) { return { isAuthenticated: true, - username: this.keycloak.tokenParsed?.preferred_username || '', - token: this.keycloak.token || '', + username: this.keycloak.tokenParsed?.preferred_username ?? '', + token: this.keycloak.token ?? '', }; } else { return { isAuthenticated: false, username: '', token: '' }; @@ -44,6 +44,6 @@ export class KeycloakHttp { async refreshToken(): Promise { await this.ensureInitialized(); await this.keycloak.updateToken(5); - return this.keycloak.token || ''; + return this.keycloak.token ?? ''; } } From bfff100b1a7bc608358b3627832558cc859ac713 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Thu, 19 Sep 2024 14:39:03 -0300 Subject: [PATCH 08/28] feat: add vue-authentication-components.md documentation to vue-oauth2-keycloak module --- .../VueOAuth2KeycloakModulesFactory.java | 3 + .../vue-authentication-components.md | 670 ++++++++++++++++++ .../VueOAuth2KeycloakModulesFactoryTest.java | 1 + 3 files changed, 674 insertions(+) create mode 100644 src/main/resources/generator/client/vue/documentation/vue-authentication-components.md diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java index 35883a47ec0..c947b16d934 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java @@ -14,6 +14,7 @@ public class VueOAuth2KeycloakModulesFactory { private static final JHipsterSource SOURCE = from("client/vue"); private static final JHipsterSource APP_SOURCE = from("client/vue/webapp/app"); + private static final JHipsterSource DOCUMENTATION_SOURCE = SOURCE.append("documentation"); private static final JHipsterDestination MAIN_DESTINATION = to("src/main/webapp/app"); @@ -24,6 +25,8 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { //@formatter:off return moduleBuilder(properties) + .documentation(documentationTitle("Vue Authentication Components"), + DOCUMENTATION_SOURCE.file("vue-authentication-components.md")) .packageJson() .addDependency(packageName("keycloak-js"), COMMON) .and() diff --git a/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md b/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md new file mode 100644 index 00000000000..156eaa55d7d --- /dev/null +++ b/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md @@ -0,0 +1,670 @@ +# Vue Authentication Components Documentation + +This document provides an overview and showcase the practical usage of the vue authentication module. + +## File Structure + +``` +src/ +├── main/ +│ └── webapp/ +│ └── app/ +│ ├── auth/ +│ │ ├── application/ +│ │ │ └── AuthRouter.ts +│ │ └── infrastructure/ +│ │ └── primary/ +│ │ ├── AuthVue.component.ts +│ │ └── AuthVue.vue +│ └── shared/ +│ └── http/ +│ └── infrastructure/ +│ └── secondary/ +│ └── AxiosAuthInterceptor.ts +├── test/ +│ └── webapp/ +│ └── unit/ +│ ├── auth/ +│ │ └── infrastructure/ +│ │ └── primary/ +│ │ └── AuthVueComponent.spec.ts +│ └── shared/ +│ └── http/ +│ └── infrastructure/ +│ └── secondary/ +│ ├── AxiosAuthInterceptor.spec.ts +│ └── AxiosStub.ts +└── router.ts +``` + +## Detailed File Explanations + +### 1. AuthRouter.ts + +Location: `src/main/webapp/app/auth/application/AuthRouter.ts` + +This file defines the route for the authentication component. + +```typescript +import type { RouteRecordRaw } from 'vue-router'; +import AuthVue from '@/auth/infrastructure/primary/AuthVue.vue'; + +export const authRoutes = (): RouteRecordRaw[] => [ + { + path: '/login', + name: 'Login', + component: AuthVue, + }, +]; +``` + +### 2. AuthVue.component.ts + +Location: `src/main/webapp/app/auth/infrastructure/primary/AuthVue.component.ts` + +This file contains the component logic for the authentication view. + +```typescript +import { defineComponent, onMounted, ref } from 'vue'; +import { inject } from '@/injections'; +import { AUTH_REPOSITORY } from '@/auth/application/AuthProvider'; +import type { AuthenticatedUser } from '@/auth/domain/AuthenticatedUser'; + +export default defineComponent({ + name: 'AuthVue', + setup() { + const authRepository = inject(AUTH_REPOSITORY); + const user = ref(null); + const isLoading = ref(true); + + onMounted(async () => { + await init(); + }); + + const init = async () => { + isLoading.value = true; + const authenticated = await authRepository.authenticated(); + if (authenticated) { + user.value = await authRepository.currentUser(); + } else { + user.value = null; + } + isLoading.value = false; + }; + + const login = async () => { + isLoading.value = true; + try { + await authRepository.login(); + const currentUser = await authRepository.currentUser(); + user.value = currentUser.isAuthenticated ? currentUser : null; + } catch (error) { + console.error('Login failed:', error); + user.value = null; + } finally { + isLoading.value = false; + } + }; + + const logout = async () => { + isLoading.value = true; + try { + await authRepository.logout(); + user.value = null; + } catch (error) { + console.error('Logout failed:', error); + } finally { + isLoading.value = false; + } + }; + + return { + user, + isLoading, + login, + logout, + }; + }, +}); +``` + +### 3. AuthVue.vue + +Location: `src/main/webapp/app/auth/infrastructure/primary/AuthVue.vue` + +This file is the template for the authentication component. + +```vue + + + +``` + +### 4. AxiosAuthInterceptor.ts + +Location: `src/main/webapp/app/shared/http/infrastructure/secondary/AxiosAuthInterceptor.ts` + +This file sets up Axios interceptors to handle authentication tokens and 401 errors. + +```typescript +import type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'; +import { inject } from '@/injections'; +import { AUTH_REPOSITORY } from '@/auth/application/AuthProvider'; + +export const setupAxiosInterceptors = (axios: AxiosInstance): void => { + const auths = inject(AUTH_REPOSITORY); + + axios.interceptors.request.use(async (config: InternalAxiosRequestConfig) => { + if (await auths.authenticated()) { + const token = await auths.refreshToken(); + config.headers.set('Authorization', `Bearer ${token}`); + } + return config; + }); + + axios.interceptors.response.use( + (response: AxiosResponse): AxiosResponse => response, + async (error: AxiosError): Promise => { + if (error.response && error.response.status === 401) { + await auths.logout(); + //TODO: Redirect to login page or update application state + } + return Promise.reject(error); + }, + ); +}; +``` + +### 5. router.ts + +Location: `src/main/webapp/app/router.ts` + +This file sets up the main router for the application, including authentication routes. + +```typescript +import { createRouter, createWebHistory } from 'vue-router'; +import { homeRoutes } from '@/home/application/HomeRouter'; +import { authRoutes } from '@/auth/application/AuthRouter'; + +const routes = [...homeRoutes(), ...authRoutes()]; + +const router = createRouter({ + history: createWebHistory(), + routes, +}); + +export default router; +``` + +### 6. AuthVueComponent.spec.ts + +Location: `src/test/webapp/unit/auth/infrastructure/primary/AuthVueComponent.spec.ts` + +This file contains unit tests for the AuthVue component. + +```typescript +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { flushPromises, mount, VueWrapper } from '@vue/test-utils'; +import AuthVue from '@/auth/infrastructure/primary/AuthVue.vue'; +import { AUTH_REPOSITORY } from '@/auth/application/AuthProvider'; +import { provide } from '@/injections'; +import sinon from 'sinon'; +import type { SinonStub } from 'sinon'; +import type { AuthRepository } from '@/auth/domain/AuthRepository'; + +interface MockAuthRepository extends AuthRepository { + currentUser: SinonStub; + login: SinonStub; + logout: SinonStub; + authenticated: SinonStub; + refreshToken: SinonStub; +} + +const mockAuthRepository: MockAuthRepository = { + currentUser: sinon.stub(), + login: sinon.stub(), + logout: sinon.stub(), + authenticated: sinon.stub(), + refreshToken: sinon.stub(), +}; + +const wrap = () => { + provide(AUTH_REPOSITORY, mockAuthRepository); + return mount(AuthVue); +}; + +const componentVm = (wrapper: VueWrapper) => wrapper.findComponent(AuthVue).vm; + +describe('AuthVue', () => { + let wrapper: VueWrapper; + let consoleErrorSpy: any; + + beforeEach(async () => { + mockAuthRepository.authenticated.reset(); + mockAuthRepository.currentUser.reset(); + mockAuthRepository.login.reset(); + + mockAuthRepository.authenticated.resolves(false); + mockAuthRepository.currentUser.resolves({ isAuthenticated: false, username: '', token: '' }); + + wrapper = wrap(); + await flushPromises(); + + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleErrorSpy.mockRestore(); + }); + + it('should render login button when user is not authenticated', async () => { + expect(wrapper.find('button').text()).toBe('Login'); + }); + + it('should render logout button and username when user is authenticated', async () => { + mockAuthRepository.authenticated.resolves(true); + mockAuthRepository.currentUser.resolves({ isAuthenticated: true, username: 'test', token: 'token' }); + + wrapper = wrap(); + await flushPromises(); + + expect(wrapper.find('p').text()).toBe('Welcome, test!'); + expect(wrapper.find('button').text()).toBe('Logout'); + }); + + describe('Login', () => { + it('should handle failed login attempt', async () => { + mockAuthRepository.login.rejects(new Error('Login failed')); + + await wrapper.find('button').trigger('click'); + await flushPromises(); + + expect(mockAuthRepository.login.called).toBe(true); + expect(componentVm(wrapper).isLoading).toBe(false); + expect(componentVm(wrapper).user).toBeNull(); + expect(wrapper.find('button').text()).toBe('Login'); + expect(consoleErrorSpy).toHaveBeenCalledWith('Login failed:', expect.any(Error)); + }); + + it('should handle successful login attempt', async () => { + mockAuthRepository.login.resolves(); + mockAuthRepository.authenticated.resolves(true); + mockAuthRepository.currentUser.resolves({ isAuthenticated: true, username: 'test', token: 'token' }); + + await wrapper.find('button').trigger('click'); + await flushPromises(); + + expect(mockAuthRepository.login.called).toBe(true); + expect(componentVm(wrapper).isLoading).toBe(false); + expect(wrapper.find('p').text()).toBe('Welcome, test!'); + expect(wrapper.find('button').text()).toBe('Logout'); + }); + + it('should set isLoading to true during login process', async () => { + mockAuthRepository.login.resolves(); + mockAuthRepository.authenticated.resolves(true); + mockAuthRepository.currentUser.resolves({ isAuthenticated: true, username: 'test', token: 'token' }); + + const loginPromise = componentVm(wrapper).login(); + expect(componentVm(wrapper).isLoading).toBe(true); + + await loginPromise; + await flushPromises(); + + expect(componentVm(wrapper).isLoading).toBe(false); + }); + + it('should set user to null when currentUser is not authenticated after login', async () => { + mockAuthRepository.login.resolves(); + mockAuthRepository.currentUser.resolves({ isAuthenticated: false, username: '', token: '' }); + + await wrapper.find('button').trigger('click'); + await flushPromises(); + + expect(mockAuthRepository.login.called).toBe(true); + expect(componentVm(wrapper).user).toBeNull(); + expect(wrapper.find('button').text()).toBe('Login'); + }); + }); + + describe('Logout', () => { + it('should handle successful logout', async () => { + mockAuthRepository.logout.resolves(true); + mockAuthRepository.authenticated.resolves(false); + + await componentVm(wrapper).logout(); + await flushPromises(); + + expect(mockAuthRepository.logout.called).toBe(true); + expect(componentVm(wrapper).isLoading).toBe(false); + expect(componentVm(wrapper).user).toBeNull(); + expect(wrapper.find('button').text()).toBe('Login'); + }); + + it('should handle failed logout attempt', async () => { + const error = new Error('Logout failed'); + mockAuthRepository.logout.rejects(error); + + await componentVm(wrapper).logout(); + await flushPromises(); + + expect(mockAuthRepository.logout.called).toBe(true); + expect(componentVm(wrapper).isLoading).toBe(false); + expect(consoleErrorSpy).toHaveBeenCalledWith('Logout failed:', error); + }); + + it('should set isLoading to true during logout process', async () => { + mockAuthRepository.logout.resolves(true); + const logoutPromise = componentVm(wrapper).logout(); + expect(componentVm(wrapper).isLoading).toBe(true); + + await logoutPromise; + await flushPromises(); + + expect(componentVm(wrapper).isLoading).toBe(false); + }); + + it('should set user to null after successful logout', async () => { + mockAuthRepository.logout.resolves(true); + componentVm(wrapper).user = { isAuthenticated: true, username: 'test', token: 'token' }; + + await componentVm(wrapper).logout(); + await flushPromises(); + + expect(componentVm(wrapper).user).toBeNull(); + }); + + it('should not change user state after failed logout', async () => { + const error = new Error('Logout failed'); + mockAuthRepository.logout.rejects(error); + const initialUser = { isAuthenticated: true, username: 'test', token: 'token' }; + componentVm(wrapper).user = initialUser; + + await componentVm(wrapper).logout(); + await flushPromises(); + + expect(componentVm(wrapper).user).toEqual(initialUser); + }); + }); +}); +``` + +### 7. KeycloakStub.ts + +Location: `src/test/webapp/unit/auth/infrastructure/secondary/KeycloakStub.ts` + +This file provides a stub for Keycloak to be used in tests. + +```typescript +import Keycloak from 'keycloak-js'; +import sinon from 'sinon'; +import type { SinonStub } from 'sinon'; + +export interface KeycloakStub extends Keycloak { + init: SinonStub; + login: SinonStub; + logout: SinonStub; + register: SinonStub; + accountManagement: SinonStub; + updateToken: SinonStub; + clearToken: SinonStub; + hasRealmRole: SinonStub; + hasResourceRole: SinonStub; + loadUserProfile: SinonStub; + loadUserInfo: SinonStub; + authenticated?: boolean; + token?: string; + tokenParsed?: { preferred_username?: string }; +} + +export const stubKeycloak = (): KeycloakStub => + ({ + init: sinon.stub(), + login: sinon.stub(), + logout: sinon.stub(), + register: sinon.stub(), + accountManagement: sinon.stub(), + updateToken: sinon.stub(), + clearToken: sinon.stub(), + hasRealmRole: sinon.stub(), + hasResourceRole: sinon.stub(), + loadUserProfile: sinon.stub(), + loadUserInfo: sinon.stub(), + authenticated: false, + token: undefined, + tokenParsed: undefined, + }) as KeycloakStub; +``` + +### 8. AxiosAuthInterceptor.spec.ts + +Location: `src/test/webapp/unit/shared/http/infrastructure/secondary/AxiosAuthInterceptor.spec.ts` + +This file contains unit tests for the AxiosAuthInterceptor. + +```typescript +import { describe, it, expect, beforeEach } from 'vitest'; +import type { InternalAxiosRequestConfig, AxiosError, AxiosResponse } from 'axios'; +import { setupAxiosInterceptors } from '@/shared/http/infrastructure/secondary/AxiosAuthInterceptor'; +import { AUTH_REPOSITORY } from '@/auth/application/AuthProvider'; +import { provide } from '@/injections'; +import sinon from 'sinon'; +import type { SinonStub } from 'sinon'; +import type { AuthRepository } from '@/auth/domain/AuthRepository'; +import { stubAxiosInstance, dataAxiosResponse } from './AxiosStub'; +import type { AxiosStubInstance } from './AxiosStub'; +import { AxiosHeaders } from 'axios'; + +interface MockAuthRepository extends AuthRepository { + authenticated: SinonStub; + refreshToken: SinonStub; + logout: SinonStub; +} + +describe('AxiosAuthInterceptor', () => { + let axiosInstance: AxiosStubInstance; + let mockAuthRepository: MockAuthRepository; + + beforeEach(() => { + axiosInstance = stubAxiosInstance(); + mockAuthRepository = { + currentUser: sinon.stub(), + login: sinon.stub(), + logout: sinon.stub(), + authenticated: sinon.stub(), + refreshToken: sinon.stub(), + }; + provide(AUTH_REPOSITORY, mockAuthRepository); + }); + + const setupInterceptors = () => { + setupAxiosInterceptors(axiosInstance); + }; + + it('should add Authorization header for authenticated requests', async () => { + mockAuthRepository.authenticated.resolves(true); + mockAuthRepository.refreshToken.resolves('fake-token'); + setupInterceptors(); + const config: InternalAxiosRequestConfig = { headers: new AxiosHeaders() }; + + const interceptedConfig = await axiosInstance.runInterceptors(config); + + expect(mockAuthRepository.authenticated.called).toBe(true); + expect(mockAuthRepository.refreshToken.called).toBe(true); + expect(interceptedConfig.headers.get('Authorization')).toBe('Bearer fake-token'); + }); + + it('should not add Authorization header for unauthenticated requests', async () => { + mockAuthRepository.authenticated.resolves(false); + setupInterceptors(); + const config: InternalAxiosRequestConfig = { headers: new AxiosHeaders() }; + + const interceptedConfig = await axiosInstance.runInterceptors(config); + + expect(mockAuthRepository.authenticated.called).toBe(true); + expect(mockAuthRepository.refreshToken.called).toBe(false); + expect(interceptedConfig.headers.get('Authorization')).toBeUndefined(); + }); + + it('should call logout on 401 response', async () => { + setupInterceptors(); + const error: AxiosError = { + response: { + status: 401, + data: {}, + statusText: '', + headers: {}, + config: {} as InternalAxiosRequestConfig, + }, + isAxiosError: true, + toJSON: () => ({}), + name: '', + message: '', + }; + const responseInterceptor = axiosInstance.interceptors.response.use.args[0][1]; + + const interceptorPromise = responseInterceptor(error); + + await expect(interceptorPromise).rejects.toEqual(error); + expect(mockAuthRepository.logout.called).toBe(true); + }); + + it('should not call logout for non-401 errors', async () => { + setupInterceptors(); + const error: AxiosError = { + response: { + status: 500, + data: {}, + statusText: 'Internal Server Error', + headers: {}, + config: {} as InternalAxiosRequestConfig, + }, + isAxiosError: true, + toJSON: () => ({}), + name: 'AxiosError', + message: 'Request failed with status code 500', + }; + + const responseInterceptor = axiosInstance.interceptors.response.use.args[0][1]; + + await expect(responseInterceptor(error)).rejects.toEqual(error); + + expect(mockAuthRepository.logout.called).toBe(false); + }); + + it('should not call logout for errors without response', async () => { + setupInterceptors(); + const error: AxiosError = { + isAxiosError: true, + toJSON: () => ({}), + name: 'AxiosError', + message: 'Network Error', + }; + + const responseInterceptor = axiosInstance.interceptors.response.use.args[0][1]; + + await expect(responseInterceptor(error)).rejects.toEqual(error); + + expect(mockAuthRepository.logout.called).toBe(false); + }); + + it('should pass through successful responses without modification', async () => { + setupInterceptors(); + + const mockResponse: AxiosResponse = { + data: { message: 'Success' }, + status: 200, + statusText: 'OK', + headers: {}, + config: {} as InternalAxiosRequestConfig, + }; + + const responseInterceptor = axiosInstance.interceptors.response.use.args[0][0]; + + const result = await responseInterceptor(mockResponse); + + expect(result).toEqual(mockResponse); + }); +}); +``` + +### 9. AxiosStub.ts + +Location: `src/test/webapp/unit/shared/http/infrastructure/secondary/AxiosStub.ts` + +This file provides a stub for Axios to be used in tests. + +```typescript +import type { AxiosInstance, AxiosInterceptorManager, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; +import type { SinonStub } from 'sinon'; +import sinon from 'sinon'; + +export interface AxiosStubInterceptorManager extends AxiosInterceptorManager { + use: SinonStub; + eject: SinonStub; + clear: SinonStub; +} + +export interface AxiosStubInstance extends AxiosInstance { + get: SinonStub; + put: SinonStub; + post: SinonStub; + delete: SinonStub; + interceptors: { + request: AxiosStubInterceptorManager; + response: AxiosStubInterceptorManager; + }; + runInterceptors: (config: InternalAxiosRequestConfig) => Promise; +} + +export const stubAxiosInstance = (): AxiosStubInstance => { + const instance = { + get: sinon.stub(), + put: sinon.stub(), + post: sinon.stub(), + delete: sinon.stub(), + interceptors: { + request: { + use: sinon.stub(), + eject: sinon.stub(), + clear: sinon.stub(), + }, + response: { + use: sinon.stub(), + eject: sinon.stub(), + clear: sinon.stub(), + }, + }, + runInterceptors: async (config: InternalAxiosRequestConfig) => { + let currentConfig = { ...config, headers: config.headers || {} }; + for (const interceptor of instance.interceptors.request.use.args) { + currentConfig = await interceptor[0](currentConfig); + } + return currentConfig; + }, + } as AxiosStubInstance; + return instance; +}; + +export const dataAxiosResponse = (data: T): AxiosResponse => + ({ + data, + }) as AxiosResponse; +``` + +## Conclusion + +This documentation offers a detailed overview of the authentication components and utilities within our Vue.js application. It covers both the primary application files related to routing and authentication, as well as the test files used to validate the functionality of these components. + +At the core of the authentication system is the `AuthVue` component, which manages user login and logout processes. The `AxiosAuthInterceptor` ensures that all authenticated requests are properly equipped with the necessary authorization headers. + +The accompanying test files (`AuthVueComponent.spec.ts`, `AxiosAuthInterceptor.spec.ts`) illustrate how to effectively test these components, while the stub files (`KeycloakStub.ts`, `AxiosStub.ts`) provide mock implementations of external dependencies, enabling more streamlined and reliable testing. diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java index 39cc8d16d95..1e0badbd305 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java @@ -25,6 +25,7 @@ void shouldBuildVueOAuth2KeycloakModule() { //@formatter:off assertThatModuleWithFiles(module, packageJsonFile(), mainFile()) + .hasFiles("documentation/vue-authentication-components.md") .hasFile("package.json") .containing(nodeDependency("keycloak-js")) .and() From 9a98860f697b0f47b42b0c91df3a9bcf148c26e4 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Thu, 19 Sep 2024 14:40:18 -0300 Subject: [PATCH 09/28] fix: remove unused import added to main.ts --- .../oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java | 1 - .../domain/VueOAuth2KeycloakModulesFactoryTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java index c947b16d934..b6c8873939d 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java @@ -45,7 +45,6 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { """ import { provideForAuth } from '@/auth/application/AuthProvider'; import { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; - import axios from 'axios'; import Keycloak from 'keycloak-js';\ """ ) diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java index 1e0badbd305..1f2981ef432 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java @@ -38,7 +38,6 @@ void shouldBuildVueOAuth2KeycloakModule() { .containing(""" import { provideForAuth } from '@/auth/application/AuthProvider'; import { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; - import axios from 'axios'; import Keycloak from 'keycloak-js'; // jhipster-needle-main-ts-import\ """ From d0a2710bee926f72062968627790ac6895d31f5c Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 09:08:39 -0300 Subject: [PATCH 10/28] feat: add package-info.java --- .../client/vue/security/oauth2_keycloak/package-info.java | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/package-info.java diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/package-info.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/package-info.java new file mode 100644 index 00000000000..cb6287251a3 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/package-info.java @@ -0,0 +1,2 @@ +@tech.jhipster.lite.BusinessContext +package tech.jhipster.lite.generator.client.vue.security.oauth2_keycloak; From feee6bdb4bbef18ce4194c9bd2baad6f88a8bb11 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 09:35:45 -0300 Subject: [PATCH 11/28] test: add cucumber test to vue-oauth2-keycloak module configuration --- src/test/features/client/vue-oauth2-keycloak.feature | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/test/features/client/vue-oauth2-keycloak.feature diff --git a/src/test/features/client/vue-oauth2-keycloak.feature b/src/test/features/client/vue-oauth2-keycloak.feature new file mode 100644 index 00000000000..3a75421048d --- /dev/null +++ b/src/test/features/client/vue-oauth2-keycloak.feature @@ -0,0 +1,10 @@ +Feature: Vue OAuth2 Keycloak module + + Scenario: Should apply Vue OAuth2 Keycloak module + When I apply modules to default project + | init | + | prettier | + | vue-core | + | vue-oauth2-keycloak | + Then I should have files in "src/main/webapp/app/auth/domain" + | AuthRepository.ts | From a82ece1d5daf2a06d686839a04e96390a3b8de20 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 09:37:07 -0300 Subject: [PATCH 12/28] feat: add vue-oauth2-keycloak module configuration --- .../VueOAuth2KeycloakApplicationService.java | 20 +++++++++++++++ .../VueOAuth2KeycloakModuleConfiguration.java | 25 +++++++++++++++++++ .../slug/domain/JHLiteModuleSlug.java | 1 + 3 files changed, 46 insertions(+) create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/application/VueOAuth2KeycloakApplicationService.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/infrastructure/primary/VueOAuth2KeycloakModuleConfiguration.java diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/application/VueOAuth2KeycloakApplicationService.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/application/VueOAuth2KeycloakApplicationService.java new file mode 100644 index 00000000000..6d74f6ee5bc --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/application/VueOAuth2KeycloakApplicationService.java @@ -0,0 +1,20 @@ +package tech.jhipster.lite.generator.client.vue.security.oauth2_keycloak.application; + +import org.springframework.stereotype.Service; +import tech.jhipster.lite.generator.client.vue.security.oauth2_keycloak.domain.VueOAuth2KeycloakModulesFactory; +import tech.jhipster.lite.module.domain.JHipsterModule; +import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties; + +@Service +public class VueOAuth2KeycloakApplicationService { + + private final VueOAuth2KeycloakModulesFactory factory; + + public VueOAuth2KeycloakApplicationService() { + factory = new VueOAuth2KeycloakModulesFactory(); + } + + public JHipsterModule buildModule(JHipsterModuleProperties properties) { + return factory.buildModule(properties); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/infrastructure/primary/VueOAuth2KeycloakModuleConfiguration.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/infrastructure/primary/VueOAuth2KeycloakModuleConfiguration.java new file mode 100644 index 00000000000..7d560157864 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/infrastructure/primary/VueOAuth2KeycloakModuleConfiguration.java @@ -0,0 +1,25 @@ +package tech.jhipster.lite.generator.client.vue.security.oauth2_keycloak.infrastructure.primary; + +import static tech.jhipster.lite.generator.slug.domain.JHLiteModuleSlug.*; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import tech.jhipster.lite.generator.client.vue.security.oauth2_keycloak.application.VueOAuth2KeycloakApplicationService; +import tech.jhipster.lite.module.domain.resource.JHipsterModuleOrganization; +import tech.jhipster.lite.module.domain.resource.JHipsterModulePropertiesDefinition; +import tech.jhipster.lite.module.domain.resource.JHipsterModuleResource; + +@Configuration +class VueOAuth2KeycloakModuleConfiguration { + + @Bean + JHipsterModuleResource vueOAuth2KeycloakModule(VueOAuth2KeycloakApplicationService oauth2Keycloak) { + return JHipsterModuleResource.builder() + .slug(VUE_OAUTH2_KEYCLOAK) + .propertiesDefinition(JHipsterModulePropertiesDefinition.builder().addIndentation().build()) + .apiDoc("Vue", "Add OAuth2 Keycloak authentication to Vue") + .organization(JHipsterModuleOrganization.builder().addDependency(VUE_CORE).build()) + .tags("client", "vue", "auth", "oauth2", "keycloak") + .factory(oauth2Keycloak::buildModule); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java b/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java index 61bc37c4962..2c77b9df411 100644 --- a/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java +++ b/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java @@ -150,6 +150,7 @@ public enum JHLiteModuleSlug implements JHipsterModuleSlugFactory { SVELTE_CORE("svelte-core"), TYPESCRIPT("typescript"), VUE_CORE("vue-core"), + VUE_OAUTH2_KEYCLOAK("vue-oauth2-keycloak"), VUE_PINIA("vue-pinia"), TS_PAGINATION_DOMAIN("ts-pagination-domain"), TS_REST_PAGINATION("ts-rest-pagination"); From b7cdfc2f9b557a737be9180a4219a87e0a7eaeb6 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 09:41:40 -0300 Subject: [PATCH 13/28] fix: cucumber test --- src/test/features/client/vue-oauth2-keycloak.feature | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/features/client/vue-oauth2-keycloak.feature b/src/test/features/client/vue-oauth2-keycloak.feature index 3a75421048d..2923c500aeb 100644 --- a/src/test/features/client/vue-oauth2-keycloak.feature +++ b/src/test/features/client/vue-oauth2-keycloak.feature @@ -1,9 +1,10 @@ -Feature: Vue OAuth2 Keycloak module +Feature: Vue oauth2 keycloak module Scenario: Should apply Vue OAuth2 Keycloak module When I apply modules to default project | init | | prettier | + | typescript | | vue-core | | vue-oauth2-keycloak | Then I should have files in "src/main/webapp/app/auth/domain" From 84b7ac2122156e6d4774b8cbe06c0129d6d2269d Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 10:21:22 -0300 Subject: [PATCH 14/28] refactor: improve VueOAuth2KeycloakModulesFactory code by using constants --- .../VueOAuth2KeycloakModulesFactory.java | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java index b6c8873939d..e9b07703370 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java @@ -18,6 +18,28 @@ public class VueOAuth2KeycloakModulesFactory { private static final JHipsterDestination MAIN_DESTINATION = to("src/main/webapp/app"); + private static final String MAIN_TS_IMPORT_NEEDLE = "// jhipster-needle-main-ts-import"; + private static final String MAIN_TS_PROVIDER_NEEDLE = "// jhipster-needle-main-ts-provider"; + + private static final String KEYCLOAK_IMPORT = + """ + import { provideForAuth } from '@/auth/application/AuthProvider'; + import { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; + import Keycloak from 'keycloak-js';\ + """; + private static final String KEYCLOAK_SETUP = + """ + const keycloakHttp = new KeycloakHttp( + %snew Keycloak({ + %surl: 'http://localhost:9080', + %srealm: 'jhipster', + %sclientId: 'web_app', + %s}), + ); + + provideForAuth(keycloakHttp);\ + """; + public JHipsterModule buildModule(JHipsterModuleProperties properties) { Assert.notNull("properties", properties); @@ -41,25 +63,11 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { .and() .mandatoryReplacements() .in(path("src/main/webapp/app/main.ts")) - .add(lineBeforeText("// jhipster-needle-main-ts-import"), - """ - import { provideForAuth } from '@/auth/application/AuthProvider'; - import { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; - import Keycloak from 'keycloak-js';\ - """ + .add(lineBeforeText(MAIN_TS_IMPORT_NEEDLE), + KEYCLOAK_IMPORT ) - .add(lineBeforeText("// jhipster-needle-main-ts-provider"), - """ - const keycloakHttp = new KeycloakHttp( - %snew Keycloak({ - %surl: 'http://localhost:9080', - %srealm: 'jhipster', - %sclientId: 'web_app', - %s}), - ); - - provideForAuth(keycloakHttp);\ - """.formatted(indentation.spaces(), + .add(lineBeforeText(MAIN_TS_PROVIDER_NEEDLE), + KEYCLOAK_SETUP.formatted(indentation.spaces(), indentation.times(2), indentation.times(2), indentation.times(2), From 72a9c6ef255c1bc7b5a5d85a1b831c028959b2ce Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 10:24:16 -0300 Subject: [PATCH 15/28] feat: use keycloak-js dependency from COMMON package instead of the ANGULAR package --- .../security/oauth2/domain/AngularOauth2ModuleFactory.java | 4 ++-- .../resources/generator/dependencies/angular/package.json | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/tech/jhipster/lite/generator/client/angular/security/oauth2/domain/AngularOauth2ModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/client/angular/security/oauth2/domain/AngularOauth2ModuleFactory.java index d1fdd830bd1..a19e0d3e9dd 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/angular/security/oauth2/domain/AngularOauth2ModuleFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/client/angular/security/oauth2/domain/AngularOauth2ModuleFactory.java @@ -1,7 +1,7 @@ package tech.jhipster.lite.generator.client.angular.security.oauth2.domain; import static tech.jhipster.lite.module.domain.JHipsterModule.*; -import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.ANGULAR; +import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.*; import static tech.jhipster.lite.module.domain.replacement.ReplacementCondition.notMatchingRegex; import java.util.regex.Pattern; @@ -114,7 +114,7 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { //@formatter:off return moduleBuilder(properties) .packageJson() - .addDependency(packageName("keycloak-js"), ANGULAR) + .addDependency(packageName("keycloak-js"), COMMON) .and() .files() .batch(SOURCE.append("auth"), APP_DESTINATION.append("auth")) diff --git a/src/main/resources/generator/dependencies/angular/package.json b/src/main/resources/generator/dependencies/angular/package.json index baceeef66fd..a05ffcf5a62 100644 --- a/src/main/resources/generator/dependencies/angular/package.json +++ b/src/main/resources/generator/dependencies/angular/package.json @@ -4,7 +4,6 @@ "@angular/core": "18.2.5", "@angular/material": "18.2.5", "angular-eslint": "18.3.1", - "keycloak-js": "25.0.6", "rxjs": "7.8.1", "tslib": "2.7.0", "zone.js": "0.14.10" From 76e78c63ec8734fd75b715f33ff0fc0637c90c30 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 14:42:29 -0300 Subject: [PATCH 16/28] fix: update constructor parameter to readonly --- .../infrastructure/secondary/KeycloakAuthRepository.ts.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache index 6644a20f9df..d5bc75d3b90 100644 --- a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache @@ -3,7 +3,7 @@ import type { AuthenticatedUser } from '@/auth/domain/AuthenticatedUser'; import { KeycloakHttp } from './KeycloakHttp'; export class KeycloakAuthRepository implements AuthRepository { - constructor(private keycloakHttp: KeycloakHttp) {} + constructor(private readonly keycloakHttp: KeycloakHttp) {} async currentUser(): Promise { try { From 42fa491cf6655ea0a2afeaa3e4afd8f19594bc60 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 15:45:29 -0300 Subject: [PATCH 17/28] docs: improve vue authentication components documentation --- .../client/vue/documentation/vue-authentication-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md b/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md index 156eaa55d7d..91fea843cf6 100644 --- a/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md +++ b/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md @@ -1,6 +1,6 @@ # Vue Authentication Components Documentation -This document provides an overview and showcase the practical usage of the vue authentication module. +This document provides an overview and demonstrates the practical usage of the vue authentication module. ## File Structure From 0de2fbbfb02cc3e11b04d7a1b383eae4171c53a0 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 16:00:44 -0300 Subject: [PATCH 18/28] feat: add type alias for AuthenticatedUser --- .../vue/webapp/app/auth/domain/AuthenticatedUser.ts.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthenticatedUser.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthenticatedUser.ts.mustache index c23945f5bdf..beacd8c6fe3 100644 --- a/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthenticatedUser.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthenticatedUser.ts.mustache @@ -1,7 +1,7 @@ type AuthenticatedUserName = string; type AuthenticatedUserToken = string; -export interface AuthenticatedUser { +export type AuthenticatedUser = { isAuthenticated: boolean; username: AuthenticatedUserName; token: AuthenticatedUserToken; From 1c1d37b0381ad30c0ddcee48c24528fbb2f0b183 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 16:07:00 -0300 Subject: [PATCH 19/28] fix: code formatting at AuthRepository.ts --- .../vue/webapp/app/auth/domain/AuthRepository.ts.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache index a98fb14437f..e6c12ed486e 100644 --- a/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache @@ -1,4 +1,4 @@ -import type {AuthenticatedUser} from "@/auth/domain/AuthenticatedUser"; +import type { AuthenticatedUser } from '@/auth/domain/AuthenticatedUser'; export interface AuthRepository { currentUser(): Promise; From c2407cde1df3058879b591a7aa9ead0797b02224 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 16:10:28 -0300 Subject: [PATCH 20/28] refactor: remove unnecessary else statement --- .../app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache index ae69bce783c..86f984b771c 100644 --- a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakHttp.ts.mustache @@ -21,9 +21,8 @@ export class KeycloakHttp { username: this.keycloak.tokenParsed?.preferred_username ?? '', token: this.keycloak.token ?? '', }; - } else { - return { isAuthenticated: false, username: '', token: '' }; } + return { isAuthenticated: false, username: '', token: '' }; } async login(): Promise { From ee0689c4f967d500b8f93c55862c598afefb2b8b Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Fri, 20 Sep 2024 16:37:28 -0300 Subject: [PATCH 21/28] refactor: use promises for error handler and remove console.log --- .../auth/domain/AuthRepository.ts.mustache | 2 +- .../KeycloakAuthRepository.ts.mustache | 46 ++++--------------- 2 files changed, 11 insertions(+), 37 deletions(-) diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache index e6c12ed486e..3d90dac92e5 100644 --- a/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/auth/domain/AuthRepository.ts.mustache @@ -3,7 +3,7 @@ import type { AuthenticatedUser } from '@/auth/domain/AuthenticatedUser'; export interface AuthRepository { currentUser(): Promise; login(): Promise; - logout(): Promise; + logout(): Promise; authenticated(): Promise; refreshToken(): Promise; } diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache index d5bc75d3b90..6d9613b07dc 100644 --- a/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/auth/infrastructure/secondary/KeycloakAuthRepository.ts.mustache @@ -5,49 +5,23 @@ import { KeycloakHttp } from './KeycloakHttp'; export class KeycloakAuthRepository implements AuthRepository { constructor(private readonly keycloakHttp: KeycloakHttp) {} - async currentUser(): Promise { - try { - return await this.keycloakHttp.currentUser(); - } catch (error) { - console.error('Authentication failed', error); - return { isAuthenticated: false, username: '', token: '' }; - } + currentUser(): Promise { + return this.keycloakHttp.currentUser(); } - async login(): Promise { - try { - await this.keycloakHttp.login(); - } catch (error) { - console.error('Login failed', error); - throw error; - } + login(): Promise { + return this.keycloakHttp.login(); } - async logout(): Promise { - try { - await this.keycloakHttp.logout(); - return true; - } catch (error) { - console.error('Logout failed', error); - return false; - } + logout(): Promise { + return this.keycloakHttp.logout(); } - async authenticated(): Promise { - try { - return await this.keycloakHttp.authenticated(); - } catch (error) { - console.error('isAuthenticated check failed', error); - return false; - } + authenticated(): Promise { + return this.keycloakHttp.authenticated(); } - async refreshToken(): Promise { - try { - return await this.keycloakHttp.refreshToken(); - } catch (error) { - console.error('Token refresh failed', error); - return ''; - } + refreshToken(): Promise { + return this.keycloakHttp.refreshToken(); } } From f81efe90e1755eabf36ede8520f291534505e4eb Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Mon, 23 Sep 2024 11:34:21 -0300 Subject: [PATCH 22/28] docs: update the AuthVue.component.ts code in vue-authentication-components.md to use promises rather than async/await --- .../vue-authentication-components.md | 78 ++++++++++++------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md b/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md index 91fea843cf6..49384df281d 100644 --- a/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md +++ b/src/main/resources/generator/client/vue/documentation/vue-authentication-components.md @@ -77,45 +77,63 @@ export default defineComponent({ const user = ref(null); const isLoading = ref(true); - onMounted(async () => { - await init(); + onMounted(() => { + init(); }); - const init = async () => { + const init = () => { isLoading.value = true; - const authenticated = await authRepository.authenticated(); - if (authenticated) { - user.value = await authRepository.currentUser(); - } else { - user.value = null; - } - isLoading.value = false; + authRepository + .authenticated() + .then(authenticated => { + if (authenticated) { + return authRepository.currentUser(); + } else { + return null; + } + }) + .then(currentUser => { + user.value = currentUser; + }) + .catch(error => { + console.error('Initialization failed:', error); + user.value = null; + }) + .finally(() => { + isLoading.value = false; + }); }; - const login = async () => { + const login = () => { isLoading.value = true; - try { - await authRepository.login(); - const currentUser = await authRepository.currentUser(); - user.value = currentUser.isAuthenticated ? currentUser : null; - } catch (error) { - console.error('Login failed:', error); - user.value = null; - } finally { - isLoading.value = false; - } + authRepository + .login() + .then(() => authRepository.currentUser()) + .then(currentUser => { + user.value = currentUser.isAuthenticated ? currentUser : null; + }) + .catch(error => { + console.error('Login failed:', error); + user.value = null; + }) + .finally(() => { + isLoading.value = false; + }); }; - const logout = async () => { + const logout = () => { isLoading.value = true; - try { - await authRepository.logout(); - user.value = null; - } catch (error) { - console.error('Logout failed:', error); - } finally { - isLoading.value = false; - } + authRepository + .logout() + .then(() => { + user.value = null; + }) + .catch(error => { + console.error('Logout failed:', error); + }) + .finally(() => { + isLoading.value = false; + }); }; return { From 0fdb2bc74d2dbbebdf843dfa838b49fcda64250a Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Mon, 23 Sep 2024 12:01:35 -0300 Subject: [PATCH 23/28] feat: add unit tests to the module to be able to add vue-oauth2-keycloak module at the ci pipeline --- .../VueOAuth2KeycloakModulesFactory.java | 9 ++ .../KeycloakAuthRepository.spec.ts.mustache | 99 +++++++++++++ .../secondary/KeycloakHttp.spec.ts.mustache | 139 ++++++++++++++++++ .../secondary/KeycloakHttpStub.ts.mustache | 28 ++++ .../secondary/KeycloakStub.ts.mustache | 37 +++++ .../VueOAuth2KeycloakModulesFactoryTest.java | 7 +- 6 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakAuthRepository.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttp.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttpStub.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakStub.ts.mustache diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java index e9b07703370..33e23a63ce3 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java @@ -17,6 +17,7 @@ public class VueOAuth2KeycloakModulesFactory { private static final JHipsterSource DOCUMENTATION_SOURCE = SOURCE.append("documentation"); private static final JHipsterDestination MAIN_DESTINATION = to("src/main/webapp/app"); + private static final JHipsterDestination TEST_DESTINATION = to("src/test/webapp"); private static final String MAIN_TS_IMPORT_NEEDLE = "// jhipster-needle-main-ts-import"; private static final String MAIN_TS_PROVIDER_NEEDLE = "// jhipster-needle-main-ts-provider"; @@ -75,6 +76,14 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { ) .and() .and() + .files() + .batch(APP_SOURCE.append("test/webapp/unit/auth/infrastructure/secondary"), TEST_DESTINATION.append("unit/auth/infrastructure/secondary")) + .addTemplate("KeycloakAuthRepository.spec.ts") + .addTemplate("KeycloakHttp.spec.ts") + .addTemplate("KeycloakHttpStub.ts") + .addTemplate("KeycloakStub.ts") + .and() + .and() .build(); //@formatter:on } diff --git a/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakAuthRepository.spec.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakAuthRepository.spec.ts.mustache new file mode 100644 index 00000000000..467de2d46d5 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakAuthRepository.spec.ts.mustache @@ -0,0 +1,99 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { KeycloakAuthRepository } from '@/auth/infrastructure/secondary/KeycloakAuthRepository'; +import type { KeycloakHttpStub } from './KeycloakHttpStub'; +import { stubKeycloakHttp, fakeAuthenticatedUser } from './KeycloakHttpStub'; + +describe('KeycloakAuthRepository', () => { + let keycloakHttpStub: KeycloakHttpStub; + let authRepository: KeycloakAuthRepository; + + beforeEach(() => { + keycloakHttpStub = stubKeycloakHttp(); + authRepository = new KeycloakAuthRepository(keycloakHttpStub); + }); + + it('should authenticate a user', async () => { + const mockUser = fakeAuthenticatedUser(); + keycloakHttpStub.currentUser.resolves(mockUser); + + const user = await authRepository.currentUser(); + + expect(user).toEqual(mockUser); + expect(keycloakHttpStub.currentUser.calledOnce).toBe(true); + }); + + it('should propagate error when currentUser fails', async () => { + const error = new Error('Authentication failed'); + keycloakHttpStub.currentUser.rejects(error); + + await expect(authRepository.currentUser()).rejects.toThrow('Authentication failed'); + expect(keycloakHttpStub.currentUser.calledOnce).toBe(true); + }); + + it('should login a user successfully', async () => { + keycloakHttpStub.login.resolves(); + + await authRepository.login(); + + expect(keycloakHttpStub.login.calledOnce).toBe(true); + }); + + it('should propagate error when login fails', async () => { + const error = new Error('Login failed'); + keycloakHttpStub.login.rejects(error); + + await expect(authRepository.login()).rejects.toThrow('Login failed'); + expect(keycloakHttpStub.login.calledOnce).toBe(true); + }); + + it('should logout a user', async () => { + keycloakHttpStub.logout.resolves(); + + await authRepository.logout(); + + expect(keycloakHttpStub.logout.calledOnce).toBe(true); + }); + + it('should propagate error when logout fails', async () => { + const error = new Error('Logout failed'); + keycloakHttpStub.logout.rejects(error); + + await expect(authRepository.logout()).rejects.toThrow('Logout failed'); + expect(keycloakHttpStub.logout.calledOnce).toBe(true); + }); + + it('should check if a user is authenticated', async () => { + keycloakHttpStub.authenticated.resolves(true); + + const isAuthenticated = await authRepository.authenticated(); + + expect(isAuthenticated).toBe(true); + expect(keycloakHttpStub.authenticated.calledOnce).toBe(true); + }); + + it('should propagate error when authenticated check fails', async () => { + const error = new Error('Authentication check failed'); + keycloakHttpStub.authenticated.rejects(error); + + await expect(authRepository.authenticated()).rejects.toThrow('Authentication check failed'); + expect(keycloakHttpStub.authenticated.calledOnce).toBe(true); + }); + + it('should refresh the token', async () => { + const newToken = 'new-test-token'; + keycloakHttpStub.refreshToken.resolves(newToken); + + const refreshedToken = await authRepository.refreshToken(); + + expect(refreshedToken).toBe(newToken); + expect(keycloakHttpStub.refreshToken.calledOnce).toBe(true); + }); + + it('should propagate error when refreshToken fails', async () => { + const error = new Error('Token refresh failed'); + keycloakHttpStub.refreshToken.rejects(error); + + await expect(authRepository.refreshToken()).rejects.toThrow('Token refresh failed'); + expect(keycloakHttpStub.refreshToken.calledOnce).toBe(true); + }); +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttp.spec.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttp.spec.ts.mustache new file mode 100644 index 00000000000..cd53a3c4c5e --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttp.spec.ts.mustache @@ -0,0 +1,139 @@ +import { describe, it, expect } from 'vitest'; +import { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; +import { stubKeycloak } from './KeycloakStub'; +import { fakeAuthenticatedUser } from './KeycloakHttpStub'; + +const createKeycloakHttp = () => { + const keycloakStub = stubKeycloak(); + const keycloakHttp = new KeycloakHttp(keycloakStub); + return { keycloakStub, keycloakHttp }; +}; + +describe('KeycloakHttp', () => { + describe('Authentication', () => { + it('should authenticate successfully', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + const fakeUser = fakeAuthenticatedUser(); + keycloakStub.init.resolves(true); + keycloakStub.authenticated = true; + keycloakStub.tokenParsed = { preferred_username: fakeUser.username }; + keycloakStub.token = fakeUser.token; + + const result = await keycloakHttp.currentUser(); + + expect(result).toEqual(fakeUser); + expect(keycloakStub.init.calledOnce).toBe(true); + }); + + it('should handle authentication failure', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + keycloakStub.init.resolves(false); + + const result = await keycloakHttp.currentUser(); + + expect(result).toEqual({ isAuthenticated: false, username: '', token: '' }); + expect(keycloakStub.init.calledOnce).toBe(true); + }); + }); + + describe('Initialization', () => { + it('should not reinitialize if already initialized', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + keycloakStub.init.resolves(true); + + await keycloakHttp.currentUser(); + await keycloakHttp.currentUser(); + + expect(keycloakStub.init.calledOnce).toBe(true); + }); + + it('should initialize only once across different method calls', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + keycloakStub.init.resolves(true); + + await keycloakHttp.currentUser(); + await keycloakHttp.login(); + await keycloakHttp.logout(); + await keycloakHttp.authenticated(); + await keycloakHttp.refreshToken(); + + expect(keycloakStub.init.calledOnce).toBe(true); + }); + }); + + it('should handle undefined preferred_username', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + keycloakStub.init.resolves(true); + keycloakStub.authenticated = true; + keycloakStub.tokenParsed = {}; + keycloakStub.token = 'test-token'; + + const result = await keycloakHttp.currentUser(); + + expect(result).toEqual({ + isAuthenticated: true, + username: '', + token: 'test-token' + }); + }); + + it('should handle undefined token', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + keycloakStub.init.resolves(true); + keycloakStub.authenticated = true; + keycloakStub.tokenParsed = { preferred_username: 'test' }; + keycloakStub.token = undefined; + + const result = await keycloakHttp.currentUser(); + + expect(result).toEqual({ + isAuthenticated: true, + username: 'test', + token: '' + }); + }); + + it('should logout', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + keycloakStub.logout.resolves(); + + await keycloakHttp.logout(); + + expect(keycloakStub.logout.calledOnce).toBe(true); + }); + + it('should check if authenticated', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + keycloakStub.init.resolves(true); + keycloakStub.authenticated = true; + keycloakStub.token = 'valid-token'; + + const result = await keycloakHttp.authenticated(); + + expect(result).toBe(true); + expect(keycloakStub.init.calledOnce).toBe(true); + }); + + it('should refresh token', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + const newToken = 'new-test-token'; + keycloakStub.updateToken.resolves(); + keycloakStub.token = newToken; + + const result = await keycloakHttp.refreshToken(); + + expect(result).toBe(newToken); + expect(keycloakStub.updateToken.calledOnce).toBe(true); + }); +}); + +it('should login successfully', async () => { + const { keycloakStub, keycloakHttp } = createKeycloakHttp(); + keycloakStub.init.resolves(true); + keycloakStub.login.resolves(); + + await keycloakHttp.login(); + + expect(keycloakStub.init.calledOnce).toBe(true); + expect(keycloakStub.login.calledOnce).toBe(true); +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttpStub.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttpStub.ts.mustache new file mode 100644 index 00000000000..62ac11884ca --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttpStub.ts.mustache @@ -0,0 +1,28 @@ +import sinon from 'sinon'; +import type { SinonStub } from 'sinon'; +import type { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; +import type { AuthenticatedUser } from '@/auth/domain/AuthenticatedUser'; + +export interface KeycloakHttpStub extends KeycloakHttp { +currentUser: SinonStub; +login: SinonStub; +logout: SinonStub; +authenticated: SinonStub; +refreshToken: SinonStub; +getKeycloakInstance: SinonStub; +} + +export const stubKeycloakHttp = (): KeycloakHttpStub => ({ +currentUser: sinon.stub(), +login: sinon.stub(), +logout: sinon.stub(), +authenticated: sinon.stub(), +refreshToken: sinon.stub(), +getKeycloakInstance: sinon.stub(), +}) as KeycloakHttpStub; + +export const fakeAuthenticatedUser = (): AuthenticatedUser => ({ +isAuthenticated: true, +username: 'testuser', +token: 'test-token' +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakStub.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakStub.ts.mustache new file mode 100644 index 00000000000..9938638c071 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/infrastructure/secondary/KeycloakStub.ts.mustache @@ -0,0 +1,37 @@ +import Keycloak from 'keycloak-js'; +import sinon from 'sinon'; +import type{ SinonStub } from 'sinon'; + +export interface KeycloakStub extends Keycloak { + init: SinonStub; + login: SinonStub; + logout: SinonStub; + register: SinonStub; + accountManagement: SinonStub; + updateToken: SinonStub; + clearToken: SinonStub; + hasRealmRole: SinonStub; + hasResourceRole: SinonStub; + loadUserProfile: SinonStub; + loadUserInfo: SinonStub; + authenticated?: boolean; + token?: string; + tokenParsed?: { preferred_username?: string }; +} + +export const stubKeycloak = (): KeycloakStub => ({ + init: sinon.stub(), + login: sinon.stub(), + logout: sinon.stub(), + register: sinon.stub(), + accountManagement: sinon.stub(), + updateToken: sinon.stub(), + clearToken: sinon.stub(), + hasRealmRole: sinon.stub(), + hasResourceRole: sinon.stub(), + loadUserProfile: sinon.stub(), + loadUserInfo: sinon.stub(), + authenticated: false, + token: undefined, + tokenParsed: undefined, +}) as KeycloakStub; diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java index 1f2981ef432..ae3a1ba5645 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java @@ -54,7 +54,12 @@ void shouldBuildVueOAuth2KeycloakModule() { provideForAuth(keycloakHttp); // jhipster-needle-main-ts-provider\ """ - ); + ) + .and() + .hasFiles("src/test/webapp/unit/auth/infrastructure/secondary/KeycloakAuthRepository.spec.ts") + .hasFiles("src/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttp.spec.ts") + .hasFiles("src/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttpStub.ts") + .hasFiles("src/test/webapp/unit/auth/infrastructure/secondary/KeycloakStub.ts"); //@formatter:on } From c5d397cba8690ffdca3ecc164dc24cb0f5aa105c Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Mon, 23 Sep 2024 12:03:33 -0300 Subject: [PATCH 24/28] feat: add vue-oauth2-keycloak module to the vueapp application at the generate.sh used at ci pipeline --- tests-ci/generate.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests-ci/generate.sh b/tests-ci/generate.sh index cc8a31acdb9..a251c441456 100755 --- a/tests-ci/generate.sh +++ b/tests-ci/generate.sh @@ -421,6 +421,7 @@ elif [[ $application == 'vueapp' ]]; then "prettier" \ "vue-core" \ "vue-pinia" \ + "vue-oauth2-keycloak" \ "playwright-component-tests" \ "cypress-e2e" From d5c26e9abf6075e560fe65b2bf3ca29eb8dc70de Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Mon, 23 Sep 2024 13:31:13 -0300 Subject: [PATCH 25/28] fix: format import statement in AuthProvider.ts.mustache --- .../vue/webapp/app/auth/application/AuthProvider.ts.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/generator/client/vue/webapp/app/auth/application/AuthProvider.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/auth/application/AuthProvider.ts.mustache index 44ef1a3c80c..f88198c5d94 100644 --- a/src/main/resources/generator/client/vue/webapp/app/auth/application/AuthProvider.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/auth/application/AuthProvider.ts.mustache @@ -1,5 +1,5 @@ import { key } from 'piqure'; -import { provide} from '@/injections'; +import { provide } from '@/injections'; import type { AuthRepository } from '@/auth/domain/AuthRepository'; import { KeycloakAuthRepository } from '@/auth/infrastructure/secondary/KeycloakAuthRepository'; import { KeycloakHttp } from '@/auth/infrastructure/secondary/KeycloakHttp'; From c85306a9964f5c977b14321283fd0747f8fecc12 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Mon, 23 Sep 2024 13:46:16 -0300 Subject: [PATCH 26/28] feat: add unit tests to AuthProvider.ts.mustache to reach 100% code coverage --- .../VueOAuth2KeycloakModulesFactory.java | 1 + .../application/AuthProvider.spec.ts.mustache | 20 +++++++++++++++++++ .../VueOAuth2KeycloakModulesFactoryTest.java | 1 + 3 files changed, 22 insertions(+) create mode 100644 src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/application/AuthProvider.spec.ts.mustache diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java index 33e23a63ce3..615eda82695 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java @@ -77,6 +77,7 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { .and() .and() .files() + .add(APP_SOURCE.template("test/webapp/unit/auth/application/AuthProvider.spec.ts"), TEST_DESTINATION.append("unit/auth/application/AuthProvider.spec.ts")) .batch(APP_SOURCE.append("test/webapp/unit/auth/infrastructure/secondary"), TEST_DESTINATION.append("unit/auth/infrastructure/secondary")) .addTemplate("KeycloakAuthRepository.spec.ts") .addTemplate("KeycloakHttp.spec.ts") diff --git a/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/application/AuthProvider.spec.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/application/AuthProvider.spec.ts.mustache new file mode 100644 index 00000000000..9e4b5621e30 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/application/AuthProvider.spec.ts.mustache @@ -0,0 +1,20 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { AUTH_REPOSITORY, provideForAuth } from '@/auth/application/AuthProvider'; +import { KeycloakAuthRepository } from '@/auth/infrastructure/secondary/KeycloakAuthRepository'; +import { inject } from '@/injections'; +import { stubKeycloakHttp } from '../infrastructure/secondary/KeycloakHttpStub'; + +describe('AuthProvider', () => { + + it('should define AUTH_REPOSITORY with the correct key', () => { + expect(AUTH_REPOSITORY.description).toBe('AuthRepository'); + }); + + it('should provide KeycloakAuthRepository with KeycloakHttp', async () => { + const keycloakHttp = stubKeycloakHttp(); + provideForAuth(keycloakHttp); + + const injectedRepository = inject(AUTH_REPOSITORY); + expect(injectedRepository).toBeInstanceOf(KeycloakAuthRepository); + }); +}); diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java index ae3a1ba5645..410ee00f1c8 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactoryTest.java @@ -56,6 +56,7 @@ void shouldBuildVueOAuth2KeycloakModule() { """ ) .and() + .hasFiles("src/test/webapp/unit/auth/application/AuthProvider.spec.ts") .hasFiles("src/test/webapp/unit/auth/infrastructure/secondary/KeycloakAuthRepository.spec.ts") .hasFiles("src/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttp.spec.ts") .hasFiles("src/test/webapp/unit/auth/infrastructure/secondary/KeycloakHttpStub.ts") From b666ca84bbcf17ace45d2ff3d79be6e56c36d3b5 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Mon, 23 Sep 2024 13:48:34 -0300 Subject: [PATCH 27/28] refactor: change template to add order --- .../domain/VueOAuth2KeycloakModulesFactory.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java index 615eda82695..7f5e0237b37 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/oauth2_keycloak/domain/VueOAuth2KeycloakModulesFactory.java @@ -61,6 +61,13 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { .addTemplate("infrastructure/secondary/KeycloakAuthRepository.ts") .addTemplate("infrastructure/secondary/KeycloakHttp.ts") .and() + .add(APP_SOURCE.template("test/webapp/unit/auth/application/AuthProvider.spec.ts"), TEST_DESTINATION.append("unit/auth/application/AuthProvider.spec.ts")) + .batch(APP_SOURCE.append("test/webapp/unit/auth/infrastructure/secondary"), TEST_DESTINATION.append("unit/auth/infrastructure/secondary")) + .addTemplate("KeycloakAuthRepository.spec.ts") + .addTemplate("KeycloakHttp.spec.ts") + .addTemplate("KeycloakHttpStub.ts") + .addTemplate("KeycloakStub.ts") + .and() .and() .mandatoryReplacements() .in(path("src/main/webapp/app/main.ts")) @@ -76,15 +83,6 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { ) .and() .and() - .files() - .add(APP_SOURCE.template("test/webapp/unit/auth/application/AuthProvider.spec.ts"), TEST_DESTINATION.append("unit/auth/application/AuthProvider.spec.ts")) - .batch(APP_SOURCE.append("test/webapp/unit/auth/infrastructure/secondary"), TEST_DESTINATION.append("unit/auth/infrastructure/secondary")) - .addTemplate("KeycloakAuthRepository.spec.ts") - .addTemplate("KeycloakHttp.spec.ts") - .addTemplate("KeycloakHttpStub.ts") - .addTemplate("KeycloakStub.ts") - .and() - .and() .build(); //@formatter:on } From b1aeaa944070b83f46d36b8eb630412c9f3f3be5 Mon Sep 17 00:00:00 2001 From: Renan Franca Date: Mon, 23 Sep 2024 14:09:58 -0300 Subject: [PATCH 28/28] fix: remove unused beforeEach hook in AuthProvider.spec.ts.mustache --- .../webapp/unit/auth/application/AuthProvider.spec.ts.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/application/AuthProvider.spec.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/application/AuthProvider.spec.ts.mustache index 9e4b5621e30..ec85b22f3a4 100644 --- a/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/application/AuthProvider.spec.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/test/webapp/unit/auth/application/AuthProvider.spec.ts.mustache @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { AUTH_REPOSITORY, provideForAuth } from '@/auth/application/AuthProvider'; import { KeycloakAuthRepository } from '@/auth/infrastructure/secondary/KeycloakAuthRepository'; import { inject } from '@/injections';