feat(interceptor): axios response interceptor
gitea/salix-front/pipeline/head There was a failure building this commit
Details
gitea/salix-front/pipeline/head There was a failure building this commit
Details
This commit is contained in:
parent
10157230d6
commit
7b2a4d75c6
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,7 @@
|
|||
"@quasar/extras": "^1.14.0",
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.6.5",
|
||||
"quasar": "^2.7.1",
|
||||
"quasar": "^2.7.3",
|
||||
"vue": "^3.0.0",
|
||||
"vue-i18n": "^9.0.0",
|
||||
"vue-router": "^4.0.0"
|
||||
|
|
54
src/App.vue
54
src/App.vue
|
@ -1,6 +1,15 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const router = useRouter();
|
||||
const session = useSession();
|
||||
const { t } = useI18n();
|
||||
const { isLoggedIn } = session;
|
||||
|
||||
quasar.iconMapFn = (iconName) => {
|
||||
if (iconName.startsWith('vn:')) {
|
||||
|
@ -11,6 +20,51 @@ quasar.iconMapFn = (iconName) => {
|
|||
};
|
||||
}
|
||||
};
|
||||
|
||||
function responseError(error) {
|
||||
let message = error.message;
|
||||
let logOut = false;
|
||||
|
||||
switch (error.response?.status) {
|
||||
case 401:
|
||||
message = 'login.loginError';
|
||||
|
||||
if (isLoggedIn()) {
|
||||
message = 'errors.statusUnauthorized';
|
||||
logOut = true;
|
||||
}
|
||||
break;
|
||||
case 403:
|
||||
message = 'errors.statusUnauthorized';
|
||||
break;
|
||||
case 500:
|
||||
message = 'errors.statusInternalServerError';
|
||||
break;
|
||||
case 502:
|
||||
message = 'errors.statusBadGateway';
|
||||
break;
|
||||
case 504:
|
||||
message = 'errors.statusGatewayTimeout';
|
||||
break;
|
||||
}
|
||||
|
||||
let translatedMessage = t(message);
|
||||
if (!translatedMessage) translatedMessage = message;
|
||||
|
||||
quasar.notify({
|
||||
message: translatedMessage,
|
||||
type: 'negative',
|
||||
});
|
||||
|
||||
if (logOut) {
|
||||
session.destroy();
|
||||
router.push({ path: '/login' });
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
axios.interceptors.response.use((response) => response, responseError);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
|
||||
import { createWrapper } from 'app/tests/jest/jestHelpers';
|
||||
import App from '../App.vue';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
|
||||
jest.mock('vue-router', () => ({
|
||||
useRouter: () => ({
|
||||
push: mockPush,
|
||||
currentRoute: { value: 'myCurrentRoute' }
|
||||
}),
|
||||
}));
|
||||
|
||||
// jest.mock('vue-i18n', () => ({
|
||||
// createI18n: () => { },
|
||||
// useI18n: () => ({
|
||||
// t: () => { }
|
||||
// }),
|
||||
// }));
|
||||
|
||||
describe('App', () => {
|
||||
let vm;
|
||||
beforeAll(() => {
|
||||
const options = {
|
||||
global: {
|
||||
stubs: ['router-view']
|
||||
}
|
||||
};
|
||||
vm = createWrapper(App, options).vm;
|
||||
});
|
||||
|
||||
it('should not set the token into session if any error occurred', async () => {
|
||||
// jest.spyOn(vm.routerView).mockReturnValue(routerView);
|
||||
jest.spyOn(vm.quasar, 'notify')
|
||||
|
||||
let error;
|
||||
try {
|
||||
const response = {
|
||||
response: {
|
||||
status: 403
|
||||
}
|
||||
};
|
||||
await vm.responseError(response);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//await vm.onSubmit();
|
||||
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
|
||||
{
|
||||
type: 'negative',
|
||||
message: 'test'
|
||||
}
|
||||
));
|
||||
});
|
||||
});
|
|
@ -1,17 +1,10 @@
|
|||
import { boot } from 'quasar/wrappers';
|
||||
import axios from 'axios';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
|
||||
|
||||
// Be careful when using SSR for cross-request state pollution
|
||||
// due to creating a Singleton instance here;
|
||||
// If any client changes this (global) instance, it might be a
|
||||
// good idea to move this instance creation inside of the
|
||||
// "export default () => {}" function below (which runs individually
|
||||
// for each client)
|
||||
const api = axios.create({ baseURL: 'https://api.example.com' });
|
||||
const { getToken } = useSession();
|
||||
|
||||
axios.defaults.baseURL = '/api/';
|
||||
|
||||
axios.interceptors.request.use(
|
||||
function (context) {
|
||||
const token = getToken();
|
||||
|
@ -26,17 +19,3 @@ axios.interceptors.request.use(
|
|||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default boot(({ app }) => {
|
||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||
|
||||
app.config.globalProperties.$axios = axios;
|
||||
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
||||
// so you won't necessarily have to import axios in each vue file
|
||||
|
||||
app.config.globalProperties.$api = api;
|
||||
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
|
||||
// so you can easily perform requests against your app's API
|
||||
});
|
||||
|
||||
export { api };
|
||||
|
|
|
@ -39,7 +39,7 @@ function updatePreferences() {
|
|||
}
|
||||
|
||||
async function saveDarkMode(value) {
|
||||
const query = `/api/UserConfigs/${user.value.id}`;
|
||||
const query = `/UserConfigs/${user.value.id}`;
|
||||
await axios.patch(query, {
|
||||
darkMode: value,
|
||||
});
|
||||
|
@ -47,7 +47,7 @@ async function saveDarkMode(value) {
|
|||
}
|
||||
|
||||
async function saveLanguage(value) {
|
||||
const query = `/api/Accounts/${user.value.id}`;
|
||||
const query = `/Accounts/${user.value.id}`;
|
||||
await axios.patch(query, {
|
||||
lang: value,
|
||||
});
|
||||
|
|
|
@ -57,7 +57,7 @@ export function useNavigation() {
|
|||
};
|
||||
|
||||
async function fetchFavorites() {
|
||||
const response = await axios.get('api/starredModules/getStarredModules');
|
||||
const response = await axios.get('StarredModules/getStarredModules');
|
||||
|
||||
const filteredModules = modules.value.filter((module) => {
|
||||
return response.data.find((element) => element.moduleFk == salixModules[module.name]);
|
||||
|
|
|
@ -5,7 +5,7 @@ export function useRole() {
|
|||
const state = useState();
|
||||
|
||||
async function fetch() {
|
||||
const { data } = await axios.get('/api/accounts/acl');
|
||||
const { data } = await axios.get('Accounts/acl');
|
||||
const roles = data.roles.map(userRoles => userRoles.role.name);
|
||||
|
||||
const userData = {
|
||||
|
|
|
@ -19,6 +19,8 @@ export default {
|
|||
errors: {
|
||||
statusUnauthorized: 'Access denied',
|
||||
statusInternalServerError: 'An internal server error has ocurred',
|
||||
statusBadGateway: 'It seems that the server has fall down',
|
||||
statusGatewayTimeout: 'Could not contact the server',
|
||||
},
|
||||
login: {
|
||||
title: 'Login',
|
||||
|
|
|
@ -19,6 +19,8 @@ export default {
|
|||
errors: {
|
||||
statusUnauthorized: 'Acceso denegado',
|
||||
statusInternalServerError: 'Ha ocurrido un error interno del servidor',
|
||||
statusBadGateway: 'Parece ser que el servidor ha caído',
|
||||
statusGatewayTimeout: 'No se ha podido contactar con el servidor',
|
||||
},
|
||||
login: {
|
||||
title: 'Inicio de sesión',
|
||||
|
|
|
@ -17,7 +17,7 @@ const entityId = computed(function () {
|
|||
const customer = ref({});
|
||||
|
||||
async function fetch() {
|
||||
const { data } = await axios.get(`/api/Clients/${entityId.value}`);
|
||||
const { data } = await axios.get(`Clients/${entityId.value}`);
|
||||
|
||||
if (data) customer.value = data;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ function navigate(id) {
|
|||
|
||||
<template>
|
||||
<q-page class="q-pa-md">
|
||||
<smart-card url="/api/Clients" sort-by="id DESC" @on-navigate="navigate" auto-load>
|
||||
<smart-card url="/Clients" sort-by="id DESC" @on-navigate="navigate" auto-load>
|
||||
<template #labels="{ row }">
|
||||
<q-list>
|
||||
<q-item class="q-pa-none">
|
||||
|
|
|
@ -17,40 +17,23 @@ const password = ref('');
|
|||
const keepLogin = ref(true);
|
||||
|
||||
async function onSubmit() {
|
||||
try {
|
||||
const { data } = await axios.post('/api/accounts/login', {
|
||||
user: username.value,
|
||||
password: password.value,
|
||||
});
|
||||
const { data } = await axios.post('Accounts/login', {
|
||||
user: username.value,
|
||||
password: password.value,
|
||||
});
|
||||
|
||||
await session.login(data.token, keepLogin.value);
|
||||
await session.login(data.token, keepLogin.value);
|
||||
|
||||
quasar.notify({
|
||||
message: t('login.loginSuccess'),
|
||||
type: 'positive',
|
||||
});
|
||||
quasar.notify({
|
||||
message: t('login.loginSuccess'),
|
||||
type: 'positive',
|
||||
});
|
||||
|
||||
const currentRoute = router.currentRoute.value;
|
||||
if (currentRoute.query && currentRoute.query.redirect) {
|
||||
router.push(currentRoute.query.redirect);
|
||||
} else {
|
||||
router.push({ name: 'Dashboard' });
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const errorCode = error.response && error.response.status;
|
||||
if (errorCode === 401) {
|
||||
quasar.notify({
|
||||
message: t('login.loginError'),
|
||||
type: 'negative',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
quasar.notify({
|
||||
message: t('errors.statusInternalServerError'),
|
||||
type: 'negative',
|
||||
});
|
||||
}
|
||||
const currentRoute = router.currentRoute.value;
|
||||
if (currentRoute.query && currentRoute.query.redirect) {
|
||||
router.push(currentRoute.query.redirect);
|
||||
} else {
|
||||
router.push({ name: 'Dashboard' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -42,16 +42,16 @@ describe('Login', () => {
|
|||
vm.session.destroy();
|
||||
});
|
||||
|
||||
it('should not set the token into session if any error occurred', async () => {
|
||||
jest.spyOn(axios, 'post').mockRejectedValue(new Error('error'));
|
||||
jest.spyOn(vm.quasar, 'notify')
|
||||
// it('should not set the token into session if any error occurred', async () => {
|
||||
// jest.spyOn(axios, 'post').mockRejectedValue(new Error('error'));
|
||||
// jest.spyOn(vm.quasar, 'notify')
|
||||
|
||||
expect(vm.session.getToken()).toEqual('');
|
||||
// expect(vm.session.getToken()).toEqual('');
|
||||
|
||||
await vm.onSubmit();
|
||||
// await vm.onSubmit();
|
||||
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
|
||||
{ 'type': 'negative' }
|
||||
));
|
||||
});
|
||||
// expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
|
||||
// { 'type': 'negative' }
|
||||
// ));
|
||||
// });
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { route } from 'quasar/wrappers';
|
||||
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router';
|
||||
import { Notify } from 'quasar';
|
||||
// import { Notify } from 'quasar';
|
||||
import routes from './routes';
|
||||
import { i18n } from 'src/boot/i18n';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
@ -46,20 +46,20 @@ export default route(function (/* { store, ssrContext } */) {
|
|||
}
|
||||
|
||||
if (isLoggedIn()) {
|
||||
try {
|
||||
const stateRoles = state.getRoles().value;
|
||||
if (stateRoles.length === 0) {
|
||||
await role.fetch();
|
||||
}
|
||||
} catch (error) {
|
||||
Notify.create({
|
||||
message: t('errors.statusUnauthorized'),
|
||||
type: 'negative',
|
||||
});
|
||||
|
||||
session.destroy();
|
||||
return next({ path: '/login' });
|
||||
// try {
|
||||
const stateRoles = state.getRoles().value;
|
||||
if (stateRoles.length === 0) {
|
||||
await role.fetch();
|
||||
}
|
||||
// } catch (error) {
|
||||
// Notify.create({
|
||||
// message: t('errors.statusUnauthorized'),
|
||||
// type: 'negative',
|
||||
// });
|
||||
|
||||
// session.destroy();
|
||||
// return next({ path: '/login' });
|
||||
// }
|
||||
|
||||
const matches = to.matched;
|
||||
const hasRequiredRoles = matches.every(route => {
|
||||
|
|
Loading…
Reference in New Issue