const S3OwnershipService = require('../../../services/s3OwnershipService'); const { Message, ConditionCheck, Rental } = require('../../../models'); jest.mock('../../../models'); describe('S3OwnershipService', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('getFileTypeFromKey', () => { it('should return "profile" for profiles folder', () => { expect(S3OwnershipService.getFileTypeFromKey('profiles/uuid.jpg')).toBe('profile'); }); it('should return "item" for items folder', () => { expect(S3OwnershipService.getFileTypeFromKey('items/uuid.jpg')).toBe('item'); }); it('should return "message" for messages folder', () => { expect(S3OwnershipService.getFileTypeFromKey('messages/uuid.jpg')).toBe('message'); }); it('should return "forum" for forum folder', () => { expect(S3OwnershipService.getFileTypeFromKey('forum/uuid.jpg')).toBe('forum'); }); it('should return "condition-check" for condition-checks folder', () => { expect(S3OwnershipService.getFileTypeFromKey('condition-checks/uuid.jpg')).toBe('condition-check'); }); it('should return null for unknown folder', () => { expect(S3OwnershipService.getFileTypeFromKey('unknown/uuid.jpg')).toBeNull(); }); it('should return null for null key', () => { expect(S3OwnershipService.getFileTypeFromKey(null)).toBeNull(); }); it('should return null for undefined key', () => { expect(S3OwnershipService.getFileTypeFromKey(undefined)).toBeNull(); }); it('should return null for empty string', () => { expect(S3OwnershipService.getFileTypeFromKey('')).toBeNull(); }); }); describe('canAccessFile', () => { describe('public folders', () => { it('should authorize access to profile images for any user', async () => { const result = await S3OwnershipService.canAccessFile('profiles/uuid.jpg', 'user-123'); expect(result).toEqual({ authorized: true }); }); it('should authorize access to item images for any user', async () => { const result = await S3OwnershipService.canAccessFile('items/uuid.jpg', 'user-123'); expect(result).toEqual({ authorized: true }); }); it('should authorize access to forum images for any user', async () => { const result = await S3OwnershipService.canAccessFile('forum/uuid.jpg', 'user-123'); expect(result).toEqual({ authorized: true }); }); }); describe('private folders', () => { it('should call verifyMessageAccess for message images', async () => { Message.findOne.mockResolvedValue({ id: 'msg-123' }); const result = await S3OwnershipService.canAccessFile('messages/uuid.jpg', 'user-123'); expect(Message.findOne).toHaveBeenCalled(); expect(result.authorized).toBe(true); }); it('should call verifyConditionCheckAccess for condition-check images', async () => { ConditionCheck.findOne.mockResolvedValue({ id: 'check-123' }); const result = await S3OwnershipService.canAccessFile('condition-checks/uuid.jpg', 'user-123'); expect(ConditionCheck.findOne).toHaveBeenCalled(); expect(result.authorized).toBe(true); }); }); describe('unknown file types', () => { it('should deny access for unknown folder', async () => { const result = await S3OwnershipService.canAccessFile('unknown/uuid.jpg', 'user-123'); expect(result).toEqual({ authorized: false, reason: 'Unknown file type' }); }); }); }); describe('verifyMessageAccess', () => { const testKey = 'messages/550e8400-e29b-41d4-a716-446655440000.jpg'; const senderId = 'sender-123'; const receiverId = 'receiver-456'; it('should authorize sender to access message image', async () => { Message.findOne.mockResolvedValue({ id: 'msg-123', senderId, receiverId, imageFilename: testKey }); const result = await S3OwnershipService.verifyMessageAccess(testKey, senderId); expect(result).toEqual({ authorized: true, reason: null }); expect(Message.findOne).toHaveBeenCalledWith({ where: expect.objectContaining({ imageFilename: testKey }) }); }); it('should authorize receiver to access message image', async () => { Message.findOne.mockResolvedValue({ id: 'msg-123', senderId, receiverId, imageFilename: testKey }); const result = await S3OwnershipService.verifyMessageAccess(testKey, receiverId); expect(result.authorized).toBe(true); }); it('should deny access to unauthorized user', async () => { Message.findOne.mockResolvedValue(null); const result = await S3OwnershipService.verifyMessageAccess(testKey, 'other-user'); expect(result).toEqual({ authorized: false, reason: 'Not a participant in this message' }); }); it('should deny access when message does not exist', async () => { Message.findOne.mockResolvedValue(null); const result = await S3OwnershipService.verifyMessageAccess('messages/nonexistent.jpg', senderId); expect(result.authorized).toBe(false); expect(result.reason).toBe('Not a participant in this message'); }); }); describe('verifyConditionCheckAccess', () => { const testKey = 'condition-checks/550e8400-e29b-41d4-a716-446655440000.jpg'; const ownerId = 'owner-123'; const renterId = 'renter-456'; it('should authorize owner to access condition check image', async () => { ConditionCheck.findOne.mockResolvedValue({ id: 'check-123', imageFilenames: [testKey], rental: { id: 'rental-123', ownerId, renterId } }); const result = await S3OwnershipService.verifyConditionCheckAccess(testKey, ownerId); expect(result).toEqual({ authorized: true, reason: null }); expect(ConditionCheck.findOne).toHaveBeenCalledWith({ where: expect.objectContaining({ imageFilenames: expect.anything() }), include: expect.arrayContaining([ expect.objectContaining({ model: Rental, as: 'rental' }) ]) }); }); it('should authorize renter to access condition check image', async () => { ConditionCheck.findOne.mockResolvedValue({ id: 'check-123', imageFilenames: [testKey], rental: { id: 'rental-123', ownerId, renterId } }); const result = await S3OwnershipService.verifyConditionCheckAccess(testKey, renterId); expect(result.authorized).toBe(true); }); it('should deny access to unauthorized user', async () => { ConditionCheck.findOne.mockResolvedValue(null); const result = await S3OwnershipService.verifyConditionCheckAccess(testKey, 'other-user'); expect(result).toEqual({ authorized: false, reason: 'Not a participant in this rental' }); }); it('should deny access when condition check does not exist', async () => { ConditionCheck.findOne.mockResolvedValue(null); const result = await S3OwnershipService.verifyConditionCheckAccess( 'condition-checks/nonexistent.jpg', ownerId ); expect(result.authorized).toBe(false); expect(result.reason).toBe('Not a participant in this rental'); }); it('should use Op.contains for imageFilenames array search', async () => { const { Op } = require('sequelize'); ConditionCheck.findOne.mockResolvedValue(null); await S3OwnershipService.verifyConditionCheckAccess(testKey, ownerId); expect(ConditionCheck.findOne).toHaveBeenCalledWith( expect.objectContaining({ where: { imageFilenames: expect.anything() } }) ); }); }); });