fix: isolate attachment submission state

This commit is contained in:
2026-08-24 01:50:04 +08:00
parent bec93d0918
commit 1ca0249e69
8 changed files with 320 additions and 21 deletions

View File

@@ -3,7 +3,7 @@ import { once } from 'node:events';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { handleCodingAttachmentRoutes } from '../../electron/api/routes/coding-attachments';
import { getHostApiToken, startHostApiServer } from '../../electron/api/server';
import { CodingAttachmentStore } from '../../electron/coding-projects/attachment-store';
@@ -44,6 +44,37 @@ async function startAttachmentServer(maxBytes = 16 * 1024 * 1024) {
};
}
async function startAttachmentFailureServer() {
const attachments = {
put: vi.fn(async () => {
throw new Error('write failed');
}),
read: vi.fn(async () => {
throw new Error('read failed');
}),
};
const context = { codingProducts: { attachments } } as unknown as HostApiContext;
const server = createServer((request, response) => {
const url = new URL(request.url ?? '/', 'http://127.0.0.1');
void handleCodingAttachmentRoutes(request, response, url, context).then((handled) => {
if (!handled) {
response.statusCode = 404;
response.end();
}
});
});
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
const address = server.address();
if (!address || typeof address === 'string') throw new Error('Attachment test server failed');
return {
baseUrl: `http://127.0.0.1:${address.port}`,
close: async () => await new Promise<void>((resolve, reject) => {
server.closeAllConnections();
server.close((error) => error ? reject(error) : resolve());
}),
};
}
async function startAuthenticatedHostServer() {
const root = await mkdtemp(path.join(tmpdir(), 'makelore-coding-host-attachments-'));
roots.push(root);
@@ -222,7 +253,33 @@ describe('coding attachment routes', () => {
);
expect(response.status).toBe(413);
expect(response.body).toMatchObject({
code: 'CODING_ATTACHMENT_TOO_LARGE',
code: 'CODING_ATTACHMENT_INVALID',
});
} finally {
await server.close();
}
});
it('uses registered errors for unreadable content and upload storage failures', async () => {
const server = await startAttachmentFailureServer();
try {
const content = await fetch(
`${server.baseUrl}/api/coding/attachments/attachment-1/content`,
);
expect(content.status).toBe(404);
await expect(content.json()).resolves.toMatchObject({
code: 'CODING_ATTACHMENT_NOT_FOUND',
});
const upload = await fetch(`${server.baseUrl}/api/coding/attachments`, {
method: 'POST',
headers: { 'Content-Type': 'image/png' },
body: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
});
expect(upload.status).toBe(500);
await expect(upload.json()).resolves.toMatchObject({
code: 'CODING_STORAGE_WRITE_FAILED',
error: '本地数据写入失败,请检查存储后重试。',
});
} finally {
await server.close();