Files
hideyoshi-blog/tests/lib/feature/article/article.service.test.ts

233 lines
8.1 KiB
TypeScript

import { getArticlesPaginated } from '@/lib/feature/article/article.external';
import {
CreateArticleModel,
UpdateArticleModel,
} from '@/lib/feature/article/article.model';
import {
deleteArticle,
getArticleByExternalId,
getArticleBySlug,
getArticlesByAuthorId,
saveArticle,
updateArticle,
} from '@/lib/feature/article/article.service';
import { CreateUserModel } from '@/lib/feature/user/user.model';
import { saveUser } from '@/lib/feature/user/user.service';
import { UUIDv4 } from '@/utils/types/uuid';
import { AbstractStartedContainer } from 'testcontainers';
import { startTestDB } from '~/tests/setup/setup-db';
jest.mock('@clerk/nextjs/server', () => ({
clerkClient: jest.fn(),
}));
describe('ArticleService', () => {
let container: AbstractStartedContainer;
let authorId: string;
beforeAll(async () => {
container = await startTestDB();
const author: CreateUserModel = {
name: 'Article Author',
email: 'author@email.com',
role: 'admin',
};
const savedAuthor = await saveUser(author);
authorId = savedAuthor.id;
}, 1_000_000);
afterAll(async () => {
await container.stop();
}, 1_000_000);
test('can save article', async () => {
const articleToSave: CreateArticleModel = {
title: 'Test Article',
slug: 'test-article',
description: 'A test article description',
coverImageUrl: 'https://example.com/cover.png',
content: 'This is the article content.',
authorId: authorId,
};
const savedArticle = await saveArticle(articleToSave);
expect(savedArticle.id).toBeDefined();
expect(savedArticle.title).toBe(articleToSave.title);
expect(savedArticle.slug).toBe(articleToSave.slug);
expect(savedArticle.description).toBe(articleToSave.description);
expect(savedArticle.coverImageUrl).toBe(articleToSave.coverImageUrl);
expect(savedArticle.content).toBe(articleToSave.content);
expect(savedArticle.authorId).toBe(authorId);
expect(savedArticle.externalId).toBeDefined();
});
test('cannot save article with existing slug', async () => {
const articleToSave: CreateArticleModel = {
title: 'Duplicate Slug Article',
slug: 'test-article',
description: 'Another article with the same slug',
coverImageUrl: 'https://example.com/cover2.png',
content: 'Duplicate content.',
authorId: authorId,
};
await expect(saveArticle(articleToSave)).rejects.toThrow(
`Article with slug ${articleToSave.slug} already exists`
);
});
test('can getArticleBySlug', async () => {
const article = await getArticleBySlug('test-article');
expect(article).toBeDefined();
expect(article?.slug).toBe('test-article');
expect(article?.title).toBe('Test Article');
expect(article?.authorId).toBe(authorId);
expect(article?.externalId).toBeDefined();
});
test('cannot getArticleBySlug with non-existing slug', async () => {
await expect(getArticleBySlug('non-existing-slug')).resolves.toBeNull();
});
test('can getArticlesByAuthorId', async () => {
const articles = await getArticlesByAuthorId(authorId);
expect(articles).toBeDefined();
expect(articles.length).toBeGreaterThanOrEqual(1);
expect(articles[0].authorId).toBe(authorId);
});
test('getArticlesByAuthorId returns empty for non-existing author', async () => {
const articles = await getArticlesByAuthorId('9999');
expect(articles).toBeDefined();
expect(articles.length).toBe(0);
});
test('can getArticleByExternalId', async () => {
const article = await getArticleBySlug('test-article');
expect(article).toBeDefined();
const foundArticle = await getArticleByExternalId(
article!.externalId as UUIDv4
);
expect(foundArticle).toBeDefined();
expect(foundArticle?.id).toBe(article!.id);
expect(foundArticle?.title).toBe(article!.title);
expect(foundArticle?.slug).toBe(article!.slug);
expect(foundArticle?.externalId).toBe(article!.externalId);
});
test('getArticleByExternalId returns null for non-existing id', async () => {
const result = await getArticleByExternalId(
'00000000-0000-4000-a000-000000000000' as UUIDv4
);
expect(result).toBeNull();
});
test('can getArticlesPaginated with defaults', async () => {
const result = await getArticlesPaginated();
expect(result).toBeDefined();
expect(result.data.length).toBeGreaterThanOrEqual(1);
expect(result.page).toBe(1);
expect(result.pageSize).toBe(10);
expect(result.total).toBeGreaterThanOrEqual(1);
expect(result.totalPages).toBeGreaterThanOrEqual(1);
});
test('can getArticlesPaginated with custom page size', async () => {
// Save extra articles to test pagination
for (let i = 0; i < 3; i++) {
await saveArticle({
title: `Paginated Article ${i}`,
slug: `paginated-article-${i}`,
description: `Description ${i}`,
coverImageUrl: `https://example.com/cover-${i}.png`,
content: `Content ${i}`,
authorId: authorId,
});
}
const firstPage = await getArticlesPaginated(1, 2);
expect(firstPage.data.length).toBe(2);
expect(firstPage.page).toBe(1);
expect(firstPage.pageSize).toBe(2);
expect(firstPage.total).toBeGreaterThanOrEqual(4);
expect(firstPage.totalPages).toBeGreaterThanOrEqual(2);
const secondPage = await getArticlesPaginated(2, 2);
expect(secondPage.data.length).toBe(2);
expect(secondPage.page).toBe(2);
expect(secondPage.pageSize).toBe(2);
});
test('can getArticlesPaginated returns empty on out-of-range page', async () => {
const result = await getArticlesPaginated(999, 10);
expect(result.data.length).toBe(0);
expect(result.page).toBe(999);
expect(result.total).toBeGreaterThanOrEqual(1);
});
test('can update article', async () => {
const dataToUpdate: UpdateArticleModel = {
title: 'Updated Article Title',
description: 'Updated description',
};
const article = await getArticleBySlug('test-article');
expect(article).toBeDefined();
const updatedArticle = await updateArticle(article!.id, dataToUpdate);
expect(updatedArticle).toBeDefined();
expect(updatedArticle.id).toBe(article!.id);
expect(updatedArticle.title).toBe(dataToUpdate.title);
expect(updatedArticle.description).toBe(dataToUpdate.description);
expect(updatedArticle.slug).toBe(article!.slug);
expect(updatedArticle.content).toBe(article!.content);
expect(updatedArticle.externalId).toBeDefined();
});
test('cannot update non-existing article', async () => {
const dataToUpdate: UpdateArticleModel = {
title: 'Updated Article Title',
};
await expect(updateArticle('9999', dataToUpdate)).rejects.toThrow(
`Article with ID 9999 not found`
);
});
test('can delete article', async () => {
const articleToSave: CreateArticleModel = {
title: 'Article to Delete',
slug: 'article-to-delete',
description: 'This article will be deleted',
coverImageUrl: 'https://example.com/delete-cover.png',
content: 'Content to delete.',
authorId: authorId,
};
const savedArticle = await saveArticle(articleToSave);
expect(savedArticle.id).toBeDefined();
await deleteArticle(savedArticle.id);
const deletedArticle = await getArticleBySlug('article-to-delete');
expect(deletedArticle).toBeNull();
});
test('cannot delete non-existing article', async () => {
await expect(deleteArticle('9999')).rejects.toThrow(
`Article with ID 9999 not found`
);
});
});