fix: invalidate persisted assistant model state

This commit is contained in:
ajnart committed 2026-08-13 19:56:16 +02:00
1 parent 6da471d24a
commit 073d9ce127
2 files changed
+96 -2

No files matched your search

+4 -2
View File
@@ -271,6 +271,8 @@ export const assistantRouter = createTRPCRouter({
const existing = await getConfigurationAsync(ctx.db);
const destinationChanged =
existing !== undefined && (existing.provider !== input.provider || existing.baseUrl !== baseUrl);
const connectionChanged =
existing !== undefined && (destinationChanged || existing.modelDiscoveryPath !== modelDiscoveryPath);
const encryptedApiKey = input.apiKey
? encryptSecret(input.apiKey)
: input.clearApiKey || destinationChanged
@@ -291,8 +293,8 @@ export const assistantRouter = createTRPCRouter({
modelDiscoveryPath,
encryptedApiKey,
encryptedHeaders,
modelId: destinationChanged ? null : existing.modelId,
enabled: destinationChanged ? false : existing.enabled,
modelId: connectionChanged ? null : existing.modelId,
enabled: connectionChanged ? false : existing.enabled,
updatedAt: new Date(),
})
.where(eq(assistantConfigurations.id, configurationId));
@@ -0,0 +1,92 @@
// @vitest-environment node
import { describe, expect, test, vi } from "vitest";
import type { Session } from "@homarr/auth";
import { createId } from "@homarr/common";
import { assistantConfigurations } from "@homarr/db/schema";
import { createDb } from "@homarr/db/test";
import { assistantRouter } from "../assistant";
vi.mock("@homarr/auth", () => ({}));
vi.mock("@homarr/common/server", () => ({
decryptSecret: vi.fn(),
encryptSecret: vi.fn(),
}));
vi.mock("@homarr/core/infrastructure/logs", () => ({
createLogger: () => ({
error: vi.fn(),
}),
}));
vi.mock("@homarr/core/infrastructure/db/env", () => ({
dbEnv: { DRIVER: "better-sqlite3" },
}));
const adminSession = {
user: {
id: createId(),
permissions: ["admin"],
colorScheme: "light",
},
expires: new Date().toISOString(),
} satisfies Session;
const createConfiguredAssistantAsync = async () => {
const db = createDb();
await db.insert(assistantConfigurations).values({
id: "default",
enabled: true,
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
modelDiscoveryPath: "/models",
encryptedApiKey: "encrypted.key",
encryptedHeaders: "encrypted.headers",
modelId: "example/model",
});
return db;
};
describe("assistantRouter.updateConnection", () => {
test("clears the persisted model when the discovery path changes without clearing credentials", async () => {
const db = await createConfiguredAssistantAsync();
const caller = assistantRouter.createCaller({ db, deviceType: undefined, session: adminSession });
await caller.updateConnection({
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
modelDiscoveryPath: "/catalog",
clearApiKey: false,
clearCustomHeaders: false,
});
const [configuration] = await db.select().from(assistantConfigurations);
expect(configuration).toMatchObject({
enabled: false,
modelDiscoveryPath: "/catalog",
modelId: null,
encryptedApiKey: "encrypted.key",
encryptedHeaders: "encrypted.headers",
});
});
test("preserves the persisted model when the saved connection is unchanged", async () => {
const db = await createConfiguredAssistantAsync();
const caller = assistantRouter.createCaller({ db, deviceType: undefined, session: adminSession });
await caller.updateConnection({
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
modelDiscoveryPath: "/models",
clearApiKey: false,
clearCustomHeaders: false,
});
const [configuration] = await db.select().from(assistantConfigurations);
expect(configuration).toMatchObject({
enabled: true,
modelDiscoveryPath: "/models",
modelId: "example/model",
});
});
});