From 81a3db2e6fc67d1909a06000bcaffc11a8c3943c Mon Sep 17 00:00:00 2001 From: matthieulapatate Date: Wed, 4 May 2022 19:08:08 +0200 Subject: [PATCH 1/6] Add jwt authentication to Vue generated application --- .../application/VueJwtApplicationService.java | 19 ++ .../vue/security/jwt/domain/VueJwt.java | 141 ++++++++++ .../jwt/domain/VueJwtDomainService.java | 251 ++++++++++++++++++ .../security/jwt/domain/VueJwtService.java | 7 + .../config/VueJwtBeanConfiguration.java | 22 ++ .../primary/rest/VueJwtResource.java | 35 +++ .../client/vue/security/jwt/package-info.java | 2 + .../project/domain/GeneratorAction.java | 1 + .../vue/test/spec/jwt/Router.spec.ts.mustache | 60 +++++ .../AuthenticationService.fixture.ts.mustache | 14 + .../domain/JWTStoreService.spec.ts.mustache | 29 ++ .../spec/jwt/primary/app/App.spec.ts.mustache | 90 +++++++ .../homepage/Homepage.spec.ts.mustache | 20 ++ .../jwt/primary/login/Login.spec.ts.mustache | 87 ++++++ .../AuthenticationRepository.spec.ts.mustache | 78 ++++++ .../jwt/secondary/UserDTO.spec.ts.mustache | 10 + .../jwt/secondary/restLogin.spec.ts.mustache | 10 + .../primary/app/App.component.ts.mustache | 16 +- .../domain/AuthenticationService.ts.mustache | 8 + .../jwt/domain/JWTStoreService.ts.mustache | 31 +++ .../webapp/app/jwt/domain/Login.ts.mustache | 5 + .../webapp/app/jwt/domain/User.ts.mustache | 4 + .../homepage/Homepage.component.ts.mustache | 8 + .../primary/homepage/Homepage.html.mustache | 3 + .../primary/homepage/Homepage.vue.mustache | 3 + .../jwt/primary/homepage/index.ts.mustache | 4 + .../primary/login/Login.component.ts.mustache | 45 ++++ .../app/jwt/primary/login/Login.html.mustache | 22 ++ .../app/jwt/primary/login/Login.vue.mustache | 229 ++++++++++++++++ .../app/jwt/primary/login/index.ts.mustache | 4 + .../AuthenticationRepository.ts.mustache | 46 ++++ .../app/jwt/secondary/UserDTO.ts.mustache | 11 + .../app/jwt/secondary/restLogin.ts.mustache | 13 + .../client/vue/webapp/app/main.ts.mustache | 2 + .../vue/webapp/app/router/router.ts.mustache | 5 +- .../jwt/src/account/restLogin.java.mustache | 50 ++++ .../test/account/restLoginTest.java.mustache | 26 ++ .../java/tech/jhipster/lite/TestUtils.java | 34 +++ .../VueJwtApplicationServiceIT.java | 37 +++ .../jwt/application/VueJwtAssert.java | 3 + .../jwt/domain/VueJwtDomainServiceTest.java | 63 +++++ .../config/VueJwtBeanConfigurationIT.java | 21 ++ .../primary/VueJwtResourceIT.java | 57 ++++ .../generator/command/package-pinia.json | 19 ++ 44 files changed, 1640 insertions(+), 5 deletions(-) create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationService.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtService.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/config/VueJwtBeanConfiguration.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/primary/rest/VueJwtResource.java create mode 100644 src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/package-info.java create mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/Router.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/domain/AuthenticationService.fixture.ts.mustache create mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/domain/JWTStoreService.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/primary/app/App.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/primary/homepage/Homepage.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/primary/login/Login.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/secondary/AuthenticationRepository.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/secondary/UserDTO.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/secondary/restLogin.spec.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/domain/AuthenticationService.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/domain/JWTStoreService.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/domain/Login.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/domain/User.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.component.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.html.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.vue.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/index.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.component.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.html.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.vue.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/index.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/secondary/AuthenticationRepository.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/secondary/UserDTO.ts.mustache create mode 100644 src/main/resources/generator/client/vue/webapp/app/jwt/secondary/restLogin.ts.mustache create mode 100644 src/main/resources/generator/server/springboot/mvc/security/jwt/src/account/restLogin.java.mustache create mode 100644 src/main/resources/generator/server/springboot/mvc/security/jwt/test/account/restLoginTest.java.mustache create mode 100644 src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationServiceIT.java create mode 100644 src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtAssert.java create mode 100644 src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainServiceTest.java create mode 100644 src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/config/VueJwtBeanConfigurationIT.java create mode 100644 src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/primary/VueJwtResourceIT.java create mode 100644 src/test/resources/generator/command/package-pinia.json diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationService.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationService.java new file mode 100644 index 00000000000..a0db2e0178a --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationService.java @@ -0,0 +1,19 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.application; + +import org.springframework.stereotype.Service; +import tech.jhipster.lite.generator.client.vue.security.jwt.domain.VueJwtService; +import tech.jhipster.lite.generator.project.domain.Project; + +@Service +public class VueJwtApplicationService { + + private final VueJwtService vueJwtService; + + public VueJwtApplicationService(VueJwtService jwtService) { + this.vueJwtService = jwtService; + } + + public void addJWT(Project project) { + vueJwtService.addJWT(project); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java new file mode 100644 index 00000000000..cc84ef78e32 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java @@ -0,0 +1,141 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.domain; + +import static tech.jhipster.lite.common.domain.FileUtils.getPath; +import static tech.jhipster.lite.generator.project.domain.Constants.PACKAGE_JSON; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import tech.jhipster.lite.common.domain.FileUtils; +import tech.jhipster.lite.generator.project.domain.Project; + +public class VueJwt { + + private VueJwt() {} + + public static final Collection MAIN_PROVIDES = List.of( + "const authenticationRepository = new AuthenticationRepository(axiosHttp, pinia);", + "app.provide('authenticationService', authenticationRepository);", + "app.provide('logger', consoleLogger);", + "app.provide('router', router);" + ); + public static final Collection MAIN_PROVIDER = List.of( + "const axiosHttp = new AxiosHttp(axios.create({ baseURL: '' }));", + "const consoleLogger = new ConsoleLogger(console);" + ); + public static final Collection MAIN_IMPORTS = List.of( + "import AuthenticationRepository from './common/secondary/AuthenticationRepository';", + "import { AxiosHttp } from './http/AxiosHttp';", + "import axios from 'axios';", + "import ConsoleLogger from './common/secondary/ConsoleLogger';", + "import Homepage from './common/primary/homepage/Homepage.vue';" + ); + + public static boolean isPiniaNotImplemented(Project project) { + return !FileUtils.containsLines(getPath(project.getFolder(), PACKAGE_JSON), List.of("\"pinia\":")); + } + + public static List primaryLoginFiles() { + return List.of("index.ts", "Login.component.ts", "Login.html", "Login.vue"); + } + + public static final Collection LOGIN_ROUTES = List.of( + "\t{", + "\tpath: '/login',", + "\tname: 'Login',", + "\tcomponent: LoginVue,", + "\t},", + "\t{", + "\tpath: '/',", + "\tname: 'Homepage',", + "\tcomponent: AppVue,", + "\t}," + ); + + public static final Collection ROUTER_IMPORTS = List.of("import { LoginVue } from '@/common/primary/login';"); + + public static Collection primaryHomepageFiles() { + return List.of("index.ts", "Homepage.component.ts", "Homepage.html", "Homepage.vue"); + } + + public static Collection domainFiles() { + return List.of("AuthenticationService.ts", "JWTStoreService.ts", "Login.ts", "User.ts"); + } + + public static Collection secondaryFiles() { + return List.of("AuthenticationRepository.ts", "UserDTO.ts"); + } + + public static Collection testDomainFiles() { + return List.of("AuthenticationService.fixture.ts", "JWTStoreService.spec.ts"); + } + + public static Collection testSecondaryFiles() { + return List.of("AuthenticationRepository.spec.ts", "restLogin.spec.ts", "UserDTO.spec.ts"); + } + + public static Map appComponent() { + return Map.of( + "import { AuthenticationService } from '@/common/domain/AuthenticationService';\n".concat( + "import { Logger } from '@/common/domain/Logger';\n" + ) + .concat("import { User } from '@/common/domain/User';\n") + .concat("import { Router } from 'vue-router';\n") + .concat("import { jwtStore } from '@/common/domain/JWTStoreService';\n") + .concat("import { inject, ref } from \"vue\";"), + "import", + "const authenticationService = inject('authenticationService') as AuthenticationService;\n".concat( + "\tconst logger = inject('logger') as Logger;\n" + ) + .concat("\tconst router = inject('router') as Router;\n") + .concat("\n") + .concat("\tlet store = jwtStore();\n") + .concat("\tlet isAuthenticated:boolean = store.isAuth;\n") + .concat("\tlet user = ref({\n") + .concat("\t\tusername: '',\n") + .concat("\t\tauthorities: [''],\n") + .concat("\t});\n") + .concat("\n") + .concat("\tconst onConnect = async (): Promise => {\n") + .concat("\t\tawait authenticationService\n") + .concat("\t\t.authenticate()\n") + .concat("\t\t.then(response => {\n") + .concat("\t\t\tuser.value = response;\n") + .concat("\t\t})\n") + .concat("\t\t.catch(error => {\n") + .concat("\t\t\tlogger.error('The token provided is not know by our service', error);\n") + .concat("\t\t});\n") + .concat("\t}\n") + .concat("\n") + .concat("\tconst onLogout = async (): Promise => {\n") + .concat("\t\tawait authenticationService\n") + .concat("\t\t.logout();\n") + .concat("\t\trouter.push(\"/login\");\n") + .concat("\t};\n"), + "setup", + "user,\n".concat("\tisAuthenticated,\n").concat("\tonConnect,\n").concat("\tonLogout,\n"), + "return" + ); + } + + public static List appHTML() { + return List.of( + "
", + "\t
", + "\t\t

You are connected as

", + "\t\t
", + "\t\t\t", + "\t\t
", + "\t\t
", + "\t\t\t

{{user.username}}

", + "\t\t\t", + "\t\t
", + "\t
", + "\t
", + "\t\t

You are not connected

", + "\t\tLogin", + "\t
", + "
" + ); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java new file mode 100644 index 00000000000..f96fce4de6c --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java @@ -0,0 +1,251 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.domain; + +import static tech.jhipster.lite.common.domain.FileUtils.getPath; +import static tech.jhipster.lite.common.domain.WordUtils.LF; +import static tech.jhipster.lite.generator.project.domain.Constants.*; + +import java.io.IOException; +import java.util.List; +import tech.jhipster.lite.common.domain.FileUtils; +import tech.jhipster.lite.error.domain.Assert; +import tech.jhipster.lite.error.domain.GeneratorException; +import tech.jhipster.lite.generator.project.domain.Project; +import tech.jhipster.lite.generator.project.domain.ProjectFile; +import tech.jhipster.lite.generator.project.domain.ProjectRepository; + +public class VueJwtDomainService implements VueJwtService { + + public static final String SOURCE = "client/vue"; + + public static final String SOURCE_JWT = "webapp/app/jwt/"; + public static final String SOURCE_TEST = "test/spec/jwt/"; + + public static final String DESTINATION_TEST = TEST_JAVASCRIPT + "/common/"; + + public static final String COMMON = "/app/common/"; + public static final String DESTINATION_PRIMARY = MAIN_WEBAPP + COMMON + PRIMARY; + public static final String SOURCE_PRIMARY = SOURCE_JWT + PRIMARY; + + public static final String DESTINATION_DOMAIN = MAIN_WEBAPP + COMMON + DOMAIN; + public static final String SOURCE_DOMAIN = SOURCE_JWT + DOMAIN; + + public static final String DESTINATION_SECONDARY = MAIN_WEBAPP + COMMON + SECONDARY; + public static final String SOURCE_SECONDARY = SOURCE_JWT + SECONDARY; + + public static final String NEEDLE_MAIN_IMPORT = "// jhipster-needle-main-ts-import"; + + public static final String NEEDLE_MAIN_PROVIDER = "// jhipster-needle-main-ts-provider"; + private static final String NEEDLE_MAIN_INSTANCIATION = "// jhipster-needle-main-ts-instanciation"; + + public static final String NEEDLE_ROUTER_IMPORT = "// jhipster-needle-router-import"; + + public static final String NEEDLE_ROUTER_ROUTES = "// jhipster-needle-router-routes"; + + public static final String LOGIN = "/login"; + + public static final String HOMEPAGE = "/homepage"; + + private final ProjectRepository projectRepository; + + public VueJwtDomainService(ProjectRepository projectRepository) { + this.projectRepository = projectRepository; + } + + @Override + public void addJWT(Project project) { + checkIfProjectNotNull(project); + if (VueJwt.isPiniaNotImplemented(project)) { + throw new GeneratorException("Pinia has not been added"); + } + addAppContext(project); + addLoginContext(project); + addHomepageContext(project); + addDomainRelated(project); + addRoutes(project); + addMain(project); + addTests(project); + addSecondary(project); + } + + private void checkIfProjectNotNull(Project project) { + Assert.notNull("project", project); + } + + public void addAppContext(Project project) { + String destinationAppHtml = DESTINATION_PRIMARY + "/app/App.html"; + String destinationAppComponent = DESTINATION_PRIMARY + "/app"; + try { + FileUtils.appendLines(getPath(project.getFolder(), destinationAppHtml), VueJwt.appHTML()); + } catch (IOException e) { + throw new GeneratorException("Error when writing to app.html. Make sur this file exist", e); + } + VueJwt + .appComponent() + .forEach((line, needle) -> + addNewNeedleLineToFile(project, line, destinationAppComponent, "App.component.ts", "// needle-jhipster-" + needle) + ); + } + + public void addLoginContext(Project project) { + String destinationPrimaryLoginContext = DESTINATION_PRIMARY + LOGIN; + String sourcePrimaryLoginContext = SOURCE_PRIMARY + LOGIN; + projectRepository.template( + ProjectFile.forProject(project).withSource(getPath(SOURCE, SOURCE_DOMAIN), "Login.ts").withDestinationFolder(DESTINATION_DOMAIN) + ); + List primaryFiles = VueJwt + .primaryLoginFiles() + .stream() + .map(entry -> + ProjectFile + .forProject(project) + .withSource(getPath(SOURCE, sourcePrimaryLoginContext), entry) + .withDestinationFolder(destinationPrimaryLoginContext) + ) + .toList(); + projectRepository.template(primaryFiles); + projectRepository.template( + ProjectFile + .forProject(project) + .withSource(getPath(SOURCE, SOURCE_SECONDARY), "restLogin.ts") + .withDestinationFolder(DESTINATION_SECONDARY) + ); + } + + public void addHomepageContext(Project project) { + String destinationPrimaryHomepageContext = DESTINATION_PRIMARY + HOMEPAGE; + String sourcePrimaryHomepageContext = SOURCE_PRIMARY + HOMEPAGE; + + List primaryHomepageFiles = VueJwt + .primaryHomepageFiles() + .stream() + .map(entry -> + ProjectFile + .forProject(project) + .withSource(getPath(SOURCE, sourcePrimaryHomepageContext), entry) + .withDestinationFolder(destinationPrimaryHomepageContext) + ) + .toList(); + + projectRepository.template(primaryHomepageFiles); + + projectRepository.add( + ProjectFile + .forProject(project) + .withSource(getPath(SOURCE, "webapp/content/images"), "JHipster-Lite-neon-green.png") + .withDestinationFolder("src/main/webapp/content/images") + ); + projectRepository.add( + ProjectFile + .forProject(project) + .withSource(getPath(SOURCE, "webapp/content/images"), "VueLogo.png") + .withDestinationFolder("src/main/webapp/content/images") + ); + } + + public void addRoutes(Project project) { + String routerPath = "src/main/webapp/app/router"; + + projectRepository.replaceText( + project, + getPath(routerPath), + ROUTER_TYPESCRIPT, + "redirect: \\{ name: 'App' \\}", + "redirect: { name: 'Homepage' }" + ); + VueJwt.ROUTER_IMPORTS.forEach(providerLine -> + addNewNeedleLineToFile(project, providerLine, routerPath, ROUTER_TYPESCRIPT, NEEDLE_ROUTER_IMPORT) + ); + VueJwt.LOGIN_ROUTES.forEach(providerLine -> + addNewNeedleLineToFile(project, providerLine, routerPath, ROUTER_TYPESCRIPT, NEEDLE_ROUTER_ROUTES) + ); + } + + public void addMain(Project project) { + String appPath = "src/main/webapp/app"; + projectRepository.replaceText(project, getPath(appPath), MAIN_TYPESCRIPT, "createApp\\(App\\)", "createApp(Homepage)"); + VueJwt.MAIN_IMPORTS.forEach(providerLine -> addNewNeedleLineToFile(project, providerLine, appPath, MAIN_TYPESCRIPT, NEEDLE_MAIN_IMPORT) + ); + VueJwt.MAIN_PROVIDER.forEach(providerLine -> + addNewNeedleLineToFile(project, providerLine, appPath, MAIN_TYPESCRIPT, NEEDLE_MAIN_INSTANCIATION) + ); + VueJwt.MAIN_PROVIDES.forEach(providerLine -> + addNewNeedleLineToFile(project, providerLine, appPath, MAIN_TYPESCRIPT, NEEDLE_MAIN_PROVIDER) + ); + } + + public void addTests(Project project) { + String testDomainPath = DESTINATION_TEST + "domain"; + String testPrimaryPath = DESTINATION_TEST + "primary"; + String testSecondaryPath = DESTINATION_TEST + "secondary"; + + List testDomainFiles = VueJwt + .testDomainFiles() + .stream() + .map(entry -> + ProjectFile.forProject(project).withSource(getPath(SOURCE, SOURCE_TEST + DOMAIN), entry).withDestinationFolder(testDomainPath) + ) + .toList(); + projectRepository.template(testDomainFiles); + projectRepository.template( + ProjectFile + .forProject(project) + .withSource(getPath(SOURCE, SOURCE_TEST + PRIMARY + "/app"), "App.spec.ts") + .withDestinationFolder(testPrimaryPath + "/app") + ); + projectRepository.template( + ProjectFile + .forProject(project) + .withSource(getPath(SOURCE, SOURCE_TEST + PRIMARY + LOGIN), "Login.spec.ts") + .withDestinationFolder(testPrimaryPath + LOGIN) + ); + projectRepository.template( + ProjectFile + .forProject(project) + .withSource(getPath(SOURCE, SOURCE_TEST + PRIMARY + HOMEPAGE), "Homepage.spec.ts") + .withDestinationFolder(testPrimaryPath + HOMEPAGE) + ); + + List testSecondaryFiles = VueJwt + .testSecondaryFiles() + .stream() + .map(entry -> + ProjectFile.forProject(project).withSource(getPath(SOURCE, SOURCE_TEST + SECONDARY), entry).withDestinationFolder(testSecondaryPath) + ) + .toList(); + + projectRepository.template(testSecondaryFiles); + projectRepository.template( + ProjectFile + .forProject(project) + .withSource(getPath(SOURCE, SOURCE_TEST), "Router.spec.ts") + .withDestinationFolder(TEST_JAVASCRIPT + "/router") + ); + } + + private void addNewNeedleLineToFile(Project project, String importLine, String folder, String file, String needle) { + String importWithNeedle = importLine + LF + needle; + projectRepository.replaceText(project, getPath(folder), file, needle, importWithNeedle); + } + + private void addSecondary(Project project) { + List secondaryFiles = VueJwt + .secondaryFiles() + .stream() + .map(entry -> + ProjectFile.forProject(project).withSource(getPath(SOURCE, SOURCE_SECONDARY), entry).withDestinationFolder(DESTINATION_SECONDARY) + ) + .toList(); + projectRepository.template(secondaryFiles); + } + + public void addDomainRelated(Project project) { + List domainFiles = VueJwt + .domainFiles() + .stream() + .map(entry -> + ProjectFile.forProject(project).withSource(getPath(SOURCE, SOURCE_DOMAIN), entry).withDestinationFolder(DESTINATION_DOMAIN) + ) + .toList(); + projectRepository.template(domainFiles); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtService.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtService.java new file mode 100644 index 00000000000..dc86ae5c392 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtService.java @@ -0,0 +1,7 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.domain; + +import tech.jhipster.lite.generator.project.domain.Project; + +public interface VueJwtService { + void addJWT(Project project); +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/config/VueJwtBeanConfiguration.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/config/VueJwtBeanConfiguration.java new file mode 100644 index 00000000000..38d5dd72ebb --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/config/VueJwtBeanConfiguration.java @@ -0,0 +1,22 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.infrastructure.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import tech.jhipster.lite.generator.client.vue.security.jwt.domain.VueJwtDomainService; +import tech.jhipster.lite.generator.client.vue.security.jwt.domain.VueJwtService; +import tech.jhipster.lite.generator.project.domain.ProjectRepository; + +@Configuration +public class VueJwtBeanConfiguration { + + private final ProjectRepository projectRepository; + + public VueJwtBeanConfiguration(ProjectRepository projectRepository) { + this.projectRepository = projectRepository; + } + + @Bean + public VueJwtService vueJwtService() { + return new VueJwtDomainService(projectRepository); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/primary/rest/VueJwtResource.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/primary/rest/VueJwtResource.java new file mode 100644 index 00000000000..1a9e23202b5 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/primary/rest/VueJwtResource.java @@ -0,0 +1,35 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.infrastructure.primary.rest; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.jhipster.lite.generator.client.vue.security.jwt.application.VueJwtApplicationService; +import tech.jhipster.lite.generator.project.domain.GeneratorAction; +import tech.jhipster.lite.generator.project.domain.Project; +import tech.jhipster.lite.generator.project.infrastructure.primary.dto.ProjectDTO; +import tech.jhipster.lite.technical.infrastructure.primary.annotation.GeneratorStep; + +@RestController +@RequestMapping("/api/clients/vue") +@Tag(name = "Vue") +class VueJwtResource { + + private final VueJwtApplicationService vueJwtApplicationService; + + public VueJwtResource(VueJwtApplicationService vueJwtApplicationService) { + this.vueJwtApplicationService = vueJwtApplicationService; + } + + @Operation(summary = "Add JWT to vue ap", description = "Add Jwt") + @ApiResponse(responseCode = "500", description = "An error occurred while adding JWT on Vue") + @PostMapping("/jwt") + @GeneratorStep(id = GeneratorAction.VUE_JWT) + public void addVueJwt(@RequestBody ProjectDTO projectDTO) { + Project project = ProjectDTO.toProject(projectDTO); + vueJwtApplicationService.addJWT(project); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/package-info.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/package-info.java new file mode 100644 index 00000000000..99059af3542 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/package-info.java @@ -0,0 +1,2 @@ +@tech.jhipster.lite.BusinessContext +package tech.jhipster.lite.generator.client.vue.security.jwt; diff --git a/src/main/java/tech/jhipster/lite/generator/project/domain/GeneratorAction.java b/src/main/java/tech/jhipster/lite/generator/project/domain/GeneratorAction.java index 2831708eaaa..0fdc73c6125 100644 --- a/src/main/java/tech/jhipster/lite/generator/project/domain/GeneratorAction.java +++ b/src/main/java/tech/jhipster/lite/generator/project/domain/GeneratorAction.java @@ -125,6 +125,7 @@ private GeneratorAction() {} public static final String VUE = "vue"; public static final String VUE_PINIA = "vue-pinia"; + public static final String VUE_JWT = "vue-jwt"; public static final String JIB = "jib"; public static final String DOCKERFILE = "dockerfile"; diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/Router.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/Router.spec.ts.mustache new file mode 100644 index 00000000000..61092b608ad --- /dev/null +++ b/src/main/resources/generator/client/vue/test/spec/jwt/Router.spec.ts.mustache @@ -0,0 +1,60 @@ +import { AppVue } from '@/common/primary/app'; +import { LoginVue } from '@/common/primary/login'; +import router from '@/router/router'; +import { createTestingPinia } from '@pinia/testing'; +import { shallowMount, VueWrapper } from '@vue/test-utils'; +import { AuthenticationService } from '@/common/domain/AuthenticationService'; +import { stubAuthenticationService } from '../common/domain/AuthenticationService.fixture'; +import { stubLogger } from '../common/domain/Logger.fixture'; +import { Logger } from '@/common/domain/Logger'; + +let wrapper: VueWrapper; + +interface WrapperOptions { + authenticationService: AuthenticationService; + logger: Logger; +} + +const wrap = (wrapperOptions?: Partial) => { + const { authenticationService, logger }: WrapperOptions = { + authenticationService: stubAuthenticationService(), + logger: stubLogger(), + ...wrapperOptions, + }; + + wrapper = shallowMount(AppVue, { + global: { + stubs: ['router-link'], + provide: { + authenticationService, + logger, + router, + }, + plugins: [createTestingPinia()], + }, + }); +}; + +describe('Router', () => { + + afterAll(async () => new Promise(resolve => window.setTimeout(resolve, 0))); + it('Should redirect to App by default', async () => { + const authenticationService = stubAuthenticationService(); + const logger = stubLogger(); + await wrap({ authenticationService, logger }); + router.push('/'); + await router.isReady(); + + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent(AppVue)).toBeTruthy(); + }); + + it('Should go to LoginVue', async () => { + router.push('/Login'); + + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent(LoginVue)).toBeTruthy(); + }); +}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/domain/AuthenticationService.fixture.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/domain/AuthenticationService.fixture.ts.mustache new file mode 100644 index 00000000000..18f6cb6b1ee --- /dev/null +++ b/src/main/resources/generator/client/vue/test/spec/jwt/domain/AuthenticationService.fixture.ts.mustache @@ -0,0 +1,14 @@ +import sinon, { SinonSpy,SinonStub } from 'sinon'; +import { AuthenticationService } from '@/common/domain/AuthenticationService'; + +export interface AuthenticationServiceFixture extends AuthenticationService { + login: SinonStub; + authenticate: SinonStub; + logout: SinonSpy; +} + +export const stubAuthenticationService = (): AuthenticationServiceFixture => ({ + login: sinon.stub(), + authenticate: sinon.stub(), + logout: sinon.spy(), +}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/domain/JWTStoreService.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/domain/JWTStoreService.spec.ts.mustache new file mode 100644 index 00000000000..ff35bf6abb0 --- /dev/null +++ b/src/main/resources/generator/client/vue/test/spec/jwt/domain/JWTStoreService.spec.ts.mustache @@ -0,0 +1,29 @@ +import { setActivePinia, createPinia, StoreDefinition } from 'pinia'; +import { jwtStore } from '@/common/domain/JWTStoreService'; + +describe('Test JWT store', () => { + const TOKEN = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; + let store: any; + + beforeEach(() => { + setActivePinia(createPinia()); + store = jwtStore(); + }); + + it('Should set token in store', () => { + store.setToken(TOKEN); + expect(store.token).toEqual(TOKEN); + }); + + it('Should remove token from store', () => { + store.setToken(TOKEN); + store.removeToken(); + expect(store.isAuth).toBeFalsy(); + }); + + it('Should tell user is loged in', () => { + store.setToken(TOKEN); + expect(store.isAuth).toBe(true); + }); +}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/primary/app/App.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/primary/app/App.spec.ts.mustache new file mode 100644 index 00000000000..fb493abcf6d --- /dev/null +++ b/src/main/resources/generator/client/vue/test/spec/jwt/primary/app/App.spec.ts.mustache @@ -0,0 +1,90 @@ +import { shallowMount, VueWrapper } from '@vue/test-utils'; +import { AppVue } from '@/common/primary/app'; +import { createTestingPinia } from '@pinia/testing'; +import { AuthenticationService } from '@/common/domain/AuthenticationService'; +import { stubAuthenticationService } from '../../domain/AuthenticationService.fixture'; +import { stubLogger } from '../../domain/Logger.fixture'; +import { Logger } from '@/common/domain/Logger'; +import sinon from 'sinon'; + +let wrapper: VueWrapper; +const $route = { path: {} }; +const router = { push: sinon.stub() }; + +interface WrapperOptions { + authenticationService: AuthenticationService; + logger: Logger; +} + +const wrap = (wrapperOptions?: Partial) => { + const { authenticationService, logger }: WrapperOptions = { + authenticationService: stubAuthenticationService(), + logger: stubLogger(), + ...wrapperOptions, + }; + + wrapper = shallowMount(AppVue, { + global: { + stubs: ['router-link'], + provide: { + authenticationService, + logger, + router, + }, + plugins: [createTestingPinia({ + initialState: { + JWTStore: {token: '123456789'}, + }, + })], + }, + }); +}; + +describe('App', () => { + it('should exist', () => { + wrap(); + + expect(wrapper.exists()).toBe(true); + }); + + it('should authenticate', async () => { + const authenticationService = stubAuthenticationService(); + const logger = stubLogger(); + authenticationService.authenticate.resolves({ username: 'username', authorities: ['admin'] }); + await wrap({ authenticationService, logger }); + + const clickButton = wrapper.find('#identify'); + await clickButton.trigger('click'); + + // @ts-ignore + expect(wrapper.vm.user).toStrictEqual({ username: 'username', authorities: ['admin'] }); + }); + + it('Should log an error when authentication fails', async () => { + const authenticationService = stubAuthenticationService(); + const logger = stubLogger(); + authenticationService.authenticate.rejects({}); + await wrap({ authenticationService, logger }); + + const clickButton = wrapper.find('#identify'); + await clickButton.trigger('click'); + + const [message] = logger.error.getCall(0).args; + expect(message).toBe('The token provided is not know by our service'); + }); + + it('Should log out', async () => { + const authenticationService = stubAuthenticationService(); + const logger = stubLogger(); + authenticationService.authenticate.resolves({ username: 'username', authorities: ['admin'] }); + await wrap({ authenticationService, logger }); + + + const clickButton = wrapper.find('#identify'); + await clickButton.trigger('click'); + const logoutButton = wrapper.find('#logout'); + await logoutButton.trigger('click'); + + sinon.assert.calledOnce(authenticationService.logout); + }); +}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/primary/homepage/Homepage.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/primary/homepage/Homepage.spec.ts.mustache new file mode 100644 index 00000000000..68268a513cd --- /dev/null +++ b/src/main/resources/generator/client/vue/test/spec/jwt/primary/homepage/Homepage.spec.ts.mustache @@ -0,0 +1,20 @@ +import { shallowMount, VueWrapper } from '@vue/test-utils'; +import { HomepageVue } from '@/common/primary/homepage'; + +let wrapper: VueWrapper; + +const wrap = () => { + wrapper = shallowMount(HomepageVue, { + global: { + stubs: ['router-link', 'router-view'] + } + }); +}; + +describe('App', () => { + it('should exist', () => { + wrap(); + + expect(wrapper.exists()).toBeTruthy(); + }); +}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/primary/login/Login.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/primary/login/Login.spec.ts.mustache new file mode 100644 index 00000000000..6fe139bb069 --- /dev/null +++ b/src/main/resources/generator/client/vue/test/spec/jwt/primary/login/Login.spec.ts.mustache @@ -0,0 +1,87 @@ +import { shallowMount, VueWrapper } from '@vue/test-utils'; +import { LoginVue } from '@/common/primary/login'; +import { createTestingPinia } from '@pinia/testing'; +import { AuthenticationService } from '@/common/domain/AuthenticationService'; +import { stubAuthenticationService } from '../../domain/AuthenticationService.fixture'; +import { stubLogger } from '../../domain/Logger.fixture'; +import { Logger } from '@/common/domain/Logger'; +import { Login } from '@/common/domain/Login'; +import sinon from 'sinon'; + +let wrapper: VueWrapper; +const $route = { path: {} }; +const router = { push: sinon.stub() }; + +interface WrapperOptions { + authenticationService: AuthenticationService; + logger: Logger; +} + +const wrap = (wrapperOptions?: Partial) => { + const { authenticationService, logger }: WrapperOptions = { + authenticationService: stubAuthenticationService(), + logger: stubLogger(), + ...wrapperOptions, + }; + + wrapper = shallowMount(LoginVue, { + global: { + provide: { + authenticationService, + logger, + router, + }, + plugins: [createTestingPinia()], + }, + }); +}; + +const fillFullForm = async (login: Login): Promise => { + const usernameInput = wrapper.find('#username'); + await usernameInput.setValue(login.username); + const passwordInput = wrapper.find('#password'); + await passwordInput.setValue(login.password); +}; + +describe('Login', () => { + it('should exist', () => { + wrap(); + + expect(wrapper.exists()).toBe(true); + }); + + it('should login', async () => { + const authenticationService = stubAuthenticationService(); + authenticationService.login.resolves({}); + await wrap({ authenticationService }); + + const login: Login = { username: 'admin', password: 'admin', rememberMe: true }; + await fillFullForm(login); + + const submitButton = wrapper.find('#submit'); + await submitButton.trigger('submit'); + + const args = authenticationService.login.getCall(0).args[0]; + + expect(args).toEqual({ username: 'admin', password: 'admin', rememberMe: false }); + + // @ts-ignore + expect(wrapper.vm.getError()).toBeFalsy(); + }); + + it('Should log an error when login fails', async () => { + const authenticationService = stubAuthenticationService(); + const logger = stubLogger(); + authenticationService.login.rejects({}); + await wrap({ authenticationService, logger }); + + const login: Login = { username: 'admin', password: 'wrong_password', rememberMe: true }; + await fillFullForm(login); + + const submitButton = wrapper.find('#submit'); + await submitButton.trigger('submit'); + + const [message] = logger.error.getCall(0).args; + expect(message).toBe('Wrong credentials have been provided'); + }); +}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/secondary/AuthenticationRepository.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/AuthenticationRepository.spec.ts.mustache new file mode 100644 index 00000000000..fc39d94fdce --- /dev/null +++ b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/AuthenticationRepository.spec.ts.mustache @@ -0,0 +1,78 @@ +import { Login } from '@/common/domain/Login'; +import { restLogin } from '@/common/secondary/restLogin'; +import { User } from '@/common/domain/User'; +import AuthenticationRepository from '@/common/secondary/AuthenticationRepository'; +import { AxiosHttpStub, stubAxiosHttp } from '../../http/AxiosHttpStub'; +import { createPinia, Pinia, setActivePinia, Store } from 'pinia'; +import { jwtStore } from '../../../../../main/webapp/app/common/domain/JWTStoreService'; + +let axiosHttpStub: AxiosHttpStub; +let piniaInstance: Pinia; +let store: Store; + +describe('AuthenticationRepository', () => { + const AUTH_TOKEN = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; + beforeEach(() => { + axiosHttpStub = stubAxiosHttp(); + piniaInstance = createPinia(); + setActivePinia(piniaInstance); + store = jwtStore(piniaInstance); + }); + it('Should login when status 200 returned', async () => { + axiosHttpStub.post.resolves({ + status: 200, + headers: { + authorization: 'Bearer ' + AUTH_TOKEN, + }, + }); + const authenticationRepository = new AuthenticationRepository(axiosHttpStub, piniaInstance); + const login: Login = { username: 'admin', password: 'admin', rememberMe: true }; + + await authenticationRepository.login(login); + + const [uri, payload] = axiosHttpStub.post.getCall(0).args; + expect(uri).toBe('/api/authenticate'); + expect(payload).toEqual({ username: 'admin', password: 'admin', rememberMe: true }); + // @ts-ignore + expect(store.token).toEqual(AUTH_TOKEN); + }); + + it('Should set empty token', async () => { + axiosHttpStub.post.resolves({ status: 401, headers: { authorization: '' } }); + const authenticationRepository = new AuthenticationRepository(axiosHttpStub, piniaInstance); + const login: Login = { username: 'admin', password: 'wrong_password', rememberMe: true }; + + await authenticationRepository.login(login); + + const [uri, payload] = axiosHttpStub.post.getCall(0).args; + expect(uri).toBe('/api/authenticate'); + expect(payload).toEqual({ username: 'admin', password: 'wrong_password', rememberMe: true }); + // @ts-ignore + expect(store.token).toEqual(''); + }); + + it('Should authenticate', async () => { + // @ts-ignore + store.setToken('fake_token'); + axiosHttpStub.get.resolves({ data: { login: 'username', authorities: ['admin'] } }); + const authenticationRepository = new AuthenticationRepository(axiosHttpStub, piniaInstance); + + const response = await authenticationRepository.authenticate(); + + const [uri, payload] = axiosHttpStub.get.getCall(0).args; + expect(uri).toBe('/api/account'); + expect(payload.headers.Authorization).toEqual('Bearer fake_token'); + expect(response).toStrictEqual({ username: 'username', authorities: ['admin'] }); + }); + + it('Should log out', async () => { + // @ts-ignore + store.setToken('fake_token'); + const authenticationRepository = new AuthenticationRepository(axiosHttpStub, piniaInstance); + + authenticationRepository.logout(); + // @ts-ignore + expect(store.token).toEqual(''); + }); +}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/secondary/UserDTO.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/UserDTO.spec.ts.mustache new file mode 100644 index 00000000000..926c0d892f0 --- /dev/null +++ b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/UserDTO.spec.ts.mustache @@ -0,0 +1,10 @@ +import { UserDTO, toUser } from '@/common/secondary/UserDTO'; +import { User } from '@/common/domain/User'; + +describe('restLogin', () => { + it('should convert to User', () => { + const userDTO: UserDTO = { login: 'username', authorities: ['admin'] }; + + expect(toUser(userDTO)).toStrictEqual({ username: 'username', authorities: ['admin'] }); + }); +}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/secondary/restLogin.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/restLogin.spec.ts.mustache new file mode 100644 index 00000000000..7b7a2757379 --- /dev/null +++ b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/restLogin.spec.ts.mustache @@ -0,0 +1,10 @@ +import { restLogin, torestLogin } from '@/common/secondary/restLogin'; +import { Login } from '@/common/domain/Login'; + +describe('restLogin', () => { + it('should convert to restLogin', () => { + const login: Login = { username: 'username', password: 'password', rememberMe: true }; + + expect(torestLogin(login)).toEqual({ username: 'username', password: 'password', rememberMe: true }); + }); +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.component.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.component.ts.mustache index 2b41fa35108..776d19f80a5 100644 --- a/src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.component.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.component.ts.mustache @@ -1,8 +1,16 @@ -export default { +import { defineComponent } from "vue"; +// needle-jhipster-import + +export default defineComponent({ name: 'App', - data: () => { + components: {}, + setup() { + const appName='Jhipster-lite'; + // needle-jhipster-setup + return { - appName: '{{baseName}}', + appName, + // needle-jhipster-return }; }, -}; +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/domain/AuthenticationService.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/domain/AuthenticationService.ts.mustache new file mode 100644 index 00000000000..265f06925cd --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/domain/AuthenticationService.ts.mustache @@ -0,0 +1,8 @@ +import { Login } from '@/common/domain/Login'; +import { User } from '@/common/domain/User'; + +export interface AuthenticationService { + authenticate(): Promise; + login(login: Login): Promise; + logout(): void; +} diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/domain/JWTStoreService.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/domain/JWTStoreService.ts.mustache new file mode 100644 index 00000000000..d55d25f1157 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/domain/JWTStoreService.ts.mustache @@ -0,0 +1,31 @@ +import { defineStore } from 'pinia'; + +export const jwtStore = defineStore({ + id: 'JWTStore', + state: () => ({ + token: '', + }), + + getters: { + isAuth(state) { + return state.token != ''; + }, + }, + actions: { + setToken(token: string) { + this.token = token; + }, + removeToken(){ + this.token=''; + } + }, + persist: { + enabled: true, + strategies: [ + { + key: 'user', + storage: localStorage, + }, + ], + }, +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/domain/Login.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/domain/Login.ts.mustache new file mode 100644 index 00000000000..bcd6e7cb0ce --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/domain/Login.ts.mustache @@ -0,0 +1,5 @@ +export interface Login { + username: string; + password: string; + rememberMe: boolean; +} diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/domain/User.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/domain/User.ts.mustache new file mode 100644 index 00000000000..3c753fd7e17 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/domain/User.ts.mustache @@ -0,0 +1,4 @@ +export interface User { + username: string; + authorities: string[]; +} diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.component.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.component.ts.mustache new file mode 100644 index 00000000000..8303dcb004f --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.component.ts.mustache @@ -0,0 +1,8 @@ +import { defineComponent} from 'vue'; + +export default defineComponent({ + name: 'Homepage', + components: {}, + setup() { + }, +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.html.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.html.mustache new file mode 100644 index 00000000000..94d2629d6ee --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.html.mustache @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.vue.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.vue.mustache new file mode 100644 index 00000000000..999c90e77a0 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.vue.mustache @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/index.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/index.ts.mustache new file mode 100644 index 00000000000..fce67b47eba --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/index.ts.mustache @@ -0,0 +1,4 @@ +import HomepageComponent from './Homepage.component'; +import HomepageVue from './Homepage.vue'; + +export { HomepageComponent, HomepageVue }; diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.component.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.component.ts.mustache new file mode 100644 index 00000000000..dcd11df96ab --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.component.ts.mustache @@ -0,0 +1,45 @@ +import { defineComponent, inject, ref } from 'vue'; +import { AuthenticationService } from '@/common/domain/AuthenticationService'; +import { Logger } from '@/common/domain/Logger'; +import { Login } from '@/common/domain/Login'; +import { Router } from 'vue-router'; + +export default defineComponent({ + name: 'Login', + components: {}, + setup() { + const authenticationService = inject('authenticationService') as AuthenticationService; + const logger = inject('logger') as Logger; + const router = inject('router') as Router; + + const form = ref({ + username: '', + password: '', + rememberMe: false, + }); + + let loginError = false; + + const onSubmit = async (): Promise => { + await authenticationService + .login(form.value) + .then(() => { + router.push('/'); + }) + .catch(error => { + loginError = true; + logger.error('Wrong credentials have been provided', error); + }); + }; + + const getError = (): boolean => { + return loginError; + }; + + return { + onSubmit, + form, + getError, + }; + }, +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.html.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.html.mustache new file mode 100644 index 00000000000..8f07768269d --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.html.mustache @@ -0,0 +1,22 @@ +
+
+

Login Form

+
+ +
+ + +
+ +
+ +
+
diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.vue.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.vue.mustache new file mode 100644 index 00000000000..034b84f1ca8 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/Login.vue.mustache @@ -0,0 +1,229 @@ + + + + + diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/index.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/index.ts.mustache new file mode 100644 index 00000000000..87d78427d59 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/login/index.ts.mustache @@ -0,0 +1,4 @@ +import LoginComponent from './Login.component'; +import LoginVue from './Login.vue'; + +export { LoginComponent, LoginVue }; diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/AuthenticationRepository.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/AuthenticationRepository.ts.mustache new file mode 100644 index 00000000000..fb92a2f06bb --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/AuthenticationRepository.ts.mustache @@ -0,0 +1,46 @@ +import { Login } from '@/common/domain/Login'; +import { restLogin, torestLogin } from '@/common/secondary/restLogin'; + +import { AxiosHttp } from '@/http/AxiosHttp'; +import { AuthenticationService } from '@/common/domain/AuthenticationService'; +import { User } from '../domain/User'; +import { toUser, UserDTO } from './UserDTO'; +import { Pinia } from 'pinia'; +import { jwtStore } from '@/common/domain/JWTStoreService'; + +export default class AuthenticationRepository implements AuthenticationService { + constructor(private axiosHttp: AxiosHttp, private piniaInstance: Pinia) {} + + async authenticate(): Promise { + return await this.axiosHttp + .get('/api/account', { headers: { Authorization: 'Bearer ' + this.getJwtToken() } }) + .then(response => toUser(response.data)); + } + + async login(login: Login): Promise { + const restLogin: restLogin = torestLogin(login); + await this.axiosHttp + .post('/api/authenticate', restLogin) + .then(response => this.saveJwtTokenIntoStore(this.parseAuthorisationHeaders(response))); + } + + logout(): void { + this.removeToken(); + } + + private saveJwtTokenIntoStore = (token: string): void => jwtStore(this.piniaInstance).setToken(token); + + private getJwtToken = (): string => jwtStore(this.piniaInstance).token; + + private removeToken = (): void => jwtStore(this.piniaInstance).removeToken(); + + parseAuthorisationHeaders(response: any): string { + const bearerToken = response.headers.authorization; + if (bearerToken && bearerToken.slice(0, 7) === 'Bearer ') { + const jwt = bearerToken.slice(7, bearerToken.length); + return jwt; + } else { + return ''; + } + } +} diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/UserDTO.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/UserDTO.ts.mustache new file mode 100644 index 00000000000..e596daf658b --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/UserDTO.ts.mustache @@ -0,0 +1,11 @@ +import { User } from '@/common/domain/User'; + +export interface UserDTO { + login: string; + authorities: string[]; +} + +export const toUser = (userDTO: UserDTO): User => ({ + username: userDTO.login, + authorities: userDTO.authorities, +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/restLogin.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/restLogin.ts.mustache new file mode 100644 index 00000000000..1356a82e3e9 --- /dev/null +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/restLogin.ts.mustache @@ -0,0 +1,13 @@ +import { Login } from '@/common/domain/Login'; + +export interface restLogin { + username: string; + password: string; + rememberMe: boolean; +} + +export const torestLogin = (login: Login): restLogin => ({ + username: login.username, + password: login.password, + rememberMe: login.rememberMe, +}); diff --git a/src/main/resources/generator/client/vue/webapp/app/main.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/main.ts.mustache index 0f5ea875b13..31a31d26a4a 100644 --- a/src/main/resources/generator/client/vue/webapp/app/main.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/main.ts.mustache @@ -2,6 +2,8 @@ import { createApp } from 'vue'; import App from './common/primary/app/App.vue'; // jhipster-needle-main-ts-import +// jhipster-needle-main-ts-instanciation + const app = createApp(App); // jhipster-needle-main-ts-provider app.mount('#app'); diff --git a/src/main/resources/generator/client/vue/webapp/app/router/router.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/router/router.ts.mustache index 26fed265ab0..a0aa9d92ee5 100644 --- a/src/main/resources/generator/client/vue/webapp/app/router/router.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/router/router.ts.mustache @@ -1,5 +1,7 @@ import { AppVue } from '@/common/primary/app'; import { createRouter, createWebHistory } from 'vue-router'; +// jhipster-needle-router-import + const routes = [ { @@ -11,6 +13,7 @@ const routes = [ name: 'App', component: AppVue, }, + // jhipster-needle-router-routes ]; const router = createRouter({ @@ -18,4 +21,4 @@ const router = createRouter({ routes, }); -export default router; +export default router; \ No newline at end of file diff --git a/src/main/resources/generator/server/springboot/mvc/security/jwt/src/account/restLogin.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/jwt/src/account/restLogin.java.mustache new file mode 100644 index 00000000000..c8179d3c2d3 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/jwt/src/account/restLogin.java.mustache @@ -0,0 +1,50 @@ +package {{packageName}}.account.infrastructure.primary.rest; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +public class restLogin { + + @NotNull + @Size(min = 1, max = 50) + private String username; + + @NotNull + @Size(min = 4, max = 100) + private String password; + + private boolean rememberMe; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isRememberMe() { + return rememberMe; + } + + public void setRememberMe(boolean rememberMe) { + this.rememberMe = rememberMe; + } + + // prettier-ignore + @Override + public String toString() { + return "restLogin{" + + "username='" + username + '\'' + + ", rememberMe=" + rememberMe + + '}'; + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/jwt/test/account/restLoginTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/jwt/test/account/restLoginTest.java.mustache new file mode 100644 index 00000000000..2567a8c4731 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/jwt/test/account/restLoginTest.java.mustache @@ -0,0 +1,26 @@ +package {{packageName}}.account.infrastructure.primary.rest; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; + +import {{packageName}}.UnitTest; +import org.junit.jupiter.api.Test; + +@UnitTest +class restLoginTest { + + @Test + void shouldBuild() { + restLogin restLogin = new restLogin(); + restLogin.setUsername("admin"); + restLogin.setPassword("password"); + restLogin.setRememberMe(true); + + assertThat(restLogin.getUsername()).isEqualTo("admin"); + assertThat(restLogin.getPassword()).isEqualTo("password"); + assertThat(restLogin.isRememberMe()).isTrue(); + + assertThat(restLogin.toString()).contains("admin"); + assertThat(restLogin.toString()).doesNotContain("password"); + } +} diff --git a/src/test/java/tech/jhipster/lite/TestUtils.java b/src/test/java/tech/jhipster/lite/TestUtils.java index 4d4784f57a3..fabc6bacdc1 100644 --- a/src/test/java/tech/jhipster/lite/TestUtils.java +++ b/src/test/java/tech/jhipster/lite/TestUtils.java @@ -137,6 +137,19 @@ public static Project tmpProjectWithPackageJsonComplete() { return project; } + public static Project tmpProjectWithPackageJsonPinia() { + Project project = tmpProject(); + copyPackageJsonByName(project, "package-pinia.json"); + return project; + } + + public static Project tmpProjectWithPiniaAndAppContext() { + Project project = tmpProject(); + copyPackageJsonByName(project, "package-pinia.json"); + copyVueAppFiles(project); + return project; + } + public static Project tmpProjectWithBuildGradle() { Project project = tmpProject(); copyBuildGradle(project); @@ -259,6 +272,27 @@ public static void copyLiquibaseMasterXml(Project project) { } } + private static void copyVueAppFiles(Project project) { + try { + FileUtils.createFolder(getPath(project.getFolder(), "src/main/webapp/app/common/primary/app")); + FileUtils.createFolder(getPath(project.getFolder(), "src/main/webapp/app/router")); + Files.copy( + getPathOf("src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.html.mustache"), + getPathOf(project.getFolder(), "src/main/webapp/app/common/primary/app", "App.html") + ); + Files.copy( + getPathOf("src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.component.ts.mustache"), + getPathOf(project.getFolder(), "src/main/webapp/app/common/primary/app", "App.component.ts") + ); + Files.copy( + getPathOf("src/main/resources/generator/client/vue/webapp/app/router/router.ts.mustache"), + getPathOf(project.getFolder(), "src/main/webapp/app/router", "router.ts") + ); + } catch (IOException e) { + throw new AssertionError(e); + } + } + private static final ObjectMapper mapper = createObjectMapper(); private static ObjectMapper createObjectMapper() { diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationServiceIT.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationServiceIT.java new file mode 100644 index 00000000000..3fb1dba501c --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationServiceIT.java @@ -0,0 +1,37 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.application; + +import static tech.jhipster.lite.TestUtils.*; +import static tech.jhipster.lite.generator.project.domain.Constants.MAIN_WEBAPP; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import tech.jhipster.lite.IntegrationTest; +import tech.jhipster.lite.generator.client.vue.core.application.VueApplicationService; +import tech.jhipster.lite.generator.project.domain.Project; + +@IntegrationTest +class VueJwtApplicationServiceIT { + + @Autowired + VueJwtApplicationService vueJwtApplicationService; + + @Autowired + VueApplicationService vueApplicationService; + + @Test + void shouldAddVueJwt() { + Project project = tmpProjectWithPackageJsonPinia(); + vueApplicationService.addVue(project); + vueApplicationService.addPinia(project); + vueJwtApplicationService.addJWT(project); + String COMMON = MAIN_WEBAPP + "/app/common"; + assertFileExist(project, COMMON + "/domain/Login.ts"); + assertFileExist(project, COMMON + "/primary/login/index.ts"); + assertFileExist(project, COMMON + "/primary/login/Login.component.ts"); + assertFileExist(project, COMMON + "/primary/login/Login.html"); + assertFileExist(project, COMMON + "/primary/login/Login.vue"); + assertFileExist(project, COMMON + "/secondary/restLogin.ts"); + assertFileExist(project, COMMON + "/domain/AuthenticationService.ts"); + assertFileExist(project, COMMON + "/domain/JWTStoreService.ts"); + } +} diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtAssert.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtAssert.java new file mode 100644 index 00000000000..1e027e4fc1b --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtAssert.java @@ -0,0 +1,3 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.application; + +public class VueJwtAssert {} diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainServiceTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainServiceTest.java new file mode 100644 index 00000000000..092d2f99bdd --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainServiceTest.java @@ -0,0 +1,63 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.domain; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static tech.jhipster.lite.TestUtils.tmpProjectWithPackageJson; +import static tech.jhipster.lite.TestUtils.tmpProjectWithPackageJsonPinia; +import static tech.jhipster.lite.TestUtils.tmpProjectWithPiniaAndAppContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import tech.jhipster.lite.UnitTest; +import tech.jhipster.lite.error.domain.GeneratorException; +import tech.jhipster.lite.error.domain.MissingMandatoryValueException; +import tech.jhipster.lite.generator.project.domain.Project; +import tech.jhipster.lite.generator.project.domain.ProjectFile; +import tech.jhipster.lite.generator.project.domain.ProjectRepository; + +@UnitTest +@ExtendWith(MockitoExtension.class) +class VueJwtDomainServiceTest { + + @Mock + ProjectRepository projectRepository; + + @InjectMocks + private VueJwtDomainService jwtDomainService; + + @Test + void shouldAddVueJwt() { + Project project = tmpProjectWithPiniaAndAppContext(); + + assertThatCode(() -> jwtDomainService.addJWT(project)).doesNotThrowAnyException(); + + verify(projectRepository, times(6)).template(any(ProjectFile.class)); + } + + @Test + void shouldNotWriteToAppContext() { + Project project = tmpProjectWithPackageJsonPinia(); + + assertThatThrownBy(() -> jwtDomainService.addJWT(project)).isExactlyInstanceOf(GeneratorException.class); + } + + @Test + void shouldNotAddVueJwt() { + Project project = tmpProjectWithPackageJson(); + + assertThatThrownBy(() -> jwtDomainService.addJWT(project)).isExactlyInstanceOf(GeneratorException.class); + } + + @Test + void shouldNotaddVueJwtWhenNoProject() { + assertThatThrownBy(() -> jwtDomainService.addJWT(null)) + .isExactlyInstanceOf(MissingMandatoryValueException.class) + .hasMessageContaining("project"); + } +} diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/config/VueJwtBeanConfigurationIT.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/config/VueJwtBeanConfigurationIT.java new file mode 100644 index 00000000000..7983c96e299 --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/config/VueJwtBeanConfigurationIT.java @@ -0,0 +1,21 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.infrastructure.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import tech.jhipster.lite.IntegrationTest; +import tech.jhipster.lite.generator.client.vue.security.jwt.domain.VueJwtDomainService; + +@IntegrationTest +class VueJwtBeanConfigurationIT { + + @Autowired + ApplicationContext applicationContext; + + @Test + void shouldGetBean() { + assertThat(applicationContext.getBean("vueJwtService")).isNotNull().isInstanceOf(VueJwtDomainService.class); + } +} diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/primary/VueJwtResourceIT.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/primary/VueJwtResourceIT.java new file mode 100644 index 00000000000..b0dac324e20 --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/infrastructure/primary/VueJwtResourceIT.java @@ -0,0 +1,57 @@ +package tech.jhipster.lite.generator.client.vue.security.jwt.infrastructure.primary; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static tech.jhipster.lite.TestUtils.*; +import static tech.jhipster.lite.common.domain.FileUtils.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import tech.jhipster.lite.IntegrationTest; +import tech.jhipster.lite.TestUtils; +import tech.jhipster.lite.generator.init.application.InitApplicationService; +import tech.jhipster.lite.generator.project.domain.Project; +import tech.jhipster.lite.generator.project.infrastructure.primary.dto.ProjectDTO; + +@IntegrationTest +@AutoConfigureMockMvc +class VueJwtResourceIT { + + @Autowired + MockMvc mockMvc; + + @Autowired + InitApplicationService initApplicationService; + + @Test + void shouldAddVueJwt() throws Exception { + ProjectDTO projectDTO = readFileToObject("json/chips.json", ProjectDTO.class).folder(tmpDirForTest()); + Project project = ProjectDTO.toProject(projectDTO); + initApplicationService.init(project); + + mockMvc + .perform(post("/api/clients/vue").contentType(MediaType.APPLICATION_JSON).content(TestUtils.convertObjectToJsonBytes(projectDTO))) + .andExpect(status().isOk()); + + mockMvc + .perform( + post("/api/clients/vue/stores/pinia") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtils.convertObjectToJsonBytes(projectDTO)) + ) + .andExpect(status().isOk()); + mockMvc + .perform( + post("/api/clients/vue/stores/pinia") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtils.convertObjectToJsonBytes(projectDTO)) + ) + .andExpect(status().isOk()); + mockMvc + .perform(post("/api/clients/vue/jwt").contentType(MediaType.APPLICATION_JSON).content(TestUtils.convertObjectToJsonBytes(projectDTO))) + .andExpect(status().isOk()); + } +} diff --git a/src/test/resources/generator/command/package-pinia.json b/src/test/resources/generator/command/package-pinia.json new file mode 100644 index 00000000000..dfe9bf70656 --- /dev/null +++ b/src/test/resources/generator/command/package-pinia.json @@ -0,0 +1,19 @@ +{ + "name": "jhlitetest", + "version": "0.0.1", + "scripts": { + "prettier:check": "prettier --check \"{,src/**/}*.{md,json,yml,html,js,ts,tsx,css,scss,vue,java,xml}\"", + "prettier:format": "prettier --write \"{,src/**/}*.{md,json,yml,html,js,ts,tsx,css,scss,vue,java,xml}\"" + }, + "devDependencies": { + "prettier-plugin-java": "1.6.1" + }, + "dependencies": { + "axios": "0.26.1", + "pinia": "2.0.13" + }, + "engines": { + "node": ">=14.18.1" + }, + "cacheDirectories": ["node_modules"] +} From 5cec11d114744e536a5d10462743010b778059b9 Mon Sep 17 00:00:00 2001 From: matthieulapatate Date: Wed, 25 May 2022 11:16:40 +0200 Subject: [PATCH 2/6] Remove restLogin java's files --- .../jwt/src/account/restLogin.java.mustache | 50 ------------------- .../test/account/restLoginTest.java.mustache | 26 ---------- 2 files changed, 76 deletions(-) delete mode 100644 src/main/resources/generator/server/springboot/mvc/security/jwt/src/account/restLogin.java.mustache delete mode 100644 src/main/resources/generator/server/springboot/mvc/security/jwt/test/account/restLoginTest.java.mustache diff --git a/src/main/resources/generator/server/springboot/mvc/security/jwt/src/account/restLogin.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/jwt/src/account/restLogin.java.mustache deleted file mode 100644 index c8179d3c2d3..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/jwt/src/account/restLogin.java.mustache +++ /dev/null @@ -1,50 +0,0 @@ -package {{packageName}}.account.infrastructure.primary.rest; - -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; - -public class restLogin { - - @NotNull - @Size(min = 1, max = 50) - private String username; - - @NotNull - @Size(min = 4, max = 100) - private String password; - - private boolean rememberMe; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public boolean isRememberMe() { - return rememberMe; - } - - public void setRememberMe(boolean rememberMe) { - this.rememberMe = rememberMe; - } - - // prettier-ignore - @Override - public String toString() { - return "restLogin{" + - "username='" + username + '\'' + - ", rememberMe=" + rememberMe + - '}'; - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/jwt/test/account/restLoginTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/jwt/test/account/restLoginTest.java.mustache deleted file mode 100644 index 2567a8c4731..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/jwt/test/account/restLoginTest.java.mustache +++ /dev/null @@ -1,26 +0,0 @@ -package {{packageName}}.account.infrastructure.primary.rest; - -import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.Assertions.assertThat; - -import {{packageName}}.UnitTest; -import org.junit.jupiter.api.Test; - -@UnitTest -class restLoginTest { - - @Test - void shouldBuild() { - restLogin restLogin = new restLogin(); - restLogin.setUsername("admin"); - restLogin.setPassword("password"); - restLogin.setRememberMe(true); - - assertThat(restLogin.getUsername()).isEqualTo("admin"); - assertThat(restLogin.getPassword()).isEqualTo("password"); - assertThat(restLogin.isRememberMe()).isTrue(); - - assertThat(restLogin.toString()).contains("admin"); - assertThat(restLogin.toString()).doesNotContain("password"); - } -} From f4733d3ee321a45699db0d0ebae6a30f6f3437fa Mon Sep 17 00:00:00 2001 From: matthieulapatate Date: Mon, 30 May 2022 07:07:54 +0200 Subject: [PATCH 3/6] Jwt authentication for vuejs application --- .../vue/security/jwt/domain/VueJwt.java | 268 ++++++++++++++---- .../jwt/domain/VueJwtDomainService.java | 35 ++- .../common/primary/app/App.spec.ts.mustache | 16 +- .../vue/test/spec/jwt/Router.spec.ts.mustache | 60 ---- .../spec/jwt/primary/app/App.spec.ts.mustache | 90 ------ .../AuthenticationRepository.spec.ts.mustache | 6 +- ...ts.mustache => RestLogin.spec.ts.mustache} | 8 +- .../jwt/secondary/UserDTO.spec.ts.mustache | 2 +- .../test/spec/router/Router.spec.ts.mustache | 20 +- .../primary/app/App.component.ts.mustache | 6 +- .../homepage/Homepage.component.ts.mustache | 2 - .../AuthenticationRepository.ts.mustache | 11 +- ...ogin.ts.mustache => RestLogin.ts.mustache} | 4 +- .../vue/webapp/app/router/router.ts.mustache | 4 +- .../VueJwtApplicationServiceIT.java | 4 +- .../jwt/domain/VueJwtDomainServiceTest.java | 2 +- tests-ci/generate.sh | 1 + 17 files changed, 278 insertions(+), 261 deletions(-) delete mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/Router.spec.ts.mustache delete mode 100644 src/main/resources/generator/client/vue/test/spec/jwt/primary/app/App.spec.ts.mustache rename src/main/resources/generator/client/vue/test/spec/jwt/secondary/{restLogin.spec.ts.mustache => RestLogin.spec.ts.mustache} (50%) rename src/main/resources/generator/client/vue/webapp/app/jwt/secondary/{restLogin.ts.mustache => RestLogin.ts.mustache} (70%) diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java index cc84ef78e32..d051400b9fa 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java @@ -40,16 +40,16 @@ public static List primaryLoginFiles() { } public static final Collection LOGIN_ROUTES = List.of( - "\t{", - "\tpath: '/login',", - "\tname: 'Login',", - "\tcomponent: LoginVue,", - "\t},", - "\t{", - "\tpath: '/',", - "\tname: 'Homepage',", - "\tcomponent: AppVue,", - "\t}," + " {", + " path: '/login',", + " name: 'Login',", + " component: LoginVue,", + " },", + " {", + " path: '/',", + " name: 'Homepage',", + " component: AppVue,", + " }," ); public static final Collection ROUTER_IMPORTS = List.of("import { LoginVue } from '@/common/primary/login';"); @@ -71,71 +71,217 @@ public static Collection testDomainFiles() { } public static Collection testSecondaryFiles() { - return List.of("AuthenticationRepository.spec.ts", "restLogin.spec.ts", "UserDTO.spec.ts"); + return List.of("AuthenticationRepository.spec.ts", "RestLogin.spec.ts", "UserDTO.spec.ts"); } public static Map appComponent() { return Map.of( - "import { AuthenticationService } from '@/common/domain/AuthenticationService';\n".concat( - "import { Logger } from '@/common/domain/Logger';\n" - ) - .concat("import { User } from '@/common/domain/User';\n") - .concat("import { Router } from 'vue-router';\n") - .concat("import { jwtStore } from '@/common/domain/JWTStoreService';\n") - .concat("import { inject, ref } from \"vue\";"), + """ + import { AuthenticationService } from '@/common/domain/AuthenticationService'; + import { Logger } from '@/common/domain/Logger'; + import { User } from '@/common/domain/User'; + import { Router } from 'vue-router'; + import { jwtStore } from '@/common/domain/JWTStoreService'; + import { inject, ref } from "vue"; + """, "import", - "const authenticationService = inject('authenticationService') as AuthenticationService;\n".concat( - "\tconst logger = inject('logger') as Logger;\n" - ) - .concat("\tconst router = inject('router') as Router;\n") - .concat("\n") - .concat("\tlet store = jwtStore();\n") - .concat("\tlet isAuthenticated:boolean = store.isAuth;\n") - .concat("\tlet user = ref({\n") - .concat("\t\tusername: '',\n") - .concat("\t\tauthorities: [''],\n") - .concat("\t});\n") - .concat("\n") - .concat("\tconst onConnect = async (): Promise => {\n") - .concat("\t\tawait authenticationService\n") - .concat("\t\t.authenticate()\n") - .concat("\t\t.then(response => {\n") - .concat("\t\t\tuser.value = response;\n") - .concat("\t\t})\n") - .concat("\t\t.catch(error => {\n") - .concat("\t\t\tlogger.error('The token provided is not know by our service', error);\n") - .concat("\t\t});\n") - .concat("\t}\n") - .concat("\n") - .concat("\tconst onLogout = async (): Promise => {\n") - .concat("\t\tawait authenticationService\n") - .concat("\t\t.logout();\n") - .concat("\t\trouter.push(\"/login\");\n") - .concat("\t};\n"), + """ + const authenticationService = inject('authenticationService') as AuthenticationService; + const logger = inject('logger') as Logger; + const router = inject('router') as Router; + + let store = jwtStore(); + let isAuthenticated:boolean = store.isAuth; + let user = ref({ + username: '', + authorities: [''], + }); + + const onConnect = async (): Promise => { + await authenticationService + .authenticate() + .then(response => { + user.value = response; + }) + .catch(error => { + logger.error('The token provided is not know by our service', error); + }); + } + + const onLogout = async (): Promise => { + authenticationService + .logout(); + router.push("/login"); + }; + """, "setup", - "user,\n".concat("\tisAuthenticated,\n").concat("\tonConnect,\n").concat("\tonLogout,\n"), + """ + user, + isAuthenticated, + onConnect, + onLogout, + """, "return" ); } + public static Map appTest() { + return Map.of( + """ + import { createTestingPinia } from '@pinia/testing'; + import { AuthenticationService } from '@/common/domain/AuthenticationService'; + import { stubAuthenticationService } from '../../domain/AuthenticationService.fixture'; + import { stubLogger } from '../../domain/Logger.fixture'; + import { Logger } from '@/common/domain/Logger'; + import sinon from 'sinon'; + """, + "test-import", + """ + const \\$route = { path: {} }; + const router = { push: sinon.stub() }; + """, + "test-variables", + """ + authenticationService: AuthenticationService; + logger: Logger; + """, + "test-wrapper-options", + """ + const { authenticationService, logger }: WrapperOptions = { + authenticationService: stubAuthenticationService(), + logger: stubLogger(), + ...wrapperOptions, + }; + """, + "test-wrapper-variable", + """ + global: { + stubs: ['router-link'], + provide: { + authenticationService, + logger, + router, + }, + plugins: [createTestingPinia({ + initialState: { + JWTStore: {token: '123456789'}, + }, + })], + }, + """, + "test-wrapper-mount", + """ + it('should authenticate', async () => { + const authenticationService = stubAuthenticationService(); + const logger = stubLogger(); + authenticationService.authenticate.resolves({ username: 'username', authorities: ['admin'] }); + await wrap({ authenticationService, logger }); + + const clickButton = wrapper.find('#identify'); + await clickButton.trigger('click'); + + // @ts-ignore + expect(wrapper.vm.user).toStrictEqual({ username: 'username', authorities: ['admin'] }); + }); + + it('Should log an error when authentication fails', async () => { + const authenticationService = stubAuthenticationService(); + const logger = stubLogger(); + authenticationService.authenticate.rejects({}); + await wrap({ authenticationService, logger }); + + const clickButton = wrapper.find('#identify'); + await clickButton.trigger('click'); + + const [message] = logger.error.getCall(0).args; + expect(message).toBe('The token provided is not know by our service'); + }); + + it('Should log out', async () => { + const authenticationService = stubAuthenticationService(); + const logger = stubLogger(); + authenticationService.authenticate.resolves({ username: 'username', authorities: ['admin'] }); + await wrap({ authenticationService, logger }); + + + const clickButton = wrapper.find('#identify'); + await clickButton.trigger('click'); + const logoutButton = wrapper.find('#logout'); + await logoutButton.trigger('click'); + + sinon.assert.calledOnce(authenticationService.logout); + }); + """, + "test-routes" + ); + } + public static List appHTML() { return List.of( "
", - "\t
", - "\t\t

You are connected as

", - "\t\t
", - "\t\t\t", - "\t\t
", - "\t\t
", - "\t\t\t

{{user.username}}

", - "\t\t\t", - "\t\t
", - "\t
", - "\t
", - "\t\t

You are not connected

", - "\t\tLogin", - "\t
", + "
", + "

You are connected as

", + "
", + " ", + "
", + "
", + "

{{user.username}}

", + " ", + "
", + "
", + "
", + "

You are not connected

", + " Login", + "
", "
" ); } + + public static Map routerspec() { + return Map.of( + """ + import { LoginVue } from '@/common/primary/login'; + import { createTestingPinia } from '@pinia/testing'; + import { AuthenticationService } from '@/common/domain/AuthenticationService'; + import { stubAuthenticationService } from '../common/domain/AuthenticationService.fixture'; + import { stubLogger } from '../common/domain/Logger.fixture'; + import { Logger } from '@/common/domain/Logger'; + """, + "test-import", + """ + authenticationService: AuthenticationService; + logger: Logger; + """, + "test-wrapper-options", + """ + const { authenticationService, logger }: WrapperOptions = { + authenticationService: stubAuthenticationService(), + logger: stubLogger(), + ...wrapperOptions, + }; + """, + "test-wrapper-variable", + """ + global: { + stubs: ['router-link'], + provide: { + authenticationService, + logger, + router, + }, + plugins: [createTestingPinia()], + }, + """, + "test-wrapper-mount", + """ + it('Should go to LoginVue', async () => { + router.push('/Login'); + await wrapper.vm.\\$nextTick(); + expect(wrapper.findComponent(LoginVue)).toBeTruthy(); + }); + afterAll(async () => new Promise(resolve => window.setTimeout(resolve, 0))); + """, + "test-routes" + ); + } } diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java index f96fce4de6c..bf731073105 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java @@ -37,9 +37,9 @@ public class VueJwtDomainService implements VueJwtService { public static final String NEEDLE_MAIN_PROVIDER = "// jhipster-needle-main-ts-provider"; private static final String NEEDLE_MAIN_INSTANCIATION = "// jhipster-needle-main-ts-instanciation"; - public static final String NEEDLE_ROUTER_IMPORT = "// jhipster-needle-router-import"; + public static final String NEEDLE_ROUTER = "// jhipster-needle-router"; - public static final String NEEDLE_ROUTER_ROUTES = "// jhipster-needle-router-routes"; + public static final String NEEDLE_APP = "// jhipster-needle-app"; public static final String LOGIN = "/login"; @@ -82,7 +82,7 @@ public void addAppContext(Project project) { VueJwt .appComponent() .forEach((line, needle) -> - addNewNeedleLineToFile(project, line, destinationAppComponent, "App.component.ts", "// needle-jhipster-" + needle) + addNewNeedleLineToFile(project, line, destinationAppComponent, "App.component.ts", NEEDLE_APP + "-" + needle) ); } @@ -106,7 +106,7 @@ public void addLoginContext(Project project) { projectRepository.template( ProjectFile .forProject(project) - .withSource(getPath(SOURCE, SOURCE_SECONDARY), "restLogin.ts") + .withSource(getPath(SOURCE, SOURCE_SECONDARY), "RestLogin.ts") .withDestinationFolder(DESTINATION_SECONDARY) ); } @@ -153,10 +153,10 @@ public void addRoutes(Project project) { "redirect: { name: 'Homepage' }" ); VueJwt.ROUTER_IMPORTS.forEach(providerLine -> - addNewNeedleLineToFile(project, providerLine, routerPath, ROUTER_TYPESCRIPT, NEEDLE_ROUTER_IMPORT) + addNewNeedleLineToFile(project, providerLine, routerPath, ROUTER_TYPESCRIPT, NEEDLE_ROUTER + "-imports") ); VueJwt.LOGIN_ROUTES.forEach(providerLine -> - addNewNeedleLineToFile(project, providerLine, routerPath, ROUTER_TYPESCRIPT, NEEDLE_ROUTER_ROUTES) + addNewNeedleLineToFile(project, providerLine, routerPath, ROUTER_TYPESCRIPT, NEEDLE_ROUTER + "-routes") ); } @@ -186,12 +186,11 @@ public void addTests(Project project) { ) .toList(); projectRepository.template(testDomainFiles); - projectRepository.template( - ProjectFile - .forProject(project) - .withSource(getPath(SOURCE, SOURCE_TEST + PRIMARY + "/app"), "App.spec.ts") - .withDestinationFolder(testPrimaryPath + "/app") - ); + VueJwt + .appTest() + .forEach((line, needle) -> + addNewNeedleLineToFile(project, line, "src/test/javascript/spec/common/primary/app", "App.spec.ts", NEEDLE_APP + "-" + needle) + ); projectRepository.template( ProjectFile .forProject(project) @@ -214,12 +213,12 @@ public void addTests(Project project) { .toList(); projectRepository.template(testSecondaryFiles); - projectRepository.template( - ProjectFile - .forProject(project) - .withSource(getPath(SOURCE, SOURCE_TEST), "Router.spec.ts") - .withDestinationFolder(TEST_JAVASCRIPT + "/router") - ); + + VueJwt + .routerspec() + .forEach((line, needle) -> + addNewNeedleLineToFile(project, line, getPath("src/test/javascript/spec/router"), "Router.spec.ts", NEEDLE_ROUTER + "-" + needle) + ); } private void addNewNeedleLineToFile(Project project, String importLine, String folder, String file, String needle) { diff --git a/src/main/resources/generator/client/vue/test/spec/common/primary/app/App.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/common/primary/app/App.spec.ts.mustache index 804ddf61459..ed3591e2a3c 100644 --- a/src/main/resources/generator/client/vue/test/spec/common/primary/app/App.spec.ts.mustache +++ b/src/main/resources/generator/client/vue/test/spec/common/primary/app/App.spec.ts.mustache @@ -1,10 +1,20 @@ import { shallowMount, VueWrapper } from '@vue/test-utils'; import { AppVue } from '@/common/primary/app'; +// jhipster-needle-app-test-import let wrapper: VueWrapper; +// jhipster-needle-app-test-variables -const wrap = () => { - wrapper = shallowMount(AppVue); +interface WrapperOptions { + // jhipster-needle-app-test-wrapper-options +} + +const wrap = (wrapperOptions?: Partial) => { + // jhipster-needle-app-test-wrapper-variable + + wrapper = shallowMount(AppVue,{ + // jhipster-needle-app-test-wrapper-mount + }); }; describe('App', () => { @@ -13,4 +23,6 @@ describe('App', () => { expect(wrapper.exists()).toBeTruthy(); }); + + // jhipster-needle-app-test-routes }); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/Router.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/Router.spec.ts.mustache deleted file mode 100644 index 61092b608ad..00000000000 --- a/src/main/resources/generator/client/vue/test/spec/jwt/Router.spec.ts.mustache +++ /dev/null @@ -1,60 +0,0 @@ -import { AppVue } from '@/common/primary/app'; -import { LoginVue } from '@/common/primary/login'; -import router from '@/router/router'; -import { createTestingPinia } from '@pinia/testing'; -import { shallowMount, VueWrapper } from '@vue/test-utils'; -import { AuthenticationService } from '@/common/domain/AuthenticationService'; -import { stubAuthenticationService } from '../common/domain/AuthenticationService.fixture'; -import { stubLogger } from '../common/domain/Logger.fixture'; -import { Logger } from '@/common/domain/Logger'; - -let wrapper: VueWrapper; - -interface WrapperOptions { - authenticationService: AuthenticationService; - logger: Logger; -} - -const wrap = (wrapperOptions?: Partial) => { - const { authenticationService, logger }: WrapperOptions = { - authenticationService: stubAuthenticationService(), - logger: stubLogger(), - ...wrapperOptions, - }; - - wrapper = shallowMount(AppVue, { - global: { - stubs: ['router-link'], - provide: { - authenticationService, - logger, - router, - }, - plugins: [createTestingPinia()], - }, - }); -}; - -describe('Router', () => { - - afterAll(async () => new Promise(resolve => window.setTimeout(resolve, 0))); - it('Should redirect to App by default', async () => { - const authenticationService = stubAuthenticationService(); - const logger = stubLogger(); - await wrap({ authenticationService, logger }); - router.push('/'); - await router.isReady(); - - await wrapper.vm.$nextTick(); - - expect(wrapper.findComponent(AppVue)).toBeTruthy(); - }); - - it('Should go to LoginVue', async () => { - router.push('/Login'); - - await wrapper.vm.$nextTick(); - - expect(wrapper.findComponent(LoginVue)).toBeTruthy(); - }); -}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/primary/app/App.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/primary/app/App.spec.ts.mustache deleted file mode 100644 index fb493abcf6d..00000000000 --- a/src/main/resources/generator/client/vue/test/spec/jwt/primary/app/App.spec.ts.mustache +++ /dev/null @@ -1,90 +0,0 @@ -import { shallowMount, VueWrapper } from '@vue/test-utils'; -import { AppVue } from '@/common/primary/app'; -import { createTestingPinia } from '@pinia/testing'; -import { AuthenticationService } from '@/common/domain/AuthenticationService'; -import { stubAuthenticationService } from '../../domain/AuthenticationService.fixture'; -import { stubLogger } from '../../domain/Logger.fixture'; -import { Logger } from '@/common/domain/Logger'; -import sinon from 'sinon'; - -let wrapper: VueWrapper; -const $route = { path: {} }; -const router = { push: sinon.stub() }; - -interface WrapperOptions { - authenticationService: AuthenticationService; - logger: Logger; -} - -const wrap = (wrapperOptions?: Partial) => { - const { authenticationService, logger }: WrapperOptions = { - authenticationService: stubAuthenticationService(), - logger: stubLogger(), - ...wrapperOptions, - }; - - wrapper = shallowMount(AppVue, { - global: { - stubs: ['router-link'], - provide: { - authenticationService, - logger, - router, - }, - plugins: [createTestingPinia({ - initialState: { - JWTStore: {token: '123456789'}, - }, - })], - }, - }); -}; - -describe('App', () => { - it('should exist', () => { - wrap(); - - expect(wrapper.exists()).toBe(true); - }); - - it('should authenticate', async () => { - const authenticationService = stubAuthenticationService(); - const logger = stubLogger(); - authenticationService.authenticate.resolves({ username: 'username', authorities: ['admin'] }); - await wrap({ authenticationService, logger }); - - const clickButton = wrapper.find('#identify'); - await clickButton.trigger('click'); - - // @ts-ignore - expect(wrapper.vm.user).toStrictEqual({ username: 'username', authorities: ['admin'] }); - }); - - it('Should log an error when authentication fails', async () => { - const authenticationService = stubAuthenticationService(); - const logger = stubLogger(); - authenticationService.authenticate.rejects({}); - await wrap({ authenticationService, logger }); - - const clickButton = wrapper.find('#identify'); - await clickButton.trigger('click'); - - const [message] = logger.error.getCall(0).args; - expect(message).toBe('The token provided is not know by our service'); - }); - - it('Should log out', async () => { - const authenticationService = stubAuthenticationService(); - const logger = stubLogger(); - authenticationService.authenticate.resolves({ username: 'username', authorities: ['admin'] }); - await wrap({ authenticationService, logger }); - - - const clickButton = wrapper.find('#identify'); - await clickButton.trigger('click'); - const logoutButton = wrapper.find('#logout'); - await logoutButton.trigger('click'); - - sinon.assert.calledOnce(authenticationService.logout); - }); -}); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/secondary/AuthenticationRepository.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/AuthenticationRepository.spec.ts.mustache index fc39d94fdce..adec68b20ff 100644 --- a/src/main/resources/generator/client/vue/test/spec/jwt/secondary/AuthenticationRepository.spec.ts.mustache +++ b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/AuthenticationRepository.spec.ts.mustache @@ -1,5 +1,5 @@ import { Login } from '@/common/domain/Login'; -import { restLogin } from '@/common/secondary/restLogin'; +import { RestLogin } from '@/common/secondary/RestLogin'; import { User } from '@/common/domain/User'; import AuthenticationRepository from '@/common/secondary/AuthenticationRepository'; import { AxiosHttpStub, stubAxiosHttp } from '../../http/AxiosHttpStub'; @@ -33,7 +33,7 @@ describe('AuthenticationRepository', () => { const [uri, payload] = axiosHttpStub.post.getCall(0).args; expect(uri).toBe('/api/authenticate'); - expect(payload).toEqual({ username: 'admin', password: 'admin', rememberMe: true }); + expect(payload).toEqual({ username: 'admin', password: 'admin', rememberMe: true }); // @ts-ignore expect(store.token).toEqual(AUTH_TOKEN); }); @@ -47,7 +47,7 @@ describe('AuthenticationRepository', () => { const [uri, payload] = axiosHttpStub.post.getCall(0).args; expect(uri).toBe('/api/authenticate'); - expect(payload).toEqual({ username: 'admin', password: 'wrong_password', rememberMe: true }); + expect(payload).toEqual({ username: 'admin', password: 'wrong_password', rememberMe: true }); // @ts-ignore expect(store.token).toEqual(''); }); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/secondary/restLogin.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/RestLogin.spec.ts.mustache similarity index 50% rename from src/main/resources/generator/client/vue/test/spec/jwt/secondary/restLogin.spec.ts.mustache rename to src/main/resources/generator/client/vue/test/spec/jwt/secondary/RestLogin.spec.ts.mustache index 7b7a2757379..e84d60751bd 100644 --- a/src/main/resources/generator/client/vue/test/spec/jwt/secondary/restLogin.spec.ts.mustache +++ b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/RestLogin.spec.ts.mustache @@ -1,10 +1,10 @@ -import { restLogin, torestLogin } from '@/common/secondary/restLogin'; +import { RestLogin, toRestLogin } from '@/common/secondary/RestLogin'; import { Login } from '@/common/domain/Login'; -describe('restLogin', () => { - it('should convert to restLogin', () => { +describe('RestLogin', () => { + it('should convert to RestLogin', () => { const login: Login = { username: 'username', password: 'password', rememberMe: true }; - expect(torestLogin(login)).toEqual({ username: 'username', password: 'password', rememberMe: true }); + expect(toRestLogin(login)).toEqual({ username: 'username', password: 'password', rememberMe: true }); }); }); diff --git a/src/main/resources/generator/client/vue/test/spec/jwt/secondary/UserDTO.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/UserDTO.spec.ts.mustache index 926c0d892f0..fa6a92e7420 100644 --- a/src/main/resources/generator/client/vue/test/spec/jwt/secondary/UserDTO.spec.ts.mustache +++ b/src/main/resources/generator/client/vue/test/spec/jwt/secondary/UserDTO.spec.ts.mustache @@ -1,7 +1,7 @@ import { UserDTO, toUser } from '@/common/secondary/UserDTO'; import { User } from '@/common/domain/User'; -describe('restLogin', () => { +describe('estLogin', () => { it('should convert to User', () => { const userDTO: UserDTO = { login: 'username', authorities: ['admin'] }; diff --git a/src/main/resources/generator/client/vue/test/spec/router/Router.spec.ts.mustache b/src/main/resources/generator/client/vue/test/spec/router/Router.spec.ts.mustache index f966f944f7a..46ef2b2d503 100644 --- a/src/main/resources/generator/client/vue/test/spec/router/Router.spec.ts.mustache +++ b/src/main/resources/generator/client/vue/test/spec/router/Router.spec.ts.mustache @@ -1,16 +1,25 @@ import { shallowMount, VueWrapper } from '@vue/test-utils'; import { AppVue } from '@/common/primary/app'; +// jhipster-needle-router-test-imports import router from '@/router/router'; let wrapper: VueWrapper; -const wrap = () => { - wrapper = shallowMount(AppVue,{ - router - }); +interface WrapperOptions { + // jhipster-needle-router-test-wrapper-options +} + +const wrap = (wrapperOptions?: Partial) => { + // jhipster-needle-router-test-wrapper-variable + + wrapper = shallowMount(AppVue,{ + // jhipster-needle-router-test-wrapper-mount + router + }); }; + describe('Router', () => { it('Should redirect to App by default', async () => { wrap(); @@ -27,4 +36,7 @@ describe('Router', () => { expect(wrapper.findComponent(AppVue)).toBeTruthy() }) + + // jhipster-needle-router-test-routes + }) diff --git a/src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.component.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.component.ts.mustache index 776d19f80a5..8da1041e51f 100644 --- a/src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.component.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/common/primary/app/App.component.ts.mustache @@ -1,16 +1,16 @@ import { defineComponent } from "vue"; -// needle-jhipster-import +// jhipster-needle-app-import export default defineComponent({ name: 'App', components: {}, setup() { const appName='Jhipster-lite'; - // needle-jhipster-setup + // jhipster-needle-app-setup return { appName, - // needle-jhipster-return + // jhipster-needle-app-return }; }, }); diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.component.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.component.ts.mustache index 8303dcb004f..92ed4e62ac8 100644 --- a/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.component.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/primary/homepage/Homepage.component.ts.mustache @@ -3,6 +3,4 @@ import { defineComponent} from 'vue'; export default defineComponent({ name: 'Homepage', components: {}, - setup() { - }, }); diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/AuthenticationRepository.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/AuthenticationRepository.ts.mustache index fb92a2f06bb..dd2b6ec0af5 100644 --- a/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/AuthenticationRepository.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/AuthenticationRepository.ts.mustache @@ -1,5 +1,5 @@ import { Login } from '@/common/domain/Login'; -import { restLogin, torestLogin } from '@/common/secondary/restLogin'; +import { RestLogin, toRestLogin } from '@/common/secondary/RestLogin'; import { AxiosHttp } from '@/http/AxiosHttp'; import { AuthenticationService } from '@/common/domain/AuthenticationService'; @@ -12,15 +12,15 @@ export default class AuthenticationRepository implements AuthenticationService { constructor(private axiosHttp: AxiosHttp, private piniaInstance: Pinia) {} async authenticate(): Promise { - return await this.axiosHttp + return this.axiosHttp .get('/api/account', { headers: { Authorization: 'Bearer ' + this.getJwtToken() } }) .then(response => toUser(response.data)); } async login(login: Login): Promise { - const restLogin: restLogin = torestLogin(login); + const restLogin: RestLogin = toRestLogin(login); await this.axiosHttp - .post('/api/authenticate', restLogin) + .post('/api/authenticate', restLogin) .then(response => this.saveJwtTokenIntoStore(this.parseAuthorisationHeaders(response))); } @@ -37,8 +37,7 @@ export default class AuthenticationRepository implements AuthenticationService { parseAuthorisationHeaders(response: any): string { const bearerToken = response.headers.authorization; if (bearerToken && bearerToken.slice(0, 7) === 'Bearer ') { - const jwt = bearerToken.slice(7, bearerToken.length); - return jwt; + return bearerToken.slice(7, bearerToken.length); } else { return ''; } diff --git a/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/restLogin.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/RestLogin.ts.mustache similarity index 70% rename from src/main/resources/generator/client/vue/webapp/app/jwt/secondary/restLogin.ts.mustache rename to src/main/resources/generator/client/vue/webapp/app/jwt/secondary/RestLogin.ts.mustache index 1356a82e3e9..bef342d5a99 100644 --- a/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/restLogin.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/jwt/secondary/RestLogin.ts.mustache @@ -1,12 +1,12 @@ import { Login } from '@/common/domain/Login'; -export interface restLogin { +export interface RestLogin { username: string; password: string; rememberMe: boolean; } -export const torestLogin = (login: Login): restLogin => ({ +export const toRestLogin = (login: Login): RestLogin => ({ username: login.username, password: login.password, rememberMe: login.rememberMe, diff --git a/src/main/resources/generator/client/vue/webapp/app/router/router.ts.mustache b/src/main/resources/generator/client/vue/webapp/app/router/router.ts.mustache index a0aa9d92ee5..a2843d577f9 100644 --- a/src/main/resources/generator/client/vue/webapp/app/router/router.ts.mustache +++ b/src/main/resources/generator/client/vue/webapp/app/router/router.ts.mustache @@ -1,6 +1,6 @@ import { AppVue } from '@/common/primary/app'; import { createRouter, createWebHistory } from 'vue-router'; -// jhipster-needle-router-import +// jhipster-needle-router-imports const routes = [ @@ -21,4 +21,4 @@ const router = createRouter({ routes, }); -export default router; \ No newline at end of file +export default router; diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationServiceIT.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationServiceIT.java index 3fb1dba501c..dd76775a940 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationServiceIT.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/application/VueJwtApplicationServiceIT.java @@ -20,7 +20,7 @@ class VueJwtApplicationServiceIT { @Test void shouldAddVueJwt() { - Project project = tmpProjectWithPackageJsonPinia(); + Project project = tmpProjectWithPackageJson(); vueApplicationService.addVue(project); vueApplicationService.addPinia(project); vueJwtApplicationService.addJWT(project); @@ -30,7 +30,7 @@ void shouldAddVueJwt() { assertFileExist(project, COMMON + "/primary/login/Login.component.ts"); assertFileExist(project, COMMON + "/primary/login/Login.html"); assertFileExist(project, COMMON + "/primary/login/Login.vue"); - assertFileExist(project, COMMON + "/secondary/restLogin.ts"); + assertFileExist(project, COMMON + "/secondary/RestLogin.ts"); assertFileExist(project, COMMON + "/domain/AuthenticationService.ts"); assertFileExist(project, COMMON + "/domain/JWTStoreService.ts"); } diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainServiceTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainServiceTest.java index 092d2f99bdd..73a98f239b2 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainServiceTest.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainServiceTest.java @@ -37,7 +37,7 @@ void shouldAddVueJwt() { assertThatCode(() -> jwtDomainService.addJWT(project)).doesNotThrowAnyException(); - verify(projectRepository, times(6)).template(any(ProjectFile.class)); + verify(projectRepository, times(4)).template(any(ProjectFile.class)); } @Test diff --git a/tests-ci/generate.sh b/tests-ci/generate.sh index 44392e8d244..bc2328829d2 100755 --- a/tests-ci/generate.sh +++ b/tests-ci/generate.sh @@ -238,6 +238,7 @@ elif [[ $application == 'vueapp' ]]; then callApi "/api/developer-tools/frontend-maven-plugin" callApi "/api/clients/vue" callApi "/api/clients/vue/stores/pinia" + callApi "/api/clients/vue/jwt" callApi "/api/clients/cypress" elif [[ $application == 'svelteapp' ]]; then From 24949e1b9d66e75c13b88a74f3116ba3ff59a6c8 Mon Sep 17 00:00:00 2001 From: matthieulapatate Date: Wed, 1 Jun 2022 21:19:10 +0200 Subject: [PATCH 4/6] Fix smell code for generated app --- .../generator/client/vue/security/jwt/domain/VueJwt.java | 1 - .../vue/security/jwt/domain/VueJwtDomainService.java | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java index d051400b9fa..cc162bf6a64 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwt.java @@ -82,7 +82,6 @@ public static Map appComponent() { import { User } from '@/common/domain/User'; import { Router } from 'vue-router'; import { jwtStore } from '@/common/domain/JWTStoreService'; - import { inject, ref } from "vue"; """, "import", """ diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java index bf731073105..bbdccffb7d8 100644 --- a/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java +++ b/src/main/java/tech/jhipster/lite/generator/client/vue/security/jwt/domain/VueJwtDomainService.java @@ -84,6 +84,13 @@ public void addAppContext(Project project) { .forEach((line, needle) -> addNewNeedleLineToFile(project, line, destinationAppComponent, "App.component.ts", NEEDLE_APP + "-" + needle) ); + projectRepository.replaceText( + project, + destinationAppComponent, + "App.component.ts", + "import \\{ defineComponent \\} from \"vue\";", + "import { defineComponent, inject, ref } from \"vue\";" + ); } public void addLoginContext(Project project) { From f2a41986c5744511ad4025aa45b9ce46b32999b8 Mon Sep 17 00:00:00 2001 From: Matthieu Rioual Date: Thu, 2 Jun 2022 11:26:24 +0200 Subject: [PATCH 5/6] Add vue cucumber tests --- src/test/features/vue.feature | 48 +++++++++++++++++++ .../infrastructure/primary/rest/VueSteps.java | 17 +++++++ 2 files changed, 65 insertions(+) create mode 100644 src/test/features/vue.feature create mode 100644 src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueSteps.java diff --git a/src/test/features/vue.feature b/src/test/features/vue.feature new file mode 100644 index 00000000000..bf6b322ad9c --- /dev/null +++ b/src/test/features/vue.feature @@ -0,0 +1,48 @@ +Feature: Vue + + Scenario: Should initialize Vue application + When I generate vue application + Then I should have files in "src/main/webapp" + | index.html | + And I should have files in "src/main/webapp/app" + | env.d.ts | + | main.ts | + And I should have files in "src/main/webapp/app/router" + | router.ts | + And I should have files in "src/main/webapp/app/http" + | AxiosHttp.ts | + And I should have files in "src/main/webapp/app/common/domain" + | Logger.ts | + | Message.ts | + And I should have files in "src/main/webapp/app/common/secondary" + | ConsoleLogger.ts | + And I should have files in "src/main/webapp/app/common/primary/app" + | App.component.ts | + | App.html | + | App.vue | + | index.ts | + + + + Scenario: Should add jwt authentication to Vue application + When I add jwt to vue application + Then I should have files in "src/main/webapp/app/common/domain" + | AuthenticationService.ts | + | JWTStoreService.ts | + | Login.ts | + | User.ts | + | README.md | + And I should have files in "src/main/webapp/app/common/primary/homepage" + | Homepage.component.ts | + | Homepage.html | + | Homepage.vue | + | index.ts | + And I should have files in "src/main/webapp/app/common/primary/login" + | Login.component.ts | + | Login.html | + | Login.vue | + | index.ts | + And I should not have files in "src/main/webapp/app/common/secondary" + | AuthenticationRepository.ts| + | RestLogin.ts | + | UserDTO.ts | diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueSteps.java b/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueSteps.java new file mode 100644 index 00000000000..313dfd9ce83 --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueSteps.java @@ -0,0 +1,17 @@ +package tech.jhipster.lite.generator.client.vue.core.infrastructure.primary.rest; + +import io.cucumber.java.en.When; +import tech.jhipster.lite.generator.ModulesSteps; + +public class VueSteps extends ModulesSteps { + + @When("I generate vue application") + public void addVue() { + applyModuleForDefaultProject("/api/clients/vue"); + } + + @When("I add jwt to vue application") + public void addVueJwt() { + applyModuleForDefaultProject("/api/clients/vue/jwt"); + } +} From 9ec4442819ffb1567c9c6123bee190976f72a73c Mon Sep 17 00:00:00 2001 From: Matthieu Rioual Date: Thu, 2 Jun 2022 15:39:14 +0200 Subject: [PATCH 6/6] Cucumber test jwt --- src/test/features/vue.feature | 12 ++-- .../jhipster/lite/generator/ModulesSteps.java | 22 +++++- .../primary/rest/VueResourceIT.java | 70 ------------------- .../infrastructure/primary/rest/VueSteps.java | 4 +- 4 files changed, 27 insertions(+), 81 deletions(-) delete mode 100644 src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueResourceIT.java diff --git a/src/test/features/vue.feature b/src/test/features/vue.feature index bf6b322ad9c..0e357877205 100644 --- a/src/test/features/vue.feature +++ b/src/test/features/vue.feature @@ -22,8 +22,6 @@ Feature: Vue | App.vue | | index.ts | - - Scenario: Should add jwt authentication to Vue application When I add jwt to vue application Then I should have files in "src/main/webapp/app/common/domain" @@ -31,7 +29,6 @@ Feature: Vue | JWTStoreService.ts | | Login.ts | | User.ts | - | README.md | And I should have files in "src/main/webapp/app/common/primary/homepage" | Homepage.component.ts | | Homepage.html | @@ -42,7 +39,8 @@ Feature: Vue | Login.html | | Login.vue | | index.ts | - And I should not have files in "src/main/webapp/app/common/secondary" - | AuthenticationRepository.ts| - | RestLogin.ts | - | UserDTO.ts | + And I should have files in "src/main/webapp/app/common/secondary" + | AuthenticationRepository.ts | + | ConsoleLogger.ts | + | RestLogin.ts | + | UserDTO.ts | diff --git a/src/test/java/tech/jhipster/lite/generator/ModulesSteps.java b/src/test/java/tech/jhipster/lite/generator/ModulesSteps.java index 9085dd08b82..fd0044c4ceb 100644 --- a/src/test/java/tech/jhipster/lite/generator/ModulesSteps.java +++ b/src/test/java/tech/jhipster/lite/generator/ModulesSteps.java @@ -21,10 +21,12 @@ public abstract class ModulesSteps { @Autowired private TestRestTemplate rest; - protected void applyModuleForDefaultProject(String moduleUrl) { + protected void applyModuleForDefaultProject(String... moduleUrl) { ProjectDTO project = newDefaultProjectDto(); - post(moduleUrl, JsonHelper.writeAsString(project)); + for (String url : moduleUrl) { + post(url, JsonHelper.writeAsString(project)); + } } protected void applyModuleForDefaultProjectWithMavenFile(String moduleUrl) { @@ -51,6 +53,22 @@ private static void addPomToproject(String folder) { } } + private static void addPackageToproject(String folder) { + Path folderPath = Paths.get(folder); + try { + Files.createDirectories(folderPath); + } catch (IOException e) { + throw new AssertionError(e); + } + + Path pomPath = folderPath.resolve("pom.xml"); + try { + Files.copy(Paths.get("src/test/resources/projects/maven/pom.xml"), pomPath); + } catch (IOException e) { + throw new AssertionError(e); + } + } + private void post(String uri, String content) { rest.exchange(uri, HttpMethod.POST, new HttpEntity<>(content, jsonHeaders()), Void.class); } diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueResourceIT.java b/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueResourceIT.java deleted file mode 100644 index e5952bb3e60..00000000000 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueResourceIT.java +++ /dev/null @@ -1,70 +0,0 @@ -package tech.jhipster.lite.generator.client.vue.core.infrastructure.primary.rest; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static tech.jhipster.lite.TestUtils.*; -import static tech.jhipster.lite.common.domain.FileUtils.*; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import tech.jhipster.lite.IntegrationTest; -import tech.jhipster.lite.TestUtils; -import tech.jhipster.lite.generator.client.vue.core.application.VueAssert; -import tech.jhipster.lite.generator.init.application.InitApplicationService; -import tech.jhipster.lite.generator.project.domain.Project; -import tech.jhipster.lite.generator.project.infrastructure.primary.dto.ProjectDTO; - -@IntegrationTest -@AutoConfigureMockMvc -class VueResourceIT { - - @Autowired - MockMvc mockMvc; - - @Autowired - InitApplicationService initApplicationService; - - @Test - void shouldAddVue() throws Exception { - ProjectDTO projectDTO = readFileToObject("json/chips.json", ProjectDTO.class).folder(tmpDirForTest()); - Project project = ProjectDTO.toProject(projectDTO); - initApplicationService.init(project); - - mockMvc - .perform(post("/api/clients/vue").contentType(MediaType.APPLICATION_JSON).content(TestUtils.convertObjectToJsonBytes(projectDTO))) - .andExpect(status().isOk()); - - VueAssert.assertDependency(project); - VueAssert.assertScripts(project); - - VueAssert.assertViteConfigFiles(project); - VueAssert.assertRootFiles(project); - VueAssert.assertAppFiles(project); - VueAssert.assertAppWithCss(project); - - VueAssert.assertJestSonar(project); - } - - @Test - void shouldAddPinia() throws Exception { - ProjectDTO projectDTO = readFileToObject("json/chips.json", ProjectDTO.class).folder(tmpDirForTest()); - Project project = ProjectDTO.toProject(projectDTO); - initApplicationService.init(project); - - mockMvc - .perform(post("/api/clients/vue").contentType(MediaType.APPLICATION_JSON).content(TestUtils.convertObjectToJsonBytes(projectDTO))) - .andExpect(status().isOk()); - - mockMvc - .perform( - post("/api/clients/vue/stores/pinia") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtils.convertObjectToJsonBytes(projectDTO)) - ) - .andExpect(status().isOk()); - VueAssert.assertPiniaDependency(project); - } -} diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueSteps.java b/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueSteps.java index 313dfd9ce83..3579b810b51 100644 --- a/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueSteps.java +++ b/src/test/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/rest/VueSteps.java @@ -7,11 +7,11 @@ public class VueSteps extends ModulesSteps { @When("I generate vue application") public void addVue() { - applyModuleForDefaultProject("/api/clients/vue"); + applyModuleForDefaultProject("/api/inits/full", "/api/clients/vue"); } @When("I add jwt to vue application") public void addVueJwt() { - applyModuleForDefaultProject("/api/clients/vue/jwt"); + applyModuleForDefaultProject("/api/inits/full", "/api/clients/vue", "/api/clients/vue/stores/pinia", "/api/clients/vue/jwt"); } }