Skip to content

Commit

Permalink
Vue: account with authentication JWT
Browse files Browse the repository at this point in the history
  • Loading branch information
abdelfetah18 committed Sep 26, 2023
1 parent fab42ae commit 16cacfb
Show file tree
Hide file tree
Showing 26 changed files with 726 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public enum JHLiteModuleSlug implements JHipsterModuleSlugFactory {
SVELTE_CORE("svelte-core"),
TYPESCRIPT("typescript"),
VUE_CORE("vue-core"),
VUE_JWT("vue-jwt"),
VUE_PINIA("vue-pinia");

private static final Map<String, JHLiteModuleSlug> moduleSlugMap = Stream
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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.VueJwtModuleFactory;
import tech.jhipster.lite.module.domain.JHipsterModule;
import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;

@Service
public class VueJwtApplicationService {

private final VueJwtModuleFactory factory;

public VueJwtApplicationService() {
factory = new VueJwtModuleFactory();
}

public JHipsterModule buildModule(JHipsterModuleProperties properties) {
return factory.buildModule(properties);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package tech.jhipster.lite.generator.client.vue.security.jwt.domain;

import static tech.jhipster.lite.module.domain.JHipsterModule.*;
import static tech.jhipster.lite.module.domain.packagejson.VersionSource.*;

import tech.jhipster.lite.module.domain.JHipsterModule;
import tech.jhipster.lite.module.domain.file.JHipsterDestination;
import tech.jhipster.lite.module.domain.file.JHipsterSource;
import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
import tech.jhipster.lite.module.domain.replacement.TextNeedleBeforeReplacer;
import tech.jhipster.lite.shared.error.domain.Assert;

public class VueJwtModuleFactory {

private static final JHipsterSource SOURCE = from("client/vue");

private static final JHipsterSource APP_SOURCE = SOURCE.append("webapp/app");
private static final JHipsterSource TEST_JAVASCRIPT_SOURCE = SOURCE.append("test/spec");

private static final JHipsterDestination APP_DESTINATION = to("src/main/webapp/app");
private static final JHipsterDestination TEST_DESTINATION = to("src/test/javascript/spec/");

private static final String PROVIDE_REST_AUTH = "app.provide(\"restAuth\", new RestAuth(axios.create({ baseURL: '' })));";
private static final String PROVIDE_LOCAL_STORAGE_AUTH = "app.provide(\"authStore\", new LocalStorageAuth(localStorage));";
private static final TextNeedleBeforeReplacer IMPORT_CREATE_APP = lineBeforeText("import { createApp } from 'vue';");

public JHipsterModule buildModule(JHipsterModuleProperties properties) {
Assert.notNull("properties", properties);

//@formatter:off
return moduleBuilder(properties)
.packageJson()
.addType("module")
.addDependency(packageName("axios"), VUE)
.addDevDependency(packageName("sass"), VUE)
.and()
.files()
.batch(APP_SOURCE.append("login/primary/loginForm"), APP_DESTINATION.append("login/primary/loginForm/"))
.addTemplate("index.ts")
.addTemplate("LoginForm.component.ts")
.addTemplate("LoginFormVue.vue")
.addTemplate("login-form.html")
.and()
.batch(APP_SOURCE.append("login/primary/loginModal"), APP_DESTINATION.append("login/primary/loginModal/"))
.addTemplate("index.ts")
.addTemplate("LoginModal.component.ts")
.addTemplate("LoginModalVue.vue")
.addTemplate("login-modal.html")
.and()
.add(APP_SOURCE.template("login/domain/Auth.ts"), APP_DESTINATION.append("login/domain/Auth.ts"))
.add(APP_SOURCE.template("login/domain/AuthRepository.ts"), APP_DESTINATION.append("login/domain/AuthRepository.ts"))
.add(APP_SOURCE.template("login/domain/UserSession.ts"), APP_DESTINATION.append("login/domain/UserSession.ts"))
.add(APP_SOURCE.template("login/secondary/LocalStorageAuth.ts"), APP_DESTINATION.append("login/secondary/LocalStorageAuth.ts"))
.add(APP_SOURCE.template("login/secondary/RestAuth.ts"), APP_DESTINATION.append("login/secondary/RestAuth.ts"))
.add(TEST_JAVASCRIPT_SOURCE.template("login/secondary/RestAuth.spec.ts"), TEST_DESTINATION.append("login/secondary/RestAuth.spec.ts"))
.add(TEST_JAVASCRIPT_SOURCE.template("login/secondary/LocalStorageAuth.spec.ts"), TEST_DESTINATION.append("login/secondary/LocalStorageAuth.spec.ts"))
.add(TEST_JAVASCRIPT_SOURCE.template("login/primary/LoginForm.spec.ts"), TEST_DESTINATION.append("login/primary/LoginForm.spec.ts"))
.and()
.mandatoryReplacements()
.in(path("src/main/webapp/app/common/primary/homepage/Homepage.component.ts"))
.add(lineBeforeText("export default {"), "import { LoginFormVue } from '@/login/primary/loginForm';" + LINE_BREAK)
.add(lineBeforeText("data: () => {"), properties.indentation().times(1) + "components: { LoginFormVue }," + LINE_BREAK)
.and()
.in(path("src/main/webapp/app/common/primary/homepage/Homepage.html"))
.add(lineBeforeText("</div>"), properties.indentation().times(1) + "<LoginFormVue />")
.and()
.in(path("src/main/webapp/app/main.ts"))
.add(lineBeforeText("app.mount('#app');"), PROVIDE_REST_AUTH)
.add(lineBeforeText("app.mount('#app');"), PROVIDE_LOCAL_STORAGE_AUTH)
.add(IMPORT_CREATE_APP, "import { RestAuth } from './login/secondary/RestAuth';")
.add(IMPORT_CREATE_APP, "import axios from 'axios';")
.add(IMPORT_CREATE_APP, "import { LocalStorageAuth } from './login/secondary/LocalStorageAuth';")
.and()
.and()
.build();
//@formatter:on
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tech.jhipster.lite.generator.client.vue.security.jwt.infrastructure.primary;

import static tech.jhipster.lite.generator.JHLiteModuleSlug.*;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tech.jhipster.lite.generator.client.vue.security.jwt.application.VueJwtApplicationService;
import tech.jhipster.lite.module.domain.resource.JHipsterModuleOrganization;
import tech.jhipster.lite.module.domain.resource.JHipsterModulePropertiesDefinition;
import tech.jhipster.lite.module.domain.resource.JHipsterModuleResource;

@Configuration
class VueJwtModuleConfiguration {

@Bean
JHipsterModuleResource vueJwtModule(VueJwtApplicationService vueJwt) {
return JHipsterModuleResource
.builder()
.slug(VUE_JWT)
.propertiesDefinition(JHipsterModulePropertiesDefinition.builder().addIndentation().build())
.apiDoc("Vue", "Add JWT Login Vue")
.organization(JHipsterModuleOrganization.builder().addDependency(VUE_CORE).build())
.tags("client", "vue", "jwt")
.factory(vueJwt::buildModule);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@tech.jhipster.lite.BusinessContext
package tech.jhipster.lite.generator.client.vue.security.jwt;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, it, expect } from 'vitest';

import { getLocalStorage, removeLocalStorage, setLocalStorage } from '@/common/services/storage';

describe('setLocalStorage function', () => {
it('Should set item in localStorage', () => {
const key = 'key';
const value = 'value';
setLocalStorage(key, value);
expect(getLocalStorage(key)).toEqual(value);
});
});

describe('removeLocalStorage function', () => {
it('Should remove item from localstorage', () => {
const key = 'key';
const value = 'value';
setLocalStorage(key, value);
expect(getLocalStorage(key)).toEqual(value);
removeLocalStorage(key);
expect(getLocalStorage(key)).toBeNull();
});
});

describe('getLocalStorage function', () => {
it('Should get item from localstorage', () => {
const key = 'key';
const value = 'value';
setLocalStorage(key, value);
expect(getLocalStorage(key)).toEqual(value);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import axios from 'axios';
import { vi, describe, it, expect, SpyInstance } from 'vitest';
import { mount, VueWrapper } from '@vue/test-utils';
import { LoginFormVue } from '@/login/primary/loginForm/index';
import { RestAuth } from '@/login/secondary/RestAuth';
import { LocalStorageAuth } from '@/login/secondary/LocalStorageAuth';

let wrapper: VueWrapper;
let restAuthSpy: SpyInstance

const wrap = () => {
const restAuth = new RestAuth(axios.create({ baseURL: '' }));

wrapper = mount(LoginFormVue,{
global: {
provide:{
restAuth,
authStore: new LocalStorageAuth(localStorage)
}
}
});

restAuthSpy = vi.spyOn(restAuth, 'get');
restAuthSpy.mockImplementationOnce(() => Promise.resolve({ id_token: '123' }));
};


const selector = (name : string) : string => `[data-selector="${name}"]`;

const login = async () => {
const submitButton = wrapper.find(selector("submitButton"));
const usernameInput = wrapper.find(selector("usernameInput"));
const passwordInput = wrapper.find(selector("passwordInput"));
const rememberMeInput = wrapper.find(selector("rememberMeInput"));
await usernameInput.setValue("admin");
await wrapper.vm.$nextTick();
await passwordInput.setValue("admin");
await wrapper.vm.$nextTick();
await rememberMeInput.setValue("true");
await wrapper.vm.$nextTick();
await submitButton.trigger("click");
await wrapper.vm.$nextTick();
};

describe('loginForm', () => {
it('should render the login button without crashing', () => {
wrap();
expect(wrapper.exists()).toBeTruthy();
});

it('render the modal on login button click', async () => {
wrap();
const loginButton = wrapper.find(selector("loginButton"));
await loginButton.trigger('click')
await wrapper.vm.$nextTick();
const modal = wrapper.find(selector("modal"));
expect(modal.exists()).toBeTruthy();
});

it('should show error message', async () => {
wrap();
const loginButton = wrapper.find(selector("loginButton"));
await loginButton.trigger('click');
await wrapper.vm.$nextTick();
const submitButton = wrapper.find(selector("submitButton"));
await submitButton.trigger('click')
await wrapper.vm.$nextTick();
const errorMessage = wrapper.find(selector("errorMessage"));
expect(errorMessage.exists()).toBeTruthy();
});

it('should logout when clicking logout button', async () => {
wrap();
await wrapper.find(selector("loginButton")).trigger('click');
await wrapper.vm.$nextTick();
await login();
await wrapper.find(selector("logoutButton")).trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.find(selector("loginButton")).exists()).toBeTruthy();
});

it('should close the modal when close button is pressed', async () => {
wrap();
await wrapper.find(selector("loginButton")).trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.find(selector("modal")).exists()).toBeTruthy();
await wrapper.find(selector("closeButton")).trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.find(selector("modal")).exists()).toBeFalsy();
});

it('should close when clicking submit button with complete fields', async () => {
wrap();
await wrapper.find(selector("loginButton")).trigger('click');
await wrapper.vm.$nextTick();
await login();
expect(restAuthSpy).toHaveBeenCalledTimes(1);
});

it('should show logout button if already logged in', async () => {
localStorage.setItem('auth',JSON.stringify({ id_token: '123', username: '123' }));

wrap();
await wrapper.vm.$nextTick();

expect(wrapper.find(selector("logoutButton")).exists()).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { UserSession } from '@/login/domain/UserSession';
import { LocalStorageAuth } from '@/login/secondary/LocalStorageAuth';
import { describe, it, expect } from 'vitest';

const fakeStorage = (): Storage => {
let store: { [key: string]: string } = {};
return {
getItem: (key: string) => store[key],
setItem: (key: string, value: string) => (store[key] = value),
removeItem: (key: string) => delete store[key],
clear: () => (store = {}),
get length() {
return Object.keys(store).length;
},
key: (index: number) => Object.keys(store)[index],
};
};

const fakeData: UserSession = {
id_token: "123",
username: "456"
};

describe('LocalStorageAuth', () => {
it('should store auth data', () => {
const storage = fakeStorage();
const authStore = new LocalStorageAuth(storage);
authStore.store(fakeData);
expect(JSON.parse(storage.getItem("auth")!)).toEqual(fakeData);
});

it('should clear the auth data from the storage', async () => {
const storage = fakeStorage();
const authStore = new LocalStorageAuth(storage);
authStore.store(fakeData);
expect(JSON.parse(storage.getItem("auth")!)).toEqual(fakeData);
authStore.clear();
expect(storage.getItem("auth")).toEqual(undefined);
});

it('should get the auth data from the storage', async () => {
const storage = fakeStorage();
const authStore = new LocalStorageAuth(storage);
authStore.store(fakeData);
expect(authStore.get()).toEqual(fakeData);
});
});
Loading

0 comments on commit 16cacfb

Please sign in to comment.