@gravitykit/block-mcp 2.0.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +15 -0
- package/LICENSE +26 -0
- package/README.md +592 -0
- package/dist/index.cjs +52721 -0
- package/package.json +70 -0
- package/src/__tests__/fixtures/block-trees.ts +199 -0
- package/src/__tests__/fixtures/error-envelopes.ts +115 -0
- package/src/__tests__/fixtures/rest-responses.ts +280 -0
- package/src/__tests__/helpers/mock-client.ts +185 -0
- package/src/__tests__/helpers/request-matchers.ts +88 -0
- package/src/__tests__/helpers/schema-asserts.ts +132 -0
- package/src/__tests__/integration/concurrency.test.ts +129 -0
- package/src/__tests__/integration/dual-storage.test.ts +156 -0
- package/src/__tests__/integration/error-envelopes.test.ts +238 -0
- package/src/__tests__/integration/global-setup.ts +17 -0
- package/src/__tests__/integration/rate-limit.test.ts +88 -0
- package/src/__tests__/integration/read-edit-read.test.ts +141 -0
- package/src/__tests__/integration/ref-stability.test.ts +175 -0
- package/src/__tests__/integration/setup.ts +201 -0
- package/src/__tests__/tools/discovery/get_pattern.test.ts +58 -0
- package/src/__tests__/tools/discovery/get_post_info.test.ts +100 -0
- package/src/__tests__/tools/discovery/get_site_usage.test.ts +41 -0
- package/src/__tests__/tools/discovery/list_block_types.test.ts +103 -0
- package/src/__tests__/tools/discovery/list_patterns.test.ts +106 -0
- package/src/__tests__/tools/discovery/list_posts.test.ts +47 -0
- package/src/__tests__/tools/discovery/resolve_url.test.ts +69 -0
- package/src/__tests__/tools/discovery/scan_storage_modes.test.ts +34 -0
- package/src/__tests__/tools/media/upload_media.test.ts +123 -0
- package/src/__tests__/tools/mutate/edit_block_tree.test.ts +439 -0
- package/src/__tests__/tools/mutate/ref_routing.test.ts +105 -0
- package/src/__tests__/tools/patterns/insert_pattern.test.ts +117 -0
- package/src/__tests__/tools/posts/create_post.test.ts +84 -0
- package/src/__tests__/tools/posts/update_post.test.ts +93 -0
- package/src/__tests__/tools/read/get_block.test.ts +96 -0
- package/src/__tests__/tools/read/get_page_blocks.test.ts +184 -0
- package/src/__tests__/tools/read/persist_refs.test.ts +35 -0
- package/src/__tests__/tools/terms/list_terms.test.ts +91 -0
- package/src/__tests__/tools/write/delete_block.test.ts +91 -0
- package/src/__tests__/tools/write/insert_blocks.test.ts +149 -0
- package/src/__tests__/tools/write/ref_routing.test.ts +177 -0
- package/src/__tests__/tools/write/replace_block_range.test.ts +90 -0
- package/src/__tests__/tools/write/rewrite_post_blocks.test.ts +126 -0
- package/src/__tests__/tools/write/update_block.test.ts +206 -0
- package/src/__tests__/tools/write/update_blocks.test.ts +173 -0
- package/src/__tests__/tools/yoast/yoast_bulk_update_seo.test.ts +112 -0
- package/src/__tests__/tools/yoast/yoast_get_seo.test.ts +78 -0
- package/src/__tests__/tools/yoast/yoast_update_seo.test.ts +105 -0
- package/src/__tests__/unit/client/ref-endpoints.test.ts +232 -0
- package/src/__tests__/unit/enrichers/cbp-enricher.test.ts +457 -0
- package/src/__tests__/unit/error-translator/translate-wp-error.test.ts +318 -0
- package/src/__tests__/unit/instructions.test.ts +374 -0
- package/src/__tests__/unit/preferences/enrich-block-list.test.ts +175 -0
- package/src/__tests__/unit/preferences/enrich-pattern-list.test.ts +227 -0
- package/src/client.ts +964 -0
- package/src/connect.ts +877 -0
- package/src/enrichers.ts +348 -0
- package/src/error-translator.ts +156 -0
- package/src/index.ts +450 -0
- package/src/instructions.ts +270 -0
- package/src/preferences.ts +273 -0
- package/src/tools/discovery.ts +251 -0
- package/src/tools/media.ts +75 -0
- package/src/tools/mutate.ts +243 -0
- package/src/tools/patterns.ts +94 -0
- package/src/tools/posts.ts +200 -0
- package/src/tools/read.ts +201 -0
- package/src/tools/terms.ts +44 -0
- package/src/tools/write.ts +542 -0
- package/src/tools/yoast.ts +224 -0
- package/src/types.ts +862 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool tests: list_patterns
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Filter forwarding (search → q, synced, min_score)
|
|
6
|
+
* - Server-side limit = offset + limit (so client slice still works)
|
|
7
|
+
* - Client-side slicing via offset
|
|
8
|
+
* - Defaults: limit 20, offset 0
|
|
9
|
+
* - Response shape: patterns, count, total, offset, next_offset, summary
|
|
10
|
+
* - Enrichment via enrichPatternList (summary is present)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
14
|
+
import { handleDiscoveryTool } from '../../../tools/discovery.js';
|
|
15
|
+
import { makeMockClient } from '../../helpers/mock-client.js';
|
|
16
|
+
import { patternsResponse } from '../../fixtures/rest-responses.js';
|
|
17
|
+
|
|
18
|
+
describe('list_patterns — filter forwarding', () => {
|
|
19
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
client = makeMockClient();
|
|
22
|
+
client.getPatterns.mockResolvedValue(patternsResponse);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('forwards search as q and other filters', async () => {
|
|
26
|
+
await handleDiscoveryTool('list_patterns', {
|
|
27
|
+
search: 'hero', synced: true, min_score: 50,
|
|
28
|
+
}, client as any);
|
|
29
|
+
expect(client.getPatterns).toHaveBeenCalledWith(expect.objectContaining({
|
|
30
|
+
q: 'hero', synced: true, min_score: 50,
|
|
31
|
+
}));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('forwards no filters when none are provided', async () => {
|
|
35
|
+
await handleDiscoveryTool('list_patterns', {}, client as any);
|
|
36
|
+
expect(client.getPatterns).toHaveBeenCalledWith(expect.objectContaining({
|
|
37
|
+
q: undefined, synced: undefined, min_score: undefined,
|
|
38
|
+
}));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('list_patterns — pagination', () => {
|
|
43
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
client = makeMockClient();
|
|
46
|
+
client.getPatterns.mockResolvedValue(patternsResponse);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('defaults to limit 20, offset 0', async () => {
|
|
50
|
+
await handleDiscoveryTool('list_patterns', {}, client as any);
|
|
51
|
+
// server limit is offset + limit = 0 + 20 = 20
|
|
52
|
+
expect(client.getPatterns).toHaveBeenCalledWith(expect.objectContaining({ limit: 20 }));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('server limit accounts for offset (offset+limit)', async () => {
|
|
56
|
+
await handleDiscoveryTool('list_patterns', { limit: 10, offset: 30 }, client as any);
|
|
57
|
+
expect(client.getPatterns).toHaveBeenCalledWith(expect.objectContaining({ limit: 40 }));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('client-side slice honors offset', async () => {
|
|
61
|
+
const many = Array.from({ length: 30 }, (_, i) => ({
|
|
62
|
+
id: i + 1, name: `pattern-${i}`,
|
|
63
|
+
type: 'synced' as const,
|
|
64
|
+
created: '2026-01-01', modified: '2026-01-01',
|
|
65
|
+
reference_count: 0,
|
|
66
|
+
preference: { score: 50 - i, tier: 'recommended' as const, reasons: [] },
|
|
67
|
+
contains_blocks: [], has_legacy_blocks: false,
|
|
68
|
+
}));
|
|
69
|
+
client.getPatterns.mockResolvedValueOnce({ patterns: many } as any);
|
|
70
|
+
const result = await handleDiscoveryTool('list_patterns', { limit: 5, offset: 10 }, client as any) as Record<string, unknown>;
|
|
71
|
+
expect((result.patterns as unknown[]).length).toBe(5);
|
|
72
|
+
expect(result.offset).toBe(10);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('next_offset is null at end of results', async () => {
|
|
76
|
+
const five = Array.from({ length: 5 }, (_, i) => ({
|
|
77
|
+
id: i + 1, name: `p-${i}`,
|
|
78
|
+
type: 'synced' as const,
|
|
79
|
+
created: '2026-01-01', modified: '2026-01-01',
|
|
80
|
+
reference_count: 0,
|
|
81
|
+
preference: { score: 50, tier: 'recommended' as const, reasons: [] },
|
|
82
|
+
contains_blocks: [], has_legacy_blocks: false,
|
|
83
|
+
}));
|
|
84
|
+
client.getPatterns.mockResolvedValueOnce({ patterns: five } as any);
|
|
85
|
+
const result = await handleDiscoveryTool('list_patterns', { limit: 10 }, client as any) as Record<string, unknown>;
|
|
86
|
+
expect(result.next_offset).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('list_patterns — response shape', () => {
|
|
91
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
client = makeMockClient();
|
|
94
|
+
client.getPatterns.mockResolvedValue(patternsResponse);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('returns patterns, count, total, offset, next_offset, summary', async () => {
|
|
98
|
+
const result = await handleDiscoveryTool('list_patterns', {}, client as any) as Record<string, unknown>;
|
|
99
|
+
expect(Array.isArray(result.patterns)).toBe(true);
|
|
100
|
+
expect(typeof result.count).toBe('number');
|
|
101
|
+
expect(typeof result.total).toBe('number');
|
|
102
|
+
expect(typeof result.offset).toBe('number');
|
|
103
|
+
// summary is created by enrichPatternList — may be a string or structured value
|
|
104
|
+
expect(result.summary).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool tests: list_posts
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Forwards all filters verbatim (search, post_type, post_status,
|
|
6
|
+
* per_page, page)
|
|
7
|
+
* - All undefined when args is empty
|
|
8
|
+
* - Returns raw client response
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
12
|
+
import { handleDiscoveryTool } from '../../../tools/discovery.js';
|
|
13
|
+
import { makeMockClient } from '../../helpers/mock-client.js';
|
|
14
|
+
|
|
15
|
+
describe('list_posts', () => {
|
|
16
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
client = makeMockClient();
|
|
19
|
+
client.findPosts.mockResolvedValue({
|
|
20
|
+
posts: [{ post_id: 1, title: 'Hello', slug: 'hello', post_type: 'post', post_status: 'publish', post_url: 'x', modified: '2026-01-01' }],
|
|
21
|
+
total: 1, page: 1, per_page: 20, total_pages: 1,
|
|
22
|
+
} as any);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('forwards all filters', async () => {
|
|
26
|
+
await handleDiscoveryTool('list_posts', {
|
|
27
|
+
search: 'foo', post_type: 'page', post_status: 'draft', per_page: 50, page: 2,
|
|
28
|
+
}, client as any);
|
|
29
|
+
expect(client.findPosts).toHaveBeenCalledWith({
|
|
30
|
+
search: 'foo', post_type: 'page', post_status: 'draft', per_page: 50, page: 2,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('passes undefineds when args is empty', async () => {
|
|
35
|
+
await handleDiscoveryTool('list_posts', {}, client as any);
|
|
36
|
+
expect(client.findPosts).toHaveBeenCalledWith({
|
|
37
|
+
search: undefined, post_type: undefined, post_status: undefined,
|
|
38
|
+
per_page: undefined, page: undefined,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('returns the raw client response', async () => {
|
|
43
|
+
const result = await handleDiscoveryTool('list_posts', {}, client as any) as Record<string, unknown>;
|
|
44
|
+
expect(Array.isArray(result.posts)).toBe(true);
|
|
45
|
+
expect(result.total).toBe(1);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool tests: resolve_url
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Requires non-empty string url
|
|
6
|
+
* - Rejects missing/null/empty/non-string url
|
|
7
|
+
* - Forwards url verbatim
|
|
8
|
+
* - Returns raw client response
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
12
|
+
import { handleDiscoveryTool } from '../../../tools/discovery.js';
|
|
13
|
+
import { makeMockClient } from '../../helpers/mock-client.js';
|
|
14
|
+
import { resolveUrlResponse } from '../../fixtures/rest-responses.js';
|
|
15
|
+
|
|
16
|
+
describe('resolve_url — validation', () => {
|
|
17
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
client = makeMockClient();
|
|
20
|
+
client.resolveUrl.mockResolvedValue(resolveUrlResponse);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('rejects missing url', async () => {
|
|
24
|
+
await expect(
|
|
25
|
+
handleDiscoveryTool('resolve_url', {}, client as any)
|
|
26
|
+
).rejects.toThrow(/url is required/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('rejects empty-string url', async () => {
|
|
30
|
+
await expect(
|
|
31
|
+
handleDiscoveryTool('resolve_url', { url: '' }, client as any)
|
|
32
|
+
).rejects.toThrow(/url is required/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('rejects non-string url', async () => {
|
|
36
|
+
await expect(
|
|
37
|
+
handleDiscoveryTool('resolve_url', { url: 123 }, client as any)
|
|
38
|
+
).rejects.toThrow(/url is required/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('rejects null url', async () => {
|
|
42
|
+
await expect(
|
|
43
|
+
handleDiscoveryTool('resolve_url', { url: null }, client as any)
|
|
44
|
+
).rejects.toThrow(/url is required/);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('resolve_url — forwarding', () => {
|
|
49
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
client = makeMockClient();
|
|
52
|
+
client.resolveUrl.mockResolvedValue(resolveUrlResponse);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('forwards a full URL', async () => {
|
|
56
|
+
await handleDiscoveryTool('resolve_url', { url: 'https://example.test/about/' }, client as any);
|
|
57
|
+
expect(client.resolveUrl).toHaveBeenCalledWith('https://example.test/about/');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('forwards a relative path', async () => {
|
|
61
|
+
await handleDiscoveryTool('resolve_url', { url: '/about/' }, client as any);
|
|
62
|
+
expect(client.resolveUrl).toHaveBeenCalledWith('/about/');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns the raw client response', async () => {
|
|
66
|
+
const result = await handleDiscoveryTool('resolve_url', { url: '/x' }, client as any);
|
|
67
|
+
expect(result).toBe(resolveUrlResponse);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool tests: scan_storage_modes
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Forwards to client.scanStorageModes with no args
|
|
6
|
+
* - Returns the raw response (no MCP-side processing)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
10
|
+
import { handleDiscoveryTool } from '../../../tools/discovery.js';
|
|
11
|
+
import { makeMockClient } from '../../helpers/mock-client.js';
|
|
12
|
+
|
|
13
|
+
describe('scan_storage_modes', () => {
|
|
14
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
client = makeMockClient();
|
|
17
|
+
client.scanStorageModes.mockResolvedValue({
|
|
18
|
+
scanned_posts: 100, unique_blocks: 25,
|
|
19
|
+
classification: { 'core/paragraph': 'static', 'core/latest-posts': 'dynamic' },
|
|
20
|
+
dual_count: 0, dynamic_count: 1, static_count: 24,
|
|
21
|
+
} as any);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('calls client.scanStorageModes with no arguments', async () => {
|
|
25
|
+
await handleDiscoveryTool('scan_storage_modes', {}, client as any);
|
|
26
|
+
expect(client.scanStorageModes).toHaveBeenCalledWith();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns the raw scan result', async () => {
|
|
30
|
+
const result = await handleDiscoveryTool('scan_storage_modes', {}, client as any) as Record<string, unknown>;
|
|
31
|
+
expect(result.scanned_posts).toBe(100);
|
|
32
|
+
expect(result.unique_blocks).toBe(25);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool tests: upload_media
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Schema: upload_media exposed
|
|
6
|
+
* - Validation: exactly one input mode (path, url, data_base64)
|
|
7
|
+
* - Validation: data_base64 requires filename
|
|
8
|
+
* - Request shape: url upload forwarded
|
|
9
|
+
* - Request shape: base64 upload forwarded
|
|
10
|
+
* - Request shape: path upload forwarded
|
|
11
|
+
* - Optional fields (alt_text, post_id) forwarded with url mode
|
|
12
|
+
* - Response shape: id, url, mime_type present
|
|
13
|
+
* - Unknown tool throws
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
17
|
+
import { MEDIA_TOOLS, handleMediaTool } from '../../../tools/media.js';
|
|
18
|
+
import { makeMockClient } from '../../helpers/mock-client.js';
|
|
19
|
+
|
|
20
|
+
describe('upload_media — schema', () => {
|
|
21
|
+
it('exposes upload_media tool', () => {
|
|
22
|
+
expect(MEDIA_TOOLS.map((t) => t.name)).toContain('upload_media');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('upload_media — validation', () => {
|
|
27
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
28
|
+
beforeEach(() => { client = makeMockClient(); vi.clearAllMocks(); });
|
|
29
|
+
|
|
30
|
+
it('rejects when no input mode is provided', async () => {
|
|
31
|
+
await expect(handleMediaTool('upload_media', { alt_text: 'x' }, client as any))
|
|
32
|
+
.rejects.toThrow(/path.*url.*data_base64/);
|
|
33
|
+
expect(client.uploadMedia).not.toHaveBeenCalled();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('rejects when multiple input modes are provided (path + url)', async () => {
|
|
37
|
+
await expect(handleMediaTool('upload_media', {
|
|
38
|
+
path: '/a.png', url: 'https://example.com/x.png',
|
|
39
|
+
}, client as any)).rejects.toThrow('only one of');
|
|
40
|
+
expect(client.uploadMedia).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('rejects when multiple input modes are provided (url + data_base64)', async () => {
|
|
44
|
+
await expect(handleMediaTool('upload_media', {
|
|
45
|
+
url: 'https://example.com/x.png', data_base64: 'aGVsbG8=', filename: 'x.png',
|
|
46
|
+
}, client as any)).rejects.toThrow('only one of');
|
|
47
|
+
expect(client.uploadMedia).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('rejects data_base64 without filename', async () => {
|
|
51
|
+
await expect(handleMediaTool('upload_media', { data_base64: 'aGVsbG8=' }, client as any))
|
|
52
|
+
.rejects.toThrow('filename');
|
|
53
|
+
expect(client.uploadMedia).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('upload_media — request shape', () => {
|
|
58
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
client = makeMockClient();
|
|
61
|
+
vi.clearAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('forwards url upload to client', async () => {
|
|
65
|
+
await handleMediaTool('upload_media', { url: 'https://example.com/img.png' }, client as any);
|
|
66
|
+
expect(client.uploadMedia).toHaveBeenCalledWith({ url: 'https://example.com/img.png' });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('forwards url upload with optional alt_text and post_id', async () => {
|
|
70
|
+
await handleMediaTool('upload_media', {
|
|
71
|
+
url: 'https://example.com/img.png', alt_text: 'hero image', post_id: 7,
|
|
72
|
+
}, client as any);
|
|
73
|
+
expect(client.uploadMedia).toHaveBeenCalledWith({
|
|
74
|
+
url: 'https://example.com/img.png', alt_text: 'hero image', post_id: 7,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('forwards base64 upload with filename', async () => {
|
|
79
|
+
await handleMediaTool('upload_media', {
|
|
80
|
+
data_base64: 'aGVsbG8=', filename: 'hello.png',
|
|
81
|
+
}, client as any);
|
|
82
|
+
expect(client.uploadMedia).toHaveBeenCalledWith({
|
|
83
|
+
data_base64: 'aGVsbG8=', filename: 'hello.png',
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('forwards path upload', async () => {
|
|
88
|
+
await handleMediaTool('upload_media', { path: '/tmp/photo.jpg' }, client as any);
|
|
89
|
+
expect(client.uploadMedia).toHaveBeenCalledWith({ path: '/tmp/photo.jpg' });
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('upload_media — response shape', () => {
|
|
94
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
client = makeMockClient();
|
|
97
|
+
vi.clearAllMocks();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('returns success, id, url, mime_type', async () => {
|
|
101
|
+
const result = await handleMediaTool('upload_media', { url: 'https://example.com/x.png' }, client as any) as any;
|
|
102
|
+
expect(result.success).toBe(true);
|
|
103
|
+
expect(result.id).toBe(1);
|
|
104
|
+
expect(result.url).toBeDefined();
|
|
105
|
+
expect(result.mime_type).toBe('image/png');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('returns filename and alt_text', async () => {
|
|
109
|
+
const result = await handleMediaTool('upload_media', { path: '/tmp/x.png' }, client as any) as any;
|
|
110
|
+
expect(result.filename).toBeDefined();
|
|
111
|
+
expect(typeof result.alt_text).toBe('string');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('upload_media — unknown tool', () => {
|
|
116
|
+
let client: ReturnType<typeof makeMockClient>;
|
|
117
|
+
beforeEach(() => { client = makeMockClient(); });
|
|
118
|
+
|
|
119
|
+
it('throws on unknown tool name', async () => {
|
|
120
|
+
await expect(handleMediaTool('unknown_tool', {}, client as any))
|
|
121
|
+
.rejects.toThrow('Unknown media tool');
|
|
122
|
+
});
|
|
123
|
+
});
|