import { describe, it, expect, beforeEach, vi } from 'vitest';
import { VergeApiClient } from '../../src/client/api';
import {
handleListArticles,
handleGetArticle,
handleCreateArticle,
handleUpdateArticle,
handlePublishArticle,
handleDeleteArticle,
} from '../../src/tools/articles';
describe('記事管理ツール', () => {
let apiClient: VergeApiClient;
beforeEach(() => {
apiClient = new VergeApiClient('https://api.example.com');
});
// ============================================================
// handleListArticles
// ============================================================
describe('handleListArticles', () => {
it('正常系: 記事一覧を取得', async () => {
const mockArticles = [
{
id: 1,
blog_id: 1,
title: 'Article 1',
content: 'Content 1',
slug: 'article-1',
status: 'published' as const,
featured_image: null,
author_id: 1,
excerpt: 'Excerpt 1',
created_at: '2024-01-01T00:00:00.000Z',
updated_at: '2024-01-01T00:00:00.000Z',
author: {
id: 1,
name: 'Author',
email: 'author@example.com',
},
categories: [],
tags: [],
},
];
vi.spyOn(apiClient, 'listArticles').mockResolvedValueOnce(mockArticles);
const input = { blog_id: 1 };
const result = await handleListArticles(input, apiClient);
expect(apiClient.listArticles).toHaveBeenCalledWith(1, {
search: undefined,
status: undefined,
categories: undefined,
tags: undefined,
});
const parsedContent = JSON.parse(result.content[0].text);
expect(parsedContent.success).toBe(true);
expect(parsedContent.count).toBe(1);
expect(parsedContent.articles).toHaveLength(1);
expect(parsedContent.articles[0].id).toBe(1);
});
it('正常系: クエリパラメータ付きで記事一覧を取得', async () => {
vi.spyOn(apiClient, 'listArticles').mockResolvedValueOnce([]);
const input = {
blog_id: 1,
search: 'JavaScript',
status: 'published' as const,
category_ids: [1, 2],
tag_ids: [3, 4],
};
await handleListArticles(input, apiClient);
expect(apiClient.listArticles).toHaveBeenCalledWith(1, {
search: 'JavaScript',
status: 'published',
categories: '1,2',
tags: '3,4',
});
});
});
// ============================================================
// handleGetArticle
// ============================================================
describe('handleGetArticle', () => {
it('正常系: 記事詳細を取得', async () => {
const mockArticle = {
id: 1,
blog_id: 1,
title: 'Article 1',
content: 'Content 1',
slug: 'article-1',
status: 'published' as const,
featured_image: null,
author_id: 1,
excerpt: 'Excerpt',
created_at: '2024-01-01T00:00:00.000Z',
updated_at: '2024-01-01T00:00:00.000Z',
author: {
id: 1,
name: 'Author',
email: 'author@example.com',
},
categories: [],
tags: [],
};
vi.spyOn(apiClient, 'getArticle').mockResolvedValueOnce(mockArticle);
const input = { article_id: 1, blog_id: 1 };
const result = await handleGetArticle(input, apiClient);
expect(apiClient.getArticle).toHaveBeenCalledWith(1, 1);
const parsedContent = JSON.parse(result.content[0].text);
expect(parsedContent.success).toBe(true);
expect(parsedContent.article.id).toBe(1);
expect(parsedContent.article.title).toBe('Article 1');
expect(parsedContent.article.content).toBe('Content 1');
});
});
// ============================================================
// handleCreateArticle
// ============================================================
describe('handleCreateArticle', () => {
it('正常系: 記事を作成', async () => {
const mockArticle = {
id: 1,
blog_id: 1,
title: 'New Article',
content: 'Content',
slug: 'new-article',
status: 'draft' as const,
featured_image: null,
author_id: 1,
excerpt: null,
created_at: '2024-01-01T00:00:00.000Z',
updated_at: '2024-01-01T00:00:00.000Z',
};
vi.spyOn(apiClient, 'createArticle').mockResolvedValueOnce(mockArticle);
const input = {
blog_id: 1,
title: 'New Article',
content: 'Content',
slug: 'new-article',
};
const result = await handleCreateArticle(input, apiClient);
expect(apiClient.createArticle).toHaveBeenCalledWith(1, {
title: 'New Article',
content: 'Content',
slug: 'new-article',
});
const parsedContent = JSON.parse(result.content[0].text);
expect(parsedContent.success).toBe(true);
expect(parsedContent.message).toBe('記事を作成しました');
expect(parsedContent.article.id).toBe(1);
expect(parsedContent.article.title).toBe('New Article');
});
it('正常系: 全オプションを指定して記事を作成', async () => {
const mockArticle = {
id: 1,
blog_id: 1,
title: 'New Article',
content: 'Content',
slug: 'new-article',
status: 'published' as const,
featured_image: 'https://example.com/image.jpg',
author_id: 2,
excerpt: 'Excerpt',
created_at: '2024-01-01T00:00:00.000Z',
updated_at: '2024-01-01T00:00:00.000Z',
};
vi.spyOn(apiClient, 'createArticle').mockResolvedValueOnce(mockArticle);
const input = {
blog_id: 1,
title: 'New Article',
content: 'Content',
slug: 'new-article',
status: 'published' as const,
featured_image: 'https://example.com/image.jpg',
excerpt: 'Excerpt',
author_id: 2,
category_ids: [1],
tag_ids: [2, 3],
};
await handleCreateArticle(input, apiClient);
expect(apiClient.createArticle).toHaveBeenCalledWith(1, {
title: 'New Article',
content: 'Content',
slug: 'new-article',
status: 'published',
featured_image: 'https://example.com/image.jpg',
excerpt: 'Excerpt',
author_id: 2,
category_ids: [1],
tag_ids: [2, 3],
});
});
});
// ============================================================
// handleUpdateArticle
// ============================================================
describe('handleUpdateArticle', () => {
it('正常系: 記事を更新', async () => {
const mockArticle = {
id: 1,
blog_id: 1,
title: 'Updated Title',
content: 'Original content',
slug: 'article-1',
status: 'published' as const,
featured_image: null,
author_id: 1,
excerpt: null,
created_at: '2024-01-01T00:00:00.000Z',
updated_at: '2024-01-02T00:00:00.000Z',
};
vi.spyOn(apiClient, 'updateArticle').mockResolvedValueOnce(mockArticle);
const input = {
article_id: 1,
blog_id: 1,
title: 'Updated Title',
};
const result = await handleUpdateArticle(input, apiClient);
expect(apiClient.updateArticle).toHaveBeenCalledWith(1, 1, {
title: 'Updated Title',
});
const parsedContent = JSON.parse(result.content[0].text);
expect(parsedContent.success).toBe(true);
expect(parsedContent.message).toBe('記事を更新しました');
expect(parsedContent.article.title).toBe('Updated Title');
});
});
// ============================================================
// handlePublishArticle
// ============================================================
describe('handlePublishArticle', () => {
it('正常系: 記事を公開', async () => {
const mockArticle = {
id: 1,
blog_id: 1,
title: 'Article 1',
content: 'Content',
slug: 'article-1',
status: 'published' as const,
featured_image: null,
author_id: 1,
excerpt: null,
created_at: '2024-01-01T00:00:00.000Z',
updated_at: '2024-01-02T00:00:00.000Z',
};
vi.spyOn(apiClient, 'updateArticle').mockResolvedValueOnce(mockArticle);
const input = {
article_id: 1,
blog_id: 1,
};
const result = await handlePublishArticle(input, apiClient);
expect(apiClient.updateArticle).toHaveBeenCalledWith(1, 1, {
status: 'published',
});
const parsedContent = JSON.parse(result.content[0].text);
expect(parsedContent.success).toBe(true);
expect(parsedContent.message).toBe('記事を公開しました');
expect(parsedContent.article.status).toBe('published');
});
});
// ============================================================
// handleDeleteArticle
// ============================================================
describe('handleDeleteArticle', () => {
it('正常系: 記事を削除', async () => {
vi.spyOn(apiClient, 'deleteArticle').mockResolvedValueOnce();
const input = {
article_id: 1,
blog_id: 1,
};
const result = await handleDeleteArticle(input, apiClient);
expect(apiClient.deleteArticle).toHaveBeenCalledWith(1, 1);
const parsedContent = JSON.parse(result.content[0].text);
expect(parsedContent.success).toBe(true);
expect(parsedContent.message).toBe('記事を削除しました');
expect(parsedContent.article_id).toBe(1);
});
});
});