"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const sinon = __importStar(require("sinon"));
const crypto_rpc_1 = require("@bitpay-labs/crypto-rpc");
const csp_1 = require("../../../../src/modules/moralis/api/csp");
const csp_2 = require("../../../../src/providers/chain-state/evm/api/csp");
const config_1 = require("../../../../src/services/config");
describe('BASE Chain State Provider', function () {
    const sandbox = sinon.createSandbox();
    beforeEach(() => {
        sandbox.stub(config_1.Config, 'get').returns({
            chains: {
                BASE: {
                    mainnet: {
                        chainSource: 'external',
                        module: './moralis',
                        needsL1Fee: true,
                        providers: [{
                                dataType: 'combined',
                                host: 'sample.example',
                                port: '1234',
                                protocol: 'http',
                            }, {
                                dataType: 'realtime',
                                host: 'sample.example',
                                port: '1234',
                                protocol: 'http',
                            }]
                    },
                    sepolia: {
                        chainSource: 'external',
                        module: './moralis',
                        needsL1Fee: true,
                        providers: [{
                                dataType: 'combined',
                                host: 'sample.example',
                                port: '1234',
                                protocol: 'ws',
                            }]
                    }
                }
            }
        });
    });
    afterEach(() => {
        csp_2.BaseEVMStateProvider.teardownRpcs();
        sandbox.restore();
    });
    describe('constructor', () => {
        it('should call initializeRpcs', function () {
            sandbox.stub(csp_2.BaseEVMStateProvider, 'initializeRpcs');
            new csp_2.BaseEVMStateProvider('BASE');
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.initializeRpcs.callCount).to.eq(1);
        });
    });
    describe('initializeRpcs', () => {
        beforeEach(function () {
            // Clear any existing RPCs before each test
            csp_2.BaseEVMStateProvider.rpcs = {};
            csp_2.BaseEVMStateProvider.rpcInitialized = {};
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcInitialized['BASE']).to.not.exist;
        });
        it('should initialize RPCs for BASE', function () {
            csp_2.BaseEVMStateProvider.initializeRpcs('BASE');
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:mainnet']).to.exist;
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:mainnet'].realtime.length).to.equal(2); // realtime + combined
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:mainnet'].historical.length).to.equal(1); // only combined
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies['BASE:mainnet']).to.deep.equal({ realtime: 0, historical: 0 });
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia']).to.exist;
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].realtime.length).to.equal(1); // combined
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].historical.length).to.equal(1); // combined
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies['BASE:sepolia']).to.deep.equal({ realtime: 0, historical: 0 });
            // 'combined' dataType will put the same object in both
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].realtime[0]).to.equal(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].historical[0]);
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcInitialized['BASE']).to.be.true;
        });
        it('should not re-initialize RPCs for BASE if already initialized', function () {
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcInitialized['BASE']).to.not.exist;
            sandbox.spy(crypto_rpc_1.CryptoRpc.prototype, 'get');
            csp_2.BaseEVMStateProvider.initializeRpcs('BASE');
            const existingRpcsMainnet = csp_2.BaseEVMStateProvider.rpcs['BASE:mainnet'];
            const existingRpcsSepolia = csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'];
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcInitialized['BASE']).to.be.true;
            (0, chai_1.expect)(crypto_rpc_1.CryptoRpc.prototype.get.callCount).to.equal(3);
            // Check for re-initialization
            crypto_rpc_1.CryptoRpc.prototype.get.resetHistory();
            csp_2.BaseEVMStateProvider.initializeRpcs('BASE');
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:mainnet']).to.equal(existingRpcsMainnet);
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia']).to.equal(existingRpcsSepolia);
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcInitialized['BASE']).to.be.true; // still true
            (0, chai_1.expect)(crypto_rpc_1.CryptoRpc.prototype.get.callCount).to.equal(0); // no new calls
        });
        it('should only initialize RPCs for provided data types', function () {
            config_1.Config.get.restore();
            sandbox.stub(config_1.Config, 'get').returns({
                chains: {
                    BASE: {
                        sepolia: {
                            chainSource: 'external',
                            module: './moralis',
                            needsL1Fee: true,
                            providers: [{
                                    dataType: 'realtime', // no historical/combined
                                    host: 'sample.example',
                                    port: '1234',
                                    protocol: 'ws',
                                }]
                        }
                    }
                }
            });
            csp_2.BaseEVMStateProvider.initializeRpcs('BASE');
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia']).to.exist;
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].realtime.length).to.equal(1);
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].historical.length).to.equal(0);
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies['BASE:sepolia']).to.deep.equal({ realtime: 0, historical: 0 });
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcInitialized['BASE']).to.be.true;
        });
    });
    describe('teardownRpcs', () => {
        it('should teardown RPCs for BASE', function () {
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcInitialized['BASE']).to.not.exist;
            csp_2.BaseEVMStateProvider.initializeRpcs('BASE');
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcInitialized['BASE']).to.be.true;
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:mainnet']).to.exist;
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia']).to.exist;
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies).to.deep.equal({ 'BASE:mainnet': { realtime: 0, historical: 0 }, 'BASE:sepolia': { realtime: 0, historical: 0 } });
            csp_2.BaseEVMStateProvider.teardownRpcs();
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs).to.deep.equal({});
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies).to.deep.equal({});
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcInitialized['BASE']).to.not.exist;
        });
        it('should work idempotently', function () {
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs).to.deep.equal({});
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies).to.deep.equal({});
            csp_2.BaseEVMStateProvider.teardownRpcs();
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs).to.deep.equal({});
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies).to.deep.equal({});
        });
        it('should not error if historical array is missing', function () {
            // If no historical or combined providers are configured, the historical array will be missing
            config_1.Config.get.restore();
            sandbox.stub(config_1.Config, 'get').returns({
                chains: {
                    BASE: {
                        sepolia: {
                            chainSource: 'external',
                            module: './moralis',
                            needsL1Fee: true,
                            providers: [{
                                    dataType: 'realtime', // no historical/combined
                                    host: 'sample.example',
                                    port: '1234',
                                    protocol: 'ws',
                                }]
                        }
                    }
                }
            });
            csp_2.BaseEVMStateProvider.initializeRpcs('BASE');
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia']).to.exist;
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].realtime.length).to.equal(1);
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].historical.length).to.equal(0);
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies['BASE:sepolia']).to.deep.equal({ realtime: 0, historical: 0 });
            csp_2.BaseEVMStateProvider.teardownRpcs();
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs).to.deep.equal({});
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies).to.deep.equal({});
        });
        it('should not error if realtime array is missing', function () {
            // If no realtime or combined providers are configured, the realtime array will be missing
            config_1.Config.get.restore();
            sandbox.stub(config_1.Config, 'get').returns({
                chains: {
                    BASE: {
                        sepolia: {
                            chainSource: 'external',
                            module: './moralis',
                            needsL1Fee: true,
                            providers: [{
                                    dataType: 'historical', // no realtime/combined
                                    host: 'sample.example',
                                    port: '1234',
                                    protocol: 'ws',
                                }]
                        }
                    }
                }
            });
            csp_2.BaseEVMStateProvider.initializeRpcs('BASE');
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia']).to.exist;
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].realtime.length).to.equal(0);
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs['BASE:sepolia'].historical.length).to.equal(1);
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies['BASE:sepolia']).to.deep.equal({ realtime: 0, historical: 0 });
            csp_2.BaseEVMStateProvider.teardownRpcs();
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcs).to.deep.equal({});
            (0, chai_1.expect)(csp_2.BaseEVMStateProvider.rpcIndicies).to.deep.equal({});
        });
    });
    describe('getWeb3', () => {
        const network = 'sepolia';
        let BASE;
        before(() => {
            BASE = new csp_1.MoralisStateProvider('BASE');
        });
        beforeEach(function () {
            csp_2.BaseEVMStateProvider.initializeRpcs('BASE');
        });
        it('should be able to get web3 with only 1 realtime provider', async () => {
            const web3Stub = { eth: { getBlockNumber: sandbox.stub().resolves(1) } };
            sandbox.stub(csp_2.BaseEVMStateProvider, 'rpcs').value({ [`BASE:${network}`]: {
                    realtime: [{ web3: web3Stub, rpc: sandbox.stub(), dataType: 'combined' }]
                } });
            const { web3 } = await BASE.getWeb3(network);
            const block = await web3.eth.getBlockNumber();
            const stub = web3.eth.getBlockNumber;
            (0, chai_1.expect)(stub.callCount).to.eq(1); // doesn't do a test call with only 1 provider
            (0, chai_1.expect)(block).to.eq(1);
        });
        it('should be able to get web3 with multiple realtime provider', async () => {
            const web3Stub = { eth: { getBlockNumber: sandbox.stub().resolves(1) } };
            sandbox.stub(csp_2.BaseEVMStateProvider, 'rpcs').value({ [`BASE:${network}`]: {
                    realtime: [
                        { web3: web3Stub, rpc: sandbox.stub(), dataType: 'combined' },
                        { web3: web3Stub, rpc: sandbox.stub(), dataType: 'combined' }
                    ]
                } });
            const { web3 } = await BASE.getWeb3(network);
            const block = await web3.eth.getBlockNumber();
            const stub = web3.eth.getBlockNumber;
            (0, chai_1.expect)(stub.callCount).to.eq(2); // does a test call to select responsive provider
            (0, chai_1.expect)(block).to.eq(1);
        });
        it('should handle when last used index is last in array', async () => {
            const web3Stub = { eth: { getBlockNumber: sandbox.stub().resolves(1) } };
            sandbox.stub(csp_2.BaseEVMStateProvider, 'rpcs').value({ [`BASE:${network}`]: {
                    realtime: [
                        { web3: web3Stub, rpc: sandbox.stub(), dataType: 'combined', index: 0 },
                        { web3: web3Stub, rpc: sandbox.stub(), dataType: 'combined', index: 1 },
                    ]
                } });
            csp_2.BaseEVMStateProvider.rpcIndicies[`BASE:${network}`].realtime = 1; // set to last index
            const response = await BASE.getWeb3(network);
            (0, chai_1.expect)(response.index).to.eq(0); // should wrap around to index 0
            const block = await response.web3.eth.getBlockNumber();
            const stub = response.web3.eth.getBlockNumber;
            (0, chai_1.expect)(stub.callCount).to.eq(2); // does a test call to select responsive provider
            (0, chai_1.expect)(block).to.eq(1);
        });
        it('should round-robin multiple web3 providers', async () => {
            const web3Stub1 = { eth: { getBlockNumber: sandbox.stub().resolves(1) } };
            const web3Stub2 = { eth: { getBlockNumber: sandbox.stub().resolves(2) } };
            sandbox.stub(csp_2.BaseEVMStateProvider, 'rpcs').value({ [`BASE:${network}`]: {
                    realtime: [
                        { web3: web3Stub1, rpc: sandbox.stub(), dataType: 'combined' },
                        { web3: web3Stub2, rpc: sandbox.stub(), dataType: 'combined' }
                    ]
                } });
            let { web3 } = await BASE.getWeb3(network);
            (0, chai_1.expect)(web3).to.equal(web3Stub2); // index starts at index 1
            ({ web3 } = await BASE.getWeb3(network));
            (0, chai_1.expect)(web3).to.equal(web3Stub1);
            ({ web3 } = await BASE.getWeb3(network));
            (0, chai_1.expect)(web3).to.equal(web3Stub2);
        });
        it('should return web3 for provider dataType', async () => {
            const web3Stub1 = { eth: { getBlockNumber: sandbox.stub().resolves(1) } };
            const web3Stub2 = { eth: { getBlockNumber: sandbox.stub().resolves(2) } };
            sandbox.stub(csp_2.BaseEVMStateProvider, 'rpcs').value({ [`BASE:${network}`]: {
                    realtime: [{ web3: web3Stub1, rpc: sandbox.stub(), dataType: 'combined' }],
                    historical: [{ web3: web3Stub2, rpc: sandbox.stub(), dataType: 'combined' }]
                } });
            let { web3 } = await BASE.getWeb3(network, { type: 'realtime' });
            (0, chai_1.expect)(web3).to.equal(web3Stub1);
            ({ web3 } = await BASE.getWeb3(network, { type: 'historical' }));
            (0, chai_1.expect)(web3).to.equal(web3Stub2);
        });
        it('should return web3 for provider dataType with round-robin for each', async () => {
            const web3StubRealtime1 = { eth: { getBlockNumber: sandbox.stub().resolves(11) } };
            const web3StubRealtime2 = { eth: { getBlockNumber: sandbox.stub().resolves(12) } };
            const web3StubHistorical1 = { eth: { getBlockNumber: sandbox.stub().resolves(21) } };
            const web3StubHistorical2 = { eth: { getBlockNumber: sandbox.stub().resolves(22) } };
            const web3StubCombined = { eth: { getBlockNumber: sandbox.stub().resolves(31) } };
            sandbox.stub(csp_2.BaseEVMStateProvider, 'rpcs').value({ [`BASE:${network}`]: {
                    realtime: [{ web3: web3StubRealtime1, rpc: sandbox.stub(), dataType: 'realtime' }, { web3: web3StubRealtime2, rpc: sandbox.stub(), dataType: 'realtime' }, { web3: web3StubCombined, rpc: sandbox.stub(), dataType: 'combined' }],
                    historical: [{ web3: web3StubHistorical1, rpc: sandbox.stub(), dataType: 'historical' }, { web3: web3StubHistorical2, rpc: sandbox.stub(), dataType: 'historical' }, { web3: web3StubCombined, rpc: sandbox.stub(), dataType: 'combined' }]
                } });
            let { web3 } = await BASE.getWeb3(network, { type: 'realtime' });
            (0, chai_1.expect)(web3).to.equal(web3StubRealtime2); // index starts at index 1
            ({ web3 } = await BASE.getWeb3(network, { type: 'historical' }));
            (0, chai_1.expect)(web3).to.equal(web3StubHistorical2); // index starts at index 1
            ({ web3 } = await BASE.getWeb3(network, { type: 'historical' }));
            (0, chai_1.expect)(web3).to.equal(web3StubCombined); // index 2
            ({ web3 } = await BASE.getWeb3(network, { type: 'historical' }));
            (0, chai_1.expect)(web3).to.equal(web3StubHistorical1); // index 0
            ({ web3 } = await BASE.getWeb3(network, { type: 'realtime' }));
            (0, chai_1.expect)(web3).to.equal(web3StubCombined); // index 2
            ({ web3 } = await BASE.getWeb3(network, { type: 'realtime' }));
            (0, chai_1.expect)(web3).to.equal(web3StubRealtime1); // index 0
        });
    });
});
//# sourceMappingURL=csp.test.js.map