diff --git a/test/dispute-flow/contract.test.ts b/test/dispute-flow/contract.test.ts new file mode 100644 index 0000000..1fe7bb2 --- /dev/null +++ b/test/dispute-flow/contract.test.ts @@ -0,0 +1,130 @@ +import { DisputeFlowService } from '../../backend/src/dispute-flow/dispute-flow.service'; +import { db } from '../../backend/src/db'; + +// Mock the database connection for testing +jest.mock('../../backend/src/db', () => ({ + db: { + query: jest.fn() + } +})); + +describe('Dispute Flow Contract Tests', () => { + let service: DisputeFlowService; + + beforeEach(() => { + service = new DisputeFlowService(); + // Reset all mocks before each test + jest.clearAllMocks(); + }); + + it('should create a dispute with all required fields', async () => { + const mockDispute = { + id: 1, + dealId: 123, + openedByUserId: 456, + status: 'open', + reasonCode: 'NO_SHOW', + summary: 'Helper did not show up', + requestedOutcome: 'refund', + createdAt: new Date(), + updatedAt: new Date() + }; + + (db.query as jest.Mock).mockResolvedValue({ rows: [mockDispute] }); + + const result = await service.createDispute({ + dealId: 123, + openedByUserId: 456, + reasonCode: 'NO_SHOW', + summary: 'Helper did not show up', + requestedOutcome: 'refund' + }); + + expect(result).toHaveProperty('id'); + expect(result).toHaveProperty('dealId'); + expect(result).toHaveProperty('openedByUserId'); + expect(result).toHaveProperty('status', 'open'); + expect(result).toHaveProperty('reasonCode'); + expect(result).toHaveProperty('summary'); + expect(result).toHaveProperty('requestedOutcome'); + }); + + it('should transition dispute status correctly through the flow', async () => { + // Mock initial creation + (db.query as jest.Mock) + .mockResolvedValueOnce({ rows: [{ id: 1, dealId: 123, openedByUserId: 456, status: 'open', reasonCode: 'NO_SHOW', summary: 'Helper did not show up', requestedOutcome: 'refund', createdAt: new Date(), updatedAt: new Date() }] }) + .mockResolvedValueOnce({}) // updateDisputeStatus + .mockResolvedValueOnce({}) // addEvidence + .mockResolvedValueOnce({}) // updateDisputeStatus to mediation + .mockResolvedValueOnce({}) // resolveDispute + .mockResolvedValueOnce({ rows: [{ id: 1, disputeId: 1, eventType: 'resolved', actorUserId: 456, payloadJson: JSON.stringify({ decision: 'refund', reason: 'Helper did not show up' }), createdAt: new Date() }] }); // getDisputeEvents + + const dispute = await service.createDispute({ + dealId: 123, + openedByUserId: 456, + reasonCode: 'NO_SHOW', + summary: 'Helper did not show up', + requestedOutcome: 'refund' + }); + + expect(dispute.status).toBe('open'); + + // Transition to evidence + await service.updateDisputeStatus(1, 'evidence', 456); + + // Add evidence + await service.addEvidence(1, { file: 'test.jpg' }, 456); + + // Transition to mediation + await service.updateDisputeStatus(1, 'mediation', 456); + + // Resolve dispute + await service.resolveDispute(1, { decision: 'refund', decisionReason: 'Helper did not show up' }, 456); + + const events = await service.getDisputeEvents(1); + expect(events).toHaveLength(4); // open, evidence, mediation, resolved + }); + + it('should log all dispute events correctly', async () => { + (db.query as jest.Mock) + .mockResolvedValueOnce({ rows: [{ id: 1, dealId: 123, openedByUserId: 456, status: 'open', reasonCode: 'NO_SHOW', summary: 'Helper did not show up', requestedOutcome: 'refund', createdAt: new Date(), updatedAt: new Date() }] }) + .mockResolvedValueOnce({}) // addEvidence + .mockResolvedValueOnce({ rows: [{ id: 1, disputeId: 1, eventType: 'evidence', actorUserId: 456, payloadJson: JSON.stringify({ file: 'test.jpg' }), createdAt: new Date() }] }); + + const dispute = await service.createDispute({ + dealId: 123, + openedByUserId: 456, + reasonCode: 'NO_SHOW', + summary: 'Helper did not show up', + requestedOutcome: 'refund' + }); + + await service.addEvidence(1, { file: 'test.jpg' }, 456); + + const events = await service.getDisputeEvents(1); + expect(events[0]).toHaveProperty('eventType', 'evidence'); + expect(events[0].payloadJson).toContain('test.jpg'); + }); + + it('should handle final decision with audit trail', async () => { + (db.query as jest.Mock) + .mockResolvedValueOnce({ rows: [{ id: 1, dealId: 123, openedByUserId: 456, status: 'open', reasonCode: 'NO_SHOW', summary: 'Helper did not show up', requestedOutcome: 'refund', createdAt: new Date(), updatedAt: new Date() }] }) + .mockResolvedValueOnce({}) // resolveDispute + .mockResolvedValueOnce({ rows: [{ id: 1, disputeId: 1, eventType: 'resolved', actorUserId: 456, payloadJson: JSON.stringify({ decision: 'refund', reason: 'Helper did not show up' }), createdAt: new Date() }] }); + + const dispute = await service.createDispute({ + dealId: 123, + openedByUserId: 456, + reasonCode: 'NO_SHOW', + summary: 'Helper did not show up', + requestedOutcome: 'refund' + }); + + await service.resolveDispute(1, { decision: 'refund', decisionReason: 'Helper did not show up' }, 456); + + const events = await service.getDisputeEvents(1); + expect(events[events.length - 1]).toHaveProperty('eventType', 'resolved'); + expect(events[events.length - 1].payloadJson).toContain('refund'); + expect(events[events.length - 1].payloadJson).toContain('Helper did not show up'); + }); +}); \ No newline at end of file diff --git a/test/dispute-flow/dispute-flow.test.ts b/test/dispute-flow/dispute-flow.test.ts new file mode 100644 index 0000000..d1a6a04 --- /dev/null +++ b/test/dispute-flow/dispute-flow.test.ts @@ -0,0 +1,123 @@ +import { DisputeFlowService } from '../../backend/src/dispute-flow/dispute-flow.service'; +import { db } from '../../backend/src/db'; + +// Mock the database connection for testing +jest.mock('../../backend/src/db', () => ({ + db: { + query: jest.fn() + } +})); + +describe('DisputeFlowService', () => { + let service: DisputeFlowService; + + beforeEach(() => { + service = new DisputeFlowService(); + // Reset all mocks before each test + jest.clearAllMocks(); + }); + + describe('createDispute', () => { + it('should create a new dispute', async () => { + const mockDispute = { + id: 1, + dealId: 123, + openedByUserId: 456, + status: 'open', + reasonCode: 'NO_SHOW', + summary: 'Helper did not show up', + requestedOutcome: 'refund', + createdAt: new Date(), + updatedAt: new Date() + }; + + (db.query as jest.Mock).mockResolvedValue({ rows: [mockDispute] }); + + const result = await service.createDispute({ + dealId: 123, + openedByUserId: 456, + reasonCode: 'NO_SHOW', + summary: 'Helper did not show up', + requestedOutcome: 'refund' + }); + + expect(db.query).toHaveBeenCalled(); + expect(result).toEqual(mockDispute); + }); + }); + + describe('addEvidence', () => { + it('should add evidence to a dispute', async () => { + (db.query as jest.Mock).mockResolvedValue({}); + + await service.addEvidence(1, { file: 'test.jpg' }, 456); + + expect(db.query).toHaveBeenCalled(); + }); + }); + + describe('updateDisputeStatus', () => { + it('should update dispute status', async () => { + (db.query as jest.Mock).mockResolvedValue({}); + + await service.updateDisputeStatus(1, 'evidence', 456); + + expect(db.query).toHaveBeenCalled(); + }); + }); + + describe('resolveDispute', () => { + it('should resolve a dispute', async () => { + (db.query as jest.Mock).mockResolvedValue({}); + + await service.resolveDispute(1, { decision: 'refund', decisionReason: 'Helper did not show up' }, 456); + + expect(db.query).toHaveBeenCalled(); + }); + }); + + describe('getDisputeById', () => { + it('should get dispute by ID', async () => { + const mockDispute = { + id: 1, + dealId: 123, + openedByUserId: 456, + status: 'open', + reasonCode: 'NO_SHOW', + summary: 'Helper did not show up', + requestedOutcome: 'refund', + createdAt: new Date(), + updatedAt: new Date() + }; + + (db.query as jest.Mock).mockResolvedValue({ rows: [mockDispute] }); + + const result = await service.getDisputeById(1); + + expect(db.query).toHaveBeenCalled(); + expect(result).toEqual(mockDispute); + }); + }); + + describe('getDisputeEvents', () => { + it('should get dispute events', async () => { + const mockEvents = [ + { + id: 1, + disputeId: 1, + eventType: 'evidence', + actorUserId: 456, + payloadJson: JSON.stringify({ file: 'test.jpg' }), + createdAt: new Date() + } + ]; + + (db.query as jest.Mock).mockResolvedValue({ rows: mockEvents }); + + const result = await service.getDisputeEvents(1); + + expect(db.query).toHaveBeenCalled(); + expect(result).toEqual(mockEvents); + }); + }); +}); \ No newline at end of file diff --git a/test/dispute-flow/integration.test.ts b/test/dispute-flow/integration.test.ts new file mode 100644 index 0000000..185df01 --- /dev/null +++ b/test/dispute-flow/integration.test.ts @@ -0,0 +1,20 @@ +import { DisputeFlowService } from '../../backend/src/dispute-flow/dispute-flow.service'; + +// Simple integration test for the dispute flow +describe('Dispute Flow Integration Test', () => { + let service: DisputeFlowService; + + beforeEach(() => { + // We'll test the logic without mocking the database + service = new DisputeFlowService(); + }); + + it('should implement complete dispute flow', async () => { + // This is a placeholder for integration testing + // In a real scenario, we would need to set up a test database + expect(service).toBeDefined(); + + // The actual implementation tests are in the unit tests + // This test just verifies that the service exists and can be instantiated + }); +}); \ No newline at end of file