#7190 - Renew tokenMultimedia #278
|
@ -3,11 +3,14 @@ import { useRole } from './useRole';
|
||||||
import { useUserConfig } from './useUserConfig';
|
import { useUserConfig } from './useUserConfig';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import useNotify from './useNotify';
|
import useNotify from './useNotify';
|
||||||
|
import { useTokenConfig } from './useTokenConfig';
|
||||||
const TOKEN_MULTIMEDIA = 'tokenMultimedia';
|
const TOKEN_MULTIMEDIA = 'tokenMultimedia';
|
||||||
const TOKEN = 'token';
|
const TOKEN = 'token';
|
||||||
|
|
||||||
export function useSession() {
|
export function useSession() {
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
let isCheckingToken = false;
|
||||||
|
let intervalId = null;
|
||||||
|
|
||||||
function getToken() {
|
function getToken() {
|
||||||
const localToken = localStorage.getItem(TOKEN);
|
const localToken = localStorage.getItem(TOKEN);
|
||||||
|
@ -22,10 +25,24 @@ export function useSession() {
|
||||||
return localTokenMultimedia || sessionTokenMultimedia || '';
|
return localTokenMultimedia || sessionTokenMultimedia || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function setToken(data) {
|
function setSession(data) {
|
||||||
const storage = data.keepLogin ? localStorage : sessionStorage;
|
let keepLogin = data.keepLogin;
|
||||||
|
const storage = keepLogin ? localStorage : sessionStorage;
|
||||||
storage.setItem(TOKEN, data.token);
|
storage.setItem(TOKEN, data.token);
|
||||||
storage.setItem(TOKEN_MULTIMEDIA, data.tokenMultimedia);
|
storage.setItem(TOKEN_MULTIMEDIA, data.tokenMultimedia);
|
||||||
|
storage.setItem('created', data.created);
|
||||||
|
storage.setItem('ttl', data.ttl);
|
||||||
|
sessionStorage.setItem('keepLogin', keepLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
function keepLogin() {
|
||||||
|
return sessionStorage.getItem('keepLogin');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setToken({ token, tokenMultimedia }) {
|
||||||
|
const storage = keepLogin() ? localStorage : sessionStorage;
|
||||||
|
storage.setItem(TOKEN, token);
|
||||||
|
storage.setItem(TOKEN_MULTIMEDIA, tokenMultimedia);
|
||||||
}
|
}
|
||||||
async function destroyToken(url, storage, key) {
|
async function destroyToken(url, storage, key) {
|
||||||
if (storage.getItem(key)) {
|
if (storage.getItem(key)) {
|
||||||
|
@ -45,11 +62,15 @@ export function useSession() {
|
||||||
tokenMultimedia: 'Accounts/logout',
|
tokenMultimedia: 'Accounts/logout',
|
||||||
token: 'VnUsers/logout',
|
token: 'VnUsers/logout',
|
||||||
};
|
};
|
||||||
|
const storage = keepLogin() ? localStorage : sessionStorage;
|
||||||
|
|
||||||
for (const [key, url] of Object.entries(tokens)) {
|
for (const [key, url] of Object.entries(tokens)) {
|
||||||
await destroyToken(url, localStorage, key);
|
await destroyToken(url, storage, key);
|
||||||
await destroyToken(url, sessionStorage, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localStorage.clear();
|
||||||
|
sessionStorage.clear();
|
||||||
|
|
||||||
const { setUser } = useState();
|
const { setUser } = useState();
|
||||||
|
|
||||||
setUser({
|
setUser({
|
||||||
|
@ -59,22 +80,75 @@ export function useSession() {
|
||||||
lang: '',
|
lang: '',
|
||||||
darkMode: null,
|
darkMode: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
stopRenewer();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(token, tokenMultimedia, keepLogin) {
|
async function login(data) {
|
||||||
setToken({ token, tokenMultimedia, keepLogin });
|
setSession(data);
|
||||||
|
|
||||||
await useRole().fetch();
|
await useRole().fetch();
|
||||||
await useUserConfig().fetch();
|
await useUserConfig().fetch();
|
||||||
|
await useTokenConfig().fetch();
|
||||||
|
|
||||||
|
startInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLoggedIn() {
|
function isLoggedIn() {
|
||||||
const localToken = localStorage.getItem(TOKEN);
|
const localToken = localStorage.getItem(TOKEN);
|
||||||
const sessionToken = sessionStorage.getItem(TOKEN);
|
const sessionToken = sessionStorage.getItem(TOKEN);
|
||||||
|
startInterval();
|
||||||
return !!(localToken || sessionToken);
|
return !!(localToken || sessionToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startInterval() {
|
||||||
|
stopRenewer();
|
||||||
|
const renewPeriod = +sessionStorage.getItem('renewPeriod');
|
||||||
|
if (!renewPeriod) return;
|
||||||
|
intervalId = setInterval(() => checkValidity(), renewPeriod * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRenewer() {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renewToken() {
|
||||||
|
const _token = getToken();
|
||||||
|
const token = await axios.post('VnUsers/renewToken', {
|
||||||
|
headers: { Authorization: _token },
|
||||||
|
});
|
||||||
|
const _tokenMultimedia = getTokenMultimedia();
|
||||||
|
const tokenMultimedia = await axios.post('VnUsers/renewToken', {
|
||||||
|
headers: { Authorization: _tokenMultimedia },
|
||||||
|
});
|
||||||
|
setToken({ token: token.data.id, tokenMultimedia: tokenMultimedia.data.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkValidity() {
|
||||||
|
const { getTokenConfig } = useState();
|
||||||
|
|
||||||
|
const tokenConfig = getTokenConfig() ?? sessionStorage.getItem('tokenConfig');
|
||||||
|
const storage = keepLogin() ? localStorage : sessionStorage;
|
||||||
|
|
||||||
|
const created = +storage.getItem('created');
|
||||||
|
const ttl = +storage.getItem('ttl');
|
||||||
|
|
||||||
|
if (isCheckingToken || !created) return;
|
||||||
|
isCheckingToken = true;
|
||||||
|
|
||||||
|
const renewPeriodInSeconds = Math.min(ttl, tokenConfig.value.renewPeriod) * 1000;
|
||||||
|
|
||||||
|
const maxDate = created + renewPeriodInSeconds;
|
||||||
|
const now = new Date().getTime();
|
||||||
|
|
||||||
|
if (isNaN(renewPeriodInSeconds) || now <= maxDate) {
|
||||||
|
|||||||
|
return (isCheckingToken = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await renewToken();
|
||||||
|
isCheckingToken = false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getToken,
|
getToken,
|
||||||
getTokenMultimedia,
|
getTokenMultimedia,
|
||||||
|
@ -82,5 +156,8 @@ export function useSession() {
|
||||||
destroy,
|
destroy,
|
||||||
login,
|
login,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
|
checkValidity,
|
||||||
|
setSession,
|
||||||
|
renewToken,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ const user = ref({
|
||||||
});
|
});
|
||||||
|
|
||||||
const roles = ref([]);
|
const roles = ref([]);
|
||||||
|
const tokenConfig = ref({});
|
||||||
const drawer = ref(true);
|
const drawer = ref(true);
|
||||||
const headerMounted = ref(false);
|
const headerMounted = ref(false);
|
||||||
|
|
||||||
|
@ -52,6 +53,15 @@ export function useState() {
|
||||||
function setRoles(data) {
|
function setRoles(data) {
|
||||||
roles.value = data;
|
roles.value = data;
|
||||||
}
|
}
|
||||||
|
function getTokenConfig() {
|
||||||
|
return computed(() => {
|
||||||
|
return tokenConfig.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTokenConfig(data) {
|
||||||
|
tokenConfig.value = data;
|
||||||
|
}
|
||||||
|
|
||||||
function set(name, data) {
|
function set(name, data) {
|
||||||
state.value[name] = ref(data);
|
state.value[name] = ref(data);
|
||||||
|
@ -70,6 +80,8 @@ export function useState() {
|
||||||
setUser,
|
setUser,
|
||||||
getRoles,
|
getRoles,
|
||||||
setRoles,
|
setRoles,
|
||||||
|
getTokenConfig,
|
||||||
|
setTokenConfig,
|
||||||
set,
|
set,
|
||||||
get,
|
get,
|
||||||
unset,
|
unset,
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useState } from './useState';
|
||||||
|
import useNotify from './useNotify';
|
||||||
|
|
||||||
|
export function useTokenConfig() {
|
||||||
|
const state = useState();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get('AccessTokenConfigs/findOne', {
|
||||||
|
filter: { fields: ['renewInterval', 'renewPeriod'] },
|
||||||
|
});
|
||||||
|
if (!data) return;
|
||||||
|
state.setTokenConfig(data);
|
||||||
|
sessionStorage.setItem('renewPeriod', data.renewPeriod);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
notify('errors.tokenConfig', 'negative');
|
||||||
|
console.error('Error fetching token config:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetch,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
}
|
|
@ -106,6 +106,7 @@ errors:
|
||||||
statusBadGateway: It seems that the server has fall down
|
statusBadGateway: It seems that the server has fall down
|
||||||
statusGatewayTimeout: Could not contact the server
|
statusGatewayTimeout: Could not contact the server
|
||||||
userConfig: Error fetching user config
|
userConfig: Error fetching user config
|
||||||
|
tokenConfig: Error fetching token config
|
||||||
writeRequest: The requested operation could not be completed
|
writeRequest: The requested operation could not be completed
|
||||||
login:
|
login:
|
||||||
title: Login
|
title: Login
|
||||||
|
|
|
@ -106,6 +106,7 @@ errors:
|
||||||
statusBadGateway: Parece ser que el servidor ha caído
|
statusBadGateway: Parece ser que el servidor ha caído
|
||||||
statusGatewayTimeout: No se ha podido contactar con el servidor
|
statusGatewayTimeout: No se ha podido contactar con el servidor
|
||||||
userConfig: Error al obtener configuración de usuario
|
userConfig: Error al obtener configuración de usuario
|
||||||
|
tokenConfig: Error al obtener configuración de token
|
||||||
writeRequest: No se pudo completar la operación solicitada
|
writeRequest: No se pudo completar la operación solicitada
|
||||||
login:
|
login:
|
||||||
title: Inicio de sesión
|
title: Inicio de sesión
|
||||||
|
|
|
@ -38,7 +38,13 @@ async function onSubmit() {
|
||||||
|
|
||||||
if (!multimediaToken) return;
|
if (!multimediaToken) return;
|
||||||
|
|
||||||
await session.login(data.token, multimediaToken.id, keepLogin.value);
|
const login = {
|
||||||
|
...data,
|
||||||
|
created: Date.now(),
|
||||||
|
tokenMultimedia: multimediaToken.id,
|
||||||
|
keepLogin: keepLogin.value,
|
||||||
|
};
|
||||||
|
await session.login(login);
|
||||||
|
|
||||||
quasar.notify({
|
quasar.notify({
|
||||||
message: t('login.loginSuccess'),
|
message: t('login.loginSuccess'),
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { useSession } from 'src/composables/useSession';
|
||||||
import { useRole } from 'src/composables/useRole';
|
import { useRole } from 'src/composables/useRole';
|
||||||
import { useUserConfig } from 'src/composables/useUserConfig';
|
import { useUserConfig } from 'src/composables/useUserConfig';
|
||||||
import { toLowerCamel } from 'src/filters';
|
import { toLowerCamel } from 'src/filters';
|
||||||
|
import { useTokenConfig } from 'src/composables/useTokenConfig';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
|
@ -55,6 +56,7 @@ export default route(function (/* { store, ssrContext } */) {
|
||||||
if (stateRoles.length === 0) {
|
if (stateRoles.length === 0) {
|
||||||
await useRole().fetch();
|
await useRole().fetch();
|
||||||
await useUserConfig().fetch();
|
await useUserConfig().fetch();
|
||||||
|
await useTokenConfig().fetch();
|
||||||
}
|
}
|
||||||
const matches = to.matched;
|
const matches = to.matched;
|
||||||
const hasRequiredRoles = matches.every((route) => {
|
const hasRequiredRoles = matches.every((route) => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { vi, describe, expect, it } from 'vitest';
|
import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest';
|
||||||
import { axios } from 'app/test/vitest/helper';
|
import { axios, flushPromises } from 'app/test/vitest/helper';
|
||||||
import { useSession } from 'composables/useSession';
|
import { useSession } from 'composables/useSession';
|
||||||
import { useState } from 'composables/useState';
|
import { useState } from 'composables/useState';
|
||||||
|
|
||||||
|
@ -63,73 +63,148 @@ describe('session', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('login', () => {
|
describe(
|
||||||
const expectedUser = {
|
'login',
|
||||||
id: 999,
|
() => {
|
||||||
name: `T'Challa`,
|
const expectedUser = {
|
||||||
nickname: 'Black Panther',
|
id: 999,
|
||||||
lang: 'en',
|
name: `T'Challa`,
|
||||||
userConfig: {
|
nickname: 'Black Panther',
|
||||||
darkMode: false,
|
lang: 'en',
|
||||||
},
|
userConfig: {
|
||||||
};
|
darkMode: false,
|
||||||
const rolesData = [
|
|
||||||
{
|
|
||||||
role: {
|
|
||||||
name: 'salesPerson',
|
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
{
|
const rolesData = [
|
||||||
role: {
|
{
|
||||||
name: 'admin',
|
role: {
|
||||||
|
name: 'salesPerson',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
];
|
role: {
|
||||||
|
name: 'admin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
it('should fetch the user roles and then set token in the sessionStorage', async () => {
|
it('should fetch the user roles and then set token in the sessionStorage', async () => {
|
||||||
const expectedRoles = ['salesPerson', 'admin'];
|
const expectedRoles = ['salesPerson', 'admin'];
|
||||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||||
data: { roles: rolesData, user: expectedUser },
|
data: { roles: rolesData, user: expectedUser },
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedToken = 'mySessionToken';
|
||||||
|
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
|
||||||
|
const keepLogin = false;
|
||||||
|
|
||||||
|
await session.login({
|
||||||
|
token: expectedToken,
|
||||||
|
tokenMultimedia: expectedTokenMultimedia,
|
||||||
|
keepLogin,
|
||||||
|
});
|
||||||
|
|
||||||
|
const roles = state.getRoles();
|
||||||
|
const localToken = localStorage.getItem('token');
|
||||||
|
const sessionToken = sessionStorage.getItem('token');
|
||||||
|
|
||||||
|
expect(roles.value).toEqual(expectedRoles);
|
||||||
|
expect(localToken).toBeNull();
|
||||||
|
expect(sessionToken).toEqual(expectedToken);
|
||||||
|
|
||||||
|
await session.destroy(); // this clears token and user for any other test
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedToken = 'mySessionToken';
|
it('should fetch the user roles and then set token in the localStorage', async () => {
|
||||||
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
|
const expectedRoles = ['salesPerson', 'admin'];
|
||||||
const keepLogin = false;
|
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||||
|
data: { roles: rolesData, user: expectedUser },
|
||||||
|
});
|
||||||
|
|
||||||
await session.login(expectedToken,expectedTokenMultimedia, keepLogin);
|
const expectedToken = 'myLocalToken';
|
||||||
|
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
|
||||||
|
const keepLogin = true;
|
||||||
|
|
||||||
const roles = state.getRoles();
|
await session.login({
|
||||||
const localToken = localStorage.getItem('token');
|
token: expectedToken,
|
||||||
const sessionToken = sessionStorage.getItem('token');
|
tokenMultimedia: expectedTokenMultimedia,
|
||||||
|
keepLogin,
|
||||||
|
});
|
||||||
|
|
||||||
expect(roles.value).toEqual(expectedRoles);
|
const roles = state.getRoles();
|
||||||
expect(localToken).toBeNull();
|
const localToken = localStorage.getItem('token');
|
||||||
expect(sessionToken).toEqual(expectedToken);
|
const sessionToken = sessionStorage.getItem('token');
|
||||||
|
|
||||||
await session.destroy(); // this clears token and user for any other test
|
expect(roles.value).toEqual(expectedRoles);
|
||||||
|
expect(localToken).toEqual(expectedToken);
|
||||||
|
expect(sessionToken).toBeNull();
|
||||||
|
|
||||||
|
await session.destroy(); // this clears token and user for any other test
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('RenewToken', () => {
|
||||||
|
const expectedToken = 'myToken';
|
||||||
|
const expectedTokenMultimedia = 'myTokenMultimedia';
|
||||||
|
const currentDate = new Date();
|
||||||
|
beforeAll(() => {
|
||||||
|
const tokenConfig = {
|
||||||
|
id: 1,
|
||||||
|
renewPeriod: 21600,
|
||||||
|
courtesyTime: 60,
|
||||||
|
renewInterval: 300,
|
||||||
|
};
|
||||||
|
state.setTokenConfig(tokenConfig);
|
||||||
|
sessionStorage.setItem('renewPeriod', 1);
|
||||||
});
|
});
|
||||||
|
it('NOT Should renewToken', async () => {
|
||||||
|
const data = {
|
||||||
|
token: expectedToken,
|
||||||
|
tokenMultimedia: expectedTokenMultimedia,
|
||||||
|
keepLogin: false,
|
||||||
|
ttl: 1,
|
||||||
|
created: Date.now(),
|
||||||
|
};
|
||||||
|
session.setSession(data);
|
||||||
|
expect(sessionStorage.getItem('keepLogin')).toBeFalsy();
|
||||||
|
expect(sessionStorage.getItem('created')).toBeDefined();
|
||||||
|
expect(sessionStorage.getItem('ttl')).toEqual(1);
|
||||||
|
await session.checkValidity();
|
||||||
|
expect(sessionStorage.getItem('token')).toEqual(expectedToken);
|
||||||
|
expect(sessionStorage.getItem('tokenMultimedia')).toEqual(
|
||||||
|
expectedTokenMultimedia
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Should renewToken', async () => {
|
||||||
|
currentDate.setMinutes(currentDate.getMinutes() - 100);
|
||||||
|
const data = {
|
||||||
|
token: expectedToken,
|
||||||
|
tokenMultimedia: expectedTokenMultimedia,
|
||||||
|
keepLogin: false,
|
||||||
|
ttl: 1,
|
||||||
|
created: currentDate,
|
||||||
|
};
|
||||||
|
session.setSession(data);
|
||||||
|
|
||||||
it('should fetch the user roles and then set token in the localStorage', async () => {
|
vi.spyOn(axios, 'post')
|
||||||
const expectedRoles = ['salesPerson', 'admin'];
|
.mockResolvedValueOnce({
|
||||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
data: { id: '' },
|
||||||
data: { roles: rolesData, user: expectedUser },
|
})
|
||||||
});
|
.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
const expectedToken = 'myLocalToken';
|
id: '',
|
||||||
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
|
},
|
||||||
const keepLogin = true;
|
});
|
||||||
|
expect(sessionStorage.getItem('keepLogin')).toBeFalsy();
|
||||||
await session.login(expectedToken, expectedTokenMultimedia, keepLogin);
|
expect(sessionStorage.getItem('created')).toBeDefined();
|
||||||
|
expect(sessionStorage.getItem('ttl')).toEqual(1);
|
||||||
const roles = state.getRoles();
|
await session.checkValidity();
|
||||||
const localToken = localStorage.getItem('token');
|
expect(sessionStorage.getItem('token')).not.toEqual(expectedToken);
|
||||||
const sessionToken = sessionStorage.getItem('token');
|
expect(sessionStorage.getItem('tokenMultimedia')).not.toEqual(
|
||||||
|
expectedTokenMultimedia
|
||||||
expect(roles.value).toEqual(expectedRoles);
|
);
|
||||||
expect(localToken).toEqual(expectedToken);
|
|
||||||
expect(sessionToken).toBeNull();
|
|
||||||
|
|
||||||
await session.destroy(); // this clears token and user for any other test
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { vi, describe, expect, it } from 'vitest';
|
||||||
|
import { axios, flushPromises } from 'app/test/vitest/helper';
|
||||||
|
import { useTokenConfig } from 'composables/useTokenConfig';
|
||||||
|
const tokenConfig = useTokenConfig();
|
||||||
|
|
||||||
|
describe('useTokenConfig', () => {
|
||||||
|
describe('fetch', () => {
|
||||||
|
it('should call setTokenConfig of the state with the expected data', async () => {
|
||||||
|
const data = {
|
||||||
|
id: 1,
|
||||||
|
renewPeriod: 21600,
|
||||||
|
courtesyTime: 60,
|
||||||
|
renewInterval: 300,
|
||||||
|
};
|
||||||
|
vi.spyOn(axios, 'get').mockResolvedValueOnce({
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.spyOn(tokenConfig.state, 'setTokenConfig');
|
||||||
|
|
||||||
|
tokenConfig.fetch();
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(tokenConfig.state.setTokenConfig).toHaveBeenCalledWith(data);
|
||||||
|
|
||||||
|
const renewPeriod = sessionStorage.getItem('renewPeriod');
|
||||||
|
expect(renewPeriod).toEqual(data.renewPeriod);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
¿?
Vaaale, puse ese comentario para que al probar la funcionalidad no tener que esperar mucho. El objetivo del comentario es que si la PR la coge alguien sin mucho contexto, puede invertir tiempo probando