/* eslint-disable @typescript-eslint/unbound-method */
import { Nango } from '@nangohq/node';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { mockErrorManagerReport } from '../utils/error.manager.mocks.js';
import { AuthModes } from '../models/index.js';
import configService from '../services/config.service.js';
import { NangoAction } from './sync.js';
import { isValidHttpUrl } from '../utils/utils.js';
import proxyService from '../services/proxy.service.js';
describe('cache', () => {
    let nangoAction;
    let nango;
    beforeEach(async () => {
        nangoAction = new NangoAction({
            secretKey: '***',
            providerConfigKey: 'github',
            connectionId: 'connection-1'
        });
        nango = new Nango({ secretKey: '***' });
        const nodeClient = (await import('@nangohq/node')).Nango;
        nodeClient.prototype.getConnection = vi.fn().mockReturnValue({ credentials: {} });
        nodeClient.prototype.setMetadata = vi.fn().mockReturnValue({});
        nodeClient.prototype.getIntegration = vi.fn().mockReturnValue({ config: { provider: 'github' } });
        vi.spyOn(proxyService, 'route').mockImplementation(() => Promise.resolve({ response: {}, activityLogs: [] }));
    });
    afterEach(() => {
        vi.clearAllMocks();
    });
    describe('Proxy', () => {
        it('memoizes connection', async () => {
            await nangoAction.proxy({ endpoint: '/issues' });
            await nangoAction.proxy({ endpoint: '/issues' });
            expect(nango.getConnection).toHaveBeenCalledTimes(1);
        });
        it('get connection if memoized connection is too old', async () => {
            await nangoAction.proxy({ endpoint: '/issues' });
            const later = Date.now() + 61000;
            vi.spyOn(Date, 'now').mockReturnValue(later);
            await nangoAction.proxy({ endpoint: '/issues' });
            expect(nango.getConnection).toHaveBeenCalledTimes(2);
        });
    });
    describe('Metadata', () => {
        it('getMetadata should reuse connection', async () => {
            await nangoAction.getConnection();
            await nangoAction.getMetadata();
            expect(nango.getConnection).toHaveBeenCalledTimes(1);
        });
        it('setMetadata should invalidate connection', async () => {
            await nangoAction.getConnection();
            await nangoAction.setMetadata({});
            await nangoAction.getConnection();
            await nangoAction.getMetadata();
            expect(nango.getConnection).toHaveBeenCalledTimes(2);
        });
    });
    describe('Integration', () => {
        it('getWebhookURL should reuse integration', async () => {
            await nangoAction.getWebhookURL();
            await nangoAction.getWebhookURL();
            expect(nango.getIntegration).toHaveBeenCalledTimes(1);
        });
    });
});
describe('Pagination', () => {
    const providerConfigKey = 'github';
    const connectionId = 'connection-1';
    const cursorPagination = {
        type: 'cursor',
        cursor_path_in_response: 'metadata.next_cursor',
        cursor_name_in_request: 'cursor',
        limit_name_in_request: 'limit',
        response_path: 'issues'
    };
    const offsetPagination = {
        type: 'offset',
        limit_name_in_request: 'per_page',
        offset_name_in_request: 'offset',
        response_path: 'issues'
    };
    const linkPagination = {
        type: 'link',
        response_path: 'issues',
        limit_name_in_request: 'limit',
        link_path_in_response_body: 'metadata.next_cursor'
    };
    const paginationConfigs = [cursorPagination, offsetPagination, linkPagination];
    let nangoAction;
    let nango;
    beforeEach(() => {
        const config = {
            secretKey: 'encrypted',
            serverUrl: 'https://example.com',
            providerConfigKey,
            connectionId,
            dryRun: true
        };
        nangoAction = new NangoAction(config);
        nango = new Nango({ secretKey: config.secretKey });
    });
    afterEach(() => {
        vi.clearAllMocks();
    });
    it('Throws error if there is no pagination config in provider template', async () => {
        const template = {
            auth_mode: AuthModes.OAuth2,
            proxy: { base_url: '' },
            authorization_url: '',
            token_url: ''
        };
        (await import('@nangohq/node')).Nango.prototype.getIntegration = vi.fn().mockReturnValue({ config: { provider: 'github' } });
        vi.spyOn(configService, 'getTemplate').mockImplementation(() => template);
        const expectedErrorMessage = 'There was no pagination configuration for this integration or configuration passed in';
        await expect(() => nangoAction.paginate({ endpoint: '' }).next()).rejects.toThrowError(expectedErrorMessage);
    });
    it('Sends pagination params in body for POST HTTP method', async () => {
        stubProviderTemplate(cursorPagination);
        mockErrorManagerReport();
        vi.spyOn(configService, 'getProviderConfig').mockImplementation(() => {
            return Promise.resolve({});
        });
        // TODO: mock to return at least one more page to check that cursor is passed in body too
        (await import('@nangohq/node')).Nango.prototype.proxy = vi.fn().mockReturnValue({ data: { issues: [] } });
        (await import('@nangohq/node')).Nango.prototype.getIntegration = vi.fn().mockReturnValue({ config: { provider: 'github' } });
        (await import('@nangohq/node')).Nango.prototype.getConnection = vi.fn().mockReturnValue({ credentials: {} });
        const endpoint = '/issues';
        await nangoAction.paginate({ endpoint, method: 'POST', paginate: { limit: 2 }, connectionId: 'abc' }).next();
        expect(nango.proxy).toHaveBeenCalledWith({
            method: 'POST',
            endpoint,
            headers: {
                'user-agent': expect.any(String)
            },
            data: { limit: 2 },
            paginate: { limit: 2 },
            connectionId: 'abc',
            providerConfigKey: 'github'
        });
    });
    it('Overrides template pagination params with ones passed in the proxy config', async () => {
        stubProviderTemplate(cursorPagination);
        (await import('@nangohq/node')).Nango.prototype.proxy = vi
            .fn()
            .mockReturnValueOnce({ data: { issues: [{}, {}, {}] } })
            .mockReturnValueOnce({ data: { issues: [] } });
        const endpoint = '/issues';
        const paginationConfigOverride = {
            type: 'offset',
            limit_name_in_request: 'per_page',
            limit: 3,
            offset_name_in_request: 'offset',
            response_path: 'issues'
        };
        const generator = nangoAction.paginate({ endpoint, paginate: paginationConfigOverride });
        for await (const batch of generator) {
            expect(batch.length).toBe(3);
        }
        expect(nango.proxy).toHaveBeenLastCalledWith({
            method: 'GET',
            endpoint,
            headers: {
                'user-agent': expect.any(String)
            },
            params: { offset: '3', per_page: 3 },
            paginate: paginationConfigOverride,
            providerConfigKey,
            connectionId
        });
    });
    it('Paginates using offset', async () => {
        stubProviderTemplate(offsetPagination);
        const firstBatch = [{ id: 1 }, { id: 2 }, { id: 3 }];
        const secondBatch = [{ id: 4 }, { id: 5 }, { id: 6 }];
        (await import('@nangohq/node')).Nango.prototype.proxy = vi
            .fn()
            .mockReturnValueOnce({ data: { issues: firstBatch } })
            .mockReturnValueOnce({ data: { issues: secondBatch } })
            .mockReturnValueOnce({ data: { issues: [] } });
        const endpoint = '/issues';
        const generator = nangoAction.paginate({ endpoint });
        const actualRecords = [];
        for await (const batch of generator) {
            actualRecords.push(...batch);
        }
        const expectedRecords = [...firstBatch, ...secondBatch];
        expect(actualRecords).toStrictEqual(expectedRecords);
    });
    it('Paginates using cursor', async () => {
        stubProviderTemplate(cursorPagination);
        const firstBatch = [{ id: 1 }, { id: 2 }, { id: 3 }];
        const secondBatch = [{ id: 4 }, { id: 5 }, { id: 6 }];
        const thirdBatch = [{ id: 7 }, { id: 8 }, { id: 9 }];
        (await import('@nangohq/node')).Nango.prototype.proxy = vi
            .fn()
            .mockReturnValueOnce({
            data: {
                issues: firstBatch,
                metadata: {
                    next_cursor: '2'
                }
            }
        })
            .mockReturnValueOnce({
            data: {
                issues: secondBatch,
                metadata: {
                    next_cursor: '2'
                }
            }
        })
            .mockReturnValueOnce({ data: { issues: thirdBatch } });
        const endpoint = '/issues';
        const generator = nangoAction.paginate({ endpoint });
        const actualRecords = [];
        for await (const batch of generator) {
            actualRecords.push(...batch);
        }
        const expectedRecords = [...firstBatch, ...secondBatch, ...thirdBatch];
        expect(actualRecords).toStrictEqual(expectedRecords);
    });
    it('Stops pagination if cursor is empty', async () => {
        stubProviderTemplate(cursorPagination);
        const onlyBatch = [{ id: 1 }, { id: 2 }, { id: 3 }];
        (await import('@nangohq/node')).Nango.prototype.proxy = vi.fn().mockReturnValueOnce({
            data: {
                issues: onlyBatch,
                metadata: {
                    next_cursor: ''
                }
            }
        });
        const endpoint = '/issues';
        const generator = nangoAction.paginate({ endpoint });
        const actualRecords = [];
        for await (const batch of generator) {
            actualRecords.push(...batch);
        }
        expect(actualRecords).toStrictEqual(onlyBatch);
    });
    it.each(paginationConfigs)('Extracts records from nested body param for $type pagination type', async (paginationConfig) => {
        stubProviderTemplate(paginationConfig);
        const firstBatch = [{ id: 1 }, { id: 2 }, { id: 3 }];
        const emptyBatch = [];
        (await import('@nangohq/node')).Nango.prototype.proxy = vi
            .fn()
            .mockReturnValueOnce({
            data: {
                issues: firstBatch,
                metadata: {
                    next_cursor: ''
                }
            }
        })
            .mockReturnValueOnce({
            data: {
                issues: emptyBatch,
                metadata: {
                    next_cursor: ''
                }
            }
        });
        const endpoint = '/issues';
        const generator = nangoAction.paginate({ endpoint });
        const actualRecords = [];
        for await (const batch of generator) {
            actualRecords.push(...batch);
        }
        expect(actualRecords).toStrictEqual(firstBatch);
    });
    it.each([
        // TODO: validate proper config is passed to proxy
        ['https://api.gihub.com/issues?page=2', 'https://api.gihub.com/issues?page=3'],
        ['/issues?page=2', '/issues?page=3']
    ])('Paginates using next URL/path %s from body', async (nextUrlOrPathValue, anotherNextUrlOrPathValue) => {
        stubProviderTemplate(linkPagination);
        const firstBatch = [{ id: 1 }, { id: 2 }, { id: 3 }];
        const secondBatch = [{ id: 4 }, { id: 5 }, { id: 6 }];
        const thirdBatch = [{ id: 7 }, { id: 8 }, { id: 9 }];
        (await import('@nangohq/node')).Nango.prototype.proxy = vi
            .fn()
            .mockReturnValueOnce({
            data: {
                issues: firstBatch,
                metadata: {
                    next_cursor: nextUrlOrPathValue
                }
            }
        })
            .mockReturnValueOnce({
            data: {
                issues: secondBatch,
                metadata: {
                    next_cursor: anotherNextUrlOrPathValue
                }
            }
        })
            .mockReturnValueOnce({ data: { issues: thirdBatch } });
        const endpoint = '/issues';
        const generator = nangoAction.paginate({ endpoint });
        const actualRecords = [];
        for await (const batch of generator) {
            actualRecords.push(...batch);
        }
        const expectedRecords = [...firstBatch, ...secondBatch, ...thirdBatch];
        let expectedEndpoint;
        if (isValidHttpUrl(anotherNextUrlOrPathValue)) {
            const url = new URL(anotherNextUrlOrPathValue);
            expectedEndpoint = url.pathname + url.search;
        }
        else {
            expectedEndpoint = anotherNextUrlOrPathValue;
        }
        expect(actualRecords).toStrictEqual(expectedRecords);
        expect(nango.proxy).toHaveBeenCalledWith(expect.objectContaining({
            endpoint: expectedEndpoint
        }));
    });
    const stubProviderTemplate = (paginationConfig) => {
        const template = buildTemplate(paginationConfig);
        vi.spyOn(configService, 'getTemplate').mockImplementation(() => template);
    };
    const buildTemplate = (paginationConfig) => {
        return {
            auth_mode: AuthModes.OAuth2,
            proxy: { base_url: 'https://api.github.com/', paginate: paginationConfig },
            authorization_url: '',
            token_url: ''
        };
    };
});
describe('Log', () => {
    it('should not log if no activityLogId', async () => {
        const nangoAction = new NangoAction({
            secretKey: '***',
            providerConfigKey: 'github',
            connectionId: 'connection-1'
        });
        await expect(async () => {
            await nangoAction.log('top');
        }).rejects.toThrowError(new Error('There is no current activity log stream to log to'));
    });
    it('should not fail on null', async () => {
        const nangoAction = new NangoAction({
            secretKey: '***',
            providerConfigKey: 'github',
            connectionId: 'connection-1',
            dryRun: true,
            activityLogId: 1
        });
        await nangoAction.log(null);
    });
});
//# sourceMappingURL=sync.unit.test.js.map