UNPKG

@substrate/api-sidecar

Version:

REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.

680 lines 33.7 kB
"use strict"; /* eslint-disable @typescript-eslint/require-await */ // Copyright 2017-2025 Parity Technologies (UK) Ltd. // This file is part of Substrate API Sidecar. // // Substrate API Sidecar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const http_errors_1 = require("http-errors"); const apiRegistry_1 = require("../../apiRegistry"); const sanitizeNumbers_1 = require("../../sanitize/sanitizeNumbers"); const registries_1 = require("../../test-helpers/registries"); const mock_1 = require("../test-helpers/mock"); const validators789629Hex_1 = require("../test-helpers/mock/data/validators789629Hex"); const stakingProgress789629_json_1 = __importDefault(require("../test-helpers/responses/pallets/stakingProgress789629.json")); const stakingProgressPostAhm_json_1 = __importDefault(require("../test-helpers/responses/pallets/stakingProgressPostAhm.json")); const stakingProgressUnappliedSlashes_json_1 = __importDefault(require("../test-helpers/responses/pallets/stakingProgressUnappliedSlashes.json")); const stakingProgressUnappliedSlashesPostAHM_json_1 = __importDefault(require("../test-helpers/responses/pallets/stakingProgressUnappliedSlashesPostAHM.json")); const PalletsStakingProgressService_1 = require("./PalletsStakingProgressService"); const epochIndexAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u64', 330)); const genesisSlotAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u64', 265084563)); const currentSlotAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u64', 265876724)); const currentIndexAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('SessionIndex', 330)); const timestampNowAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u64', 1703123456789)); const eraElectionStatusAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('ElectionStatus', { Close: null })); const validatorsAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Vec<ValidatorId>', validators789629Hex_1.validators789629Hex)); const forceEraAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Forcing', 'NotForcing')); const unappliedSlashesEntries = () => { return Promise.resolve([['5640', []]]); }; const validatorCountAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u32', 197)); const mockHistoricApi = { ...mock_1.defaultMockApi, consts: { babe: { epochDuration: registries_1.polkadotRegistry.createType('u64', 2400), }, staking: { electionLookAhead: registries_1.polkadotRegistry.createType('BlockNumber'), sessionsPerEra: registries_1.polkadotRegistry.createType('SessionIndex', 6), }, }, query: { babe: { currentSlot: currentSlotAt, epochIndex: epochIndexAt, genesisSlot: genesisSlotAt, }, session: { currentIndex: currentIndexAt, validators: validatorsAt, }, timestamp: { now: timestampNowAt, }, staking: { activeEra: mock_1.activeEraAt, eraElectionStatus: eraElectionStatusAt, erasStartSessionIndex: mock_1.erasStartSessionIndexAt, forceEra: forceEraAt, bondedEras: () => Promise.resolve([ [40, 276], [41, 282], [42, 288], [43, 294], [44, 300], [45, 306], [46, 312], [47, 318], [48, 324], [49, 330], ].map((el) => [registries_1.polkadotRegistry.createType('u32', el[0]), registries_1.polkadotRegistry.createType('u32', el[1])])), unappliedSlashes: { entries: unappliedSlashesEntries, }, validatorCount: validatorCountAt, }, }, }; const mockApi = { ...mock_1.defaultMockApi, at: (_hash) => mockHistoricApi, }; const unappliedSlashes = [ { validator: '5CD2Q2EnKaKvjWza3ufMxaXizBTTDgm9kPB3DCZ4VA9j7Ud6', own: '0', others: [['5GxDBrTuFgCAN49xrpRFWJiA969R2Ny5NnTa8cSPBh8hWHY9', '6902377436592']], reporters: [], payout: '345118871829', toJSON: function () { return { validator: this.validator, own: this.own, others: this.others.map(([account, amount]) => ({ account, amount })), reporters: this.reporters, payout: this.payout, }; }, }, ]; const unappliedSlashesEntriesUnappliedSlashes = () => { return Promise.resolve([['5640', unappliedSlashes]]); }; const mockHistoricApiUnappliedSlashes = { ...mock_1.defaultMockApi, consts: { babe: { epochDuration: registries_1.polkadotRegistry.createType('u64', 2400), }, staking: { electionLookAhead: registries_1.polkadotRegistry.createType('BlockNumber'), sessionsPerEra: registries_1.polkadotRegistry.createType('SessionIndex', 6), }, }, query: { babe: { currentSlot: currentSlotAt, epochIndex: epochIndexAt, genesisSlot: genesisSlotAt, }, session: { currentIndex: currentIndexAt, validators: validatorsAt, }, timestamp: { now: timestampNowAt, }, staking: { activeEra: mock_1.activeEraAt, eraElectionStatus: eraElectionStatusAt, erasStartSessionIndex: mock_1.erasStartSessionIndexAt, forceEra: forceEraAt, bondedEras: () => Promise.resolve([ [40, 276], [41, 282], [42, 288], [43, 294], [44, 300], [45, 306], [46, 312], [47, 318], [48, 324], [49, 330], ].map((el) => [registries_1.polkadotRegistry.createType('u32', el[0]), registries_1.polkadotRegistry.createType('u32', el[1])])), unappliedSlashes: { entries: unappliedSlashesEntriesUnappliedSlashes, }, validatorCount: validatorCountAt, }, }, }; const mockApiUnappliedSlashes = { ...{ ...mockHistoricApiUnappliedSlashes, query: { ...mockHistoricApiUnappliedSlashes.query, staking: { ...mockHistoricApiUnappliedSlashes.query.staking, unappliedSlashes: { entries: unappliedSlashesEntriesUnappliedSlashes, }, }, }, }, at: (_hash) => ({ ...mockHistoricApiUnappliedSlashes, query: { ...mockHistoricApiUnappliedSlashes.query, staking: { ...mockHistoricApiUnappliedSlashes.query.staking, unappliedSlashes: { entries: unappliedSlashesEntriesUnappliedSlashes, }, }, }, }), }; const mockRCNextApi = { ...mock_1.defaultMockApi, consts: { ...mock_1.defaultMockApi.consts, babe: { epochDuration: registries_1.polkadotRegistry.createType('u64', 2400), }, }, query: { ...mock_1.defaultMockApi.query, babe: { currentSlot: currentSlotAt, epochIndex: epochIndexAt, genesisSlot: genesisSlotAt, skippedEpochs: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Vec<(u64, u32)>', [])), }, staking: undefined, session: { currentIndex: currentIndexAt, validators: validatorsAt, }, }, at: (_hash) => ({ ...mockHistoricApi, consts: { ...mockHistoricApi.consts, babe: { epochDuration: registries_1.polkadotRegistry.createType('u64', 2400), }, }, query: { ...mockHistoricApi.query, babe: { currentSlot: currentSlotAt, epochIndex: epochIndexAt, genesisSlot: genesisSlotAt, skippedEpochs: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Vec<(u64, u32)>', [])), }, staking: undefined, session: { currentIndex: currentIndexAt, validators: validatorsAt, }, }, }), }; const mockAHHistoricApi = { ...mockHistoricApi, consts: { ...mockHistoricApi.consts, babe: null, staking: { electionLookAhead: registries_1.polkadotRegistry.createType('BlockNumber'), sessionsPerEra: registries_1.polkadotRegistry.createType('SessionIndex', 6), }, }, query: { ...mockHistoricApi.query, session: null, timestamp: { now: timestampNowAt, }, staking: { activeEra: mock_1.activeEraAt, eraElectionStatus: eraElectionStatusAt, erasStartSessionIndex: mock_1.erasStartSessionIndexAt, forceEra: forceEraAt, bondedEras: () => Promise.resolve([ [40, 276], [41, 282], [42, 288], [43, 294], [44, 300], [45, 306], [46, 312], [47, 318], [48, 324], [49, 330], ].map((el) => [registries_1.polkadotRegistry.createType('u32', el[0]), registries_1.polkadotRegistry.createType('u32', el[1])])), unappliedSlashes: { entries: unappliedSlashesEntries, }, validatorCount: validatorCountAt, }, }, }; const mockAHNextApi = { ...mock_1.defaultMockApi, consts: { ...mockHistoricApi.consts, babe: null, }, query: { ...mock_1.defaultMockApi.query, session: null, timestamp: { now: timestampNowAt, }, staking: { activeEra: mock_1.activeEraAt, eraElectionStatus: eraElectionStatusAt, erasStartSessionIndex: mock_1.erasStartSessionIndexAt, forceEra: forceEraAt, unappliedSlashes: { entries: unappliedSlashesEntries, }, validatorCount: validatorCountAt, }, }, at: (_hash) => mockAHHistoricApi, }; // Helper functions for creating mock data function mockSkippedEpochs(epochs) { return registries_1.polkadotRegistry.createType('Vec<(u64, u32)>', epochs.map(([epoch, session]) => [ registries_1.polkadotRegistry.createType('u64', epoch), registries_1.polkadotRegistry.createType('u32', session), ])); } function createMockAssetHubHistoricApiWithNoneActiveEra() { return { ...mockAHHistoricApi, query: { ...mockAHHistoricApi.query, staking: { ...mockAHHistoricApi.query.staking, activeEra: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<ActiveEraInfo>', null)), }, }, }; } function createMockAssetHubHistoricApiWithNoneEraStartSessionIndex() { return { ...mockAHHistoricApi, query: { ...mockAHHistoricApi.query, staking: { ...mockAHHistoricApi.query.staking, bondedEras: () => Promise.resolve().then(() => []), }, }, }; } function createMockRelayChainApiWithSkippedEpochs(skippedEpochs) { return { ...mockRCNextApi, query: { ...mockRCNextApi.query, babe: { ...mockRCNextApi.query.babe, skippedEpochs: () => Promise.resolve().then(() => mockSkippedEpochs(skippedEpochs)), }, }, at: (_hash) => ({ ...mockRCNextApi, query: { ...mockRCNextApi.query, babe: { ...mockRCNextApi.query.babe, skippedEpochs: () => Promise.resolve().then(() => mockSkippedEpochs(skippedEpochs)), }, }, }), }; } describe('PalletStakingProgressService', () => { beforeAll(() => { jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApi); }); beforeEach(() => { // Reset all mocks before each test to prevent interference jest.clearAllMocks(); // Reset ApiPromiseRegistry mocks to default state jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApi); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => []); // Reset assetHubInfo to default state apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: false, isAssetHubMigrated: false, }; }); afterEach(() => { // Clean up after each test jest.restoreAllMocks(); }); describe('derivePalletStakingProgress before AHM', () => { mockHistoricApi.query.session.validators = validatorsAt; it('works when ApiPromise works (block 789629)', async () => { /** * Mock PalletStakingProgressService instance. */ const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot'); expect((0, sanitizeNumbers_1.sanitizeNumbers)(await palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629))).toStrictEqual(stakingProgress789629_json_1.default); }); it('throws when ErasStartSessionIndex.isNone', async () => { mockHistoricApi.query.staking.bondedEras = () => Promise.resolve([]); mockHistoricApi.query.staking.erasStartSessionIndex = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<SessionIndex>', null)); /** * Mock PalletStakingProgressService instance. */ const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot'); await expect(palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toStrictEqual(new http_errors_1.InternalServerError('EraStartSessionIndex is None when Some was expected.')); mockHistoricApi.query.staking.erasStartSessionIndex = mock_1.erasStartSessionIndexAt; }); it('throws when activeEra.isNone', async () => { mockHistoricApi.query.staking.activeEra = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<ActiveEraInfo>', null)); /** * Mock PalletStakingProgressService instance. */ const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot'); await expect(palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toStrictEqual(new http_errors_1.InternalServerError('ActiveEra is None when Some was expected.')); mockHistoricApi.query.staking.activeEra = mock_1.activeEraAt; mockHistoricApi.query.session.validators = validatorsAt; }); it('works with entries in unappliedSlashes', async () => { /** * Mock PalletStakingProgressService instance. */ const palletStakingProgressServiceUnappliedSlashes = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiUnappliedSlashes); expect((0, sanitizeNumbers_1.sanitizeNumbers)(await palletStakingProgressServiceUnappliedSlashes.derivePalletStakingProgress(mock_1.blockHash789629))).toStrictEqual(stakingProgressUnappliedSlashes_json_1.default); }); }); describe('derivePalletStakingProgress after AHM', () => { beforeEach(() => { // Set up Asset Hub state for these tests apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; }); it('it throws if historicApi does not have staking', async () => { const PalletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockRCNextApi); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'polkadot', api: mockRCNextApi, }, ]; }); await expect(PalletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toThrow('Staking pallet not found for queried runtime'); }); it('it throws if sidecar is connected to AH and querying historical block', async () => { const PalletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); // Create a mock that throws an error for historical blocks const mockApiThatThrowsForHistorical = { ...mockAHNextApi, at: (hash) => { if (hash.eq(mock_1.blockHash100000)) { throw new Error('At is currently unsupported for pallet staking validators connected to assethub'); } return mockAHNextApi.at(hash); }, }; jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiThatThrowsForHistorical); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'polkadot', api: mockRCNextApi, }, ]; }); await expect(PalletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash100000)).rejects.toThrow('At is currently unsupported for pallet staking validators connected to assethub'); }); it('it throws if sidecar is connected to AH but no RC connection is available', async () => { const PalletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return []; }); await expect(PalletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toThrow('Relay chain API not found'); }); it('works when ApiPromise works (block 789629)', async () => { const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); // needs both RC and AH connection jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'polkadot', api: mockRCNextApi, }, ]; }); expect((0, sanitizeNumbers_1.sanitizeNumbers)(await palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629))).toStrictEqual(stakingProgressPostAhm_json_1.default); }); it('throws when ErasStartSessionIndex.isNone', async () => { // Create a fresh mock for this specific test const mockHistoricApiWithNoneEraStartSessionIndex = { ...mockAHHistoricApi, query: { ...mockAHHistoricApi.query, staking: { ...mockAHHistoricApi.query.staking, bondedEras: () => Promise.resolve().then(() => []), erasStartSessionIndex: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<SessionIndex>', undefined)), }, }, }; const mockApiWithNoneEraStartSessionIndex = { ...mockAHNextApi, at: (_hash) => Promise.resolve(mockHistoricApiWithNoneEraStartSessionIndex), }; jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiWithNoneEraStartSessionIndex); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'polkadot', api: mockRCNextApi, }, ]; }); const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); await expect(palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toStrictEqual(new http_errors_1.InternalServerError('EraStartSessionIndex is None when Some was expected.')); }); it('throws when activeEra.isNone', async () => { // Create a fresh mock for this specific test const mockHistoricApiWithNoneActiveEra = { ...mockAHHistoricApi, query: { ...mockAHHistoricApi.query, staking: { ...mockAHHistoricApi.query.staking, activeEra: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<ActiveEraInfo>', null)), }, }, }; const mockApiWithNoneActiveEra = { ...mockAHNextApi, at: (_hash) => Promise.resolve(mockHistoricApiWithNoneActiveEra), }; jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiWithNoneActiveEra); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'polkadot', api: mockRCNextApi, }, ]; }); const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); await expect(palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toStrictEqual(new http_errors_1.InternalServerError('ActiveEra is None when Some was expected.')); }); it('works with entries in unappliedSlashes', async () => { // Create a fresh mock for this specific test const mockApiUnappliedSlashesForAH = { ...mockApiUnappliedSlashes, query: { ...mockApiUnappliedSlashes.query, session: null, }, consts: { ...mockApiUnappliedSlashes.consts, session: null, }, }; jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiUnappliedSlashesForAH); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'polkadot', api: mockRCNextApi, }, ]; }); const palletStakingProgressServiceUnappliedSlashes = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); expect((0, sanitizeNumbers_1.sanitizeNumbers)(await palletStakingProgressServiceUnappliedSlashes.derivePalletStakingProgress(mock_1.blockHash789629))).toStrictEqual(stakingProgressUnappliedSlashesPostAHM_json_1.default); }); }); describe('Asset Hub BABE Calculations', () => { describe('derivePalletStakingProgress with Asset Hub BABE calculations', () => { beforeEach(() => { apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; }); it('uses Asset Hub calculation when isAssetHub && isAssetHubMigrated', async () => { const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi); jest .spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType') .mockImplementation(() => [{ specName: 'polkadot', api: mockRCNextApi }]); const result = await service.derivePalletStakingProgress(mock_1.blockHash789629); // Verify the result has the expected structure expect(result).toHaveProperty('at'); expect(result).toHaveProperty('activeEra'); expect(result).toHaveProperty('forceEra'); expect(result).toHaveProperty('nextSessionEstimate'); expect(result).toHaveProperty('unappliedSlashes'); }); it('throws when ActiveEra is None for Asset Hub', async () => { const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); // Create a fresh mock for this specific test const mockHistoricApiWithNoneActiveEra = createMockAssetHubHistoricApiWithNoneActiveEra(); const mockApiWithNoneActiveEra = { ...mockAHNextApi, at: (_hash) => Promise.resolve(mockHistoricApiWithNoneActiveEra), }; jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiWithNoneActiveEra); jest .spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType') .mockImplementation(() => [{ specName: 'polkadot', api: mockRCNextApi }]); await expect(service.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toThrow('ActiveEra is None when Some was expected.'); }); it('throws when EraStartSessionIndex is None for Asset Hub', async () => { const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); // Create a fresh mock for this specific test const mockHistoricApiWithNoneEraStartSessionIndex = createMockAssetHubHistoricApiWithNoneEraStartSessionIndex(); const mockApiWithNoneEraStartSessionIndex = { ...mockAHNextApi, at: (_hash) => Promise.resolve(mockHistoricApiWithNoneEraStartSessionIndex), }; jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiWithNoneEraStartSessionIndex); jest .spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType') .mockImplementation(() => [{ specName: 'polkadot', api: mockRCNextApi }]); await expect(service.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toThrow('EraStartSessionIndex is None when Some was expected.'); }); it('calculates session index with skipped epochs for Asset Hub', async () => { const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => [{ specName: 'polkadot', api: createMockRelayChainApiWithSkippedEpochs([[16, 12]]) }]); const result = await service.derivePalletStakingProgress(mock_1.blockHash789629); // Verify the result has the expected structure expect(result).toHaveProperty('at'); expect(result).toHaveProperty('activeEra'); expect(result).toHaveProperty('forceEra'); expect(result).toHaveProperty('nextSessionEstimate'); expect(result).toHaveProperty('unappliedSlashes'); }); it('uses hardcoded BABE values for time-based calculations in Asset Hub', async () => { const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi); jest .spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType') .mockImplementation(() => [{ specName: 'polkadot', api: mockRCNextApi }]); const result = await service.derivePalletStakingProgress(mock_1.blockHash789629); // Verify the result has the expected structure and uses time-based calculations expect(result).toHaveProperty('at'); expect(result).toHaveProperty('activeEra'); expect(result).toHaveProperty('forceEra'); expect(result).toHaveProperty('nextSessionEstimate'); expect(result).toHaveProperty('unappliedSlashes'); }); }); }); describe('Non-Asset Hub calculations', () => { beforeEach(() => { // Reset to non-Asset Hub state for these tests apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: false, isAssetHubMigrated: false, }; // Ensure the mock has the correct bondedEras return type mockHistoricApi.query.staking.bondedEras = () => Promise.resolve([ [40, 276], [41, 282], [42, 288], [43, 294], [44, 300], [45, 306], [46, 312], [47, 318], [48, 324], [49, 330], ].map((el) => [registries_1.polkadotRegistry.createType('u32', el[0]), registries_1.polkadotRegistry.createType('u32', el[1])])); }); it('uses regular calculation when not Asset Hub', async () => { const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot'); // Use the regular mock (not Asset Hub mock) for this test jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApi); const result = await service.derivePalletStakingProgress(mock_1.blockHash789629); // Verify the result has the expected structure and matches the original response expect((0, sanitizeNumbers_1.sanitizeNumbers)(result)).toStrictEqual(stakingProgress789629_json_1.default); }); }); }); //# sourceMappingURL=PalletsStakingProgressService.spec.js.map