Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions src/app/api/providers/[id]/sync-models/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -836,26 +836,30 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
prunedComboRefs,
});
} catch (error: any) {
// Log error
await saveTimedCallLog({
method: "POST",
path: `/api/providers/${id}/sync-models`,
status: 500,
model: "model-sync",
provider: logProvider,
sourceFormat: "-",
connectionId: id,
duration: Date.now() - start,
error: error.message || "Sync failed",
requestType: "model-sync",
...(channelLabel
? {
responseBody: {
channel: channelLabel,
},
}
: {}),
}).catch(() => {});
// Keep the audit sink explicit for the call-scoped error-helper gate.
await timings
.measure("call-log", () =>
saveCallLog({
method: "POST",
path: `/api/providers/${id}/sync-models`,
status: 500,
model: "model-sync",
provider: logProvider,
sourceFormat: "-",
connectionId: id,
duration: Date.now() - start,
error: error.message || "Sync failed",
requestType: "model-sync",
...(channelLabel
? {
responseBody: {
channel: channelLabel,
},
}
: {}),
})
)
.catch(() => {});

return NextResponse.json(
{ error: sanitizeErrorMessage(error) || "Failed to sync models" },
Expand Down
62 changes: 62 additions & 0 deletions tests/unit/model-sync-error-helper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";
// @ts-expect-error — .mjs gate module has no type declarations; runtime shape is known.
import { findErrorHelperViolations } from "../../scripts/check/check-error-helper.mjs";

const ROUTE = "src/app/api/providers/[id]/sync-models/route.ts";
const source = readFileSync(new URL(`../../${ROUTE}`, import.meta.url), "utf8");

function scan(source: string): string[] {
return findErrorHelperViolations([{ path: ROUTE, source }], new Set<string>());
}

test("model sync keeps its error log recognizable without a gate exemption", () => {
assert.deepEqual(scan(source), []);
});

test("timing an internal call log does not turn it into a client response", () => {
const fixture = `export async function fail(error: Error) {
await timings.measure("call-log", () =>
saveCallLog({
status: 500,
error: error.message || "Sync failed",
})
).catch(() => {});
return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 });
}`;
assert.deepEqual(scan(fixture), []);
});

test("a timed call log must not hide a neighboring raw HTTP error", () => {
const fixture = `export async function fail(error: Error) {
await timings.measure("call-log", () =>
saveCallLog({
status: 500,
error: error.message || "Sync failed",
})
).catch(() => {});
return NextResponse.json({ error: error.message }, { status: 500 });
}`;
assert.deepEqual(scan(fixture), [ROUTE]);
});

test("timing a raw HTTP response does not make it an internal log", () => {
const fixture = `export async function fail(error: Error) {
return timings.measure("call-log", () =>
NextResponse.json({
error: error.message || "Sync failed",
}, { status: 500 })
);
}`;
assert.deepEqual(scan(fixture), [ROUTE]);
});

test("the real model sync response remains checked independently of its log", () => {
const unsafe = source.replace(
'sanitizeErrorMessage(error) || "Failed to sync models"',
'error.message || "Failed to sync models"'
);
assert.notEqual(unsafe, source, "the response mutation must reach the real catch path");
assert.deepEqual(scan(unsafe), [ROUTE]);
});
Loading