const Post = require('../../models/Post'); const User = require('../../models/User'); const mongoose = require('mongoose'); describe('Post Model', () => { let user; beforeEach(async () => { user = await User.create({ name: 'Test User', email: 'test@example.com', password: 'password123', }); }); describe('Schema Validation', () => { it('should create a valid text post', async () => { const postData = { user: user._id, content: 'This is a test post', type: 'text', }; const post = new Post(postData); const savedPost = await post.save(); expect(savedPost._id).toBeDefined(); expect(savedPost.content).toBe(postData.content); expect(savedPost.type).toBe(postData.type); expect(savedPost.user.toString()).toBe(user._id.toString()); expect(savedPost.likes).toEqual([]); expect(savedPost.comments).toEqual([]); }); it('should require user field', async () => { const post = new Post({ content: 'Post without user', type: 'text', }); let error; try { await post.save(); } catch (err) { error = err; } expect(error).toBeDefined(); expect(error.errors.user).toBeDefined(); }); it('should require content field', async () => { const post = new Post({ user: user._id, type: 'text', }); let error; try { await post.save(); } catch (err) { error = err; } expect(error).toBeDefined(); expect(error.errors.content).toBeDefined(); }); it('should require type field', async () => { const post = new Post({ user: user._id, content: 'Post without type', }); let error; try { await post.save(); } catch (err) { error = err; } expect(error).toBeDefined(); expect(error.errors.type).toBeDefined(); }); }); describe('Post Types', () => { const validTypes = ['text', 'image', 'achievement']; validTypes.forEach(type => { it(`should accept "${type}" as valid type`, async () => { const post = await Post.create({ user: user._id, content: `This is a ${type} post`, type, }); expect(post.type).toBe(type); }); }); it('should reject invalid post type', async () => { const post = new Post({ user: user._id, content: 'Invalid type post', type: 'invalid_type', }); let error; try { await post.save(); } catch (err) { error = err; } expect(error).toBeDefined(); expect(error.errors.type).toBeDefined(); }); }); describe('Image Posts', () => { it('should allow image URL for image posts', async () => { const post = await Post.create({ user: user._id, content: 'Check out this photo', type: 'image', imageUrl: 'https://example.com/image.jpg', cloudinaryPublicId: 'post_123', }); expect(post.imageUrl).toBe('https://example.com/image.jpg'); expect(post.cloudinaryPublicId).toBe('post_123'); }); it('should allow text post without image URL', async () => { const post = await Post.create({ user: user._id, content: 'Just a text post', type: 'text', }); expect(post.imageUrl).toBeUndefined(); expect(post.cloudinaryPublicId).toBeUndefined(); }); }); describe('Likes', () => { it('should allow adding likes', async () => { const post = await Post.create({ user: user._id, content: 'Post to be liked', type: 'text', }); const liker = await User.create({ name: 'Liker', email: 'liker@example.com', password: 'password123', }); post.likes.push(liker._id); await post.save(); expect(post.likes).toHaveLength(1); expect(post.likes[0].toString()).toBe(liker._id.toString()); }); it('should allow multiple likes', async () => { const post = await Post.create({ user: user._id, content: 'Popular post', type: 'text', }); const liker1 = await User.create({ name: 'Liker 1', email: 'liker1@example.com', password: 'password123', }); const liker2 = await User.create({ name: 'Liker 2', email: 'liker2@example.com', password: 'password123', }); post.likes.push(liker1._id, liker2._id); await post.save(); expect(post.likes).toHaveLength(2); }); it('should start with empty likes array', async () => { const post = await Post.create({ user: user._id, content: 'New post', type: 'text', }); expect(post.likes).toEqual([]); expect(post.likes).toHaveLength(0); }); }); describe('Comments', () => { it('should allow adding comments', async () => { const post = await Post.create({ user: user._id, content: 'Post with comments', type: 'text', }); const commentId = new mongoose.Types.ObjectId(); post.comments.push(commentId); await post.save(); expect(post.comments).toHaveLength(1); expect(post.comments[0].toString()).toBe(commentId.toString()); }); it('should start with empty comments array', async () => { const post = await Post.create({ user: user._id, content: 'New post', type: 'text', }); expect(post.comments).toEqual([]); expect(post.comments).toHaveLength(0); }); it('should allow multiple comments', async () => { const post = await Post.create({ user: user._id, content: 'Post with multiple comments', type: 'text', }); const comment1 = new mongoose.Types.ObjectId(); const comment2 = new mongoose.Types.ObjectId(); const comment3 = new mongoose.Types.ObjectId(); post.comments.push(comment1, comment2, comment3); await post.save(); expect(post.comments).toHaveLength(3); }); }); describe('Timestamps', () => { it('should automatically set createdAt and updatedAt', async () => { const post = await Post.create({ user: user._id, content: 'Timestamp post', type: 'text', }); expect(post.createdAt).toBeDefined(); expect(post.updatedAt).toBeDefined(); expect(post.createdAt).toBeInstanceOf(Date); expect(post.updatedAt).toBeInstanceOf(Date); }); it('should update updatedAt on modification', async () => { const post = await Post.create({ user: user._id, content: 'Update test post', type: 'text', }); const originalUpdatedAt = post.updatedAt; // Wait a bit to ensure timestamp difference await new Promise(resolve => setTimeout(resolve, 10)); post.content = 'Updated content'; await post.save(); expect(post.updatedAt.getTime()).toBeGreaterThan(originalUpdatedAt.getTime()); }); }); describe('Relationships', () => { it('should reference User model', async () => { const post = await Post.create({ user: user._id, content: 'User relationship post', type: 'text', }); const populatedPost = await Post.findById(post._id).populate('user'); expect(populatedPost.user).toBeDefined(); expect(populatedPost.user.name).toBe('Test User'); expect(populatedPost.user.email).toBe('test@example.com'); }); it('should populate likes with user data', async () => { const liker = await User.create({ name: 'Liker', email: 'liker@example.com', password: 'password123', }); const post = await Post.create({ user: user._id, content: 'Post with likes', type: 'text', likes: [liker._id], }); const populatedPost = await Post.findById(post._id).populate('likes'); expect(populatedPost.likes).toHaveLength(1); expect(populatedPost.likes[0].name).toBe('Liker'); }); }); describe('Content Validation', () => { it('should trim content', async () => { const post = await Post.create({ user: user._id, content: ' Content with spaces ', type: 'text', }); expect(post.content).toBe('Content with spaces'); }); it('should enforce maximum content length', async () => { const longContent = 'a'.repeat(5001); // Assuming 5000 char limit const post = new Post({ user: user._id, content: longContent, type: 'text', }); let error; try { await post.save(); } catch (err) { error = err; } // This test will pass if there's a maxlength validation if (error) { expect(error.errors.content).toBeDefined(); } }); }); describe('Achievement Posts', () => { it('should create achievement type posts', async () => { const post = await Post.create({ user: user._id, content: 'Completed 10 tasks!', type: 'achievement', }); expect(post.type).toBe('achievement'); expect(post.content).toBe('Completed 10 tasks!'); }); }); describe('Indexes', () => { it('should have index on user field', async () => { const indexes = await Post.collection.getIndexes(); const hasUserIndex = Object.values(indexes).some(index => index.some(field => field[0] === 'user') ); expect(hasUserIndex).toBe(true); }); it('should have index on createdAt field', async () => { const indexes = await Post.collection.getIndexes(); const hasCreatedAtIndex = Object.values(indexes).some(index => index.some(field => field[0] === 'createdAt') ); expect(hasCreatedAtIndex).toBe(true); }); }); });