feat(api): add tool filtering and DB-backed cleanup settings

Add feature flag support to skip disabled/experimental tools at startup
by reading disabledTools and enableExperimentalTools from the settings
table. Refactor cleanup.ts to read tempFileMaxAgeHours from DB settings
(with env var fallback) and respect the startupCleanup setting.
This commit is contained in:
Siddharth Kumar Sah
2026-03-26 01:10:51 +08:00
parent 585d66f0c9
commit acfff754b5
4 changed files with 458 additions and 55 deletions
+215
View File
@@ -0,0 +1,215 @@
/**
* Integration tests for Phase 1 settings keys:
* disabledTools, enableExperimentalTools, tempFileMaxAgeHours, startupCleanup
*
* These verify the settings store correctly persists and retrieves these keys
* via the PUT/GET /api/v1/settings endpoints.
*/
import { eq } from "drizzle-orm";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { db, schema } from "../../apps/api/src/db/index.js";
import { buildTestApp, loginAsAdmin, type TestApp } from "./test-server.js";
let testApp: TestApp;
let app: TestApp["app"];
let adminToken: string;
beforeAll(async () => {
testApp = await buildTestApp();
app = testApp.app;
adminToken = await loginAsAdmin(app);
}, 30_000);
afterAll(async () => {
await testApp.cleanup();
}, 10_000);
// ═══════════════════════════════════════════════════════════════════════════
// disabledTools
// ═══════════════════════════════════════════════════════════════════════════
describe("disabledTools setting", () => {
it("can be saved as a JSON array and retrieved", async () => {
const disabledTools = ["resize", "crop", "rotate"];
// Save
const putRes = await app.inject({
method: "PUT",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
payload: { disabledTools },
});
expect(putRes.statusCode).toBe(200);
expect(JSON.parse(putRes.body).ok).toBe(true);
// Retrieve via GET all
const getRes = await app.inject({
method: "GET",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
});
expect(getRes.statusCode).toBe(200);
const body = JSON.parse(getRes.body);
const parsed = JSON.parse(body.settings.disabledTools);
expect(parsed).toEqual(disabledTools);
// Retrieve via GET specific key
const keyRes = await app.inject({
method: "GET",
url: "/api/v1/settings/disabledTools",
headers: { authorization: `Bearer ${adminToken}` },
});
expect(keyRes.statusCode).toBe(200);
const keyBody = JSON.parse(keyRes.body);
expect(JSON.parse(keyBody.value)).toEqual(disabledTools);
});
it("can be updated to an empty array", async () => {
const putRes = await app.inject({
method: "PUT",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
payload: { disabledTools: [] },
});
expect(putRes.statusCode).toBe(200);
const keyRes = await app.inject({
method: "GET",
url: "/api/v1/settings/disabledTools",
headers: { authorization: `Bearer ${adminToken}` },
});
expect(keyRes.statusCode).toBe(200);
expect(JSON.parse(JSON.parse(keyRes.body).value)).toEqual([]);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// enableExperimentalTools
// ═══════════════════════════════════════════════════════════════════════════
describe("enableExperimentalTools setting", () => {
it("can be saved and retrieved as 'true'", async () => {
const putRes = await app.inject({
method: "PUT",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
payload: { enableExperimentalTools: "true" },
});
expect(putRes.statusCode).toBe(200);
const keyRes = await app.inject({
method: "GET",
url: "/api/v1/settings/enableExperimentalTools",
headers: { authorization: `Bearer ${adminToken}` },
});
expect(keyRes.statusCode).toBe(200);
expect(JSON.parse(keyRes.body).value).toBe("true");
});
it("can be saved and retrieved as 'false'", async () => {
const putRes = await app.inject({
method: "PUT",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
payload: { enableExperimentalTools: "false" },
});
expect(putRes.statusCode).toBe(200);
const keyRes = await app.inject({
method: "GET",
url: "/api/v1/settings/enableExperimentalTools",
headers: { authorization: `Bearer ${adminToken}` },
});
expect(keyRes.statusCode).toBe(200);
expect(JSON.parse(keyRes.body).value).toBe("false");
});
});
// ═══════════════════════════════════════════════════════════════════════════
// tempFileMaxAgeHours
// ═══════════════════════════════════════════════════════════════════════════
describe("tempFileMaxAgeHours setting", () => {
it("can be saved and retrieved as a numeric string", async () => {
const putRes = await app.inject({
method: "PUT",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
payload: { tempFileMaxAgeHours: "48" },
});
expect(putRes.statusCode).toBe(200);
const keyRes = await app.inject({
method: "GET",
url: "/api/v1/settings/tempFileMaxAgeHours",
headers: { authorization: `Bearer ${adminToken}` },
});
expect(keyRes.statusCode).toBe(200);
expect(JSON.parse(keyRes.body).value).toBe("48");
});
it("can be updated to a different value", async () => {
// Set initial value
await app.inject({
method: "PUT",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
payload: { tempFileMaxAgeHours: "12" },
});
// Update
const putRes = await app.inject({
method: "PUT",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
payload: { tempFileMaxAgeHours: "72" },
});
expect(putRes.statusCode).toBe(200);
const keyRes = await app.inject({
method: "GET",
url: "/api/v1/settings/tempFileMaxAgeHours",
headers: { authorization: `Bearer ${adminToken}` },
});
expect(JSON.parse(keyRes.body).value).toBe("72");
});
});
// ═══════════════════════════════════════════════════════════════════════════
// startupCleanup
// ═══════════════════════════════════════════════════════════════════════════
describe("startupCleanup setting", () => {
it("can be saved and retrieved as 'true'", async () => {
const putRes = await app.inject({
method: "PUT",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
payload: { startupCleanup: "true" },
});
expect(putRes.statusCode).toBe(200);
const keyRes = await app.inject({
method: "GET",
url: "/api/v1/settings/startupCleanup",
headers: { authorization: `Bearer ${adminToken}` },
});
expect(keyRes.statusCode).toBe(200);
expect(JSON.parse(keyRes.body).value).toBe("true");
});
it("can be saved and retrieved as 'false'", async () => {
const putRes = await app.inject({
method: "PUT",
url: "/api/v1/settings",
headers: { authorization: `Bearer ${adminToken}` },
payload: { startupCleanup: "false" },
});
expect(putRes.statusCode).toBe(200);
const keyRes = await app.inject({
method: "GET",
url: "/api/v1/settings/startupCleanup",
headers: { authorization: `Bearer ${adminToken}` },
});
expect(keyRes.statusCode).toBe(200);
expect(JSON.parse(keyRes.body).value).toBe("false");
});
});