mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: allow reinstall of AI bundles with broken model files (#214)
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
|||||||
acquireInstallLock,
|
acquireInstallLock,
|
||||||
getAiDir,
|
getAiDir,
|
||||||
getFeatureStates,
|
getFeatureStates,
|
||||||
|
getInstallScriptPath,
|
||||||
getManifestPath,
|
getManifestPath,
|
||||||
getModelsDir,
|
getModelsDir,
|
||||||
invalidateCache,
|
invalidateCache,
|
||||||
@@ -35,6 +36,7 @@ import {
|
|||||||
markUninstalled,
|
markUninstalled,
|
||||||
releaseInstallLock,
|
releaseInstallLock,
|
||||||
setInstallProgress,
|
setInstallProgress,
|
||||||
|
verifyBundleModels,
|
||||||
} from "../lib/feature-status.js";
|
} from "../lib/feature-status.js";
|
||||||
import { requirePermission } from "../permissions.js";
|
import { requirePermission } from "../permissions.js";
|
||||||
import { requireAuth } from "../plugins/auth.js";
|
import { requireAuth } from "../plugins/auth.js";
|
||||||
@@ -137,7 +139,11 @@ export async function registerFeatureRoutes(app: FastifyInstance): Promise<void>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isFeatureInstalled(bundleId)) {
|
if (isFeatureInstalled(bundleId)) {
|
||||||
return reply.status(409).send({ error: `Bundle "${bundleId}" is already installed` });
|
const modelError = verifyBundleModels(bundleId);
|
||||||
|
if (!modelError) {
|
||||||
|
return reply.status(409).send({ error: `Bundle "${bundleId}" is already installed` });
|
||||||
|
}
|
||||||
|
markUninstalled(bundleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!acquireInstallLock(bundleId)) {
|
if (!acquireInstallLock(bundleId)) {
|
||||||
@@ -145,7 +151,7 @@ export async function registerFeatureRoutes(app: FastifyInstance): Promise<void>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const jobId = crypto.randomUUID();
|
const jobId = crypto.randomUUID();
|
||||||
const scriptPath = "/app/packages/ai/python/install_feature.py";
|
const scriptPath = getInstallScriptPath();
|
||||||
const manifestPath = getManifestPath();
|
const manifestPath = getManifestPath();
|
||||||
const modelsDir = getModelsDir();
|
const modelsDir = getModelsDir();
|
||||||
|
|
||||||
|
|||||||
@@ -525,6 +525,41 @@ describe("Composite state - getFeatureStates", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("auto-repair state transition (install endpoint logic)", () => {
|
||||||
|
it("markUninstalled clears stale entry when models are broken, allowing reinstall", () => {
|
||||||
|
mod.markInstalled("background-removal", "1.0.0", ["u2net.onnx"]);
|
||||||
|
writeTestManifest({
|
||||||
|
"background-removal": {
|
||||||
|
models: [{ id: "u2net", path: "u2net.onnx" }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mod.invalidateCache();
|
||||||
|
|
||||||
|
expect(mod.isFeatureInstalled("background-removal")).toBe(true);
|
||||||
|
const modelError = mod.verifyBundleModels("background-removal");
|
||||||
|
expect(modelError).not.toBeNull();
|
||||||
|
|
||||||
|
mod.markUninstalled("background-removal");
|
||||||
|
expect(mod.isFeatureInstalled("background-removal")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not clear entry when models are healthy", () => {
|
||||||
|
mod.markInstalled("background-removal", "1.0.0", ["u2net.onnx"]);
|
||||||
|
writeTestManifest({
|
||||||
|
"background-removal": {
|
||||||
|
models: [{ id: "u2net", path: "u2net.onnx" }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
writeFileSync(join(modelsDir, "u2net.onnx"), Buffer.alloc(1024));
|
||||||
|
mod.invalidateCache();
|
||||||
|
|
||||||
|
expect(mod.isFeatureInstalled("background-removal")).toBe(true);
|
||||||
|
const modelError = mod.verifyBundleModels("background-removal");
|
||||||
|
expect(modelError).toBeNull();
|
||||||
|
expect(mod.isFeatureInstalled("background-removal")).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("verifyBundleModels", () => {
|
describe("verifyBundleModels", () => {
|
||||||
it("returns null when all models exist and meet minSize", () => {
|
it("returns null when all models exist and meet minSize", () => {
|
||||||
writeTestManifest({
|
writeTestManifest({
|
||||||
|
|||||||
Reference in New Issue
Block a user