test(e2e): fix remaining server config and mcp image failures
Follow-up to the first e2e fix: with nanoid/editRes.edits resolved, the suites failed one layer deeper. Both layers were never green since the e2e jobs were added (non-blocking in CI), so the failures had stacked up. server e2e (jest-e2e.json) — align module resolution/transform with the working unit/integration jest configs so AppModule's full import graph loads: - moduleFileExtensions: add "tsx" (React-Email .tsx templates are pulled in via the auth controller chain). - transform: ^.+\.(t|j)s$ -> ^.+\.(t|j)sx?$ so .tsx is transformed. - moduleNameMapper: add ^src/(.*)$ -> <rootDir>/../src/$1 (code imports via the absolute 'src/...' alias). Verified locally: the module graph now fully resolves (only env vars, supplied by CI, remain). mcp e2e (test-e2e.mjs) — insert_image/replace_image accept only http(s) URLs the server fetches; the test passed local file paths and died with "Invalid image URL". Serve the PNG bytes over a throwaway 127.0.0.1 HTTP server (the Docmost server runs on the same CI host) and pass URLs. The featPng negative test is untouched: replaceImage checks the attachmentId and throws before fetching, so its local path is never validated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"moduleFileExtensions": ["js", "json", "ts", "tsx"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".e2e-spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
"^.+\\.(t|j)sx?$": "ts-jest"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"/node_modules/(?!(\\.pnpm/)?(nanoid|uuid|image-dimensions|marked|happy-dom|lib0)(@|/))"
|
||||
@@ -12,6 +12,7 @@
|
||||
"moduleNameMapper": {
|
||||
"^@docmost/db/(.*)$": "<rootDir>/../src/database/$1",
|
||||
"^@docmost/transactional/(.*)$": "<rootDir>/../src/integrations/transactional/$1",
|
||||
"^@docmost/ee/(.*)$": "<rootDir>/../src/ee/$1"
|
||||
"^@docmost/ee/(.*)$": "<rootDir>/../src/ee/$1",
|
||||
"^src/(.*)$": "<rootDir>/../src/$1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { writeFileSync, unlinkSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { deflateSync } from "node:zlib";
|
||||
import { createServer } from "node:http";
|
||||
|
||||
const API = process.env.DOCMOST_API_URL;
|
||||
if (!API || !process.env.DOCMOST_EMAIL || !process.env.DOCMOST_PASSWORD) {
|
||||
@@ -149,11 +150,24 @@ async function main() {
|
||||
check("update_page_json: paragraph appended", JSON.stringify(pj4.content).includes("добавленный через update_page_json"));
|
||||
check("update_page_json: custom node id preserved", lastNode.attrs?.id === "testidjsonpush", lastNode.attrs?.id);
|
||||
|
||||
// 6b. images: upload / insert / replace (clean src, fresh attachment on replace)
|
||||
const pngA = join(tmpdir(), `mcp-e2e-img-a-${Date.now()}.png`);
|
||||
const pngB = join(tmpdir(), `mcp-e2e-img-b-${Date.now()}.png`);
|
||||
writeFileSync(pngA, makePng(255, 0, 0)); // red
|
||||
writeFileSync(pngB, makePng(0, 0, 255)); // blue (a DIFFERENT valid PNG)
|
||||
// 6b. images: upload / insert / replace (clean src, fresh attachment on replace).
|
||||
// insert_image / replace_image take an http(s) URL that the SERVER fetches;
|
||||
// local file paths are intentionally unsupported. The Docmost server runs on
|
||||
// the same host as this test, so serve the PNG bytes over a throwaway
|
||||
// localhost HTTP server it can reach.
|
||||
const bytesA = makePng(255, 0, 0); // red
|
||||
const bytesB = makePng(0, 0, 255); // blue (a DIFFERENT valid PNG)
|
||||
const imgServer = createServer((req, res) => {
|
||||
res.writeHead(200, { "Content-Type": "image/png" });
|
||||
res.end(req.url === "/b.png" ? bytesB : bytesA);
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
imgServer.once("error", reject);
|
||||
imgServer.listen(0, "127.0.0.1", resolve);
|
||||
});
|
||||
const imgPort = imgServer.address().port;
|
||||
const urlA = `http://127.0.0.1:${imgPort}/a.png`;
|
||||
const urlB = `http://127.0.0.1:${imgPort}/b.png`;
|
||||
try {
|
||||
// Independent login to fetch file bytes with the same cookie the editor uses.
|
||||
const login = await axios.post(
|
||||
@@ -173,7 +187,7 @@ async function main() {
|
||||
});
|
||||
|
||||
// insert_image: append the first PNG, src must be clean (no ?v=) and fetchable.
|
||||
const ins = await client.insertImage(pageId, pngA);
|
||||
const ins = await client.insertImage(pageId, urlA);
|
||||
check("insert_image: src has no ?v= cache-buster", !ins.src.includes("?v="), ins.src);
|
||||
const fileA = await fetchFile(ins.src);
|
||||
check("insert_image: file fetch returns 200", fileA.status === 200, `status=${fileA.status}`);
|
||||
@@ -199,7 +213,7 @@ async function main() {
|
||||
|
||||
// replace_image: must create a NEW attachment with a clean, fetchable URL.
|
||||
// The 200 fetch is the assertion that catches the in-place-overwrite HTTP 500 regression.
|
||||
const rep = await client.replaceImage(pageId, oldAttachmentId, pngB);
|
||||
const rep = await client.replaceImage(pageId, oldAttachmentId, urlB);
|
||||
check("replace_image: new attachment id differs from old", rep.newAttachmentId !== oldAttachmentId, `${oldAttachmentId} -> ${rep.newAttachmentId}`);
|
||||
check("replace_image: src has no ?v= cache-buster", !rep.src.includes("?v="), rep.src);
|
||||
const fileB = await fetchFile(rep.src);
|
||||
@@ -215,8 +229,7 @@ async function main() {
|
||||
check("replace_image: page has new attachment id", !!findImage(pjImg2.content.content, rep.newAttachmentId), rep.newAttachmentId);
|
||||
check("replace_image: old attachment id repointed away", !findImage(pjImg2.content.content, oldAttachmentId), oldAttachmentId);
|
||||
} finally {
|
||||
try { unlinkSync(pngA); } catch {}
|
||||
try { unlinkSync(pngB); } catch {}
|
||||
imgServer.close();
|
||||
}
|
||||
|
||||
// 6c. rich formatting: callout type, task list, inline marks, table alignment,
|
||||
|
||||
Reference in New Issue
Block a user