mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-22 00:30:19 +02:00
Log plugin bridge 502 causes
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
84c85b930b
commit
19c01ada4e
2 files changed
+119
No files matched your search
@@ -42,6 +42,7 @@ async function createApp(
|
||||
jobDeps?: unknown;
|
||||
toolDeps?: unknown;
|
||||
bridgeDeps?: unknown;
|
||||
captureJsonContext?: (context: unknown, body: unknown) => void;
|
||||
} = {},
|
||||
) {
|
||||
const [{ pluginRoutes }, { errorHandler }] = await Promise.all([
|
||||
@@ -56,6 +57,16 @@ async function createApp(
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
if (routeOverrides.captureJsonContext) {
|
||||
app.use((_req, res, next) => {
|
||||
const originalJson = res.json.bind(res);
|
||||
res.json = ((body: unknown) => {
|
||||
routeOverrides.captureJsonContext?.((res as any).__errorContext, body);
|
||||
return originalJson(body);
|
||||
}) as typeof res.json;
|
||||
next();
|
||||
});
|
||||
}
|
||||
app.use((req, _res, next) => {
|
||||
req.actor = actor as typeof req.actor;
|
||||
next();
|
||||
@@ -627,6 +638,40 @@ describe.sequential("plugin tool and bridge authz", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("attaches worker bridge errors to the HTTP logger context", async () => {
|
||||
readyPlugin();
|
||||
const call = vi.fn().mockRejectedValue(new Error("missing source_objects column"));
|
||||
const captured: Array<{ context: any; body: unknown }> = [];
|
||||
const { app } = await createApp(boardActor(), {}, {
|
||||
bridgeDeps: {
|
||||
workerManager: { call },
|
||||
},
|
||||
captureJsonContext: (context, body) => {
|
||||
captured.push({ context, body });
|
||||
},
|
||||
});
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/api/plugins/${pluginId}/data/source-objects`)
|
||||
.send({ companyId: companyA });
|
||||
|
||||
expect(res.status).toBe(502);
|
||||
expect(res.body).toMatchObject({
|
||||
code: "UNKNOWN",
|
||||
message: "missing source_objects column",
|
||||
});
|
||||
expect(captured.at(-1)?.context?.error).toMatchObject({
|
||||
message: "missing source_objects column",
|
||||
details: {
|
||||
pluginId,
|
||||
pluginKey: "paperclip.example",
|
||||
bridgeMethod: "getData",
|
||||
dataKey: "source-objects",
|
||||
bridgeCode: "UNKNOWN",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects manual job triggers for non-admin board users", async () => {
|
||||
const scheduler = { triggerJob: vi.fn() };
|
||||
const jobStore = { getJobByIdForPlugin: vi.fn() };
|
||||
|
||||
@@ -1021,6 +1021,34 @@ export function pluginRoutes(
|
||||
};
|
||||
}
|
||||
|
||||
function attachPluginBridgeErrorContext(
|
||||
req: Request,
|
||||
res: Response,
|
||||
err: unknown,
|
||||
bridgeError: PluginBridgeErrorResponse,
|
||||
metadata: Record<string, unknown>,
|
||||
): void {
|
||||
const rootError = err instanceof Error ? err : new Error(String(err));
|
||||
(res as any).__errorContext = {
|
||||
error: {
|
||||
message: bridgeError.message,
|
||||
stack: rootError.stack,
|
||||
name: rootError.name,
|
||||
details: {
|
||||
...metadata,
|
||||
bridgeCode: bridgeError.code,
|
||||
bridgeDetails: bridgeError.details,
|
||||
},
|
||||
},
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
reqBody: req.body,
|
||||
reqParams: req.params,
|
||||
reqQuery: req.query,
|
||||
};
|
||||
(res as any).err = rootError;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/plugins/:pluginId/bridge/data
|
||||
*
|
||||
@@ -1072,6 +1100,11 @@ export function pluginRoutes(
|
||||
code: "WORKER_UNAVAILABLE",
|
||||
message: `Plugin is not ready (current status: ${plugin.status})`,
|
||||
};
|
||||
attachPluginBridgeErrorContext(req, res, new Error(bridgeError.message), bridgeError, {
|
||||
pluginId: plugin.id,
|
||||
pluginKey: plugin.pluginKey,
|
||||
bridgeMethod: "getData",
|
||||
});
|
||||
res.status(502).json(bridgeError);
|
||||
return;
|
||||
}
|
||||
@@ -1098,6 +1131,12 @@ export function pluginRoutes(
|
||||
res.json({ data: result });
|
||||
} catch (err) {
|
||||
const bridgeError = mapRpcErrorToBridgeError(err);
|
||||
attachPluginBridgeErrorContext(req, res, err, bridgeError, {
|
||||
pluginId: plugin.id,
|
||||
pluginKey: plugin.pluginKey,
|
||||
bridgeMethod: "getData",
|
||||
dataKey: body.key,
|
||||
});
|
||||
res.status(502).json(bridgeError);
|
||||
}
|
||||
});
|
||||
@@ -1153,6 +1192,11 @@ export function pluginRoutes(
|
||||
code: "WORKER_UNAVAILABLE",
|
||||
message: `Plugin is not ready (current status: ${plugin.status})`,
|
||||
};
|
||||
attachPluginBridgeErrorContext(req, res, new Error(bridgeError.message), bridgeError, {
|
||||
pluginId: plugin.id,
|
||||
pluginKey: plugin.pluginKey,
|
||||
bridgeMethod: "performAction",
|
||||
});
|
||||
res.status(502).json(bridgeError);
|
||||
return;
|
||||
}
|
||||
@@ -1179,6 +1223,12 @@ export function pluginRoutes(
|
||||
res.json({ data: result });
|
||||
} catch (err) {
|
||||
const bridgeError = mapRpcErrorToBridgeError(err);
|
||||
attachPluginBridgeErrorContext(req, res, err, bridgeError, {
|
||||
pluginId: plugin.id,
|
||||
pluginKey: plugin.pluginKey,
|
||||
bridgeMethod: "performAction",
|
||||
actionKey: body.key,
|
||||
});
|
||||
res.status(502).json(bridgeError);
|
||||
}
|
||||
});
|
||||
@@ -1235,6 +1285,12 @@ export function pluginRoutes(
|
||||
code: "WORKER_UNAVAILABLE",
|
||||
message: `Plugin is not ready (current status: ${plugin.status})`,
|
||||
};
|
||||
attachPluginBridgeErrorContext(req, res, new Error(bridgeError.message), bridgeError, {
|
||||
pluginId: plugin.id,
|
||||
pluginKey: plugin.pluginKey,
|
||||
bridgeMethod: "getData",
|
||||
dataKey: key,
|
||||
});
|
||||
res.status(502).json(bridgeError);
|
||||
return;
|
||||
}
|
||||
@@ -1260,6 +1316,12 @@ export function pluginRoutes(
|
||||
res.json({ data: result });
|
||||
} catch (err) {
|
||||
const bridgeError = mapRpcErrorToBridgeError(err);
|
||||
attachPluginBridgeErrorContext(req, res, err, bridgeError, {
|
||||
pluginId: plugin.id,
|
||||
pluginKey: plugin.pluginKey,
|
||||
bridgeMethod: "getData",
|
||||
dataKey: key,
|
||||
});
|
||||
res.status(502).json(bridgeError);
|
||||
}
|
||||
});
|
||||
@@ -1312,6 +1374,12 @@ export function pluginRoutes(
|
||||
code: "WORKER_UNAVAILABLE",
|
||||
message: `Plugin is not ready (current status: ${plugin.status})`,
|
||||
};
|
||||
attachPluginBridgeErrorContext(req, res, new Error(bridgeError.message), bridgeError, {
|
||||
pluginId: plugin.id,
|
||||
pluginKey: plugin.pluginKey,
|
||||
bridgeMethod: "performAction",
|
||||
actionKey: key,
|
||||
});
|
||||
res.status(502).json(bridgeError);
|
||||
return;
|
||||
}
|
||||
@@ -1337,6 +1405,12 @@ export function pluginRoutes(
|
||||
res.json({ data: result });
|
||||
} catch (err) {
|
||||
const bridgeError = mapRpcErrorToBridgeError(err);
|
||||
attachPluginBridgeErrorContext(req, res, err, bridgeError, {
|
||||
pluginId: plugin.id,
|
||||
pluginKey: plugin.pluginKey,
|
||||
bridgeMethod: "performAction",
|
||||
actionKey: key,
|
||||
});
|
||||
res.status(502).json(bridgeError);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user