"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const testing_1 = require("@nestjs/testing");
const typeorm_1 = require("@nestjs/typeorm");
const bullmq_1 = require("@nestjs/bullmq");
const common_1 = require("@nestjs/common");
const webhook_service_1 = require("../services/webhook.service");
const entities_1 = require("../entities");
describe('WebhookService', () => {
    let service;
    let webhookRepo;
    let deliveryRepo;
    let webhookQueue;
    const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
    const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
    const mockWebhook = {
        id: 'webhook-001',
        tenantId: mockTenantId,
        name: 'Test Webhook',
        description: 'Test webhook description',
        url: 'https://example.com/webhook',
        events: ['user.created', 'user.updated'],
        headers: { 'X-Custom': 'header' },
        secret: 'whsec_testsecret123',
        isActive: true,
        createdBy: mockUserId,
        createdAt: new Date(),
    };
    const mockDelivery = {
        id: 'delivery-001',
        webhookId: 'webhook-001',
        tenantId: mockTenantId,
        eventType: 'user.created',
        payload: { type: 'user.created', data: { id: 'user-001' } },
        status: entities_1.DeliveryStatus.PENDING,
        attempt: 1,
        maxAttempts: 3,
        createdAt: new Date(),
    };
    beforeEach(async () => {
        const mockWebhookRepo = {
            create: jest.fn(),
            save: jest.fn(),
            findOne: jest.fn(),
            find: jest.fn(),
            remove: jest.fn(),
        };
        const mockDeliveryRepo = {
            create: jest.fn(),
            save: jest.fn(),
            findOne: jest.fn(),
            find: jest.fn(),
            createQueryBuilder: jest.fn(),
        };
        const mockQueue = {
            add: jest.fn(),
        };
        const module = await testing_1.Test.createTestingModule({
            providers: [
                webhook_service_1.WebhookService,
                { provide: (0, typeorm_1.getRepositoryToken)(entities_1.WebhookEntity), useValue: mockWebhookRepo },
                { provide: (0, typeorm_1.getRepositoryToken)(entities_1.WebhookDeliveryEntity), useValue: mockDeliveryRepo },
                { provide: (0, bullmq_1.getQueueToken)('webhooks'), useValue: mockQueue },
            ],
        }).compile();
        service = module.get(webhook_service_1.WebhookService);
        webhookRepo = module.get((0, typeorm_1.getRepositoryToken)(entities_1.WebhookEntity));
        deliveryRepo = module.get((0, typeorm_1.getRepositoryToken)(entities_1.WebhookDeliveryEntity));
        webhookQueue = module.get((0, bullmq_1.getQueueToken)('webhooks'));
    });
    afterEach(() => {
        jest.clearAllMocks();
    });
    const mockStatsQueryBuilder = () => {
        const qb = {
            select: jest.fn().mockReturnThis(),
            where: jest.fn().mockReturnThis(),
            setParameters: jest.fn().mockReturnThis(),
            getRawOne: jest.fn().mockResolvedValue({
                totalDeliveries: 10,
                successfulDeliveries: 8,
                failedDeliveries: 2,
                pendingDeliveries: 0,
                lastDeliveryAt: new Date(),
            }),
        };
        deliveryRepo.createQueryBuilder.mockReturnValue(qb);
        return qb;
    };
    describe('create', () => {
        it('should create webhook successfully', async () => {
            webhookRepo.create.mockReturnValue(mockWebhook);
            webhookRepo.save.mockResolvedValue(mockWebhook);
            mockStatsQueryBuilder();
            const dto = {
                name: 'Test Webhook',
                url: 'https://example.com/webhook',
                events: ['user.created', 'user.updated'],
            };
            const result = await service.create(mockTenantId, mockUserId, dto);
            expect(result).toHaveProperty('id');
            expect(result).toHaveProperty('secret');
            expect(webhookRepo.create).toHaveBeenCalled();
            expect(webhookRepo.save).toHaveBeenCalled();
        });
        it('should throw for invalid events', async () => {
            const dto = {
                name: 'Test Webhook',
                url: 'https://example.com/webhook',
                events: ['invalid.event'],
            };
            await expect(service.create(mockTenantId, mockUserId, dto)).rejects.toThrow(common_1.BadRequestException);
        });
    });
    describe('findAll', () => {
        it('should return all webhooks for tenant', async () => {
            webhookRepo.find.mockResolvedValue([mockWebhook]);
            mockStatsQueryBuilder();
            const result = await service.findAll(mockTenantId);
            expect(result).toHaveLength(1);
            expect(webhookRepo.find).toHaveBeenCalledWith({
                where: { tenantId: mockTenantId },
                order: { createdAt: 'DESC' },
            });
        });
        it('should return empty array when no webhooks', async () => {
            webhookRepo.find.mockResolvedValue([]);
            const result = await service.findAll(mockTenantId);
            expect(result).toHaveLength(0);
        });
    });
    describe('findOne', () => {
        it('should return webhook by id', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            mockStatsQueryBuilder();
            const result = await service.findOne(mockTenantId, 'webhook-001');
            expect(result).toHaveProperty('id', 'webhook-001');
            expect(result).not.toHaveProperty('secret');
        });
        it('should throw when webhook not found', async () => {
            webhookRepo.findOne.mockResolvedValue(null);
            await expect(service.findOne(mockTenantId, 'invalid')).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('update', () => {
        it('should update webhook successfully', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            webhookRepo.save.mockResolvedValue({
                ...mockWebhook,
                name: 'Updated Webhook',
            });
            mockStatsQueryBuilder();
            const result = await service.update(mockTenantId, 'webhook-001', {
                name: 'Updated Webhook',
            });
            expect(result.name).toBe('Updated Webhook');
        });
        it('should validate events on update', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            await expect(service.update(mockTenantId, 'webhook-001', {
                events: ['invalid.event'],
            })).rejects.toThrow(common_1.BadRequestException);
        });
        it('should throw when webhook not found', async () => {
            webhookRepo.findOne.mockResolvedValue(null);
            await expect(service.update(mockTenantId, 'invalid', { name: 'New' })).rejects.toThrow(common_1.NotFoundException);
        });
        it('should update isActive status', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            webhookRepo.save.mockResolvedValue({
                ...mockWebhook,
                isActive: false,
            });
            mockStatsQueryBuilder();
            const result = await service.update(mockTenantId, 'webhook-001', {
                isActive: false,
            });
            expect(result.isActive).toBe(false);
        });
    });
    describe('remove', () => {
        it('should remove webhook successfully', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            webhookRepo.remove.mockResolvedValue(mockWebhook);
            await service.remove(mockTenantId, 'webhook-001');
            expect(webhookRepo.remove).toHaveBeenCalledWith(mockWebhook);
        });
        it('should throw when webhook not found', async () => {
            webhookRepo.findOne.mockResolvedValue(null);
            await expect(service.remove(mockTenantId, 'invalid')).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('regenerateSecret', () => {
        it('should regenerate secret successfully', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            webhookRepo.save.mockResolvedValue({
                ...mockWebhook,
                secret: 'whsec_newsecret123',
            });
            const result = await service.regenerateSecret(mockTenantId, 'webhook-001');
            expect(result).toHaveProperty('secret');
            expect(result.secret).toMatch(/^whsec_/);
        });
        it('should throw when webhook not found', async () => {
            webhookRepo.findOne.mockResolvedValue(null);
            await expect(service.regenerateSecret(mockTenantId, 'invalid')).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('testWebhook', () => {
        it('should queue test delivery', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            deliveryRepo.create.mockReturnValue(mockDelivery);
            deliveryRepo.save.mockResolvedValue(mockDelivery);
            webhookQueue.add.mockResolvedValue({});
            const result = await service.testWebhook(mockTenantId, 'webhook-001', {});
            expect(result).toHaveProperty('id');
            expect(result.status).toBe(entities_1.DeliveryStatus.PENDING);
            expect(webhookQueue.add).toHaveBeenCalledWith('deliver', expect.any(Object), { priority: 1 });
        });
        it('should use custom payload', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            deliveryRepo.create.mockReturnValue(mockDelivery);
            deliveryRepo.save.mockResolvedValue(mockDelivery);
            webhookQueue.add.mockResolvedValue({});
            await service.testWebhook(mockTenantId, 'webhook-001', {
                eventType: 'custom.event',
                payload: { custom: 'data' },
            });
            expect(deliveryRepo.create).toHaveBeenCalledWith(expect.objectContaining({
                eventType: 'custom.event',
            }));
        });
    });
    describe('getDeliveries', () => {
        it('should return paginated deliveries', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            const qb = {
                where: jest.fn().mockReturnThis(),
                andWhere: jest.fn().mockReturnThis(),
                orderBy: jest.fn().mockReturnThis(),
                skip: jest.fn().mockReturnThis(),
                take: jest.fn().mockReturnThis(),
                getManyAndCount: jest
                    .fn()
                    .mockResolvedValue([[mockDelivery], 1]),
            };
            deliveryRepo.createQueryBuilder.mockReturnValue(qb);
            const result = await service.getDeliveries(mockTenantId, 'webhook-001', {});
            expect(result.items).toHaveLength(1);
            expect(result.total).toBe(1);
        });
        it('should filter by status', async () => {
            webhookRepo.findOne.mockResolvedValue(mockWebhook);
            const qb = {
                where: jest.fn().mockReturnThis(),
                andWhere: jest.fn().mockReturnThis(),
                orderBy: jest.fn().mockReturnThis(),
                skip: jest.fn().mockReturnThis(),
                take: jest.fn().mockReturnThis(),
                getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
            };
            deliveryRepo.createQueryBuilder.mockReturnValue(qb);
            await service.getDeliveries(mockTenantId, 'webhook-001', {
                status: entities_1.DeliveryStatus.FAILED,
            });
            expect(qb.andWhere).toHaveBeenCalledWith('d.status = :status', {
                status: entities_1.DeliveryStatus.FAILED,
            });
        });
        it('should throw when webhook not found', async () => {
            webhookRepo.findOne.mockResolvedValue(null);
            await expect(service.getDeliveries(mockTenantId, 'invalid', {})).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('retryDelivery', () => {
        it('should retry failed delivery', async () => {
            const failedDelivery = {
                ...mockDelivery,
                status: entities_1.DeliveryStatus.FAILED,
                webhook: mockWebhook,
            };
            deliveryRepo.findOne.mockResolvedValue(failedDelivery);
            deliveryRepo.save.mockResolvedValue({
                ...failedDelivery,
                status: entities_1.DeliveryStatus.RETRYING,
            });
            webhookQueue.add.mockResolvedValue({});
            const result = await service.retryDelivery(mockTenantId, 'webhook-001', 'delivery-001');
            expect(result.status).toBe(entities_1.DeliveryStatus.RETRYING);
            expect(webhookQueue.add).toHaveBeenCalled();
        });
        it('should throw for non-failed delivery', async () => {
            deliveryRepo.findOne.mockResolvedValue({
                ...mockDelivery,
                status: entities_1.DeliveryStatus.DELIVERED,
            });
            await expect(service.retryDelivery(mockTenantId, 'webhook-001', 'delivery-001')).rejects.toThrow(common_1.BadRequestException);
        });
        it('should throw when delivery not found', async () => {
            deliveryRepo.findOne.mockResolvedValue(null);
            await expect(service.retryDelivery(mockTenantId, 'webhook-001', 'invalid')).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('getStats', () => {
        it('should return webhook statistics', async () => {
            mockStatsQueryBuilder();
            const result = await service.getStats('webhook-001');
            expect(result).toHaveProperty('totalDeliveries', 10);
            expect(result).toHaveProperty('successfulDeliveries', 8);
            expect(result).toHaveProperty('failedDeliveries', 2);
            expect(result).toHaveProperty('successRate', 80);
        });
    });
    describe('dispatch', () => {
        it('should dispatch event to subscribed webhooks', async () => {
            webhookRepo.find.mockResolvedValue([mockWebhook]);
            deliveryRepo.create.mockReturnValue(mockDelivery);
            deliveryRepo.save.mockResolvedValue(mockDelivery);
            webhookQueue.add.mockResolvedValue({});
            await service.dispatch(mockTenantId, 'user.created', { id: 'user-001' });
            expect(webhookQueue.add).toHaveBeenCalled();
            expect(deliveryRepo.save).toHaveBeenCalled();
        });
        it('should not dispatch for unsubscribed events', async () => {
            webhookRepo.find.mockResolvedValue([mockWebhook]);
            await service.dispatch(mockTenantId, 'invoice.paid', { id: 'inv-001' });
            expect(webhookQueue.add).not.toHaveBeenCalled();
        });
        it('should skip inactive webhooks', async () => {
            webhookRepo.find.mockResolvedValue([]);
            await service.dispatch(mockTenantId, 'user.created', { id: 'user-001' });
            expect(webhookQueue.add).not.toHaveBeenCalled();
        });
    });
    describe('getAvailableEvents', () => {
        it('should return list of available events', () => {
            const events = service.getAvailableEvents();
            expect(events).toBeInstanceOf(Array);
            expect(events.length).toBeGreaterThan(0);
            expect(events[0]).toHaveProperty('name');
            expect(events[0]).toHaveProperty('description');
        });
    });
    describe('signPayload', () => {
        it('should sign payload correctly', () => {
            const payload = { test: 'data' };
            const secret = 'whsec_testsecret';
            const signature = service.signPayload(payload, secret);
            expect(signature).toMatch(/^t=\d+,v1=[a-f0-9]+$/);
        });
    });
});
//# sourceMappingURL=webhook.service.spec.js.map