fix(account): ignore stale downloaded history

This commit is contained in:
2026-05-19 01:33:34 +08:00
parent bbd9cbccb7
commit ac1f46bd73
2 changed files with 105 additions and 3 deletions
+16
View File
@@ -419,6 +419,7 @@ const favoriteRequestGeneration = ref(0);
const downloadedApps = ref<DownloadedAppRecord[]>([]); const downloadedApps = ref<DownloadedAppRecord[]>([]);
const downloadedLoading = ref(false); const downloadedLoading = ref(false);
const downloadedError = ref(""); const downloadedError = ref("");
const downloadedRequestGeneration = ref(0);
const systemInfo = ref<SystemInfo>({ distro: "unknown" }); const systemInfo = ref<SystemInfo>({ distro: "unknown" });
type PendingDownloadRecord = Omit< type PendingDownloadRecord = Omit<
DownloadedAppRecord, DownloadedAppRecord,
@@ -1411,6 +1412,7 @@ const clearFavoriteState = () => {
}; };
const clearDownloadedState = () => { const clearDownloadedState = () => {
downloadedRequestGeneration.value += 1;
downloadedApps.value = []; downloadedApps.value = [];
downloadedLoading.value = false; downloadedLoading.value = false;
downloadedError.value = ""; downloadedError.value = "";
@@ -1419,6 +1421,13 @@ const clearDownloadedState = () => {
const isCurrentFavoriteRequest = (generation: number): boolean => const isCurrentFavoriteRequest = (generation: number): boolean =>
favoriteRequestGeneration.value === generation && isLoggedIn.value; favoriteRequestGeneration.value === generation && isLoggedIn.value;
const isCurrentDownloadedRequest = (
generation: number,
userId: number,
): boolean =>
downloadedRequestGeneration.value === generation &&
currentUser.value?.id === userId;
const handleLogout = () => { const handleLogout = () => {
logout(); logout();
pendingDownloadRecords.clear(); pendingDownloadRecords.clear();
@@ -1455,18 +1464,25 @@ const handleFlarumLogin = async (payload: FlarumLoginPayload) => {
const loadDownloadedHistory = async (): Promise<void> => { const loadDownloadedHistory = async (): Promise<void> => {
if (!requireLogin("请登录后查看和管理账号信息。")) return; if (!requireLogin("请登录后查看和管理账号信息。")) return;
const userId = currentUser.value?.id;
if (userId === undefined) return;
const generation = downloadedRequestGeneration.value;
downloadedLoading.value = true; downloadedLoading.value = true;
downloadedError.value = ""; downloadedError.value = "";
try { try {
const result = await listDownloadedApps(1, 50); const result = await listDownloadedApps(1, 50);
if (!isCurrentDownloadedRequest(generation, userId)) return;
downloadedApps.value = result.items; downloadedApps.value = result.items;
} catch (error: unknown) { } catch (error: unknown) {
if (!isCurrentDownloadedRequest(generation, userId)) return;
downloadedApps.value = []; downloadedApps.value = [];
downloadedError.value = (error as Error)?.message || "读取下载历史失败"; downloadedError.value = (error as Error)?.message || "读取下载历史失败";
} finally { } finally {
if (isCurrentDownloadedRequest(generation, userId)) {
downloadedLoading.value = false; downloadedLoading.value = false;
} }
}
}; };
const syncInstalledAppsNow = () => { const syncInstalledAppsNow = () => {
@@ -2,9 +2,13 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/vue";
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import App from "@/App.vue"; import App from "@/App.vue";
import { listFavoriteFolders } from "@/modules/backendApi"; import { listDownloadedApps, listFavoriteFolders } from "@/modules/backendApi";
import { setAuthSession } from "@/global/authState"; import { setAuthSession } from "@/global/authState";
import type { FavoriteFolder, FavoriteItem } from "@/global/typedefinition"; import type {
DownloadedAppList,
FavoriteFolder,
FavoriteItem,
} from "@/global/typedefinition";
const invoke = vi.fn(); const invoke = vi.fn();
@@ -39,6 +43,31 @@ const createDeferred = <T>() => {
return { promise, resolve }; return { promise, resolve };
}; };
const downloadedList = (
items: DownloadedAppList["items"],
): DownloadedAppList => ({
items,
total: items.length,
page: 1,
pageSize: 50,
});
const setSecondUserSession = () => {
setAuthSession({
accessToken: "backend-token-b",
tokenType: "bearer",
user: {
id: 2,
flarumUserId: "84",
username: "second",
displayName: "Second User",
avatarUrl: "https://bbs.spark-app.store/avatar-b.png",
forumLevel: "用户",
forumGroups: ["用户"],
},
});
};
vi.mock("axios", () => { vi.mock("axios", () => {
const get = vi.fn(async (url: string) => { const get = vi.fn(async (url: string) => {
if (url.includes("categories.json")) { if (url.includes("categories.json")) {
@@ -109,6 +138,7 @@ vi.mock("@/modules/backendApi", () => ({
bulkDeleteFavoriteItems: vi.fn(), bulkDeleteFavoriteItems: vi.fn(),
createFavoriteFolder: vi.fn(), createFavoriteFolder: vi.fn(),
exchangeFlarumToken: vi.fn(), exchangeFlarumToken: vi.fn(),
listDownloadedApps: vi.fn(async () => downloadedList([])),
listFavoriteFolders: vi.fn(async () => favoriteFolders), listFavoriteFolders: vi.fn(async () => favoriteFolders),
listFavoriteItems: vi.fn(async () => favoriteItems), listFavoriteItems: vi.fn(async () => favoriteItems),
setBackendToken: vi.fn(), setBackendToken: vi.fn(),
@@ -351,4 +381,60 @@ describe("App account placeholders", () => {
expect(screen.queryByText("旧账号收藏夹")).toBeNull(); expect(screen.queryByText("旧账号收藏夹")).toBeNull();
expect(screen.queryByRole("dialog", { name: "选择收藏夹" })).toBeNull(); expect(screen.queryByRole("dialog", { name: "选择收藏夹" })).toBeNull();
}); });
it("ignores downloaded history that resolves after switching users", async () => {
const firstHistory = createDeferred<DownloadedAppList>();
const secondHistory = createDeferred<DownloadedAppList>();
vi.mocked(listDownloadedApps)
.mockReturnValueOnce(firstHistory.promise)
.mockReturnValueOnce(secondHistory.promise);
render(App);
await fireEvent.click(await screen.findByRole("button", { name: /Momen/ }));
await fireEvent.click(screen.getByText("用户管理"));
expect(await screen.findByText("正在加载下载历史...")).toBeTruthy();
await fireEvent.click(
await screen.findByRole("button", { name: /^Momen$/ }),
);
if (!screen.queryByText("退出登录")) {
await fireEvent.click(
await screen.findByRole("button", { name: /^Momen$/ }),
);
}
await fireEvent.click(screen.getByText("退出登录"));
setSecondUserSession();
const secondUserButton = await screen.findByRole("button", {
name: /^Second User$/,
});
if (!screen.queryByText("用户管理")) {
await fireEvent.click(secondUserButton);
}
await fireEvent.click(await screen.findByText("用户管理"));
secondHistory.resolve(downloadedList([]));
expect(await screen.findByText("暂无下载记录。")).toBeTruthy();
firstHistory.resolve(
downloadedList([
{
id: 77,
appKey: "app:office:old-account-app",
pkgname: "old-account-app",
name: "旧账号应用",
category: "office",
selectedOrigin: "apm",
version: "1.0.0",
packageArch: "amd64",
downloadedAt: "2026-05-18T00:00:00Z",
},
]),
);
await firstHistory.promise;
await Promise.resolve();
await Promise.resolve();
expect(screen.queryByText("旧账号应用")).toBeNull();
expect(screen.getByText("暂无下载记录。")).toBeTruthy();
});
}); });