fix(core): soft-delete source connections with deleted_at
Migration 0003 adds source_connections.deleted_at. deleteConnection sets it; listing, lookups, updates, the scheduler and the sync handler ignore deleted connections, so a paused credential-less connection stays visible and can be resumed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0a540208e1
commit
e3a759d768
@@ -7,7 +7,9 @@ import { forEachDialect } from "@sttracker/db/testing";
|
||||
import { definePlugin, type SourcePlugin } from "@sttracker/plugin-sdk/types";
|
||||
import { listAttachmentLinks, readAttachment, requireLinkTarget, storeAttachment } from "../src/attachments.ts";
|
||||
import { createAppToken, listAppTokens, revokeAppToken } from "../src/auth.ts";
|
||||
import { createConnection, deleteConnection, devicePoll, deviceStart, getConnection, listRuns, syncHandler } from "../src/connections.ts";
|
||||
import { createConnection, deleteConnection, devicePoll, deviceStart, getConnection, listConnections, listRuns, syncHandler, updateConnection } from "../src/connections.ts";
|
||||
import { startScheduler } from "../src/jobs.ts";
|
||||
import { DropJobError } from "../src/jobs.ts";
|
||||
import { listSuggestions, quickAdd } from "../src/entries.ts";
|
||||
import { addEngagement } from "../src/engagements.ts";
|
||||
import { NotFoundError, ValidationError } from "../src/errors.ts";
|
||||
@@ -114,6 +116,41 @@ forEachDialect("api support", (getDb) => {
|
||||
await expect(getConnection(db, u, id)).rejects.toBeInstanceOf(NotFoundError);
|
||||
});
|
||||
|
||||
test("a paused connection without credentials stays listed and can be unpaused; a deleted one is gone", async () => {
|
||||
const db = getDb();
|
||||
const u = await makeUser(db);
|
||||
const id = await createConnection(db, SECRET, u, { pluginId: "echo", name: "E", config: {} }, echo.manifest);
|
||||
await updateConnection(db, SECRET, u, id, { status: "paused" }, echo.manifest);
|
||||
expect((await listConnections(db, u)).map((c) => c.id)).toEqual([id]);
|
||||
expect((await getConnection(db, u, id)).status).toBe("paused");
|
||||
await updateConnection(db, SECRET, u, id, { status: "active" }, echo.manifest);
|
||||
expect((await getConnection(db, u, id)).status).toBe("active");
|
||||
|
||||
await deleteConnection(db, u, id);
|
||||
const row = await db.k.selectFrom("source_connections").selectAll().where("id", "=", id).executeTakeFirstOrThrow();
|
||||
expect(row).toMatchObject({ status: "paused", credentials_enc: null });
|
||||
expect(row.deleted_at).not.toBeNull();
|
||||
expect(await listConnections(db, u)).toEqual([]);
|
||||
await expect(getConnection(db, u, id)).rejects.toBeInstanceOf(NotFoundError);
|
||||
await expect(updateConnection(db, SECRET, u, id, { status: "active" }, echo.manifest)).rejects.toBeInstanceOf(NotFoundError);
|
||||
await expect(deleteConnection(db, u, id)).rejects.toBeInstanceOf(NotFoundError);
|
||||
});
|
||||
|
||||
test("deleted connections are never scheduled and their queued syncs are dropped", async () => {
|
||||
const db = getDb();
|
||||
const u = await makeUser(db);
|
||||
const id = await createConnection(db, SECRET, u, { pluginId: "echo", name: "E", config: {}, scheduleMinutes: 1 }, echo.manifest);
|
||||
// Simulate a delete that left status "error" (e.g. a row edited by hand): deleted_at alone must exclude it.
|
||||
await deleteConnection(db, u, id);
|
||||
await db.k.updateTable("source_connections").set({ status: "error" }).where("id", "=", id).execute();
|
||||
const sched = startScheduler(db, { tickMs: 3_600_000 });
|
||||
await sched.tickNow();
|
||||
sched.stop();
|
||||
expect(await db.k.selectFrom("jobs").select("id").execute()).toEqual([]);
|
||||
const sync = syncHandler({ db, secret: SECRET, registry: reg });
|
||||
await expect(sync({ connectionId: id })).rejects.toBeInstanceOf(DropJobError);
|
||||
});
|
||||
|
||||
test("syncHandler runs file mode from a base64 payload", async () => {
|
||||
const db = getDb();
|
||||
const u = await makeUser(db);
|
||||
|
||||
Reference in New Issue
Block a user