"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const types_1 = require("../../polkadot/types");
const sinon_1 = __importDefault(require("sinon"));
const internal_1 = require("../../internal");
const mocks_1 = require("../../testUtils/mocks");
const types_2 = require("../../types");
const utils_1 = require("../../types/utils");
const utilsConversionModule = __importStar(require("../../utils/conversion"));
jest.mock('~/base/TransactionQueue', require("../../testUtils/mocks/procedure").mockTransactionQueueModule('~/base/TransactionQueue'));
jest.mock('~/base/PolymeshTransaction', require("../../testUtils/mocks/polymeshTransaction").mockPolymeshTransactionModule('~/base/PolymeshTransaction'));
describe('Procedure class', () => {
    let context;
    beforeAll(() => {
        mocks_1.dsMockUtils.initMocks();
        mocks_1.entityMockUtils.initMocks();
        mocks_1.procedureMockUtils.initMocks();
        mocks_1.polymeshTransactionMockUtils.initMocks();
    });
    beforeEach(() => {
        context = mocks_1.dsMockUtils.getContextInstance();
    });
    afterEach(() => {
        mocks_1.dsMockUtils.reset();
        mocks_1.entityMockUtils.reset();
        mocks_1.procedureMockUtils.reset();
        mocks_1.polymeshTransactionMockUtils.reset();
    });
    afterAll(() => {
        mocks_1.dsMockUtils.cleanup();
        mocks_1.entityMockUtils.cleanup();
        mocks_1.procedureMockUtils.cleanup();
    });
    describe('method: checkAuthorization', () => {
        test('should return whether the current user has sufficient authorization to run the procedure', () => __awaiter(void 0, void 0, void 0, function* () {
            const prepareFunc = sinon_1.default.stub();
            const authFunc = sinon_1.default.stub();
            const authorization = {
                roles: true,
                permissions: true,
            };
            authFunc.resolves(authorization);
            let procedure = new internal_1.Procedure(prepareFunc, authFunc);
            const args = 'args';
            let result = yield procedure.checkAuthorization(args, context);
            expect(result).toEqual({
                agentPermissions: { result: true },
                signerPermissions: { result: true },
                roles: { result: true },
                accountFrozen: false,
                noIdentity: false,
            });
            context = mocks_1.dsMockUtils.getContextInstance({
                checkRoles: {
                    result: false,
                    missingRoles: [{ type: types_2.RoleType.TickerOwner, ticker: 'ticker' }],
                },
                checkPermissions: {
                    result: false,
                    missingPermissions: {
                        assets: null,
                        portfolios: null,
                        transactions: null,
                    },
                },
            });
            authFunc.resolves({
                roles: [{ type: types_2.RoleType.TickerOwner, ticker: 'ticker' }],
                permissions: {
                    assets: null,
                    portfolios: null,
                    transactions: null,
                },
            });
            result = yield procedure.checkAuthorization(args, context);
            expect(result).toEqual({
                agentPermissions: { result: true },
                signerPermissions: {
                    result: false,
                    missingPermissions: {
                        assets: null,
                        portfolios: null,
                        transactions: null,
                    },
                },
                roles: { result: false, missingRoles: [{ type: types_2.RoleType.TickerOwner, ticker: 'ticker' }] },
                accountFrozen: false,
                noIdentity: false,
            });
            context = mocks_1.dsMockUtils.getContextInstance({ hasAssetPermissions: true });
            authFunc.resolves({
                roles: [{ type: types_2.RoleType.TickerOwner, ticker: 'ticker' }],
                permissions: {
                    assets: [mocks_1.entityMockUtils.getAssetInstance({ ticker: 'SOME_TICKER' })],
                    portfolios: null,
                    transactions: [types_1.TxTags.asset.Redeem],
                },
            });
            result = yield procedure.checkAuthorization(args, context);
            expect(result).toEqual({
                agentPermissions: { result: true },
                signerPermissions: { result: true },
                roles: { result: true },
                accountFrozen: false,
                noIdentity: false,
            });
            authFunc.resolves({
                roles: [{ type: types_2.RoleType.TickerOwner, ticker: 'ticker' }],
                permissions: {
                    assets: [mocks_1.entityMockUtils.getAssetInstance({ ticker: 'SOME_TICKER' })],
                    portfolios: null,
                    transactions: [types_1.TxTags.asset.Redeem],
                },
                agentPermissions: 'Some Message',
                signerPermissions: 'Another Message',
            });
            result = yield procedure.checkAuthorization(args, context);
            expect(result).toEqual({
                agentPermissions: {
                    result: false,
                    message: 'Some Message',
                },
                signerPermissions: { result: false, message: 'Another Message' },
                roles: { result: true },
                accountFrozen: false,
                noIdentity: false,
            });
            authFunc.resolves({
                roles: [{ type: types_2.RoleType.TickerOwner, ticker: 'ticker' }],
                signerPermissions: {
                    assets: [mocks_1.entityMockUtils.getAssetInstance({ ticker: 'SOME_TICKER' })],
                    portfolios: null,
                    transactions: [types_1.TxTags.asset.Redeem],
                },
                agentPermissions: {
                    assets: [mocks_1.entityMockUtils.getAssetInstance({ ticker: 'SOME_TICKER' })],
                    portfolios: null,
                    transactions: [types_1.TxTags.asset.Redeem],
                },
            });
            result = yield procedure.checkAuthorization(args, context);
            expect(result).toEqual({
                agentPermissions: { result: true },
                signerPermissions: { result: true },
                roles: { result: true },
                accountFrozen: false,
                noIdentity: false,
            });
            authFunc.resolves({
                roles: [{ type: types_2.RoleType.TickerOwner, ticker: 'ticker' }],
                permissions: {
                    assets: [mocks_1.entityMockUtils.getAssetInstance({ ticker: 'SOME_TICKER' })],
                    portfolios: null,
                },
            });
            result = yield procedure.checkAuthorization(args, context);
            expect(result).toEqual({
                agentPermissions: { result: true },
                signerPermissions: { result: true },
                roles: { result: true },
                accountFrozen: false,
                noIdentity: false,
            });
            context = mocks_1.dsMockUtils.getContextInstance();
            context.getCurrentAccount.returns(mocks_1.entityMockUtils.getAccountInstance({
                getIdentity: null,
                isFrozen: false,
            }));
            result = yield procedure.checkAuthorization(args, context);
            expect(result).toEqual({
                agentPermissions: { result: true },
                signerPermissions: { result: true },
                roles: { result: false, missingRoles: [{ type: types_2.RoleType.TickerOwner, ticker: 'ticker' }] },
                accountFrozen: false,
                noIdentity: true,
            });
            procedure = new internal_1.Procedure(prepareFunc, { permissions: true, roles: true });
            result = yield procedure.checkAuthorization(args, context);
            expect(result).toEqual({
                agentPermissions: { result: true },
                signerPermissions: { result: true },
                roles: { result: true },
                accountFrozen: false,
                noIdentity: false,
            });
        }));
        test('should throw an error if the Procedure requires permissions over more than one Asset', () => {
            const prepareFunc = sinon_1.default.stub();
            const authFunc = sinon_1.default.stub();
            authFunc.resolves({
                permissions: {
                    assets: [
                        mocks_1.entityMockUtils.getAssetInstance({ ticker: 'SOME_TICKER' }),
                        mocks_1.entityMockUtils.getAssetInstance({ ticker: 'OTHER_TICKER' }),
                    ],
                    transactions: [types_1.TxTags.asset.Freeze],
                },
            });
            const procedure = new internal_1.Procedure(prepareFunc, authFunc);
            const args = 'args';
            return expect(procedure.checkAuthorization(args, context)).rejects.toThrow('Procedures cannot require permissions for more than one Asset. Please contact the Polymath team');
        });
    });
    describe('method: prepare', () => {
        let posRatioToBigNumberStub;
        let balanceToBigNumberStub;
        let txTagToProtocolOpStub;
        let txTags;
        let fees;
        let rawCoefficient;
        let rawFees;
        let numerator;
        let denominator;
        let coefficient;
        beforeAll(() => {
            posRatioToBigNumberStub = sinon_1.default.stub(utilsConversionModule, 'posRatioToBigNumber');
            balanceToBigNumberStub = sinon_1.default.stub(utilsConversionModule, 'balanceToBigNumber');
            txTagToProtocolOpStub = sinon_1.default.stub(utilsConversionModule, 'txTagToProtocolOp');
            txTags = [types_1.TxTags.asset.RegisterTicker, types_1.TxTags.identity.CddRegisterDid];
            fees = [250, 0];
            numerator = 7;
            denominator = 3;
            rawCoefficient = mocks_1.dsMockUtils.createMockPosRatio(numerator, denominator);
            rawFees = fees.map(mocks_1.dsMockUtils.createMockBalance);
            coefficient = new bignumber_js_1.default(numerator).dividedBy(new bignumber_js_1.default(denominator));
        });
        beforeEach(() => {
            mocks_1.dsMockUtils.createQueryStub('protocolFee', 'coefficient', {
                returnValue: rawCoefficient,
            });
            mocks_1.dsMockUtils
                .createQueryStub('protocolFee', 'baseFees')
                .withArgs('AssetRegisterTicker')
                .resolves(rawFees[0]);
            mocks_1.dsMockUtils
                .createQueryStub('protocolFee', 'baseFees')
                .withArgs('IdentityRegisterDid')
                .resolves(rawFees[1]);
            posRatioToBigNumberStub.withArgs(rawCoefficient).returns(coefficient);
            txTags.forEach(txTag => txTagToProtocolOpStub.withArgs(txTag, context).returns(txTag));
            rawFees.forEach((rawFee, index) => balanceToBigNumberStub.withArgs(rawFee).returns(new bignumber_js_1.default(fees[index])));
        });
        test('should prepare and return a transaction queue with the corresponding transactions, arguments, fees and return value', () => __awaiter(void 0, void 0, void 0, function* () {
            const ticker = 'MY_ASSET';
            const secondaryAccounts = ['0x1', '0x2'];
            const procArgs = {
                ticker,
                secondaryAccounts,
            };
            const tx1 = mocks_1.dsMockUtils.createTxStub('asset', 'registerTicker');
            const tx2 = mocks_1.dsMockUtils.createTxStub('identity', 'cddRegisterDid');
            const returnValue = 'good';
            const func1 = function (args) {
                return __awaiter(this, void 0, void 0, function* () {
                    this.addTransaction({ transaction: tx1, args: [args.ticker] });
                    this.addTransaction({ transaction: tx2, args: [args.secondaryAccounts] });
                    return returnValue;
                });
            };
            const constructorStub = mocks_1.procedureMockUtils.getTransactionQueueConstructorStub();
            const proc1 = new internal_1.Procedure(func1);
            let queue = yield proc1.prepare({ args: procArgs }, context, { signer: 'something' });
            expect(queue).toMatchObject({
                transactions: [
                    { transaction: tx1, args: [ticker] },
                    { transaction: tx2, args: [secondaryAccounts] },
                ],
            });
            sinon_1.default.assert.calledWith(constructorStub, sinon_1.default.match({
                transactions: sinon_1.default.match([
                    sinon_1.default.match({ transaction: tx1, args: [ticker] }),
                    sinon_1.default.match({ transaction: tx2, args: [secondaryAccounts] }),
                ]),
            }), Object.assign(Object.assign({}, context), { currentPair: { address: 'something' } }));
            const func2 = function (args) {
                return __awaiter(this, void 0, void 0, function* () {
                    return this.addProcedure(proc1, args);
                });
            };
            mocks_1.dsMockUtils.reset();
            const proc2 = new internal_1.Procedure(func2);
            context = mocks_1.dsMockUtils.getContextInstance();
            queue = yield proc2.prepare({ args: procArgs }, context);
            expect(queue).toMatchObject({
                transactions: [
                    { transaction: tx1, args: [ticker] },
                    { transaction: tx2, args: [secondaryAccounts] },
                ],
                procedureResult: returnValue,
            });
            sinon_1.default.assert.calledWith(constructorStub, sinon_1.default.match({
                transactions: sinon_1.default.match([
                    sinon_1.default.match({ transaction: tx1, args: [ticker] }),
                    sinon_1.default.match({ transaction: tx2, args: [secondaryAccounts] }),
                ]),
                procedureResult: returnValue,
            }), context);
        }));
        test('should throw any errors encountered during preparation', () => {
            const ticker = 'MY_ASSET';
            const secondaryAccounts = ['0x1', '0x2'];
            const procArgs = {
                ticker,
                secondaryAccounts,
            };
            const errorMsg = 'failed';
            const func = function () {
                return __awaiter(this, void 0, void 0, function* () {
                    throw new Error(errorMsg);
                });
            };
            const proc = new internal_1.Procedure(func);
            return expect(proc.prepare({ args: procArgs }, context)).rejects.toThrow(errorMsg);
        });
        test("should throw an error if the caller doesn't have the appropriate roles", () => __awaiter(void 0, void 0, void 0, function* () {
            const ticker = 'MY_ASSET';
            const secondaryAccounts = ['0x1', '0x2'];
            const procArgs = {
                ticker,
                secondaryAccounts,
            };
            const func = function () {
                return __awaiter(this, void 0, void 0, function* () {
                    return 'success';
                });
            };
            let proc = new internal_1.Procedure(func, {
                roles: [{ type: 'FakeRole' }],
            });
            context = mocks_1.dsMockUtils.getContextInstance({
                isFrozen: false,
                checkRoles: {
                    result: false,
                    missingRoles: [{ type: 'FakeRole' }],
                },
                checkPermissions: {
                    result: false,
                },
                checkAssetPermissions: {
                    result: false,
                },
            });
            yield expect(proc.prepare({ args: procArgs }, context)).rejects.toThrow("Current Identity doesn't have the required roles to execute this procedure");
            proc = new internal_1.Procedure(func, {
                permissions: {
                    assets: [],
                    transactions: [],
                    portfolios: [],
                },
            });
            yield expect(proc.prepare({ args: procArgs }, context)).rejects.toThrow("Current Account doesn't have the required permissions to execute this procedure");
            proc = new internal_1.Procedure(func, {
                permissions: {
                    assets: [mocks_1.entityMockUtils.getAssetInstance({ ticker: 'SOME_TICKER' })],
                    transactions: [types_1.TxTags.asset.Freeze],
                    portfolios: [],
                },
            });
            context = mocks_1.dsMockUtils.getContextInstance({
                checkAssetPermissions: { result: false, missingPermissions: [types_1.TxTags.asset.Freeze] },
            });
            yield expect(proc.prepare({ args: procArgs }, context)).rejects.toThrow("Current Identity doesn't have the required permissions to execute this procedure");
            context = mocks_1.dsMockUtils.getContextInstance();
            context.getCurrentAccount.returns(mocks_1.entityMockUtils.getAccountInstance({
                getIdentity: null,
            }));
            yield expect(proc.prepare({ args: procArgs }, context)).rejects.toThrow('This procedure requires the Current Account to have an associated Identity');
            proc = new internal_1.Procedure(func, {
                permissions: 'Some Failure Message',
            });
            yield expect(proc.prepare({ args: procArgs }, context)).rejects.toThrow("Current Account doesn't have the required permissions to execute this procedure");
            proc = new internal_1.Procedure(func, () => __awaiter(void 0, void 0, void 0, function* () { return ({ roles: 'Failed just because' }); }));
            yield expect(proc.prepare({ args: procArgs }, context)).rejects.toThrow("Current Identity doesn't have the required roles to execute this procedure");
            context = mocks_1.dsMockUtils.getContextInstance({ isFrozen: true });
            yield expect(proc.prepare({ args: procArgs }, context)).rejects.toThrow("Current Account can't execute this procedure because it is frozen");
        }));
    });
    describe('method: addTransaction', () => {
        test('should return an array of post transaction values corresponding to the resolver functions passed to it', () => __awaiter(void 0, void 0, void 0, function* () {
            const resolvedNum = 1;
            const resolvedStr = 'something';
            const transaction = mocks_1.dsMockUtils.createTxStub('asset', 'registerTicker');
            const proc = new internal_1.Procedure(() => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            proc._context = context;
            const values = proc.addTransaction({
                transaction,
                resolvers: utils_1.tuple(() => __awaiter(void 0, void 0, void 0, function* () { return resolvedNum; }), () => __awaiter(void 0, void 0, void 0, function* () { return resolvedStr; })),
                args: [1],
            });
            yield Promise.all(values.map(value => value.run({})));
            const [num, str] = values;
            expect(num.value).toBe(resolvedNum);
            expect(str.value).toBe(resolvedStr);
        }));
    });
    describe('method: addBatchTransaction', () => {
        test('should return an array of post transaction values corresponding to the resolver functions passed to it', () => __awaiter(void 0, void 0, void 0, function* () {
            const ticker = 'MY_ASSET';
            const resolvedNum = 1;
            const resolvedStr = 'something';
            const tx = mocks_1.dsMockUtils.createTxStub('asset', 'registerTicker');
            const proc = new internal_1.Procedure(() => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            proc._context = context;
            const values = proc.addBatchTransaction({
                transactions: [
                    {
                        transaction: tx,
                        args: [ticker],
                    },
                    {
                        transaction: tx,
                        args: [ticker],
                    },
                ],
                resolvers: utils_1.tuple(() => __awaiter(void 0, void 0, void 0, function* () { return resolvedNum; }), () => __awaiter(void 0, void 0, void 0, function* () { return resolvedStr; })),
            });
            yield Promise.all(values.map(value => value.run({})));
            const [num, str] = values;
            expect(num.value).toBe(resolvedNum);
            expect(str.value).toBe(resolvedStr);
        }));
        test('should add a non-batch transaction to the queue if only one transaction is passed', () => __awaiter(void 0, void 0, void 0, function* () {
            const ticker = 'MY_ASSET';
            const tx = mocks_1.dsMockUtils.createTxStub('asset', 'registerTicker');
            const proc = new internal_1.Procedure(() => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            proc._context = context;
            proc.addBatchTransaction({ transactions: [{ transaction: tx, args: [ticker] }] });
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const transactions = proc.transactions;
            expect(transactions[0] instanceof internal_1.PolymeshTransaction).toBe(true);
            expect(transactions.length).toBe(1);
        }));
    });
    describe('method: addProcedure', () => {
        test('should return the return value of the passed procedure', () => __awaiter(void 0, void 0, void 0, function* () {
            const returnValue = 1;
            const proc1 = new internal_1.Procedure(() => __awaiter(void 0, void 0, void 0, function* () { return returnValue; }));
            const proc2 = new internal_1.Procedure(() => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            proc2._context = context;
            const result = yield proc2.addProcedure(proc1);
            expect(result).toBe(returnValue);
        }));
        test('should throw any validation errors encountered while preparing the passed procedure', () => {
            const errorMsg = 'Procedure Error';
            const proc1 = new internal_1.Procedure(() => __awaiter(void 0, void 0, void 0, function* () {
                throw new Error(errorMsg);
            }));
            const proc2 = new internal_1.Procedure(() => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            proc2._context = context;
            const result = proc2.addProcedure(proc1);
            return expect(result).rejects.toThrow(errorMsg);
        });
    });
    describe('method: storage', () => {
        let proc;
        beforeAll(() => {
            proc = new internal_1.Procedure(() => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
        });
        test('should return the storage', () => {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            proc._storage = { something: 'yeah' };
            expect(proc.storage).toEqual({ something: 'yeah' });
        });
        test("should throw an error if the storage hasn't been set", () => {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            proc._storage = null;
            expect(() => proc.storage).toThrow('Attempt to access storage before it was set');
        });
    });
    describe('method: context', () => {
        let proc;
        beforeAll(() => {
            proc = new internal_1.Procedure(() => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
        });
        test('should return the context', () => {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            proc._context = 'context';
            expect(proc.context).toBe('context');
        });
        test("should throw an error if the context hasn't been set", () => {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            proc._context = null;
            expect(() => proc.context).toThrow('Attempt to access context before it was set');
        });
    });
});
//# sourceMappingURL=Procedure.js.map