Add centralized API client with axios interceptors, remove vue-axios

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 12:44:33 +08:00
parent 6af7253d08
commit 147b16ca34
29 changed files with 301 additions and 270 deletions

15
package-lock.json generated
View File

@@ -32,7 +32,6 @@
"primevue": "^4.5.4", "primevue": "^4.5.4",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"vue": "^3.5.29", "vue": "^3.5.29",
"vue-axios": "^3.5.2",
"vue-chartjs": "^5.3.3", "vue-chartjs": "^5.3.3",
"vue-router": "^5.0.3", "vue-router": "^5.0.3",
"vue-sweetalert2": "^5.0.11", "vue-sweetalert2": "^5.0.11",
@@ -7997,15 +7996,6 @@
} }
} }
}, },
"node_modules/vue-axios": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-3.5.2.tgz",
"integrity": "sha512-GP+dct7UlAWkl1qoP3ppw0z6jcSua5/IrMpjB5O8bh089iIiJ+hdxPYH2NPEpajlYgkW5EVMP95ttXWdas1O0g==",
"peerDependencies": {
"axios": "*",
"vue": "^3.0.0 || ^2.0.0"
}
},
"node_modules/vue-chartjs": { "node_modules/vue-chartjs": {
"version": "5.3.3", "version": "5.3.3",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.3.tgz", "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.3.tgz",
@@ -13305,11 +13295,6 @@
"@vue/shared": "3.5.29" "@vue/shared": "3.5.29"
} }
}, },
"vue-axios": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-3.5.2.tgz",
"integrity": "sha512-GP+dct7UlAWkl1qoP3ppw0z6jcSua5/IrMpjB5O8bh089iIiJ+hdxPYH2NPEpajlYgkW5EVMP95ttXWdas1O0g=="
},
"vue-chartjs": { "vue-chartjs": {
"version": "5.3.3", "version": "5.3.3",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.3.tgz", "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.3.tgz",

View File

@@ -39,7 +39,6 @@
"primevue": "^4.5.4", "primevue": "^4.5.4",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"vue": "^3.5.29", "vue": "^3.5.29",
"vue-axios": "^3.5.2",
"vue-chartjs": "^5.3.3", "vue-chartjs": "^5.3.3",
"vue-router": "^5.0.3", "vue-router": "^5.0.3",
"vue-sweetalert2": "^5.0.11", "vue-sweetalert2": "^5.0.11",

33
src/api/auth.js Normal file
View File

@@ -0,0 +1,33 @@
import axios from 'axios';
import { getCookie, setCookie, setCookieWithoutExpiration } from '@/utils/cookieUtil.js';
/**
* Refresh the access token using the refresh token cookie.
* Uses plain axios (not apiClient) to avoid interceptor loops.
* @returns {Promise<string>} The new access token.
*/
export async function refreshTokenAndGetNew() {
const api = '/api/oauth/token';
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
};
const data = {
grant_type: 'refresh_token',
refresh_token: getCookie('luciaRefreshToken'),
};
const response = await axios.post(api, data, config);
const newAccessToken = response.data.access_token;
const newRefreshToken = response.data.refresh_token;
setCookieWithoutExpiration('luciaToken', newAccessToken);
// Expire in ~6 months
const expiredMs = new Date();
expiredMs.setMonth(expiredMs.getMonth() + 6);
const days = Math.ceil((expiredMs.getTime() - Date.now()) / (24 * 60 * 60 * 1000));
setCookie('luciaRefreshToken', newRefreshToken, days);
return newAccessToken;
}

78
src/api/client.js Normal file
View File

@@ -0,0 +1,78 @@
import axios from 'axios';
import { getCookie, deleteCookie } from '@/utils/cookieUtil.js';
const apiClient = axios.create();
// Request interceptor: automatically attach Authorization header
apiClient.interceptors.request.use((config) => {
const token = getCookie('luciaToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor: handle 401 by attempting token refresh
let isRefreshing = false;
let pendingRequests = [];
function onRefreshSuccess(newToken) {
pendingRequests.forEach((cb) => cb(newToken));
pendingRequests = [];
}
function onRefreshFailure(error) {
pendingRequests.forEach((cb) => cb(null, error));
pendingRequests = [];
}
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Only attempt refresh on 401, and not for auth endpoints or already-retried requests
if (
error.response?.status !== 401 ||
originalRequest._retried ||
originalRequest.url === '/api/oauth/token'
) {
return Promise.reject(error);
}
if (isRefreshing) {
// Queue this request until the refresh completes
return new Promise((resolve, reject) => {
pendingRequests.push((newToken, err) => {
if (err) return reject(err);
originalRequest.headers.Authorization = `Bearer ${newToken}`;
originalRequest._retried = true;
resolve(apiClient(originalRequest));
});
});
}
isRefreshing = true;
originalRequest._retried = true;
try {
// Dynamic import to avoid circular dependency with login store
const { refreshTokenAndGetNew } = await import('@/api/auth.js');
const newToken = await refreshTokenAndGetNew();
isRefreshing = false;
onRefreshSuccess(newToken);
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return apiClient(originalRequest);
} catch (refreshError) {
isRefreshing = false;
onRefreshFailure(refreshError);
// Refresh failed: clear auth and redirect to login
deleteCookie('luciaToken');
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
);
export default apiClient;

View File

@@ -3,8 +3,6 @@ import { createApp, markRaw } from "vue";
import App from "./App.vue"; import App from "./App.vue";
import router from "./router"; import router from "./router";
import pinia from '@/stores/main.js'; import pinia from '@/stores/main.js';
import axios from 'axios';
import VueAxios from 'vue-axios';
import moment from 'moment'; import moment from 'moment';
import mitt from 'mitt'; import mitt from 'mitt';
import ToastPlugin from 'vue-toast-notification'; import ToastPlugin from 'vue-toast-notification';
@@ -49,15 +47,9 @@ import ContextMenu from 'primevue/contextmenu';
const emitter = mitt(); const emitter = mitt();
const app = createApp(App); const app = createApp(App);
// 將 Axios 實例添加到 Vue 的應用實例中
app.config.globalProperties.$http = axios;
app.$http = axios; // 將 $http 添加到 Vue 實例中
// Pinia Set // Pinia Set
pinia.use(({ store }) => { pinia.use(({ store }) => {
store.$router = markRaw(router); store.$router = markRaw(router);
store.$axios = markRaw(axios);
store.$toast = markRaw(ToastPlugin);
store.$http = app.$http;
}); });
// can use `this.$moment` in Vue.js // can use `this.$moment` in Vue.js
@@ -72,7 +64,6 @@ cytoscape.use( popper((ref) => ref) );
app.use(pinia); app.use(pinia);
app.use(router); app.use(router);
app.use(VueAxios, axios);
app.use(VueSweetalert2); app.use(VueSweetalert2);
app.use(ToastPlugin, { // use `this.$toast` in Vue.js app.use(ToastPlugin, { // use `this.$toast` in Vue.js
position: 'bottom', position: 'bottom',

View File

@@ -1,25 +1,17 @@
import router from "@/router/index";
import loadingStore from '@/stores/loading.js'; import loadingStore from '@/stores/loading.js';
import {useToast} from 'vue-toast-notification'; import {useToast} from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css'; import 'vue-toast-notification/dist/theme-sugar.css';
import axios from "axios";
import { deleteCookie } from "@/utils/cookieUtil.js";
// Delay loading and toast // Delay loading and toast
const delay = (s = 0) => new Promise((resolve, reject) => setTimeout(resolve, s)); const delay = (s = 0) => new Promise((resolve, reject) => setTimeout(resolve, s));
/** /**
* API catch error function * API catch error function.
* @param {object} Error 後端 ERROR * 401 errors are handled by the axios response interceptor in api/client.js.
* @param {object} error 後端 ERROR
* @param {string} toastMessage Toast 的提示文字 * @param {string} toastMessage Toast 的提示文字
* @returns {string} Error HTTP Status
*/ */
export default async function apiError(error, toastMessage) { export default async function apiError(error, toastMessage) {
if(error.request?.status === 401) {
delete axios.defaults.headers.common["Authorization"];
deleteCookie("luciaToken");
return router.push('/login');
}
const loading = loadingStore(); const loading = loadingStore();
const $toast = useToast(); const $toast = useToast();
await delay(); await delay();

View File

@@ -1,4 +1,5 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import apiClient from '@/api/client.js';
import apiError from '@/module/apiError'; import apiError from '@/module/apiError';
import piniaLoginStore from '@/stores/login'; import piniaLoginStore from '@/stores/login';
import { JUST_CREATE_ACCOUNT_HOT_DURATION_MINS } from '@/constants/constants'; import { JUST_CREATE_ACCOUNT_HOT_DURATION_MINS } from '@/constants/constants';
@@ -87,7 +88,7 @@ export default defineStore('acctMgmtStore', {
async getAllUserAccounts() { async getAllUserAccounts() {
const apiGetUserList = `/api/users`; const apiGetUserList = `/api/users`;
try { try {
const response = await this.$axios.get(apiGetUserList); const response = await apiClient.get(apiGetUserList);
const customizedResponseData = await this.customizeAllUserList(response.data); const customizedResponseData = await this.customizeAllUserList(response.data);
this.allUserAccoutList = await this.moveCurrentLoginUserToFirstRow(customizedResponseData); this.allUserAccoutList = await this.moveCurrentLoginUserToFirstRow(customizedResponseData);
} catch (error) { } catch (error) {
@@ -135,7 +136,7 @@ export default defineStore('acctMgmtStore', {
const apiCreateAccount = `/api/users`; const apiCreateAccount = `/api/users`;
try { try {
const response = await this.$axios.post(apiCreateAccount, userToCreate); const response = await apiClient.post(apiCreateAccount, userToCreate);
if (response.status === 200) { if (response.status === 200) {
this.isOneAccountJustCreate = true; this.isOneAccountJustCreate = true;
this.justCreateUsername = userToCreate.username; this.justCreateUsername = userToCreate.username;
@@ -154,7 +155,7 @@ export default defineStore('acctMgmtStore', {
const apiDelete = `/api/users/${userToDelete}`; const apiDelete = `/api/users/${userToDelete}`;
try { try {
const response = await this.$axios.delete(apiDelete); const response = await apiClient.delete(apiDelete);
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
apiError(error, 'Failed to delete the account.'); apiError(error, 'Failed to delete the account.');
@@ -170,7 +171,7 @@ export default defineStore('acctMgmtStore', {
const apiEdit = `/api/users/${userToEdit}`; const apiEdit = `/api/users/${userToEdit}`;
try { try {
const response = await this.$axios.put(apiEdit, { const response = await apiClient.put(apiEdit, {
username: editDetail.newUsername ? editDetail.newUsername : editDetail.username, username: editDetail.newUsername ? editDetail.newUsername : editDetail.username,
password: editDetail.password, password: editDetail.password,
name: editDetail.name, name: editDetail.name,
@@ -186,7 +187,7 @@ export default defineStore('acctMgmtStore', {
const apiEdit = `/api/users/${userToEdit}`; const apiEdit = `/api/users/${userToEdit}`;
try { try {
const response = await this.$axios.put(apiEdit, { const response = await apiClient.put(apiEdit, {
username: userToEdit, username: userToEdit,
name: newName, name: newName,
is_active: this.currentViewingUser.is_active, is_active: this.currentViewingUser.is_active,
@@ -202,7 +203,7 @@ export default defineStore('acctMgmtStore', {
const apiEdit = `/api/users/${userToEdit}`; const apiEdit = `/api/users/${userToEdit}`;
try { try {
const response = await this.$axios.put(apiEdit, { const response = await apiClient.put(apiEdit, {
username: userToEdit, username: userToEdit,
name: this.currentViewingUser.name, name: this.currentViewingUser.name,
password: newPwd, password: newPwd,
@@ -223,7 +224,7 @@ export default defineStore('acctMgmtStore', {
const apiAddRole = `/api/users/${usernameToEdit}/roles/${roleCode}`; const apiAddRole = `/api/users/${usernameToEdit}/roles/${roleCode}`;
try { try {
const response = await this.$axios.put(apiAddRole); const response = await apiClient.put(apiAddRole);
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
apiError(error, 'Failed to add role to the account.'); apiError(error, 'Failed to add role to the account.');
@@ -238,7 +239,7 @@ export default defineStore('acctMgmtStore', {
const apiDeleteRole = `/api/users/${usernameToEdit}/roles/${roleCode}`; const apiDeleteRole = `/api/users/${usernameToEdit}/roles/${roleCode}`;
try { try {
const response = await this.$axios.delete(apiDeleteRole); const response = await apiClient.delete(apiDeleteRole);
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
apiError(error, 'Failed to delete a role frome the account.'); apiError(error, 'Failed to delete a role frome the account.');
@@ -252,7 +253,7 @@ export default defineStore('acctMgmtStore', {
async getUserDetail(uniqueUsername: string): Promise<boolean> { async getUserDetail(uniqueUsername: string): Promise<boolean> {
const apiUserDetail = `/api/users/${uniqueUsername}`; const apiUserDetail = `/api/users/${uniqueUsername}`;
try { try {
const response = await this.$axios.get(apiUserDetail); const response = await apiClient.get(apiUserDetail);
this.currentViewingUser = response.data; this.currentViewingUser = response.data;
this.currentViewingUser.is_admin = response.data.roles.some(role => role.code === 'admin'); this.currentViewingUser.is_admin = response.data.roles.some(role => role.code === 'admin');
return response.status === 200; return response.status === 200;

View File

@@ -1,5 +1,6 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import moment from "moment"; import moment from "moment";
import apiClient from "@/api/client.js";
import apiError from '@/module/apiError.js'; import apiError from '@/module/apiError.js';
import { Decimal } from 'decimal.js'; import { Decimal } from 'decimal.js';
@@ -136,7 +137,7 @@ export default defineStore('allMapDataStore', {
else api = `/api/logs/${logId}/discover`; else api = `/api/logs/${logId}/discover`;
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allProcessMap = response.data.process_map; this.allProcessMap = response.data.process_map;
this.allBpmn = response.data.bpmn; this.allBpmn = response.data.bpmn;
this.allStats = response.data.stats; this.allStats = response.data.stats;
@@ -163,10 +164,10 @@ export default defineStore('allMapDataStore', {
try { try {
let baseResponse; let baseResponse;
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allTrace = response.data; this.allTrace = response.data;
if(baseLogId) { if(baseLogId) {
baseResponse = await this.$axios.get(baseApi); baseResponse = await apiClient.get(baseApi);
this.allBaseTrace = baseResponse.data; this.allBaseTrace = baseResponse.data;
} }
} catch(error) { } catch(error) {
@@ -190,7 +191,7 @@ export default defineStore('allMapDataStore', {
else api = `/api/logs/${logId}/traces/${traceId}?start=${start}&page_size=20`; else api = `/api/logs/${logId}/traces/${traceId}?start=${start}&page_size=20`;
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allTraceTaskSeq = response.data.task_seq; this.allTraceTaskSeq = response.data.task_seq;
this.allCase = response.data.cases; this.allCase = response.data.cases;
this.allCase.forEach(c => { this.allCase.forEach(c => {
@@ -229,7 +230,7 @@ export default defineStore('allMapDataStore', {
const api = `/api/logs/${logId}/traces/${traceId}?start=${start}&page_size=20`; const api = `/api/logs/${logId}/traces/${traceId}?start=${start}&page_size=20`;
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allBaseTraceTaskSeq = response.data.task_seq; this.allBaseTraceTaskSeq = response.data.task_seq;
this.allBaseCase = response.data.cases; this.allBaseCase = response.data.cases;
this.allBaseCase.forEach(c => { this.allBaseCase.forEach(c => {
@@ -266,7 +267,7 @@ export default defineStore('allMapDataStore', {
const api = `/api/filters/params?log_id=${logId}`; const api = `/api/filters/params?log_id=${logId}`;
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allFilterTask = response.data.tasks; this.allFilterTask = response.data.tasks;
this.allFilterStartToEnd = response.data.sources; this.allFilterStartToEnd = response.data.sources;
this.allFilterEndToStart = response.data.sinks; this.allFilterEndToStart = response.data.sinks;
@@ -294,7 +295,7 @@ export default defineStore('allMapDataStore', {
const api = `/api/filters/has-result?log_id=${logId}`; const api = `/api/filters/has-result?log_id=${logId}`;
try { try {
const response = await this.$axios.post(api, this.postRuleData) const response = await apiClient.post(api, this.postRuleData)
this.hasResultRule = response.data.result; this.hasResultRule = response.data.result;
} catch(error) { } catch(error) {
apiError(error, 'Failed to load the Has Result.'); apiError(error, 'Failed to load the Has Result.');
@@ -308,7 +309,7 @@ export default defineStore('allMapDataStore', {
const api = `/api/temp-filters?log_id=${logId}`; const api = `/api/temp-filters?log_id=${logId}`;
try { try {
const response = await this.$axios.post(api, this.postRuleData) const response = await apiClient.post(api, this.postRuleData)
this.tempFilterId = response.data.id; this.tempFilterId = response.data.id;
} catch(error) { } catch(error) {
apiError(error, 'Failed to add the Temporary Filters.'); apiError(error, 'Failed to add the Temporary Filters.');
@@ -327,7 +328,7 @@ export default defineStore('allMapDataStore', {
}; };
try { try {
const response = await this.$axios.post(api, createFilterObj); const response = await apiClient.post(api, createFilterObj);
this.createFilterId = response.data.id; this.createFilterId = response.data.id;
this.tempFilterId = null; this.tempFilterId = null;
}catch(error) { }catch(error) {
@@ -343,7 +344,7 @@ export default defineStore('allMapDataStore', {
if(createFilterId){ if(createFilterId){
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.temporaryData = response.data.rules; this.temporaryData = response.data.rules;
this.logId = response.data.log.id; this.logId = response.data.log.id;
this.filterName = response.data.name; this.filterName = response.data.name;
@@ -362,7 +363,7 @@ export default defineStore('allMapDataStore', {
const data = this.postRuleData; const data = this.postRuleData;
try { try {
const response = await this.$axios.put(api, data); const response = await apiClient.put(api, data);
this.isUpdateFilter = response.status === 200; this.isUpdateFilter = response.status === 200;
this.tempFilterId = null; this.tempFilterId = null;
}catch(error) { }catch(error) {

View File

@@ -1,4 +1,5 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import apiClient from "@/api/client.js";
import apiError from '@/module/apiError.js'; import apiError from '@/module/apiError.js';
export default defineStore('compareStore', { export default defineStore('compareStore', {
@@ -21,7 +22,7 @@ export default defineStore('compareStore', {
const api = `/api/compare?datasets=${encodeURIComponent(queryString)}`; const api = `/api/compare?datasets=${encodeURIComponent(queryString)}`;
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allCompareDashboardData = response.data; this.allCompareDashboardData = response.data;
} catch(error) { } catch(error) {
apiError(error, 'Failed to load the Compare.'); apiError(error, 'Failed to load the Compare.');
@@ -44,7 +45,7 @@ export default defineStore('compareStore', {
break; break;
} }
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
return response.data.stats; return response.data.stats;
} catch(error) { } catch(error) {
@@ -58,7 +59,7 @@ export default defineStore('compareStore', {
async getFileName(id) { async getFileName(id) {
id = Number(id) id = Number(id)
try { try {
const response = await this.$axios.get('/api/files'); const response = await apiClient.get('/api/files');
const file = response.data.find(i => i.id === id); const file = response.data.find(i => i.id === id);
if(file) return file.name; if(file) return file.name;

View File

@@ -2,6 +2,7 @@ import { defineStore } from "pinia";
import moment from "moment"; import moment from "moment";
import { Decimal } from 'decimal.js'; import { Decimal } from 'decimal.js';
import abbreviateNumber from '@/module/abbreviateNumber.js'; import abbreviateNumber from '@/module/abbreviateNumber.js';
import apiClient from "@/api/client.js";
import apiError from '@/module/apiError.js'; import apiError from '@/module/apiError.js';
export default defineStore('conformanceStore', { export default defineStore('conformanceStore', {
@@ -201,7 +202,7 @@ export default defineStore('conformanceStore', {
api = `/api/log-checks/params?log_id=${logId}`; api = `/api/log-checks/params?log_id=${logId}`;
} }
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allConformanceTask = response.data.tasks; this.allConformanceTask = response.data.tasks;
this.allCfmSeqStart = response.data.sources; this.allCfmSeqStart = response.data.sources;
this.allCfmSeqEnd = response.data.sinks; this.allCfmSeqEnd = response.data.sinks;
@@ -230,7 +231,7 @@ export default defineStore('conformanceStore', {
} }
try { try {
const response = await this.$axios.post(api, data); const response = await apiClient.post(api, data);
if(filterId !== null) { if(filterId !== null) {
this.conformanceFilterTempCheckId = response.data.id; this.conformanceFilterTempCheckId = response.data.id;
} }
@@ -266,7 +267,7 @@ export default defineStore('conformanceStore', {
api = `/api/log-checks/${logCreateCheckId}`; api = `/api/log-checks/${logCreateCheckId}`;
} }
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
if(!getRouteFile) { if(!getRouteFile) {
this.allConformanceTempReportData = response.data this.allConformanceTempReportData = response.data
} else { } else {
@@ -302,7 +303,7 @@ export default defineStore('conformanceStore', {
} }
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allIssueTraces = response.data.traces; this.allIssueTraces = response.data.traces;
} catch(error) { } catch(error) {
apiError(error, 'Failed to Get the detail of a temporary log conformance issue.'); apiError(error, 'Failed to Get the detail of a temporary log conformance issue.');
@@ -336,7 +337,7 @@ export default defineStore('conformanceStore', {
} }
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allTaskSeq = response.data.task_seq; this.allTaskSeq = response.data.task_seq;
this.allCases = response.data.cases; this.allCases = response.data.cases;
return response.data.cases; return response.data.cases;
@@ -366,7 +367,7 @@ export default defineStore('conformanceStore', {
else if(logCreateCheckId !== null) api = `/api/log-checks/${logCreateCheckId}/loops/${loopNo}`; else if(logCreateCheckId !== null) api = `/api/log-checks/${logCreateCheckId}/loops/${loopNo}`;
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allLoopTraces = response.data.traces; this.allLoopTraces = response.data.traces;
} catch(error) { } catch(error) {
apiError(error, 'Failed to Get the detail of a temporary log conformance loop.'); apiError(error, 'Failed to Get the detail of a temporary log conformance loop.');
@@ -392,7 +393,7 @@ export default defineStore('conformanceStore', {
else if(logCreateCheckId !== null) api = `/api/log-checks/${logCreateCheckId}/loops/${loopNo}/traces/${traceId}?start=${start}&page_size=20`; else if(logCreateCheckId !== null) api = `/api/log-checks/${logCreateCheckId}/loops/${loopNo}/traces/${traceId}?start=${start}&page_size=20`;
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allLoopTaskSeq = response.data.task_seq; this.allLoopTaskSeq = response.data.task_seq;
this.allLoopCases = response.data.cases; this.allLoopCases = response.data.cases;
return response.data.cases; return response.data.cases;
@@ -422,7 +423,7 @@ export default defineStore('conformanceStore', {
else api = `/api/log-checks?log_id=${logId}`; else api = `/api/log-checks?log_id=${logId}`;
try { try {
const response = await this.$axios.post(api, data); const response = await apiClient.post(api, data);
if(filterId !== null) { if(filterId !== null) {
this.conformanceFilterCreateCheckId = response.data.id; this.conformanceFilterCreateCheckId = response.data.id;
this.conformanceFilterTempCheckId = null; this.conformanceFilterTempCheckId = null;
@@ -449,7 +450,7 @@ export default defineStore('conformanceStore', {
else if(logCreateCheckId !== null) api = `/api/log-checks/${logCreateCheckId}`; else if(logCreateCheckId !== null) api = `/api/log-checks/${logCreateCheckId}`;
try { try {
const response = await this.$axios.put(api, data); const response = await apiClient.put(api, data);
this.isUpdateConformance = response.status === 200; this.isUpdateConformance = response.status === 200;
this.conformanceLogTempCheckId = null; this.conformanceLogTempCheckId = null;
this.conformanceFilterTempCheckId = null; this.conformanceFilterTempCheckId = null;

View File

@@ -1,5 +1,5 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import axios from "axios"; import apiClient from "@/api/client.js";
import moment from 'moment'; import moment from 'moment';
import apiError from '@/module/apiError.js'; import apiError from '@/module/apiError.js';
import Swal from 'sweetalert2'; import Swal from 'sweetalert2';
@@ -66,7 +66,7 @@ export default defineStore('filesStore', {
let parentLog = ''; let parentLog = '';
try { try {
const response = await axios.get(api); const response = await apiClient.get(api);
this.allEventFiles = response.data; this.allEventFiles = response.data;
this.allEventFiles.forEach(o => { this.allEventFiles.forEach(o => {
@@ -119,7 +119,7 @@ export default defineStore('filesStore', {
uploadloader(); // 進度條 uploadloader(); // 進度條
try { try {
const response = await axios.post(api, fromData, config); const response = await apiClient.post(api, fromData, config);
this.uploadId = response.data.id; this.uploadId = response.data.id;
this.$router.push({name: 'Upload'}); this.$router.push({name: 'Upload'});
@@ -145,7 +145,7 @@ export default defineStore('filesStore', {
const api = `/api/logs/csv-uploads/${uploadId}`; const api = `/api/logs/csv-uploads/${uploadId}`;
try { try {
const response = await axios.get(api); const response = await apiClient.get(api);
this.allUploadDetail = response.data.preview; this.allUploadDetail = response.data.preview;
} catch(error) { } catch(error) {
apiError(error, 'Failed to get upload detail.'); apiError(error, 'Failed to get upload detail.');
@@ -161,7 +161,7 @@ export default defineStore('filesStore', {
uploadloader(); // 進度條 uploadloader(); // 進度條
try { try {
const response = await axios.post(api, data); const response = await apiClient.post(api, data);
this.uploadLogId = await response.data.id; this.uploadLogId = await response.data.id;
await Swal.close(); // 關閉進度條 await Swal.close(); // 關閉進度條
@@ -211,7 +211,7 @@ export default defineStore('filesStore', {
break; break;
} }
try { try {
await axios.put(api, data); await apiClient.put(api, data);
this.uploadFileName = null; this.uploadFileName = null;
await this.fetchAllFiles(); await this.fetchAllFiles();
} catch(error) { } catch(error) {
@@ -241,7 +241,7 @@ export default defineStore('filesStore', {
break; break;
} }
try { try {
const response = await axios.get(api); const response = await apiClient.get(api);
this.allDependentsData = response.data; this.allDependentsData = response.data;
} catch(error) { } catch(error) {
apiError(error, 'Failed to get Dependents of the files.'); apiError(error, 'Failed to get Dependents of the files.');
@@ -276,7 +276,7 @@ export default defineStore('filesStore', {
break; break;
} }
try { try {
await axios.delete(api); await apiClient.delete(api);
await this.fetchAllFiles(); await this.fetchAllFiles();
await deleteSuccess(); await deleteSuccess();
} catch(error) { } catch(error) {
@@ -296,7 +296,7 @@ export default defineStore('filesStore', {
loading.isLoading = true; loading.isLoading = true;
api = `/api/deletion/${id}`; api = `/api/deletion/${id}`;
try { try {
await axios.delete(api); await apiClient.delete(api);
} catch(error) { } catch(error) {
apiError(error, 'Failed to Remove a Deletion Record.') apiError(error, 'Failed to Remove a Deletion Record.')
} finally { } finally {
@@ -329,7 +329,7 @@ export default defineStore('filesStore', {
return; return;
} }
try { try {
const response = await axios.get(api); const response = await apiClient.get(api);
const csvData = response.data; const csvData = response.data;
const blob = new Blob([csvData], { type: 'text/csv' }); const blob = new Blob([csvData], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);

View File

@@ -1,5 +1,6 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import axios from 'axios'; import axios from 'axios';
import apiClient from '@/api/client.js';
import apiError from '@/module/apiError.js'; import apiError from '@/module/apiError.js';
import { deleteCookie, setCookie, setCookieWithoutExpiration, getCookie } from "../utils/cookieUtil"; import { deleteCookie, setCookie, setCookieWithoutExpiration, getCookie } from "../utils/cookieUtil";
@@ -51,7 +52,7 @@ export default defineStore('loginStore', {
window.location.href = decodedUrl; window.location.href = decodedUrl;
} else { } else {
this.$router.push('/files'); this.$router.push('/files');
} }
} catch(error) { } catch(error) {
this.isInvalid = true; this.isInvalid = true;
}; };
@@ -78,8 +79,6 @@ export default defineStore('loginStore', {
setCookieWithoutExpiration("luciaToken", newAccessToken); setCookieWithoutExpiration("luciaToken", newAccessToken);
setCookie("luciaRefreshToken", newRefreshToken, Math.ceil((this.expired - Date.now()) / (24 * 60 * 60 * 1000))); setCookie("luciaRefreshToken", newRefreshToken, Math.ceil((this.expired - Date.now()) / (24 * 60 * 60 * 1000)));
axios.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
} }
} catch(error) { } catch(error) {
// 若refresh token 失敗則導向至登入頁面 // 若refresh token 失敗則導向至登入頁面
@@ -91,7 +90,6 @@ export default defineStore('loginStore', {
* Logout, token expired * Logout, token expired
*/ */
logOut() { logOut() {
delete axios.defaults.headers.common["Authorization"];
deleteCookie("luciaToken"); deleteCookie("luciaToken");
this.isLoggedIn = false; this.isLoggedIn = false;
@@ -106,7 +104,7 @@ export default defineStore('loginStore', {
const api = '/api/my-account'; const api = '/api/my-account';
try { try {
const response = await axios.get(api); const response = await apiClient.get(api);
this.userData = response.data; this.userData = response.data;
} catch(error) { } catch(error) {
@@ -120,7 +118,7 @@ export default defineStore('loginStore', {
const api = '/api/my-account'; const api = '/api/my-account';
try { try {
await axios.get(api); await apiClient.get(api);
} catch(error) { } catch(error) {
this.$router.push('/login'); this.$router.push('/login');
}; };

View File

@@ -1,4 +1,5 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import apiClient from "@/api/client.js";
import apiError from '@/module/apiError.js'; import apiError from '@/module/apiError.js';
export default defineStore('performanceStore', { export default defineStore('performanceStore', {
@@ -36,7 +37,7 @@ export default defineStore('performanceStore', {
break; break;
} }
try { try {
const response = await this.$axios.get(api); const response = await apiClient.get(api);
this.allPerformanceData = response.data; this.allPerformanceData = response.data;
} catch(error) { } catch(error) {
apiError(error, 'Failed to load the Performance.'); apiError(error, 'Failed to load the Performance.');

View File

@@ -4,11 +4,3 @@ declare module '*.vue' {
const component: DefineComponent<{}, {}, any>; const component: DefineComponent<{}, {}, any>;
export default component; export default component;
} }
declare module 'vue/types/vue' {
import { AxiosInstance } from 'axios';
interface Vue {
$axios: AxiosInstance;
}
}

View File

@@ -1,7 +1,8 @@
import { AxiosInstance } from 'axios'; import 'pinia';
import { Router } from 'vue-router';
declare module 'pinia' { declare module 'pinia' {
export interface PiniaCustomProperties { export interface PiniaCustomProperties {
$axios: AxiosInstance; $router: Router;
} }
} }

View File

@@ -1,7 +1 @@
import { AxiosInstance } from 'axios'; // vue-axios type declarations removed — API client is used directly
declare module 'vue/types/vue' {
interface Vue {
$axios: AxiosInstance;
}
}

14
src/types/vue.d.ts vendored
View File

@@ -1,15 +1 @@
import { AxiosInstance } from 'axios';
import { App } from 'vue'; import { App } from 'vue';
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$http: AxiosInstance;
}
}
// 將類型擴展到 Vue 應用實例
declare module 'vue' {
interface App {
$http: AxiosInstance;
}
}

View File

@@ -56,8 +56,6 @@
import { onBeforeMount, computed, } from 'vue'; import { onBeforeMount, computed, } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios';
import { getCookie } from "@/utils/cookieUtil.js";
import LoadingStore from '@/stores/loading.js'; import LoadingStore from '@/stores/loading.js';
import AllMapDataStore from '@/stores/allMapData.js'; import AllMapDataStore from '@/stores/allMapData.js';
import ConformanceStore from '@/stores/conformance.js'; import ConformanceStore from '@/stores/conformance.js';
@@ -519,10 +517,6 @@
if (isCheckPage) { if (isCheckPage) {
const conformanceStore = ConformanceStore(); const conformanceStore = ConformanceStore();
// Save token in Headers.
const token = getCookie('luciaToken');
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
switch (to.params.type) { switch (to.params.type) {
case 'log': case 'log':
conformanceStore.conformanceLogCreateCheckId = to.params.fileId; conformanceStore.conformanceLogCreateCheckId = to.params.fileId;

View File

@@ -9,13 +9,11 @@
</template> </template>
<script> <script>
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import axios from 'axios';
import LoadingStore from '@/stores/loading.js'; import LoadingStore from '@/stores/loading.js';
import ConformanceStore from '@/stores/conformance.js'; import ConformanceStore from '@/stores/conformance.js';
import StatusBar from '@/components/Discover/StatusBar.vue'; import StatusBar from '@/components/Discover/StatusBar.vue';
import ConformanceResults from '@/components/Discover/Conformance/ConformanceResults.vue'; import ConformanceResults from '@/components/Discover/Conformance/ConformanceResults.vue';
import ConformanceSidebar from '@/components/Discover/Conformance/ConformanceSidebar.vue'; import ConformanceSidebar from '@/components/Discover/Conformance/ConformanceSidebar.vue';
import { getCookie } from "@/utils/cookieUtil.js";
export default { export default {
setup() { setup() {
@@ -99,10 +97,6 @@ export default {
// ([^;]*):捕獲 "luciaToken" 的值,直到遇到下一個分號或字符串結尾。 // ([^;]*):捕獲 "luciaToken" 的值,直到遇到下一個分號或字符串結尾。
// .*$:匹配剩餘的字符,確保完整的提取。 // .*$:匹配剩餘的字符,確保完整的提取。
// |^.*$:在找不到 "luciaToken" 的情況下,匹配整個字符串。 // |^.*$:在找不到 "luciaToken" 的情況下,匹配整個字符串。
// 實際應用
const token = getCookie('luciaToken');
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
switch (to.params.type) { switch (to.params.type) {
case 'log': case 'log':
conformanceStore.setConformanceLogCreateCheckId(to.params.fileId); conformanceStore.setConformanceLogCreateCheckId(to.params.fileId);

View File

@@ -64,8 +64,6 @@
import { onBeforeMount, computed, } from 'vue'; import { onBeforeMount, computed, } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios';
import { getCookie } from "@/utils/cookieUtil.js";
import LoadingStore from '@/stores/loading.js'; import LoadingStore from '@/stores/loading.js';
import AllMapDataStore from '@/stores/allMapData.js'; import AllMapDataStore from '@/stores/allMapData.js';
import ConformanceStore from '@/stores/conformance.js'; import ConformanceStore from '@/stores/conformance.js';
@@ -537,10 +535,6 @@ export default {
if (isCheckPage) { if (isCheckPage) {
const conformanceStore = ConformanceStore(); const conformanceStore = ConformanceStore();
// Save token in Headers.
const token = getCookie('luciaToken');
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
switch (to.params.type) { switch (to.params.type) {
case 'log': case 'log':
conformanceStore.conformanceLogCreateCheckId = to.params.fileId; conformanceStore.conformanceLogCreateCheckId = to.params.fileId;

View File

@@ -137,7 +137,6 @@
</template> </template>
<script> <script>
import { storeToRefs, mapActions, } from 'pinia'; import { storeToRefs, mapActions, } from 'pinia';
import axios from 'axios';
import moment from 'moment'; import moment from 'moment';
import LoadingStore from '@/stores/loading.js'; import LoadingStore from '@/stores/loading.js';
import PerformanceStore from '@/stores/performance.js'; import PerformanceStore from '@/stores/performance.js';
@@ -152,7 +151,6 @@ import { simpleTimeLabel, followTimeLabel,
} from '@/module/timeLabel.js'; } from '@/module/timeLabel.js';
import FreqChart from './FreqChart.vue'; import FreqChart from './FreqChart.vue';
import { PRIME_VUE_TICKS_LIMIT } from '../../../constants/constants.js'; import { PRIME_VUE_TICKS_LIMIT } from '../../../constants/constants.js';
import { getCookie } from "@/utils/cookieUtil.js";
const primeVueTicksLimit = PRIME_VUE_TICKS_LIMIT; const primeVueTicksLimit = PRIME_VUE_TICKS_LIMIT;
@@ -921,10 +919,6 @@ export default {
if (isCheckPage) { if (isCheckPage) {
const conformanceStore = ConformanceStore(); const conformanceStore = ConformanceStore();
// Save token in Headers.
const token = getCookie('luciaToken');
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
switch (to.params.type) { switch (to.params.type) {
case 'log': case 'log':
conformanceStore.conformanceLogCreateCheckId = to.params.fileId; conformanceStore.conformanceLogCreateCheckId = to.params.fileId;

View File

@@ -85,11 +85,6 @@ export default {
'refreshToken', 'refreshToken',
],), ],),
}, },
created() {
// Save token in Headers.
const token = getCookie('luciaToken');
this.$http.defaults.headers.common['Authorization'] = `Bearer ${token}`;
},
// 重新整理畫面以及第一次進入網頁時beforeRouteEnter這個hook會被執行然而beforeRouteUpdate不會被執行 // 重新整理畫面以及第一次進入網頁時beforeRouteEnter這個hook會被執行然而beforeRouteUpdate不會被執行
// PSEUDOCODE // PSEUDOCODE
// if (not logged in) { // if (not logged in) {

View File

@@ -5,6 +5,13 @@ vi.mock('@/module/apiError.js', () => ({
default: vi.fn(), default: vi.fn(),
})); }));
const { mockGet, mockPost, mockPut, mockDelete } = vi.hoisted(() => ({
mockGet: vi.fn(), mockPost: vi.fn(), mockPut: vi.fn(), mockDelete: vi.fn(),
}));
vi.mock('@/api/client.js', () => ({
default: { get: mockGet, post: mockPost, put: mockPut, delete: mockDelete },
}));
// Mock login store to avoid its side effects // Mock login store to avoid its side effects
vi.mock('@/stores/login.ts', () => { vi.mock('@/stores/login.ts', () => {
const { defineStore } = require('pinia'); const { defineStore } = require('pinia');
@@ -24,17 +31,10 @@ import useAcctMgmtStore from '@/stores/acctMgmt.ts';
describe('acctMgmtStore', () => { describe('acctMgmtStore', () => {
let store; let store;
const mockAxios = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
};
beforeEach(() => { beforeEach(() => {
setActivePinia(createPinia()); setActivePinia(createPinia());
store = useAcctMgmtStore(); store = useAcctMgmtStore();
store.$axios = mockAxios;
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@@ -84,12 +84,12 @@ describe('acctMgmtStore', () => {
describe('createNewAccount', () => { describe('createNewAccount', () => {
it('posts to /api/users and sets flag on success', async () => { it('posts to /api/users and sets flag on success', async () => {
mockAxios.post.mockResolvedValue({ status: 200 }); mockPost.mockResolvedValue({ status: 200 });
const user = { username: 'newuser', password: 'pass' }; const user = { username: 'newuser', password: 'pass' };
await store.createNewAccount(user); await store.createNewAccount(user);
expect(mockAxios.post).toHaveBeenCalledWith( expect(mockPost).toHaveBeenCalledWith(
'/api/users', user, '/api/users', user,
); );
expect(store.isOneAccountJustCreate).toBe(true); expect(store.isOneAccountJustCreate).toBe(true);
@@ -99,18 +99,18 @@ describe('acctMgmtStore', () => {
describe('deleteAccount', () => { describe('deleteAccount', () => {
it('returns true on success', async () => { it('returns true on success', async () => {
mockAxios.delete.mockResolvedValue({ status: 200 }); mockDelete.mockResolvedValue({ status: 200 });
const result = await store.deleteAccount('alice'); const result = await store.deleteAccount('alice');
expect(mockAxios.delete).toHaveBeenCalledWith( expect(mockDelete).toHaveBeenCalledWith(
'/api/users/alice', '/api/users/alice',
); );
expect(result).toBe(true); expect(result).toBe(true);
}); });
it('returns false on error', async () => { it('returns false on error', async () => {
mockAxios.delete.mockRejectedValue(new Error('fail')); mockDelete.mockRejectedValue(new Error('fail'));
const result = await store.deleteAccount('alice'); const result = await store.deleteAccount('alice');
@@ -120,7 +120,7 @@ describe('acctMgmtStore', () => {
describe('editAccount', () => { describe('editAccount', () => {
it('puts edited data', async () => { it('puts edited data', async () => {
mockAxios.put.mockResolvedValue({ status: 200 }); mockPut.mockResolvedValue({ status: 200 });
const detail = { const detail = {
username: 'alice', username: 'alice',
password: 'newpw', password: 'newpw',
@@ -130,7 +130,7 @@ describe('acctMgmtStore', () => {
const result = await store.editAccount('alice', detail); const result = await store.editAccount('alice', detail);
expect(mockAxios.put).toHaveBeenCalledWith( expect(mockPut).toHaveBeenCalledWith(
'/api/users/alice', '/api/users/alice',
expect.objectContaining({ password: 'newpw' }), expect.objectContaining({ password: 'newpw' }),
); );
@@ -140,11 +140,11 @@ describe('acctMgmtStore', () => {
describe('addRoleToUser', () => { describe('addRoleToUser', () => {
it('puts role assignment', async () => { it('puts role assignment', async () => {
mockAxios.put.mockResolvedValue({ status: 200 }); mockPut.mockResolvedValue({ status: 200 });
const result = await store.addRoleToUser('alice', 'admin'); const result = await store.addRoleToUser('alice', 'admin');
expect(mockAxios.put).toHaveBeenCalledWith( expect(mockPut).toHaveBeenCalledWith(
'/api/users/alice/roles/admin', '/api/users/alice/roles/admin',
); );
expect(result).toBe(true); expect(result).toBe(true);
@@ -153,11 +153,11 @@ describe('acctMgmtStore', () => {
describe('deleteRoleToUser', () => { describe('deleteRoleToUser', () => {
it('deletes role', async () => { it('deletes role', async () => {
mockAxios.delete.mockResolvedValue({ status: 200 }); mockDelete.mockResolvedValue({ status: 200 });
const result = await store.deleteRoleToUser('alice', 'admin'); const result = await store.deleteRoleToUser('alice', 'admin');
expect(mockAxios.delete).toHaveBeenCalledWith( expect(mockDelete).toHaveBeenCalledWith(
'/api/users/alice/roles/admin', '/api/users/alice/roles/admin',
); );
expect(result).toBe(true); expect(result).toBe(true);
@@ -166,7 +166,7 @@ describe('acctMgmtStore', () => {
describe('getUserDetail', () => { describe('getUserDetail', () => {
it('fetches user and sets admin flag', async () => { it('fetches user and sets admin flag', async () => {
mockAxios.get.mockResolvedValue({ mockGet.mockResolvedValue({
status: 200, status: 200,
data: { data: {
username: 'alice', username: 'alice',
@@ -181,7 +181,7 @@ describe('acctMgmtStore', () => {
}); });
it('returns false on error', async () => { it('returns false on error', async () => {
mockAxios.get.mockRejectedValue(new Error('not found')); mockGet.mockRejectedValue(new Error('not found'));
const result = await store.getUserDetail('ghost'); const result = await store.getUserDetail('ghost');

View File

@@ -5,20 +5,21 @@ vi.mock('@/module/apiError.js', () => ({
default: vi.fn(), default: vi.fn(),
})); }));
const { mockGet, mockPost, mockPut } = vi.hoisted(() => ({
mockGet: vi.fn(), mockPost: vi.fn(), mockPut: vi.fn(),
}));
vi.mock('@/api/client.js', () => ({
default: { get: mockGet, post: mockPost, put: mockPut },
}));
import useAllMapDataStore from '@/stores/allMapData.js'; import useAllMapDataStore from '@/stores/allMapData.js';
describe('allMapDataStore', () => { describe('allMapDataStore', () => {
let store; let store;
const mockAxios = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
};
beforeEach(() => { beforeEach(() => {
setActivePinia(createPinia()); setActivePinia(createPinia());
store = useAllMapDataStore(); store = useAllMapDataStore();
store.$axios = mockAxios;
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@@ -37,11 +38,11 @@ describe('allMapDataStore', () => {
stats: { cases: 10 }, stats: { cases: 10 },
insights: {}, insights: {},
}; };
mockAxios.get.mockResolvedValue({ data: mockData }); mockGet.mockResolvedValue({ data: mockData });
await store.getAllMapData(); await store.getAllMapData();
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/logs/1/discover', '/api/logs/1/discover',
); );
expect(store.allProcessMap).toEqual({ nodes: [] }); expect(store.allProcessMap).toEqual({ nodes: [] });
@@ -51,7 +52,7 @@ describe('allMapDataStore', () => {
it('fetches temp filter discover data when set', async () => { it('fetches temp filter discover data when set', async () => {
store.logId = 1; store.logId = 1;
store.tempFilterId = 5; store.tempFilterId = 5;
mockAxios.get.mockResolvedValue({ mockGet.mockResolvedValue({
data: { data: {
process_map: {}, process_map: {},
bpmn: {}, bpmn: {},
@@ -62,7 +63,7 @@ describe('allMapDataStore', () => {
await store.getAllMapData(); await store.getAllMapData();
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/temp-filters/5/discover', '/api/temp-filters/5/discover',
); );
}); });
@@ -70,7 +71,7 @@ describe('allMapDataStore', () => {
it('fetches created filter discover data', async () => { it('fetches created filter discover data', async () => {
store.logId = 1; store.logId = 1;
store.createFilterId = 3; store.createFilterId = 3;
mockAxios.get.mockResolvedValue({ mockGet.mockResolvedValue({
data: { data: {
process_map: {}, process_map: {},
bpmn: {}, bpmn: {},
@@ -81,14 +82,14 @@ describe('allMapDataStore', () => {
await store.getAllMapData(); await store.getAllMapData();
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/filters/3/discover', '/api/filters/3/discover',
); );
}); });
it('does not throw on API failure', async () => { it('does not throw on API failure', async () => {
store.logId = 1; store.logId = 1;
mockAxios.get.mockRejectedValue(new Error('fail')); mockGet.mockRejectedValue(new Error('fail'));
await expect(store.getAllMapData()) await expect(store.getAllMapData())
.resolves.not.toThrow(); .resolves.not.toThrow();
@@ -111,11 +112,11 @@ describe('allMapDataStore', () => {
trace: [], trace: [],
attrs: [], attrs: [],
}; };
mockAxios.get.mockResolvedValue({ data: mockData }); mockGet.mockResolvedValue({ data: mockData });
await store.getFilterParams(); await store.getFilterParams();
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/filters/params?log_id=1', '/api/filters/params?log_id=1',
); );
expect(store.allFilterTask).toEqual(['A', 'B']); expect(store.allFilterTask).toEqual(['A', 'B']);
@@ -129,13 +130,13 @@ describe('allMapDataStore', () => {
it('posts rule data and stores result', async () => { it('posts rule data and stores result', async () => {
store.logId = 1; store.logId = 1;
store.postRuleData = [{ type: 'task' }]; store.postRuleData = [{ type: 'task' }];
mockAxios.post.mockResolvedValue({ mockPost.mockResolvedValue({
data: { result: true }, data: { result: true },
}); });
await store.checkHasResult(); await store.checkHasResult();
expect(mockAxios.post).toHaveBeenCalledWith( expect(mockPost).toHaveBeenCalledWith(
'/api/filters/has-result?log_id=1', '/api/filters/has-result?log_id=1',
[{ type: 'task' }], [{ type: 'task' }],
); );
@@ -147,7 +148,7 @@ describe('allMapDataStore', () => {
it('creates temp filter and stores id', async () => { it('creates temp filter and stores id', async () => {
store.logId = 1; store.logId = 1;
store.postRuleData = []; store.postRuleData = [];
mockAxios.post.mockResolvedValue({ data: { id: 77 } }); mockPost.mockResolvedValue({ data: { id: 77 } });
await store.addTempFilterId(); await store.addTempFilterId();
@@ -160,11 +161,11 @@ describe('allMapDataStore', () => {
store.logId = 1; store.logId = 1;
store.tempFilterId = 77; store.tempFilterId = 77;
store.postRuleData = [{ type: 'rule' }]; store.postRuleData = [{ type: 'rule' }];
mockAxios.post.mockResolvedValue({ data: { id: 88 } }); mockPost.mockResolvedValue({ data: { id: 88 } });
await store.addFilterId('myFilter'); await store.addFilterId('myFilter');
expect(mockAxios.post).toHaveBeenCalledWith( expect(mockPost).toHaveBeenCalledWith(
'/api/filters?log_id=1', '/api/filters?log_id=1',
{ name: 'myFilter', rules: [{ type: 'rule' }] }, { name: 'myFilter', rules: [{ type: 'rule' }] },
); );
@@ -178,11 +179,11 @@ describe('allMapDataStore', () => {
store.createFilterId = 88; store.createFilterId = 88;
store.tempFilterId = 77; store.tempFilterId = 77;
store.postRuleData = [{ type: 'updated' }]; store.postRuleData = [{ type: 'updated' }];
mockAxios.put.mockResolvedValue({ status: 200 }); mockPut.mockResolvedValue({ status: 200 });
await store.updateFilter(); await store.updateFilter();
expect(mockAxios.put).toHaveBeenCalledWith( expect(mockPut).toHaveBeenCalledWith(
'/api/filters/88', '/api/filters/88',
[{ type: 'updated' }], [{ type: 'updated' }],
); );
@@ -195,7 +196,7 @@ describe('allMapDataStore', () => {
it('does not crash when baseLogId is falsy', async () => { it('does not crash when baseLogId is falsy', async () => {
store.logId = 1; store.logId = 1;
store.baseLogId = null; store.baseLogId = null;
mockAxios.get.mockResolvedValue({ data: [{ id: 1 }] }); mockGet.mockResolvedValue({ data: [{ id: 1 }] });
await store.getAllTrace(); await store.getAllTrace();

View File

@@ -5,18 +5,19 @@ vi.mock('@/module/apiError.js', () => ({
default: vi.fn(), default: vi.fn(),
})); }));
const { mockGet } = vi.hoisted(() => ({ mockGet: vi.fn() }));
vi.mock('@/api/client.js', () => ({
default: { get: mockGet },
}));
import useCompareStore from '@/stores/compare.js'; import useCompareStore from '@/stores/compare.js';
describe('compareStore', () => { describe('compareStore', () => {
let store; let store;
const mockAxios = {
get: vi.fn(),
};
beforeEach(() => { beforeEach(() => {
setActivePinia(createPinia()); setActivePinia(createPinia());
store = useCompareStore(); store = useCompareStore();
store.$axios = mockAxios;
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@@ -33,19 +34,19 @@ describe('compareStore', () => {
it('fetches compare data with encoded params', async () => { it('fetches compare data with encoded params', async () => {
const params = [{ type: 'log', id: 1 }]; const params = [{ type: 'log', id: 1 }];
const mockData = { time: {}, freq: {} }; const mockData = { time: {}, freq: {} };
mockAxios.get.mockResolvedValue({ data: mockData }); mockGet.mockResolvedValue({ data: mockData });
await store.getCompare(params); await store.getCompare(params);
const encoded = encodeURIComponent(JSON.stringify(params)); const encoded = encodeURIComponent(JSON.stringify(params));
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
`/api/compare?datasets=${encoded}`, `/api/compare?datasets=${encoded}`,
); );
expect(store.allCompareDashboardData).toEqual(mockData); expect(store.allCompareDashboardData).toEqual(mockData);
}); });
it('does not throw on API failure', async () => { it('does not throw on API failure', async () => {
mockAxios.get.mockRejectedValue(new Error('fail')); mockGet.mockRejectedValue(new Error('fail'));
await expect(store.getCompare([])) await expect(store.getCompare([]))
.resolves.not.toThrow(); .resolves.not.toThrow();
@@ -54,26 +55,26 @@ describe('compareStore', () => {
describe('getStateData', () => { describe('getStateData', () => {
it('fetches log discover stats', async () => { it('fetches log discover stats', async () => {
mockAxios.get.mockResolvedValue({ mockGet.mockResolvedValue({
data: { stats: { cases: 100 } }, data: { stats: { cases: 100 } },
}); });
const result = await store.getStateData('log', 1); const result = await store.getStateData('log', 1);
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/logs/1/discover', '/api/logs/1/discover',
); );
expect(result).toEqual({ cases: 100 }); expect(result).toEqual({ cases: 100 });
}); });
it('fetches filter discover stats', async () => { it('fetches filter discover stats', async () => {
mockAxios.get.mockResolvedValue({ mockGet.mockResolvedValue({
data: { stats: { cases: 50 } }, data: { stats: { cases: 50 } },
}); });
const result = await store.getStateData('filter', 3); const result = await store.getStateData('filter', 3);
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/filters/3/discover', '/api/filters/3/discover',
); );
expect(result).toEqual({ cases: 50 }); expect(result).toEqual({ cases: 50 });
@@ -82,7 +83,7 @@ describe('compareStore', () => {
describe('getFileName', () => { describe('getFileName', () => {
it('finds file name by id', async () => { it('finds file name by id', async () => {
mockAxios.get.mockResolvedValue({ mockGet.mockResolvedValue({
data: [ data: [
{ id: 1, name: 'file1.csv' }, { id: 1, name: 'file1.csv' },
{ id: 2, name: 'file2.csv' }, { id: 2, name: 'file2.csv' },
@@ -95,7 +96,7 @@ describe('compareStore', () => {
}); });
it('returns undefined for non-existent id', async () => { it('returns undefined for non-existent id', async () => {
mockAxios.get.mockResolvedValue({ mockGet.mockResolvedValue({
data: [{ id: 1, name: 'file1.csv' }], data: [{ id: 1, name: 'file1.csv' }],
}); });

View File

@@ -5,21 +5,22 @@ vi.mock('@/module/apiError.js', () => ({
default: vi.fn(), default: vi.fn(),
})); }));
const { mockGet, mockPost, mockPut } = vi.hoisted(() => ({
mockGet: vi.fn(), mockPost: vi.fn(), mockPut: vi.fn(),
}));
vi.mock('@/api/client.js', () => ({
default: { get: mockGet, post: mockPost, put: mockPut },
}));
import apiError from '@/module/apiError.js'; import apiError from '@/module/apiError.js';
import useConformanceStore from '@/stores/conformance.js'; import useConformanceStore from '@/stores/conformance.js';
describe('conformanceStore', () => { describe('conformanceStore', () => {
let store; let store;
const mockAxios = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
};
beforeEach(() => { beforeEach(() => {
setActivePinia(createPinia()); setActivePinia(createPinia());
store = useConformanceStore(); store = useConformanceStore();
store.$axios = mockAxios;
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@@ -42,11 +43,11 @@ describe('conformanceStore', () => {
waiting_time: {}, waiting_time: {},
cycle_time: {}, cycle_time: {},
}; };
mockAxios.get.mockResolvedValue({ data: mockData }); mockGet.mockResolvedValue({ data: mockData });
await store.getConformanceParams(); await store.getConformanceParams();
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/log-checks/params?log_id=1', '/api/log-checks/params?log_id=1',
); );
expect(store.allConformanceTask).toEqual([{ label: 'A' }]); expect(store.allConformanceTask).toEqual([{ label: 'A' }]);
@@ -63,11 +64,11 @@ describe('conformanceStore', () => {
waiting_time: {}, waiting_time: {},
cycle_time: {}, cycle_time: {},
}; };
mockAxios.get.mockResolvedValue({ data: mockData }); mockGet.mockResolvedValue({ data: mockData });
await store.getConformanceParams(); await store.getConformanceParams();
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/filter-checks/params?filter_id=5', '/api/filter-checks/params?filter_id=5',
); );
}); });
@@ -77,11 +78,11 @@ describe('conformanceStore', () => {
it('posts to log temp-check and stores id', async () => { it('posts to log temp-check and stores id', async () => {
store.conformanceLogId = 1; store.conformanceLogId = 1;
store.conformanceFilterId = null; store.conformanceFilterId = null;
mockAxios.post.mockResolvedValue({ data: { id: 42 } }); mockPost.mockResolvedValue({ data: { id: 42 } });
await store.addConformanceCheckId({ rule: 'test' }); await store.addConformanceCheckId({ rule: 'test' });
expect(mockAxios.post).toHaveBeenCalledWith( expect(mockPost).toHaveBeenCalledWith(
'/api/temp-log-checks?log_id=1', '/api/temp-log-checks?log_id=1',
{ rule: 'test' }, { rule: 'test' },
); );
@@ -90,11 +91,11 @@ describe('conformanceStore', () => {
it('posts to filter temp-check when filter set', async () => { it('posts to filter temp-check when filter set', async () => {
store.conformanceFilterId = 3; store.conformanceFilterId = 3;
mockAxios.post.mockResolvedValue({ data: { id: 99 } }); mockPost.mockResolvedValue({ data: { id: 99 } });
await store.addConformanceCheckId({ rule: 'test' }); await store.addConformanceCheckId({ rule: 'test' });
expect(mockAxios.post).toHaveBeenCalledWith( expect(mockPost).toHaveBeenCalledWith(
'/api/temp-filter-checks?filter_id=3', '/api/temp-filter-checks?filter_id=3',
{ rule: 'test' }, { rule: 'test' },
); );
@@ -106,11 +107,11 @@ describe('conformanceStore', () => {
it('fetches temp log check report', async () => { it('fetches temp log check report', async () => {
store.conformanceLogTempCheckId = 10; store.conformanceLogTempCheckId = 10;
const mockData = { file: {}, charts: {} }; const mockData = { file: {}, charts: {} };
mockAxios.get.mockResolvedValue({ data: mockData }); mockGet.mockResolvedValue({ data: mockData });
await store.getConformanceReport(); await store.getConformanceReport();
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/temp-log-checks/10', '/api/temp-log-checks/10',
); );
expect(store.allConformanceTempReportData).toEqual(mockData); expect(store.allConformanceTempReportData).toEqual(mockData);
@@ -119,7 +120,7 @@ describe('conformanceStore', () => {
it('stores routeFile when getRouteFile=true', async () => { it('stores routeFile when getRouteFile=true', async () => {
store.conformanceLogTempCheckId = 10; store.conformanceLogTempCheckId = 10;
const mockData = { file: { name: 'test.csv' } }; const mockData = { file: { name: 'test.csv' } };
mockAxios.get.mockResolvedValue({ data: mockData }); mockGet.mockResolvedValue({ data: mockData });
await store.getConformanceReport(true); await store.getConformanceReport(true);
@@ -134,11 +135,11 @@ describe('conformanceStore', () => {
store.conformanceFilterId = null; store.conformanceFilterId = null;
store.conformanceLogTempCheckId = 10; store.conformanceLogTempCheckId = 10;
store.conformanceRuleData = { type: 'test' }; store.conformanceRuleData = { type: 'test' };
mockAxios.post.mockResolvedValue({ data: { id: 100 } }); mockPost.mockResolvedValue({ data: { id: 100 } });
await store.addConformanceCreateCheckId('myRule'); await store.addConformanceCreateCheckId('myRule');
expect(mockAxios.post).toHaveBeenCalledWith( expect(mockPost).toHaveBeenCalledWith(
'/api/log-checks?log_id=1', '/api/log-checks?log_id=1',
{ name: 'myRule', rule: { type: 'test' } }, { name: 'myRule', rule: { type: 'test' } },
); );
@@ -151,11 +152,11 @@ describe('conformanceStore', () => {
it('updates existing log check', async () => { it('updates existing log check', async () => {
store.conformanceLogCreateCheckId = 50; store.conformanceLogCreateCheckId = 50;
store.conformanceRuleData = { type: 'updated' }; store.conformanceRuleData = { type: 'updated' };
mockAxios.put.mockResolvedValue({ status: 200 }); mockPut.mockResolvedValue({ status: 200 });
await store.updateConformance(); await store.updateConformance();
expect(mockAxios.put).toHaveBeenCalledWith( expect(mockPut).toHaveBeenCalledWith(
'/api/log-checks/50', '/api/log-checks/50',
{ type: 'updated' }, { type: 'updated' },
); );

View File

@@ -21,13 +21,14 @@ vi.mock('@/router/index.ts', () => ({
default: { push: vi.fn(), currentRoute: { value: { path: '/' } } }, default: { push: vi.fn(), currentRoute: { value: { path: '/' } } },
})); }));
import axios from 'axios'; const { mockGet, mockPost, mockPut, mockDelete } = vi.hoisted(() => ({
import useFilesStore from '@/stores/files.js'; mockGet: vi.fn(), mockPost: vi.fn(), mockPut: vi.fn(), mockDelete: vi.fn(),
}));
vi.mock('@/api/client.js', () => ({
default: { get: mockGet, post: mockPost, put: mockPut, delete: mockDelete },
}));
vi.spyOn(axios, 'get').mockImplementation(vi.fn()); import useFilesStore from '@/stores/files.js';
vi.spyOn(axios, 'post').mockImplementation(vi.fn());
vi.spyOn(axios, 'put').mockImplementation(vi.fn());
vi.spyOn(axios, 'delete').mockImplementation(vi.fn());
describe('filesStore', () => { describe('filesStore', () => {
let store; let store;
@@ -63,7 +64,7 @@ describe('filesStore', () => {
describe('fetchAllFiles', () => { describe('fetchAllFiles', () => {
it('fetches and transforms file data', async () => { it('fetches and transforms file data', async () => {
axios.get.mockResolvedValue({ mockGet.mockResolvedValue({
data: [ data: [
{ {
type: 'log', type: 'log',
@@ -85,7 +86,7 @@ describe('filesStore', () => {
await store.fetchAllFiles(); await store.fetchAllFiles();
expect(axios.get).toHaveBeenCalledWith('/api/files'); expect(mockGet).toHaveBeenCalledWith('/api/files');
expect(store.allEventFiles).toHaveLength(2); expect(store.allEventFiles).toHaveLength(2);
expect(store.allEventFiles[0].fileType).toBe('Log'); expect(store.allEventFiles[0].fileType).toBe('Log');
expect(store.allEventFiles[0].icon).toBe('work_history'); expect(store.allEventFiles[0].icon).toBe('work_history');
@@ -96,19 +97,19 @@ describe('filesStore', () => {
}); });
it('does not throw on API failure', async () => { it('does not throw on API failure', async () => {
axios.get.mockRejectedValue(new Error('Network error')); mockGet.mockRejectedValue(new Error('Network error'));
await expect(store.fetchAllFiles()).resolves.toBeUndefined(); await expect(store.fetchAllFiles()).resolves.toBeUndefined();
}); });
}); });
describe('upload', () => { describe('upload', () => {
it('uploads file and navigates to Upload page', async () => { it('uploads file and navigates to Upload page', async () => {
axios.post.mockResolvedValue({ data: { id: 42 } }); mockPost.mockResolvedValue({ data: { id: 42 } });
const formData = new FormData(); const formData = new FormData();
await store.upload(formData); await store.upload(formData);
expect(axios.post).toHaveBeenCalledWith( expect(mockPost).toHaveBeenCalledWith(
'/api/logs/csv-uploads', '/api/logs/csv-uploads',
formData, formData,
expect.objectContaining({ expect.objectContaining({
@@ -123,25 +124,25 @@ describe('filesStore', () => {
describe('getUploadDetail', () => { describe('getUploadDetail', () => {
it('fetches upload preview', async () => { it('fetches upload preview', async () => {
store.uploadId = 10; store.uploadId = 10;
axios.get.mockResolvedValue({ mockGet.mockResolvedValue({
data: { preview: { columns: ['a', 'b'] } }, data: { preview: { columns: ['a', 'b'] } },
}); });
await store.getUploadDetail(); await store.getUploadDetail();
expect(axios.get).toHaveBeenCalledWith('/api/logs/csv-uploads/10'); expect(mockGet).toHaveBeenCalledWith('/api/logs/csv-uploads/10');
expect(store.allUploadDetail).toEqual({ columns: ['a', 'b'] }); expect(store.allUploadDetail).toEqual({ columns: ['a', 'b'] });
}); });
}); });
describe('rename', () => { describe('rename', () => {
it('renames a log file', async () => { it('renames a log file', async () => {
axios.put.mockResolvedValue({}); mockPut.mockResolvedValue({});
axios.get.mockResolvedValue({ data: [] }); mockGet.mockResolvedValue({ data: [] });
await store.rename('log', 5, 'new-name'); await store.rename('log', 5, 'new-name');
expect(axios.put).toHaveBeenCalledWith('/api/logs/5/name', { expect(mockPut).toHaveBeenCalledWith('/api/logs/5/name', {
name: 'new-name', name: 'new-name',
}); });
}); });
@@ -149,30 +150,30 @@ describe('filesStore', () => {
describe('getDependents', () => { describe('getDependents', () => {
it('fetches dependents for a log', async () => { it('fetches dependents for a log', async () => {
axios.get.mockResolvedValue({ data: [{ id: 1 }, { id: 2 }] }); mockGet.mockResolvedValue({ data: [{ id: 1 }, { id: 2 }] });
await store.getDependents('log', 7); await store.getDependents('log', 7);
expect(axios.get).toHaveBeenCalledWith('/api/logs/7/dependents'); expect(mockGet).toHaveBeenCalledWith('/api/logs/7/dependents');
expect(store.allDependentsData).toEqual([{ id: 1 }, { id: 2 }]); expect(store.allDependentsData).toEqual([{ id: 1 }, { id: 2 }]);
}); });
}); });
describe('deleteFile', () => { describe('deleteFile', () => {
it('calls axios.delete before fetchAllFiles', async () => { it('calls mockDelete before fetchAllFiles', async () => {
const callOrder = []; const callOrder = [];
axios.delete.mockImplementation(async () => { mockDelete.mockImplementation(async () => {
callOrder.push('delete'); callOrder.push('delete');
return {}; return {};
}); });
axios.get.mockImplementation(async () => { mockGet.mockImplementation(async () => {
callOrder.push('get'); callOrder.push('get');
return { data: [] }; return { data: [] };
}); });
await store.deleteFile('log', 1); await store.deleteFile('log', 1);
expect(axios.delete).toHaveBeenCalledWith('/api/logs/1'); expect(mockDelete).toHaveBeenCalledWith('/api/logs/1');
expect(callOrder.indexOf('delete')).toBeLessThan( expect(callOrder.indexOf('delete')).toBeLessThan(
callOrder.indexOf('get'), callOrder.indexOf('get'),
); );
@@ -183,37 +184,37 @@ describe('filesStore', () => {
store.deleteFile('log', null), store.deleteFile('log', null),
).resolves.toBeUndefined(); ).resolves.toBeUndefined();
expect(axios.delete).not.toHaveBeenCalled(); expect(mockDelete).not.toHaveBeenCalled();
}); });
}); });
describe('deletionRecord', () => { describe('deletionRecord', () => {
it('deletes a deletion record', async () => { it('deletes a deletion record', async () => {
axios.delete.mockResolvedValue({}); mockDelete.mockResolvedValue({});
await store.deletionRecord(5); await store.deletionRecord(5);
expect(axios.delete).toHaveBeenCalledWith('/api/deletion/5'); expect(mockDelete).toHaveBeenCalledWith('/api/deletion/5');
}); });
}); });
describe('downloadFileCSV', () => { describe('downloadFileCSV', () => {
it('downloads CSV for a log', async () => { it('downloads CSV for a log', async () => {
axios.get.mockResolvedValue({ data: 'col1,col2\na,b' }); mockGet.mockResolvedValue({ data: 'col1,col2\na,b' });
window.URL.createObjectURL = vi.fn().mockReturnValue('blob:test'); window.URL.createObjectURL = vi.fn().mockReturnValue('blob:test');
window.URL.revokeObjectURL = vi.fn(); window.URL.revokeObjectURL = vi.fn();
await store.downloadFileCSV('log', 3, 'my-file'); await store.downloadFileCSV('log', 3, 'my-file');
expect(axios.get).toHaveBeenCalledWith('/api/logs/3/csv'); expect(mockGet).toHaveBeenCalledWith('/api/logs/3/csv');
expect(window.URL.revokeObjectURL).toHaveBeenCalledWith('blob:test'); expect(window.URL.revokeObjectURL).toHaveBeenCalledWith('blob:test');
}); });
it('returns early for unsupported type', async () => { it('returns early for unsupported type', async () => {
await store.downloadFileCSV('log-check', 3, 'file'); await store.downloadFileCSV('log-check', 3, 'file');
expect(axios.get).not.toHaveBeenCalled(); expect(mockGet).not.toHaveBeenCalled();
}); });
}); });
}); });

View File

@@ -7,11 +7,16 @@ vi.mock('@/module/apiError.js', () => ({
})); }));
import axios from 'axios'; import axios from 'axios';
const { mockClientGet } = vi.hoisted(() => ({ mockClientGet: vi.fn() }));
vi.mock('@/api/client.js', () => ({
default: { get: mockClientGet },
}));
import useLoginStore from '@/stores/login.ts'; import useLoginStore from '@/stores/login.ts';
// Mock axios methods // Mock axios methods (used for signIn/refreshToken which call plain axios)
vi.spyOn(axios, 'post').mockImplementation(vi.fn()); vi.spyOn(axios, 'post').mockImplementation(vi.fn());
vi.spyOn(axios, 'get').mockImplementation(vi.fn());
describe('loginStore', () => { describe('loginStore', () => {
let store; let store;
@@ -118,13 +123,13 @@ describe('loginStore', () => {
describe('getUserData', () => { describe('getUserData', () => {
it('stores user data on success', async () => { it('stores user data on success', async () => {
axios.get.mockResolvedValue({ mockClientGet.mockResolvedValue({
data: { username: 'testuser', name: 'Test User' }, data: { username: 'testuser', name: 'Test User' },
}); });
await store.getUserData(); await store.getUserData();
expect(axios.get).toHaveBeenCalledWith('/api/my-account'); expect(mockClientGet).toHaveBeenCalledWith('/api/my-account');
expect(store.userData).toEqual({ expect(store.userData).toEqual({
username: 'testuser', username: 'testuser',
name: 'Test User', name: 'Test User',
@@ -134,7 +139,7 @@ describe('loginStore', () => {
describe('checkLogin', () => { describe('checkLogin', () => {
it('does not redirect on success', async () => { it('does not redirect on success', async () => {
axios.get.mockResolvedValue({ data: {} }); mockClientGet.mockResolvedValue({ data: {} });
await store.checkLogin(); await store.checkLogin();
@@ -142,7 +147,7 @@ describe('loginStore', () => {
}); });
it('redirects to login on error', async () => { it('redirects to login on error', async () => {
axios.get.mockRejectedValue(new Error('401')); mockClientGet.mockRejectedValue(new Error('401'));
await store.checkLogin(); await store.checkLogin();
@@ -186,10 +191,6 @@ describe('loginStore', () => {
}), }),
); );
// Should update axios default Authorization header
expect(axios.defaults.headers.common['Authorization'])
.toBe('Bearer new-access-token');
// Verify cookies were set with Secure flag // Verify cookies were set with Secure flag
const cookieSetter = vi.spyOn(document, 'cookie', 'set'); const cookieSetter = vi.spyOn(document, 'cookie', 'set');
vi.clearAllMocks(); vi.clearAllMocks();

View File

@@ -5,18 +5,19 @@ vi.mock('@/module/apiError.js', () => ({
default: vi.fn(), default: vi.fn(),
})); }));
const { mockGet } = vi.hoisted(() => ({ mockGet: vi.fn() }));
vi.mock('@/api/client.js', () => ({
default: { get: mockGet },
}));
import usePerformanceStore from '@/stores/performance.js'; import usePerformanceStore from '@/stores/performance.js';
describe('performanceStore', () => { describe('performanceStore', () => {
let store; let store;
const mockAxios = {
get: vi.fn(),
};
beforeEach(() => { beforeEach(() => {
setActivePinia(createPinia()); setActivePinia(createPinia());
store = usePerformanceStore(); store = usePerformanceStore();
store.$axios = mockAxios;
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@@ -33,28 +34,28 @@ describe('performanceStore', () => {
describe('getPerformance', () => { describe('getPerformance', () => {
it('fetches log performance data', async () => { it('fetches log performance data', async () => {
const mockData = { time: { charts: [] }, freq: { charts: [] } }; const mockData = { time: { charts: [] }, freq: { charts: [] } };
mockAxios.get.mockResolvedValue({ data: mockData }); mockGet.mockResolvedValue({ data: mockData });
await store.getPerformance('log', 1); await store.getPerformance('log', 1);
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/logs/1/performance', '/api/logs/1/performance',
); );
expect(store.allPerformanceData).toEqual(mockData); expect(store.allPerformanceData).toEqual(mockData);
}); });
it('fetches filter performance data', async () => { it('fetches filter performance data', async () => {
mockAxios.get.mockResolvedValue({ data: { time: {} } }); mockGet.mockResolvedValue({ data: { time: {} } });
await store.getPerformance('filter', 5); await store.getPerformance('filter', 5);
expect(mockAxios.get).toHaveBeenCalledWith( expect(mockGet).toHaveBeenCalledWith(
'/api/filters/5/performance', '/api/filters/5/performance',
); );
}); });
it('does not throw on API failure', async () => { it('does not throw on API failure', async () => {
mockAxios.get.mockRejectedValue(new Error('Network error')); mockGet.mockRejectedValue(new Error('Network error'));
// Should not throw - apiError handles it // Should not throw - apiError handles it
await expect(store.getPerformance('log', 1)) await expect(store.getPerformance('log', 1))