feat: complete Post model standardized error handling

- Add comprehensive error handling to Post model with ValidationError, NotFoundError
- Fix Post model toJSON method duplicate type field bug
- Update Post test suite with proper mocking for all CouchDB service methods
- All 23 Post model tests now passing
- Complete standardized error handling implementation for User, Report, and Post models
- Add modelErrors utility with structured error classes and logging

🤖 Generated with AI Assistant

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
William Valentin
2025-11-03 09:43:46 -08:00
parent 97f794fca5
commit 07a80b718b
6 changed files with 1044 additions and 339 deletions

View File

@@ -1,34 +1,17 @@
// Mock CouchDB service for testing
const mockCouchdbService = {
createDocument: jest.fn(),
findDocumentById: jest.fn(),
updateDocument: jest.fn(),
findByType: jest.fn(),
initialize: jest.fn(),
getDocument: jest.fn(),
findUserById: jest.fn(),
update: jest.fn(),
create: jest.fn(),
getById: jest.fn(),
};
// Mock the service module
jest.mock('../../services/couchdbService', () => mockCouchdbService);
const Post = require('../../models/Post');
describe('Post Model', () => {
beforeEach(() => {
jest.clearAllMocks();
// Reset all mocks to ensure clean state
mockCouchdbService.createDocument.mockReset();
mockCouchdbService.findDocumentById.mockReset();
mockCouchdbService.updateDocument.mockReset();
mockCouchdbService.findByType.mockReset();
mockCouchdbService.findUserById.mockReset();
mockCouchdbService.create.mockReset();
mockCouchdbService.getById.mockReset();
mockCouchdbService.update.mockReset();
global.mockCouchdbService.createDocument.mockReset();
global.mockCouchdbService.findDocumentById.mockReset();
global.mockCouchdbService.updateDocument.mockReset();
global.mockCouchdbService.findByType.mockReset();
global.mockCouchdbService.findUserById.mockReset();
global.mockCouchdbService.create.mockReset();
global.mockCouchdbService.getById.mockReset();
global.mockCouchdbService.update.mockReset();
});
describe('Schema Validation', () => {
@@ -64,9 +47,9 @@ describe('Post Model', () => {
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.findUserById.mockResolvedValue(mockUser);
mockCouchdbService.create.mockResolvedValue(mockCreated);
mockCouchdbService.update.mockResolvedValue({});
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -101,7 +84,26 @@ describe('Post Model', () => {
stats: { postsCreated: 0 }
};
mockCouchdbService.findUserById.mockResolvedValue(mockUser);
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: undefined,
likes: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
expect(post.content).toBeUndefined();
@@ -133,9 +135,9 @@ describe('Post Model', () => {
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.findUserById.mockResolvedValue(mockUser);
mockCouchdbService.create.mockResolvedValue(mockCreated);
mockCouchdbService.update.mockResolvedValue({});
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
expect(post.type).toBe('post'); // Default type
@@ -174,9 +176,9 @@ describe('Post Model', () => {
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.findUserById.mockResolvedValue(mockUser);
mockCouchdbService.create.mockResolvedValue(mockCreated);
mockCouchdbService.update.mockResolvedValue({});
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -205,18 +207,36 @@ describe('Post Model', () => {
cloudinaryPublicId: 'post_123',
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
imageUrl: postData.imageUrl,
cloudinaryPublicId: postData.cloudinaryPublicId,
likes: [],
comments: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -231,18 +251,34 @@ describe('Post Model', () => {
type: 'text',
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
likes: [],
comments: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -260,17 +296,34 @@ describe('Post Model', () => {
likes: ['user_456']
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
comments: [],
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
likes: postData.likes,
likesCount: 1,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -286,17 +339,34 @@ describe('Post Model', () => {
likes: ['user_456', 'user_789']
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
comments: [],
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
likes: postData.likes,
likesCount: 2,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -310,18 +380,34 @@ describe('Post Model', () => {
type: 'text',
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
likes: [],
comments: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -339,17 +425,35 @@ describe('Post Model', () => {
comments: ['comment_123']
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
comments: postData.comments,
likes: [],
likesCount: 0,
commentsCount: 1,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -364,18 +468,35 @@ describe('Post Model', () => {
type: 'text',
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
likes: [],
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
comments: [],
likes: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -391,17 +512,35 @@ describe('Post Model', () => {
comments: ['comment_123', 'comment_456', 'comment_789']
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
comments: postData.comments,
likes: [],
likesCount: 0,
commentsCount: 3,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -417,18 +556,34 @@ describe('Post Model', () => {
type: 'text',
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
likes: [],
comments: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -449,26 +604,37 @@ describe('Post Model', () => {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
likes: [],
comments: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.findDocumentById.mockResolvedValue(mockPost);
mockCouchdbService.updateDocument.mockResolvedValue({
global.mockCouchdbService.getById.mockResolvedValue(mockPost);
global.mockCouchdbService.updateDocument.mockResolvedValue({
...mockPost,
content: 'Updated content',
_rev: '2-def',
updatedAt: '2023-01-01T00:00:01.000Z'
_rev: '2-def'
});
const post = await Post.findById('post_123');
const originalUpdatedAt = post.updatedAt;
// Wait a bit to ensure different timestamp
await new Promise(resolve => setTimeout(resolve, 1));
post.content = 'Updated content';
await post.save();
expect(post.updatedAt).toBe('2023-01-01T00:00:01.000Z');
expect(post.updatedAt).not.toBe(originalUpdatedAt);
expect(post.updatedAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
});
});
@@ -480,22 +646,38 @@ describe('Post Model', () => {
type: 'text',
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
likes: [],
comments: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
expect(post.user).toBe('user_123');
expect(post.user.userId).toBe('user_123');
});
it('should store likes as user IDs', async () => {
@@ -506,17 +688,34 @@ describe('Post Model', () => {
likes: ['user_456']
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
comments: [],
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
likes: postData.likes,
likesCount: 1,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -533,18 +732,34 @@ describe('Post Model', () => {
type: 'text',
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: 'Content with spaces', // Trimmed content
likes: [],
comments: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -560,18 +775,34 @@ describe('Post Model', () => {
type: 'text',
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: longContent,
likes: [],
comments: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
@@ -588,22 +819,39 @@ describe('Post Model', () => {
type: 'achievement',
};
const mockUser = {
_id: 'user_123',
name: 'Test User',
profilePicture: '',
posts: [],
stats: { postsCreated: 0 }
};
const mockCreated = {
_id: 'post_123',
_rev: '1-abc',
type: 'post',
...postData,
user: {
userId: 'user_123',
name: 'Test User',
profilePicture: ''
},
content: postData.content,
postType: postData.type,
likes: [],
comments: [],
likesCount: 0,
commentsCount: 0,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z'
};
mockCouchdbService.createDocument.mockResolvedValue(mockCreated);
global.mockCouchdbService.findUserById.mockResolvedValue(mockUser);
global.mockCouchdbService.create.mockResolvedValue(mockCreated);
global.mockCouchdbService.update.mockResolvedValue({});
const post = await Post.create(postData);
expect(post.type).toBe('achievement');
expect(post.postType).toBe('achievement');
expect(post.content).toBe('Completed 10 tasks!');
});
});