fix(updates): auto-continue client upgrade on update-all
This commit is contained in:
10 files changed
+200
-61
No files matched your search
@@ -204,6 +204,7 @@ export interface UpgradeAllAgentDriversResult {
|
||||
export interface AgentUpdateBlocker {
|
||||
db_type: string;
|
||||
label: string;
|
||||
connections: string[];
|
||||
}
|
||||
|
||||
export type AgentOfflineArtifactKind = "jar" | "native";
|
||||
|
||||
+70
-3
@@ -3,6 +3,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clearPendingComponentUpdatesAfterAppUpdate,
|
||||
continuePreparedAppUpdate,
|
||||
hasPendingComponentUpdatesAfterAppRestart,
|
||||
markPendingComponentUpdatesAfterAppUpdate,
|
||||
resolveUpdateAllAction,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
runPendingComponentUpdatePlan,
|
||||
shouldCloseUpdateCenterAfterComponentUpdate,
|
||||
takePendingComponentUpdatesAfterAppRestart,
|
||||
updateBlockerLabels,
|
||||
} from "@/lib/updates/componentUpdateOrchestration";
|
||||
|
||||
describe("component update orchestration", () => {
|
||||
@@ -17,6 +19,16 @@ describe("component update orchestration", () => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it("lists blocked connection names and falls back to the driver label", () => {
|
||||
expect(
|
||||
updateBlockerLabels([
|
||||
{ label: "MySQL", connections: ["生产 MySQL", "报表 MySQL"] },
|
||||
{ label: "Oracle", connections: [] },
|
||||
{ label: "DuckDB", connections: ["生产 MySQL"] },
|
||||
]),
|
||||
).toEqual(["生产 MySQL", "报表 MySQL", "Oracle"]);
|
||||
});
|
||||
|
||||
it("keeps pending component updates across a failed or ordinary startup", () => {
|
||||
expect(markPendingComponentUpdatesAfterAppUpdate("0.6.16", "0.6.17")).toBe(true);
|
||||
|
||||
@@ -162,19 +174,74 @@ describe("component update orchestration", () => {
|
||||
expect(takePendingComponentUpdatesAfterAppRestart("0.6.17")?.plan).toEqual({ kind: "auto" });
|
||||
});
|
||||
|
||||
it("downloads DBX before deferring component updates", () => {
|
||||
it("downloads DBX before installing it with component updates", () => {
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: true, appUpdatePrepared: false, hasComponentUpdates: true })).toBe("download-app");
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: true, appUpdatePrepared: true, hasComponentUpdates: true })).toBe("defer-components");
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: true, appUpdatePrepared: true, hasComponentUpdates: true })).toBe("install-app");
|
||||
});
|
||||
|
||||
it("persists the selected component categories when the DBX package is already prepared", () => {
|
||||
const action = resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: true, appUpdatePrepared: true, hasComponentUpdates: true });
|
||||
expect(action).toBe("defer-components");
|
||||
expect(action).toBe("install-app");
|
||||
|
||||
expect(markPendingComponentUpdatesAfterAppUpdate("0.6.16", "0.6.17", { kind: "manual", categories: ["drivers", "mcp"] })).toBe(true);
|
||||
expect(takePendingComponentUpdatesAfterAppRestart("0.6.17")?.plan).toEqual({ kind: "manual", categories: ["drivers", "mcp"] });
|
||||
});
|
||||
|
||||
it("continues from update all into the prepared app install", async () => {
|
||||
const rememberComponentUpdates = vi.fn(async () => true);
|
||||
const installComponents = vi.fn(async () => {});
|
||||
const installDownloadedUpdate = vi.fn(async () => {});
|
||||
const restartApp = vi.fn(async () => {});
|
||||
|
||||
await continuePreparedAppUpdate({
|
||||
hasComponentUpdates: true,
|
||||
restartOnly: false,
|
||||
rememberComponentUpdates,
|
||||
installComponents,
|
||||
installDownloadedUpdate,
|
||||
restartApp,
|
||||
});
|
||||
|
||||
expect(rememberComponentUpdates).toHaveBeenCalledOnce();
|
||||
expect(installDownloadedUpdate).toHaveBeenCalledOnce();
|
||||
expect(restartApp).not.toHaveBeenCalled();
|
||||
expect(installComponents).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("continues from update all into restart when the app update is already staged", async () => {
|
||||
const restartApp = vi.fn(async () => {});
|
||||
|
||||
await continuePreparedAppUpdate({
|
||||
hasComponentUpdates: true,
|
||||
restartOnly: true,
|
||||
rememberComponentUpdates: async () => true,
|
||||
installComponents: async () => {},
|
||||
installDownloadedUpdate: async () => {},
|
||||
restartApp,
|
||||
});
|
||||
|
||||
expect(restartApp).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("falls back to component updates without restarting when the restart plan cannot be persisted", async () => {
|
||||
const installComponents = vi.fn(async () => {});
|
||||
const installDownloadedUpdate = vi.fn(async () => {});
|
||||
const restartApp = vi.fn(async () => {});
|
||||
|
||||
await continuePreparedAppUpdate({
|
||||
hasComponentUpdates: true,
|
||||
restartOnly: false,
|
||||
rememberComponentUpdates: async () => false,
|
||||
installComponents,
|
||||
installDownloadedUpdate,
|
||||
restartApp,
|
||||
});
|
||||
|
||||
expect(installComponents).toHaveBeenCalledOnce();
|
||||
expect(installDownloadedUpdate).not.toHaveBeenCalled();
|
||||
expect(restartApp).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates components immediately when no DBX update exists or the package cannot be installed", () => {
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: false, appUpdateCanInstall: true, appUpdatePrepared: false, hasComponentUpdates: true })).toBe("update-components");
|
||||
expect(resolveUpdateAllAction({ hasAppUpdate: true, appUpdateCanInstall: false, appUpdatePrepared: false, hasComponentUpdates: true })).toBe("update-components");
|
||||
|
||||
@@ -14,7 +14,11 @@ export interface PendingComponentUpdates {
|
||||
plan: PendingComponentUpdatePlan;
|
||||
}
|
||||
|
||||
export type UpdateAllAction = "none" | "download-app" | "defer-components" | "update-components";
|
||||
export type UpdateAllAction = "none" | "download-app" | "install-app" | "update-components";
|
||||
|
||||
export function updateBlockerLabels(blockers: Array<{ label: string; connections?: string[] }>): string[] {
|
||||
return [...new Set(blockers.flatMap((blocker) => (blocker.connections?.length ? blocker.connections : [blocker.label])))];
|
||||
}
|
||||
|
||||
function normalizeVersion(version: string): string {
|
||||
return version.trim().replace(/^[vV]/, "");
|
||||
@@ -112,7 +116,16 @@ export function takePendingComponentUpdatesAfterAppRestart(currentVersion: strin
|
||||
|
||||
export function resolveUpdateAllAction(options: { hasAppUpdate: boolean; appUpdateCanInstall: boolean; appUpdatePrepared: boolean; hasComponentUpdates: boolean }): UpdateAllAction {
|
||||
if (!options.hasAppUpdate || !options.appUpdateCanInstall) return options.hasComponentUpdates ? "update-components" : "none";
|
||||
return options.appUpdatePrepared ? "defer-components" : "download-app";
|
||||
return options.appUpdatePrepared ? "install-app" : "download-app";
|
||||
}
|
||||
|
||||
export async function continuePreparedAppUpdate(options: { hasComponentUpdates: boolean; restartOnly: boolean; rememberComponentUpdates: () => Promise<boolean>; installComponents: () => Promise<void>; installDownloadedUpdate: () => Promise<void>; restartApp: () => Promise<void> }): Promise<void> {
|
||||
if (options.hasComponentUpdates && !(await options.rememberComponentUpdates())) {
|
||||
await options.installComponents();
|
||||
return;
|
||||
}
|
||||
if (options.restartOnly) await options.restartApp();
|
||||
else await options.installDownloadedUpdate();
|
||||
}
|
||||
|
||||
export function shouldCloseUpdateCenterAfterComponentUpdate(options: { failedCount: number; skippedDriverCount: number; hasAppUpdate: boolean; remainingComponentUpdateCount: number }): boolean {
|
||||
|
||||
Reference in new issue
Block a user