From 015b2da30cc104cc37ba8217ecc4f054f9bb9f9b Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Sat, 20 Jun 2026 14:52:56 +0800 Subject: [PATCH] test(artifact): add end-to-end tool tests for upload/error paths Co-Authored-By: glm-5.2 --- .../__tests__/ArtifactTool.test.ts | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 packages/builtin-tools/src/tools/ArtifactTool/__tests__/ArtifactTool.test.ts diff --git a/packages/builtin-tools/src/tools/ArtifactTool/__tests__/ArtifactTool.test.ts b/packages/builtin-tools/src/tools/ArtifactTool/__tests__/ArtifactTool.test.ts new file mode 100644 index 000000000..d8d00f730 --- /dev/null +++ b/packages/builtin-tools/src/tools/ArtifactTool/__tests__/ArtifactTool.test.ts @@ -0,0 +1,112 @@ +import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs' +import { tmpdir } from 'os' +import { join } from 'path' +import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' +import { ArtifactTool } from '../ArtifactTool.js' + +const TEST_DIR = join(tmpdir(), 'artifact-tool-test') +const TEST_FILE = join(TEST_DIR, 'report.html') +const MISSING_FILE = join(TEST_DIR, 'does-not-exist.html') +const DIR_AS_FILE = TEST_DIR + +const originalFetch = globalThis.fetch + +function mockFetchSuccess(body: object): typeof fetch { + return mock(() => + Promise.resolve( + new Response(JSON.stringify(body), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }), + ), + ) as unknown as typeof fetch +} + +describe('ArtifactTool.call', () => { + beforeEach(() => { + mkdirSync(TEST_DIR, { recursive: true }) + writeFileSync(TEST_FILE, '

test report

', 'utf8') + process.env.CLAUDE_ARTIFACTS_TOKEN = 'test-token' + process.env.CLAUDE_ARTIFACTS_URL = 'https://example.test' + }) + + afterEach(() => { + if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true, force: true }) + delete process.env.CLAUDE_ARTIFACTS_TOKEN + delete process.env.CLAUDE_ARTIFACTS_URL + globalThis.fetch = originalFetch + }) + + test('uploads existing HTML file and returns id/url/expiresAt', async () => { + globalThis.fetch = mockFetchSuccess({ + id: 'abc123', + url: 'https://example.test/7d/abc123.html', + expiresAt: '2026-06-27T10:00:00.000Z', + }) + + const result = await ArtifactTool.call({ file_path: TEST_FILE, ttl: 7 }) + + expect(result.data).toMatchObject({ + id: 'abc123', + url: 'https://example.test/7d/abc123.html', + expiresAt: '2026-06-27T10:00:00.000Z', + }) + expect((result.data as { error?: string }).error).toBeUndefined() + }) + + test('passes hash through when overwriting', async () => { + const fetchMock = mockFetchSuccess({ + id: 'stable-id', + url: 'https://example.test/7d/stable-id.html', + expiresAt: '2026-06-27T10:00:00.000Z', + }) + globalThis.fetch = fetchMock + + await ArtifactTool.call({ file_path: TEST_FILE, hash: 'stable-id', ttl: 7 }) + + const calledUrl = ( + fetchMock as unknown as { mock: { calls: [string | URL | Request][] } } + ).mock.calls[0][0] + expect(calledUrl.toString()).toContain('hash=stable-id') + }) + + test('returns error when file does not exist (no HTTP call)', async () => { + let fetchCalled = false + globalThis.fetch = mock(() => { + fetchCalled = true + return Promise.resolve(new Response('{}')) + }) as unknown as typeof fetch + + const result = await ArtifactTool.call({ file_path: MISSING_FILE, ttl: 7 }) + + expect(fetchCalled).toBe(false) + expect((result.data as { error?: string }).error).toContain( + 'does not exist', + ) + }) + + test('returns error when path is a directory', async () => { + const result = await ArtifactTool.call({ file_path: DIR_AS_FILE, ttl: 7 }) + + expect((result.data as { error?: string }).error).toContain( + 'not a regular file', + ) + }) + + test('returns error verbatim when backend rejects', async () => { + globalThis.fetch = mock(() => + Promise.resolve( + new Response(JSON.stringify({ error: 'payload_too_large' }), { + status: 200, + }), + ), + ) as unknown as typeof fetch + + // Force the size guard to pass by writing a small file but having backend complain. + const result = await ArtifactTool.call({ file_path: TEST_FILE, ttl: 7 }) + + expect((result.data as { error?: string }).error).toContain( + 'payload_too_large', + ) + }) +})