test: add database query and mutation tests
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
||||
import { db } from '@/db';
|
||||
import { users, content, siteConfig } from '@/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
jest.mock('@/db', () => {
|
||||
const mockDb = {
|
||||
select: jest.fn().mockReturnThis(),
|
||||
from: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
limit: jest.fn().mockReturnThis(),
|
||||
insert: jest.fn().mockReturnThis(),
|
||||
values: jest.fn().mockReturnThis(),
|
||||
returning: jest.fn().mockReturnThis(),
|
||||
update: jest.fn().mockReturnThis(),
|
||||
set: jest.fn().mockReturnThis(),
|
||||
delete: jest.fn().mockReturnThis(),
|
||||
};
|
||||
return {
|
||||
db: mockDb,
|
||||
};
|
||||
});
|
||||
|
||||
describe('database mutations', () => {
|
||||
let mockDb: any;
|
||||
|
||||
beforeEach(() => {
|
||||
const { db: dbInstance } = require('@/db');
|
||||
mockDb = dbInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('user mutations', () => {
|
||||
it('should insert new user', async () => {
|
||||
const newUser = {
|
||||
id: 'user-123',
|
||||
email: 'newuser@example.com',
|
||||
name: 'New User',
|
||||
role: 'editor',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([newUser]);
|
||||
|
||||
const result = await db.insert(users).values(newUser).returning();
|
||||
const user = result[0];
|
||||
|
||||
expect(user).toBeDefined();
|
||||
expect(user.id).toBe('user-123');
|
||||
expect(user.email).toBe('newuser@example.com');
|
||||
expect(mockDb.insert).toHaveBeenCalledWith(users);
|
||||
expect(mockDb.values).toHaveBeenCalledWith(newUser);
|
||||
});
|
||||
|
||||
it('should update user', async () => {
|
||||
const updatedUser = {
|
||||
id: 'user-123',
|
||||
email: 'updated@example.com',
|
||||
name: 'Updated User',
|
||||
role: 'admin',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([updatedUser]);
|
||||
|
||||
const result = await db
|
||||
.update(users)
|
||||
.set({ name: 'Updated User', role: 'admin' })
|
||||
.where(eq(users.id, 'user-123'))
|
||||
.returning();
|
||||
const user = result[0];
|
||||
|
||||
expect(user).toBeDefined();
|
||||
expect(user.name).toBe('Updated User');
|
||||
expect(user.role).toBe('admin');
|
||||
expect(mockDb.update).toHaveBeenCalledWith(users);
|
||||
expect(mockDb.set).toHaveBeenCalledWith({ name: 'Updated User', role: 'admin' });
|
||||
});
|
||||
|
||||
it('should delete user', async () => {
|
||||
const deletedUser = {
|
||||
id: 'user-123',
|
||||
email: 'deleted@example.com',
|
||||
name: 'Deleted User',
|
||||
role: 'editor',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([deletedUser]);
|
||||
|
||||
const result = await db.delete(users).where(eq(users.id, 'user-123')).returning();
|
||||
const user = result[0];
|
||||
|
||||
expect(user).toBeDefined();
|
||||
expect(user.id).toBe('user-123');
|
||||
expect(mockDb.delete).toHaveBeenCalledWith(users);
|
||||
expect(mockDb.where).toHaveBeenCalledWith(eq(users.id, 'user-123'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('content mutations', () => {
|
||||
it('should insert new content', async () => {
|
||||
const newContent = {
|
||||
id: 'content-123',
|
||||
type: 'news',
|
||||
title: 'New Content',
|
||||
slug: 'new-content',
|
||||
content: 'Content body',
|
||||
status: 'draft',
|
||||
authorId: 'user-123',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([newContent]);
|
||||
|
||||
const result = await db.insert(content).values(newContent).returning();
|
||||
const item = result[0];
|
||||
|
||||
expect(item).toBeDefined();
|
||||
expect(item.id).toBe('content-123');
|
||||
expect(item.title).toBe('New Content');
|
||||
expect(mockDb.insert).toHaveBeenCalledWith(content);
|
||||
expect(mockDb.values).toHaveBeenCalledWith(newContent);
|
||||
});
|
||||
|
||||
it('should update content', async () => {
|
||||
const updatedContent = {
|
||||
id: 'content-123',
|
||||
type: 'news',
|
||||
title: 'Updated Content',
|
||||
slug: 'updated-content',
|
||||
content: 'Updated content body',
|
||||
status: 'published',
|
||||
authorId: 'user-123',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([updatedContent]);
|
||||
|
||||
const result = await db
|
||||
.update(content)
|
||||
.set({ title: 'Updated Content', status: 'published' })
|
||||
.where(eq(content.id, 'content-123'))
|
||||
.returning();
|
||||
const item = result[0];
|
||||
|
||||
expect(item).toBeDefined();
|
||||
expect(item.title).toBe('Updated Content');
|
||||
expect(item.status).toBe('published');
|
||||
expect(mockDb.update).toHaveBeenCalledWith(content);
|
||||
expect(mockDb.set).toHaveBeenCalledWith({ title: 'Updated Content', status: 'published' });
|
||||
});
|
||||
|
||||
it('should delete content', async () => {
|
||||
const deletedContent = {
|
||||
id: 'content-123',
|
||||
type: 'news',
|
||||
title: 'Deleted Content',
|
||||
slug: 'deleted-content',
|
||||
content: 'Deleted content body',
|
||||
status: 'archived',
|
||||
authorId: 'user-123',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([deletedContent]);
|
||||
|
||||
const result = await db.delete(content).where(eq(content.id, 'content-123')).returning();
|
||||
const item = result[0];
|
||||
|
||||
expect(item).toBeDefined();
|
||||
expect(item.id).toBe('content-123');
|
||||
expect(mockDb.delete).toHaveBeenCalledWith(content);
|
||||
expect(mockDb.where).toHaveBeenCalledWith(eq(content.id, 'content-123'));
|
||||
});
|
||||
|
||||
it('should publish content', async () => {
|
||||
const publishedContent = {
|
||||
id: 'content-123',
|
||||
type: 'news',
|
||||
title: 'Published Content',
|
||||
slug: 'published-content',
|
||||
content: 'Published content body',
|
||||
status: 'published',
|
||||
publishedAt: new Date(),
|
||||
authorId: 'user-123',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([publishedContent]);
|
||||
|
||||
const result = await db
|
||||
.update(content)
|
||||
.set({ status: 'published', publishedAt: new Date() })
|
||||
.where(eq(content.id, 'content-123'))
|
||||
.returning();
|
||||
const item = result[0];
|
||||
|
||||
expect(item).toBeDefined();
|
||||
expect(item.status).toBe('published');
|
||||
expect(item.publishedAt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('site config mutations', () => {
|
||||
it('should insert new config', async () => {
|
||||
const newConfig = {
|
||||
id: 'config-123',
|
||||
key: 'new.config',
|
||||
value: { setting: 'value' },
|
||||
category: 'general',
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([newConfig]);
|
||||
|
||||
const result = await db.insert(siteConfig).values(newConfig).returning();
|
||||
const config = result[0];
|
||||
|
||||
expect(config).toBeDefined();
|
||||
expect(config.key).toBe('new.config');
|
||||
expect(config.value).toEqual({ setting: 'value' });
|
||||
expect(mockDb.insert).toHaveBeenCalledWith(siteConfig);
|
||||
});
|
||||
|
||||
it('should update config', async () => {
|
||||
const updatedConfig = {
|
||||
id: 'config-123',
|
||||
key: 'updated.config',
|
||||
value: { setting: 'updated value' },
|
||||
category: 'general',
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([updatedConfig]);
|
||||
|
||||
const result = await db
|
||||
.update(siteConfig)
|
||||
.set({ value: { setting: 'updated value' } })
|
||||
.where(eq(siteConfig.key, 'updated.config'))
|
||||
.returning();
|
||||
const config = result[0];
|
||||
|
||||
expect(config).toBeDefined();
|
||||
expect(config.value).toEqual({ setting: 'updated value' });
|
||||
expect(mockDb.update).toHaveBeenCalledWith(siteConfig);
|
||||
});
|
||||
|
||||
it('should upsert config', async () => {
|
||||
const upsertedConfig = {
|
||||
id: 'config-123',
|
||||
key: 'upsert.config',
|
||||
value: { setting: 'upserted value' },
|
||||
category: 'general',
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.returning.mockResolvedValue([upsertedConfig]);
|
||||
|
||||
const result = await db
|
||||
.insert(siteConfig)
|
||||
.values({
|
||||
key: 'upsert.config',
|
||||
value: { setting: 'upserted value' },
|
||||
category: 'general',
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.returning();
|
||||
const config = result[0];
|
||||
|
||||
expect(config).toBeDefined();
|
||||
expect(config.key).toBe('upsert.config');
|
||||
});
|
||||
});
|
||||
|
||||
describe('batch operations', () => {
|
||||
it('should insert multiple users', async () => {
|
||||
const newUsers = [
|
||||
{ id: 'user-1', email: 'user1@example.com', name: 'User 1', role: 'editor', createdAt: new Date(), updatedAt: new Date() },
|
||||
{ id: 'user-2', email: 'user2@example.com', name: 'User 2', role: 'viewer', createdAt: new Date(), updatedAt: new Date() },
|
||||
{ id: 'user-3', email: 'user3@example.com', name: 'User 3', role: 'editor', createdAt: new Date(), updatedAt: new Date() },
|
||||
];
|
||||
mockDb.returning.mockResolvedValue(newUsers);
|
||||
|
||||
const result = await db.insert(users).values(newUsers).returning();
|
||||
|
||||
expect(result.length).toBe(3);
|
||||
expect(mockDb.values).toHaveBeenCalledWith(newUsers);
|
||||
});
|
||||
|
||||
it('should insert multiple content items', async () => {
|
||||
const newContent = [
|
||||
{ id: 'content-1', type: 'news', title: 'News 1', slug: 'news-1', content: 'Content 1', status: 'published', authorId: 'user-1', createdAt: new Date(), updatedAt: new Date() },
|
||||
{ id: 'content-2', type: 'news', title: 'News 2', slug: 'news-2', content: 'Content 2', status: 'published', authorId: 'user-1', createdAt: new Date(), updatedAt: new Date() },
|
||||
];
|
||||
mockDb.returning.mockResolvedValue(newContent);
|
||||
|
||||
const result = await db.insert(content).values(newContent).returning();
|
||||
|
||||
expect(result.length).toBe(2);
|
||||
expect(mockDb.values).toHaveBeenCalledWith(newContent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should handle duplicate key error', async () => {
|
||||
mockDb.returning.mockRejectedValue(new Error('UNIQUE constraint failed: users.email'));
|
||||
|
||||
await expect(
|
||||
db.insert(users).values({
|
||||
id: 'user-123',
|
||||
email: 'existing@example.com',
|
||||
name: 'Test User',
|
||||
role: 'editor',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}).returning()
|
||||
).rejects.toThrow('UNIQUE constraint failed');
|
||||
});
|
||||
|
||||
it('should handle foreign key constraint error', async () => {
|
||||
mockDb.returning.mockRejectedValue(new Error('FOREIGN KEY constraint failed'));
|
||||
|
||||
await expect(
|
||||
db.insert(content).values({
|
||||
id: 'content-123',
|
||||
type: 'news',
|
||||
title: 'Test Content',
|
||||
slug: 'test-content',
|
||||
content: 'Test content body',
|
||||
status: 'draft',
|
||||
authorId: 'non-existent-user',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}).returning()
|
||||
).rejects.toThrow('FOREIGN KEY constraint failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,255 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
||||
import { db } from '@/db';
|
||||
import { users, content, siteConfig } from '@/db/schema';
|
||||
import { eq, and, desc, like } from 'drizzle-orm';
|
||||
|
||||
jest.mock('@/db', () => {
|
||||
const mockDb = {
|
||||
select: jest.fn().mockReturnThis(),
|
||||
from: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
limit: jest.fn().mockReturnThis(),
|
||||
insert: jest.fn().mockReturnThis(),
|
||||
values: jest.fn().mockReturnThis(),
|
||||
returning: jest.fn().mockReturnThis(),
|
||||
update: jest.fn().mockReturnThis(),
|
||||
set: jest.fn().mockReturnThis(),
|
||||
delete: jest.fn().mockReturnThis(),
|
||||
};
|
||||
return {
|
||||
db: mockDb,
|
||||
};
|
||||
});
|
||||
|
||||
describe('database queries', () => {
|
||||
let mockDb: any;
|
||||
|
||||
beforeEach(() => {
|
||||
const { db: dbInstance } = require('@/db');
|
||||
mockDb = dbInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('user queries', () => {
|
||||
it('should query user by id', async () => {
|
||||
const mockUser = {
|
||||
id: '123',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
role: 'editor',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.limit.mockResolvedValue([mockUser]);
|
||||
|
||||
const result = await db.select().from(users).where(eq(users.id, '123')).limit(1);
|
||||
const user = result[0];
|
||||
|
||||
expect(user).toBeDefined();
|
||||
expect(user.id).toBe('123');
|
||||
expect(user.email).toBe('test@example.com');
|
||||
});
|
||||
|
||||
it('should query user by email', async () => {
|
||||
const mockUser = {
|
||||
id: '123',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
role: 'editor',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.limit.mockResolvedValue([mockUser]);
|
||||
|
||||
const result = await db.select().from(users).where(eq(users.email, 'test@example.com')).limit(1);
|
||||
const user = result[0];
|
||||
|
||||
expect(user).toBeDefined();
|
||||
expect(user.email).toBe('test@example.com');
|
||||
});
|
||||
|
||||
it('should return null for non-existent user', async () => {
|
||||
mockDb.limit.mockResolvedValue([]);
|
||||
|
||||
const result = await db.select().from(users).where(eq(users.id, 'non-existent')).limit(1);
|
||||
const user = result[0];
|
||||
|
||||
expect(user).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should query users by role', async () => {
|
||||
const mockUsers = [
|
||||
{ id: '1', email: 'admin@example.com', name: 'Admin', role: 'admin', createdAt: new Date(), updatedAt: new Date() },
|
||||
{ id: '2', email: 'admin2@example.com', name: 'Admin2', role: 'admin', createdAt: new Date(), updatedAt: new Date() },
|
||||
];
|
||||
mockDb.limit.mockResolvedValue(mockUsers);
|
||||
|
||||
const result = await db.select().from(users).where(eq(users.role, 'admin')).limit(10);
|
||||
|
||||
expect(result.length).toBe(2);
|
||||
expect(result.every(u => u.role === 'admin')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('content queries', () => {
|
||||
it('should query content by id', async () => {
|
||||
const mockContent = {
|
||||
id: 'content-1',
|
||||
type: 'news',
|
||||
title: 'Test News',
|
||||
slug: 'test-news',
|
||||
content: 'Test content',
|
||||
status: 'published',
|
||||
authorId: '123',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.limit.mockResolvedValue([mockContent]);
|
||||
|
||||
const result = await db.select().from(content).where(eq(content.id, 'content-1')).limit(1);
|
||||
const item = result[0];
|
||||
|
||||
expect(item).toBeDefined();
|
||||
expect(item.id).toBe('content-1');
|
||||
expect(item.title).toBe('Test News');
|
||||
});
|
||||
|
||||
it('should query content by slug', async () => {
|
||||
const mockContent = {
|
||||
id: 'content-1',
|
||||
type: 'news',
|
||||
title: 'Test News',
|
||||
slug: 'test-news',
|
||||
content: 'Test content',
|
||||
status: 'published',
|
||||
authorId: '123',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.limit.mockResolvedValue([mockContent]);
|
||||
|
||||
const result = await db.select().from(content).where(eq(content.slug, 'test-news')).limit(1);
|
||||
const item = result[0];
|
||||
|
||||
expect(item).toBeDefined();
|
||||
expect(item.slug).toBe('test-news');
|
||||
});
|
||||
|
||||
it('should query published content', async () => {
|
||||
const mockContent = [
|
||||
{ id: '1', type: 'news', title: 'News 1', slug: 'news-1', content: 'Content 1', status: 'published', authorId: '123', createdAt: new Date(), updatedAt: new Date() },
|
||||
{ id: '2', type: 'news', title: 'News 2', slug: 'news-2', content: 'Content 2', status: 'published', authorId: '123', createdAt: new Date(), updatedAt: new Date() },
|
||||
];
|
||||
mockDb.limit.mockResolvedValue(mockContent);
|
||||
|
||||
const result = await db.select().from(content).where(eq(content.status, 'published')).limit(10);
|
||||
|
||||
expect(result.length).toBe(2);
|
||||
expect(result.every(c => c.status === 'published')).toBe(true);
|
||||
});
|
||||
|
||||
it('should query content by type', async () => {
|
||||
const mockContent = [
|
||||
{ id: '1', type: 'news', title: 'News 1', slug: 'news-1', content: 'Content 1', status: 'published', authorId: '123', createdAt: new Date(), updatedAt: new Date() },
|
||||
{ id: '2', type: 'news', title: 'News 2', slug: 'news-2', content: 'Content 2', status: 'published', authorId: '123', createdAt: new Date(), updatedAt: new Date() },
|
||||
];
|
||||
mockDb.limit.mockResolvedValue(mockContent);
|
||||
|
||||
const result = await db.select().from(content).where(eq(content.type, 'news')).limit(10);
|
||||
|
||||
expect(result.length).toBe(2);
|
||||
expect(result.every(c => c.type === 'news')).toBe(true);
|
||||
});
|
||||
|
||||
it('should query content with multiple conditions', async () => {
|
||||
const mockContent = {
|
||||
id: 'content-1',
|
||||
type: 'news',
|
||||
title: 'Test News',
|
||||
slug: 'test-news',
|
||||
content: 'Test content',
|
||||
status: 'published',
|
||||
authorId: '123',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.limit.mockResolvedValue([mockContent]);
|
||||
|
||||
const result = await db
|
||||
.select()
|
||||
.from(content)
|
||||
.where(and(eq(content.status, 'published'), eq(content.type, 'news')))
|
||||
.limit(1);
|
||||
const item = result[0];
|
||||
|
||||
expect(item).toBeDefined();
|
||||
expect(item.status).toBe('published');
|
||||
expect(item.type).toBe('news');
|
||||
});
|
||||
});
|
||||
|
||||
describe('site config queries', () => {
|
||||
it('should query config by key', async () => {
|
||||
const mockConfig = {
|
||||
id: 'config-1',
|
||||
key: 'site.title',
|
||||
value: { title: 'My Site' },
|
||||
category: 'general',
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
mockDb.limit.mockResolvedValue([mockConfig]);
|
||||
|
||||
const result = await db.select().from(siteConfig).where(eq(siteConfig.key, 'site.title')).limit(1);
|
||||
const config = result[0];
|
||||
|
||||
expect(config).toBeDefined();
|
||||
expect(config.key).toBe('site.title');
|
||||
expect(config.value).toEqual({ title: 'My Site' });
|
||||
});
|
||||
|
||||
it('should query config by category', async () => {
|
||||
const mockConfigs = [
|
||||
{ id: '1', key: 'site.title', value: { title: 'My Site' }, category: 'general', updatedAt: new Date() },
|
||||
{ id: '2', key: 'site.description', value: { description: 'My Description' }, category: 'general', updatedAt: new Date() },
|
||||
];
|
||||
mockDb.limit.mockResolvedValue(mockConfigs);
|
||||
|
||||
const result = await db.select().from(siteConfig).where(eq(siteConfig.category, 'general')).limit(10);
|
||||
|
||||
expect(result.length).toBe(2);
|
||||
expect(result.every(c => c.category === 'general')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return null for non-existent config', async () => {
|
||||
mockDb.limit.mockResolvedValue([]);
|
||||
|
||||
const result = await db.select().from(siteConfig).where(eq(siteConfig.key, 'non.existent')).limit(1);
|
||||
const config = result[0];
|
||||
|
||||
expect(config).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('query ordering', () => {
|
||||
it('should order content by created date', async () => {
|
||||
const mockContent = [
|
||||
{ id: '1', type: 'news', title: 'News 1', slug: 'news-1', content: 'Content 1', status: 'published', authorId: '123', createdAt: new Date('2024-01-01'), updatedAt: new Date() },
|
||||
{ id: '2', type: 'news', title: 'News 2', slug: 'news-2', content: 'Content 2', status: 'published', authorId: '123', createdAt: new Date('2024-01-02'), updatedAt: new Date() },
|
||||
];
|
||||
mockDb.limit.mockResolvedValue(mockContent);
|
||||
|
||||
const result = await db
|
||||
.select()
|
||||
.from(content)
|
||||
.orderBy(desc(content.createdAt))
|
||||
.limit(10);
|
||||
|
||||
expect(result.length).toBe(2);
|
||||
expect(mockDb.orderBy).toHaveBeenCalledWith(desc(content.createdAt));
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user