From f1090dd4ea309202b76b308351e8beba3ece4b57 Mon Sep 17 00:00:00 2001 From: pshenmic Date: Thu, 27 Jun 2024 05:38:05 +0700 Subject: [PATCH 1/6] Implement isActive validators filter --- .../src/controllers/ValidatorsController.js | 33 +- packages/api/src/dao/ValidatorsDAO.js | 36 +- packages/api/src/models/Validator.js | 10 +- packages/api/src/tenderdashRpc.js | 6 + .../api/test/integration/validators.spec.js | 812 ++++++++++++++---- 5 files changed, 740 insertions(+), 157 deletions(-) diff --git a/packages/api/src/controllers/ValidatorsController.js b/packages/api/src/controllers/ValidatorsController.js index 1806b967b..b6535eb35 100644 --- a/packages/api/src/controllers/ValidatorsController.js +++ b/packages/api/src/controllers/ValidatorsController.js @@ -1,4 +1,6 @@ const ValidatorsDAO = require('../dao/ValidatorsDAO') +const TenderdashRPC = require('../tenderdashRpc') +const Validator = require('../models/Validator') class ValidatorsController { constructor (knex) { @@ -14,19 +16,42 @@ class ValidatorsController { return response.status(404).send({ message: 'not found' }) } - response.send(validator) + const validators = await TenderdashRPC.getValidators() + + const isActive = validators.some(validator => validator.proTxHash === proTxHash) + + response.send(new Validator(validator.proTxHash, isActive)) } getValidators = async (request, response) => { - const { page = 1, limit = 10, order = 'asc' } = request.query + const { page = 1, limit = 10, order = 'asc', isActive = undefined } = request.query if (order !== 'asc' && order !== 'desc') { return response.status(400).send({ message: `invalid ordering value ${order}. only 'asc' or 'desc' is valid values` }) } - const validators = await this.validatorsDAO.getValidators(Number(page), Number(limit), order) + const activeValidators = await TenderdashRPC.getValidators() + + if (typeof isActive !== 'undefined') { + if (isActive !== 'true' && isActive !== 'false') { + return response.status(400).send({ message: `invalid isActive value ${order}. only boolean values are accepted` }) + } + } - response.send(validators) + const validators = await this.validatorsDAO.getValidators( + Number(page), + Number(limit), + order, + typeof isActive === 'undefined' ? undefined : isActive === 'true', + activeValidators + ) + + return response.send({ + ...validators, + resultSet: validators.resultSet.map(validator => + new Validator(validator.proTxHash, activeValidators.some(activeValidator => + activeValidator.proTxHash === validator.proTxHash))) + }) } } diff --git a/packages/api/src/dao/ValidatorsDAO.js b/packages/api/src/dao/ValidatorsDAO.js index 566ea00df..e06db5d7c 100644 --- a/packages/api/src/dao/ValidatorsDAO.js +++ b/packages/api/src/dao/ValidatorsDAO.js @@ -6,7 +6,7 @@ module.exports = class ValidatorsDAO { this.knex = knex } - getValidatorByProTxHash = async (proTxHash) => { + getValidatorByProTxHash = async (proTxHash, validators) => { const [row] = await this.knex('validators') .select('validators.pro_tx_hash as pro_tx_hash') .where('validators.pro_tx_hash', proTxHash) @@ -15,17 +15,43 @@ module.exports = class ValidatorsDAO { return null } - return Validator.fromRow(row) + return new Validator(proTxHash) } - getValidators = async (page, limit, order) => { + /** + * Get all active / non + * + * @param page {number} + * @param limit {number} + * @param order {string} + * @param isActive {undefined | boolean} + * @param validators {[{}]} validators (from Tenderdash RPC) + * + * @returns {Promise} + */ + getValidators = async (page, limit, order, isActive, validators) => { const fromRank = ((page - 1) * limit) + 1 const toRank = fromRank + limit - 1 const subquery = this.knex('validators') - .select(this.knex('validators').count('pro_tx_hash').as('total_count'), - 'validators.pro_tx_hash as pro_tx_hash', 'id') + .select(this.knex('validators') + .modify(function (knex) { + if (isActive !== undefined && isActive) { + knex.whereIn('validators.pro_tx_hash', validators.map(validator => validator.proTxHash)) + } else if (isActive !== undefined && !isActive) { + knex.whereNotIn('validators.pro_tx_hash', validators.map(validator => validator.proTxHash)) + } + }) + .count('pro_tx_hash').as('total_count'), + 'validators.pro_tx_hash as pro_tx_hash', 'id') .select(this.knex.raw(`rank() over (order by id ${order}) rank`)) + .modify(function (knex) { + if (isActive !== undefined && isActive) { + knex.whereIn('validators.pro_tx_hash', validators.map(validator => validator.proTxHash)) + } else if (isActive !== undefined && !isActive) { + knex.whereNotIn('validators.pro_tx_hash', validators.map(validator => validator.proTxHash)) + } + }) .as('validators') const rows = await this.knex(subquery) diff --git a/packages/api/src/models/Validator.js b/packages/api/src/models/Validator.js index 5744f3614..73795f669 100644 --- a/packages/api/src/models/Validator.js +++ b/packages/api/src/models/Validator.js @@ -1,12 +1,14 @@ module.exports = class Validator { proTxHash + isActive - constructor (proTxHash) { - this.proTxHash = proTxHash + constructor (proTxHash, isActive) { + this.proTxHash = proTxHash ?? null + this.isActive = isActive ?? null } // eslint-disable-next-line camelcase - static fromRow ({ pro_tx_hash }) { - return new Validator(pro_tx_hash) + static fromRow ({ pro_tx_hash, is_active }) { + return new Validator(pro_tx_hash, is_active) } } diff --git a/packages/api/src/tenderdashRpc.js b/packages/api/src/tenderdashRpc.js index 303bd111b..d1fd38156 100644 --- a/packages/api/src/tenderdashRpc.js +++ b/packages/api/src/tenderdashRpc.js @@ -102,6 +102,12 @@ class TenderdashRPC { return genesis } + + static async getValidators () { + const { validators } = await call('validators', 'GET') + + return validators + } } module.exports = TenderdashRPC diff --git a/packages/api/test/integration/validators.spec.js b/packages/api/test/integration/validators.spec.js index 865751dce..5a120ebb1 100644 --- a/packages/api/test/integration/validators.spec.js +++ b/packages/api/test/integration/validators.spec.js @@ -1,9 +1,10 @@ -const { describe, it, before, after } = require('node:test') +const { describe, it, before, after, mock } = require('node:test') const assert = require('node:assert').strict const supertest = require('supertest') const server = require('../../src/server') const fixtures = require('../utils/fixtures') const { getKnex } = require('../../src/utils') +const tenderdashRpc = require('../../src/tenderdashRpc') describe('Validators routes', () => { let app @@ -21,6 +22,8 @@ describe('Validators routes', () => { await fixtures.cleanup(knex) + mock.method(tenderdashRpc, 'getValidators', async () => []) + for (let i = 0; i < 30; i++) { const validator = await fixtures.validator(knex) validators.push(validator) @@ -33,7 +36,7 @@ describe('Validators routes', () => { }) describe('getValidatorByProTxHash()', async () => { - it('should return validator by proTxHash', async () => { + it('should return inactive validator by proTxHash', async () => { const [validator] = validators const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) @@ -41,168 +44,689 @@ describe('Validators routes', () => { .expect('Content-Type', 'application/json; charset=utf-8') const expectedValidator = { - proTxHash: validator.pro_tx_hash + proTxHash: validator.pro_tx_hash, + isActive: false } assert.deepEqual(expectedValidator, body) }) - it('should return 404 if validator not found', async () => { - await client.get('/validator/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF') - .expect(404) - .expect('Content-Type', 'application/json; charset=utf-8') - }) - }) - - describe('getValidators()', async () => { - it('should return default set of validators', async () => { - const { body } = await client.get('/validators') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length) - assert.equal(body.resultSet.length, 10) - - const expectedValidators = validators - .slice(0, 10) - .map(row => ({ - proTxHash: row.pro_tx_hash - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should return default set of validators order desc', async () => { - const { body } = await client.get('/validators?order=desc') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length) - assert.equal(body.resultSet.length, 10) - - const expectedValidators = validators - .slice(validators.length - 10, validators.length) - .sort((a, b) => b.id - a.id) - .map(row => ({ - proTxHash: row.pro_tx_hash - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should be able to walk through pages', async () => { - const { body } = await client.get('/validators?page=2') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 2) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length) - assert.equal(body.resultSet.length, 10) - - const expectedValidators = validators - .slice(10, 20) - .map(row => ({ - proTxHash: row.pro_tx_hash - })) + it('should return validator by proTxHash', async () => { + const [validator] = validators - assert.deepEqual(expectedValidators, body.resultSet) - }) + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve([{ proTxHash: validator.pro_tx_hash }])) - it('should return custom page size', async () => { - const { body } = await client.get('/validators?limit=7') + const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, validators.length) - assert.equal(body.resultSet.length, 7) - - const expectedValidators = validators - .slice(0, 7) - .map(row => ({ - proTxHash: row.pro_tx_hash - })) + const expectedValidator = { + proTxHash: validator.pro_tx_hash, + isActive: true + } - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(expectedValidator, body) }) - it('should allow to walk through pages with custom page size', async () => { - const { body } = await client.get('/validators?limit=7&page=2') - .expect(200) + it('should return 404 if validator not found', async () => { + await client.get('/validator/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF') + .expect(404) .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 2) - assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, validators.length) - assert.equal(body.resultSet.length, 7) - - const expectedValidators = validators - .slice(7, 14) - .map(row => ({ - proTxHash: row.pro_tx_hash - })) - - assert.deepEqual(expectedValidators, body.resultSet) }) + }) - it('should allow to walk through pages with custom page size desc', async () => { - const { body } = await client.get('/validators?limit=7&page=4&order=desc') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 4) - assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, validators.length) - assert.equal(body.resultSet.length, 7) - - const expectedValidators = validators - .sort((a, b) => b.id - a.id) - .slice(21, 28) - .map(row => ({ - proTxHash: row.pro_tx_hash - })) - - assert.deepEqual(expectedValidators, body.resultSet) + describe('getValidators()', async () => { + describe('no filter', async () => { + it('should return default set of validators', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .slice(0, 10) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return default set of validators order desc', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?order=desc') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .slice(validators.length - 10, validators.length) + .sort((a, b) => b.id - a.id) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should be able to walk through pages', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?page=2') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .slice(10, 20) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return custom page size', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=7') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = validators + .slice(0, 7) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=7&page=2') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = validators + .slice(7, 14) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size desc', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=7&page=4&order=desc') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = validators + .sort((a, b) => b.id - a.id) + .slice(21, 28) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when when it is out of bounds', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=7&page=5&order=desc') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 5) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 2) + + const expectedValidators = validators + .sort((a, b) => b.id - a.id) + .slice(28, 30) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when there is none on the one bound', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=10&page=4&order=desc') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, -1) + assert.equal(body.resultSet.length, 0) + + const expectedValidators = [] + + assert.deepEqual(expectedValidators, body.resultSet) + }) }) - it('should return less items when when it is out of bounds', async () => { - const { body } = await client.get('/validators?limit=7&page=5&order=desc') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 5) - assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, validators.length) - assert.equal(body.resultSet.length, 2) - - const expectedValidators = validators - .sort((a, b) => b.id - a.id) - .slice(28, 30) - .map(row => ({ - proTxHash: row.pro_tx_hash - })) - assert.deepEqual(expectedValidators, body.resultSet) + describe('with isActive=true filter', async () => { + it('should return default set of validators', async () => { + const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 10) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .sort((a, b) => a.id - b.id) + .slice(0, 10) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return default set of validators order desc', async () => { + const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 10) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?order=desc&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .sort((a, b) => b.id - a.id) + .slice(0, 10) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should be able to walk through pages', async () => { + const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 20) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?page=2&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .sort((a, b) => a.id - b.id) + .slice(10, 20) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return custom page size', async () => { + const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 14) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=7&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = validators + .sort((a, b) => a.id - b.id) + .slice(0, 7) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size', async () => { + const activeValidators = validators.slice(0, 14) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=7&page=2&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = validators + .slice(7, 14) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size desc', async () => { + const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 12) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=3&page=4&order=desc&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 3) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 3) + + const expectedValidators = validators + .sort((a, b) => b.id - a.id) + .slice(9, 12) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when when it is out of bounds', async () => { + const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 14) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=4&page=4&order=desc&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 4) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 2) + + const expectedValidators = validators + .sort((a, b) => b.id - a.id) + .slice(12, 14) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) + })) + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when there is none on the one bound', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=10&page=4&order=desc&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, -1) + assert.equal(body.resultSet.length, 0) + + const expectedValidators = [] + + assert.deepEqual(expectedValidators, body.resultSet) + }) }) - it('should return less items when there is none on the one bound', async () => { - const { body } = await client.get('/validators?limit=10&page=4&order=desc') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 4) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, -1) - assert.equal(body.resultSet.length, 0) - - const expectedValidators = [] - - assert.deepEqual(expectedValidators, body.resultSet) + describe('with isActive=false filter', async () => { + it('should return default set of validators', async () => { + const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 10) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?isActive=false') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, validators.length - activeValidators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .sort((a, b) => a.id - b.id) + .slice(10, 20) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: false + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return default set of validators order desc', async () => { + const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 10) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?order=desc&isActive=false') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, validators.length - activeValidators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .sort((a, b) => b.id - a.id) + .slice(10, 20) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: false + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should be able to walk through pages', async () => { + const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 10) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?page=2&isActive=false') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, validators.length - activeValidators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .sort((a, b) => a.id - b.id) + .slice(20, 30) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: false + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return custom page size', async () => { + const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 10) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=7&isActive=false') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, validators.length - activeValidators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = validators + .sort((a, b) => a.id - b.id) + .slice(10, 17) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: false + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size', async () => { + const activeValidators = validators.slice(0, 14) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=7&page=2&isActive=false') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, validators.length - activeValidators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = validators + .slice(21, 28) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: false + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size desc', async () => { + const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 10) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=3&page=4&order=desc&isActive=false') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 3) + assert.equal(body.pagination.total, validators.length - activeValidators.length) + assert.equal(body.resultSet.length, 3) + + const expectedValidators = validators + .sort((a, b) => b.id - a.id) + .slice(19, 22) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: false + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when when it is out of bounds', async () => { + const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 10) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=30&page=1&order=desc&isActive=false') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 30) + assert.equal(body.pagination.total, validators.length - activeValidators.length) + assert.equal(body.resultSet.length, 20) + + const expectedValidators = validators + .sort((a, b) => b.id - a.id) + .slice(10, 30) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: false + })) + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when there is none on the one bound', async () => { + const activeValidators = validators.slice(0, 3) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) + + const { body } = await client.get('/validators?limit=10&page=4&order=desc&isActive=false') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, -1) + assert.equal(body.resultSet.length, 0) + + const expectedValidators = [] + + assert.deepEqual(expectedValidators, body.resultSet) + }) }) }) }) From 9171437695fa045880992f578d6294c80393530f Mon Sep 17 00:00:00 2001 From: pshenmic Date: Thu, 4 Jul 2024 02:22:05 +0700 Subject: [PATCH 2/6] Fix validators integration tests --- .../src/controllers/ValidatorsController.js | 6 +- packages/api/src/dao/ValidatorsDAO.js | 46 +- .../api/test/integration/validators.spec.js | 1222 +++++++++-------- 3 files changed, 665 insertions(+), 609 deletions(-) diff --git a/packages/api/src/controllers/ValidatorsController.js b/packages/api/src/controllers/ValidatorsController.js index b6535eb35..8275eb358 100644 --- a/packages/api/src/controllers/ValidatorsController.js +++ b/packages/api/src/controllers/ValidatorsController.js @@ -20,7 +20,7 @@ class ValidatorsController { const isActive = validators.some(validator => validator.proTxHash === proTxHash) - response.send(new Validator(validator.proTxHash, isActive)) + response.send(new Validator(validator.proTxHash, isActive, validator.proposedBlocksAmount, validator.lastProposedBlockHeader)) } getValidators = async (request, response) => { @@ -50,7 +50,9 @@ class ValidatorsController { ...validators, resultSet: validators.resultSet.map(validator => new Validator(validator.proTxHash, activeValidators.some(activeValidator => - activeValidator.proTxHash === validator.proTxHash))) + activeValidator.proTxHash === validator.proTxHash), + validator.proposedBlocksAmount, + validator.lastProposedBlockHeader)) }) } } diff --git a/packages/api/src/dao/ValidatorsDAO.js b/packages/api/src/dao/ValidatorsDAO.js index cda23bf82..1a61d1406 100644 --- a/packages/api/src/dao/ValidatorsDAO.js +++ b/packages/api/src/dao/ValidatorsDAO.js @@ -7,8 +7,10 @@ module.exports = class ValidatorsDAO { } getValidatorByProTxHash = async (proTxHash) => { - const [row] = await this.knex(validatorsSubquery) + const validatorsSubquery = this.knex('validators') .select( + 'validators.pro_tx_hash as pro_tx_hash', + 'validators.id', this.knex('blocks') .count('*') .whereRaw('blocks.validator = validators.pro_tx_hash') @@ -18,7 +20,13 @@ module.exports = class ValidatorsDAO { .whereRaw('pro_tx_hash = blocks.validator') .orderBy('height', 'desc') .limit(1) - .as('proposed_block_hash'), + .as('proposed_block_hash') + ) + .where('validators.pro_tx_hash', proTxHash) + .as('validators') + + const subquery = this.knex(validatorsSubquery) + .select( 'pro_tx_hash', 'id', 'proposed_blocks_amount', @@ -30,7 +38,21 @@ module.exports = class ValidatorsDAO { 'blocks.block_version as block_version' ) .leftJoin('blocks', 'blocks.hash', 'proposed_block_hash') - .where('validators.pro_tx_hash', proTxHash) + .as('blocks') + + const [row] = await this.knex(subquery) + .select( + 'id', + 'pro_tx_hash', + 'proposed_blocks_amount', + 'block_hash', + 'latest_height', + 'latest_timestamp', + 'l1_locked_height', + 'app_version', + 'block_version' + ) + .where('pro_tx_hash', proTxHash) if (!row) { return null @@ -61,9 +83,9 @@ module.exports = class ValidatorsDAO { this.knex('validators') .modify(function (knex) { if (isActive !== undefined && isActive) { - knex.whereIn('validators.pro_tx_hash', validators.map(validator => validator.proTxHash)) + knex.whereIn('pro_tx_hash', validators.map(validator => validator.proTxHash)) } else if (isActive !== undefined && !isActive) { - knex.whereNotIn('validators.pro_tx_hash', validators.map(validator => validator.proTxHash)) + knex.whereNotIn('pro_tx_hash', validators.map(validator => validator.proTxHash)) } }) .count('pro_tx_hash').as('total_count'), @@ -94,6 +116,13 @@ module.exports = class ValidatorsDAO { 'blocks.app_version as app_version', 'blocks.block_version as block_version' ) + .modify(function (knex) { + if (isActive !== undefined && isActive) { + knex.whereIn('pro_tx_hash', validators.map(validator => validator.proTxHash)) + } else if (isActive !== undefined && !isActive) { + knex.whereNotIn('pro_tx_hash', validators.map(validator => validator.proTxHash)) + } + }) .leftJoin('blocks', 'blocks.hash', 'proposed_block_hash') .as('blocks') @@ -111,13 +140,6 @@ module.exports = class ValidatorsDAO { 'app_version', 'block_version' ) - .modify(function (knex) { - if (isActive !== undefined && isActive) { - knex.whereIn('validators.pro_tx_hash', validators.map(validator => validator.proTxHash)) - } else if (isActive !== undefined && !isActive) { - knex.whereNotIn('validators.pro_tx_hash', validators.map(validator => validator.proTxHash)) - } - }) .whereBetween('rank', [fromRank, toRank]) .orderBy('id', order) diff --git a/packages/api/test/integration/validators.spec.js b/packages/api/test/integration/validators.spec.js index 8774b7690..c7b066336 100644 --- a/packages/api/test/integration/validators.spec.js +++ b/packages/api/test/integration/validators.spec.js @@ -13,6 +13,8 @@ describe('Validators routes', () => { let knex let validators + let activeValidators + let inactiveValidators let blocks before(async () => { @@ -27,7 +29,7 @@ describe('Validators routes', () => { mock.method(tenderdashRpc, 'getValidators', async () => []) - for (let i = 0; i < 25; i++) { + for (let i = 0; i < 50; i++) { const validator = await fixtures.validator(knex) validators.push(validator) } @@ -35,10 +37,18 @@ describe('Validators routes', () => { for (let i = 1; i <= 50; i++) { const block = await fixtures.block( knex, - { validator: validators[i % 24].pro_tx_hash, height: i } + { validator: validators[i % 30].pro_tx_hash, height: i } ) blocks.push(block) } + + activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 30) + inactiveValidators = validators.sort((a, b) => a.id - b.id).slice(30, 50) + + mock.method(tenderdashRpc, 'getValidators', + async () => + Promise.resolve(activeValidators.map(activeValidator => + ({ proTxHash: activeValidator.pro_tx_hash })))) }) after(async () => { @@ -46,245 +56,703 @@ describe('Validators routes', () => { await knex.destroy() }) - describe('getValidatorByProTxHash()', async () => { - it('should return inactive validator by proTxHash', async () => { - const [validator] = validators - - const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - const expectedValidator = { - proTxHash: validator.pro_tx_hash, - isActive: false - } - - assert.deepEqual(expectedValidator, body) - }) - - it('should return validator by proTxHash', async () => { - const [validator] = validators - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve([{ proTxHash: validator.pro_tx_hash }])) - - const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - const expectedValidator = { - proTxHash: validator.pro_tx_hash, - isActive: true, - proposedBlocksAmount: blocks.filter((block) => block.validator === validator.pro_tx_hash).length, - lastProposedBlockHeader: blocks - .filter((block) => block.validator === validator.pro_tx_hash) - .map((block) => BlockHeader.fromRow(block)) - .map((blockHeader) => ({ - hash: blockHeader.hash, - height: blockHeader.height, - timestamp: blockHeader.timestamp.toISOString(), - blockVersion: blockHeader.blockVersion, - appVersion: blockHeader.appVersion, - l1LockedHeight: blockHeader.l1LockedHeight, - validator: blockHeader.validator - })) - .toReversed()[0] ?? null - } - - assert.deepEqual(expectedValidator, body) - }) - - it('should return 404 if validator not found', async () => { - await client.get('/validator/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF') - .expect(404) - .expect('Content-Type', 'application/json; charset=utf-8') - }) - }) + // describe('getValidatorByProTxHash()', async () => { + // it('should return inactive validator by proTxHash', async () => { + // const [validator] = inactiveValidators + // + // const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // const expectedValidator = { + // proTxHash: validator.pro_tx_hash, + // isActive: false, + // proposedBlocksAmount: 0, + // lastProposedBlockHeader: null + // } + // + // assert.deepEqual(body, expectedValidator) + // }) + // + // it('should return active validator by proTxHash', async () => { + // const [validator] = activeValidators + // + // const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // const expectedValidator = { + // proTxHash: validator.pro_tx_hash, + // isActive: true, + // proposedBlocksAmount: blocks.filter((block) => block.validator === validator.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === validator.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // } + // + // assert.deepEqual(body, expectedValidator) + // }) + // + // it('should return 404 if validator not found', async () => { + // await client.get('/validator/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF') + // .expect(404) + // .expect('Content-Type', 'application/json; charset=utf-8') + // }) + // }) describe('getValidators()', async () => { - describe('no filter', async () => { + // describe('no filter', async () => { + // it('should return default set of validators', async () => { + // const { body } = await client.get('/validators') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 1) + // assert.equal(body.pagination.limit, 10) + // assert.equal(body.pagination.total, validators.length) + // assert.equal(body.resultSet.length, 10) + // + // const expectedValidators = validators + // .slice(0, 10) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(body.resultSet, expectedValidators) + // }) + // + // it('should return default set of validators order desc', async () => { + // const { body } = await client.get('/validators?order=desc') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 1) + // assert.equal(body.pagination.limit, 10) + // assert.equal(body.pagination.total, validators.length) + // assert.equal(body.resultSet.length, 10) + // + // const expectedValidators = validators + // .toReversed() + // .slice(0, 10) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should be able to walk through pages', async () => { + // const { body } = await client.get('/validators?page=2') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 2) + // assert.equal(body.pagination.limit, 10) + // assert.equal(body.pagination.total, validators.length) + // assert.equal(body.resultSet.length, 10) + // + // const expectedValidators = validators + // .slice(10, 20) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: validators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should return custom page size', async () => { + // const { body } = await client.get('/validators?limit=7') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 1) + // assert.equal(body.pagination.limit, 7) + // assert.equal(body.pagination.total, validators.length) + // assert.equal(body.resultSet.length, 7) + // + // const expectedValidators = validators + // .slice(0, 7) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: validators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should allow to walk through pages with custom page size', async () => { + // const { body } = await client.get('/validators?limit=7&page=2') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 2) + // assert.equal(body.pagination.limit, 7) + // assert.equal(body.pagination.total, validators.length) + // assert.equal(body.resultSet.length, 7) + // + // const expectedValidators = validators + // .slice(7, 14) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should allow to walk through pages with custom page size desc', async () => { + // const { body } = await client.get('/validators?limit=5&page=4&order=desc') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 4) + // assert.equal(body.pagination.limit, 5) + // assert.equal(body.pagination.total, validators.length) + // assert.equal(body.resultSet.length, 5) + // + // const expectedValidators = validators + // .toReversed() + // .slice(15, 20) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should return less items when when it is out of bounds', async () => { + // const { body } = await client.get('/validators?limit=6&page=9') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 9) + // assert.equal(body.pagination.limit, 6) + // assert.equal(body.pagination.total, validators.length) + // assert.equal(body.resultSet.length, 2) + // + // const expectedValidators = validators + // .slice(48, 50) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should return less items when there is none on the one bound', async () => { + // const { body } = await client.get('/validators?limit=10&page=6') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 6) + // assert.equal(body.pagination.limit, 10) + // assert.equal(body.pagination.total, -1) + // assert.equal(body.resultSet.length, 0) + // + // const expectedValidators = [] + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // }) + + // describe('filter isActive = true', async () => { + // it('should return default set of validators', async () => { + // const { body } = await client.get('/validators?isActive=true') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 1) + // assert.equal(body.pagination.limit, 10) + // assert.equal(body.pagination.total, activeValidators.length) + // assert.equal(body.resultSet.length, 10) + // + // const expectedValidators = activeValidators + // .slice(0, 10) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: true, + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(body.resultSet, expectedValidators) + // }) + // + // it('should return default set of validators order desc', async () => { + // const { body } = await client.get('/validators?order=desc&isActive=true') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 1) + // assert.equal(body.pagination.limit, 10) + // assert.equal(body.pagination.total, activeValidators.length) + // assert.equal(body.resultSet.length, 10) + // + // const expectedValidators = activeValidators + // .toReversed() + // .slice(0, 10) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: true, + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should be able to walk through pages', async () => { + // const { body } = await client.get('/validators?page=2&isActive=true') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 2) + // assert.equal(body.pagination.limit, 10) + // assert.equal(body.pagination.total, activeValidators.length) + // assert.equal(body.resultSet.length, 10) + // + // const expectedValidators = activeValidators + // .slice(10, 20) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: true, + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should return custom page size', async () => { + // const { body } = await client.get('/validators?limit=7&isActive=true') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 1) + // assert.equal(body.pagination.limit, 7) + // assert.equal(body.pagination.total, activeValidators.length) + // assert.equal(body.resultSet.length, 7) + // + // const expectedValidators = activeValidators + // .slice(0, 7) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: true, + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should allow to walk through pages with custom page size', async () => { + // const { body } = await client.get('/validators?limit=7&page=2&isActive=true') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 2) + // assert.equal(body.pagination.limit, 7) + // assert.equal(body.pagination.total, activeValidators.length) + // assert.equal(body.resultSet.length, 7) + // + // const expectedValidators = activeValidators + // .slice(7, 14) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: true, + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should allow to walk through pages with custom page size desc', async () => { + // const { body } = await client.get('/validators?limit=5&page=4&order=desc&isActive=true') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 4) + // assert.equal(body.pagination.limit, 5) + // assert.equal(body.pagination.total, activeValidators.length) + // assert.equal(body.resultSet.length, 5) + // + // const expectedValidators = activeValidators + // .toReversed() + // .slice(15, 20) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: true, + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should return less items when when it is out of bounds', async () => { + // const { body } = await client.get('/validators?limit=4&page=8&isActive=true') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 8) + // assert.equal(body.pagination.limit, 4) + // assert.equal(body.pagination.total, activeValidators.length) + // assert.equal(body.resultSet.length, 2) + // + // const expectedValidators = activeValidators + // .slice(28, 30) + // .map(row => ({ + // proTxHash: row.pro_tx_hash, + // isActive: true, + // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + // lastProposedBlockHeader: blocks + // .filter((block) => block.validator === row.pro_tx_hash) + // .map((block) => BlockHeader.fromRow(block)) + // .map((blockHeader) => ({ + // hash: blockHeader.hash, + // height: blockHeader.height, + // timestamp: blockHeader.timestamp.toISOString(), + // blockVersion: blockHeader.blockVersion, + // appVersion: blockHeader.appVersion, + // l1LockedHeight: blockHeader.l1LockedHeight, + // validator: blockHeader.validator + // })) + // .toReversed()[0] ?? null + // })) + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // + // it('should return less items when there is none on the one bound', async () => { + // const { body } = await client.get('/validators?limit=10&page=4&isActive=true') + // .expect(200) + // .expect('Content-Type', 'application/json; charset=utf-8') + // + // assert.equal(body.pagination.page, 4) + // assert.equal(body.pagination.limit, 10) + // assert.equal(body.pagination.total, -1) + // assert.equal(body.resultSet.length, 0) + // + // const expectedValidators = [] + // + // assert.deepEqual(expectedValidators, body.resultSet) + // }) + // }) + + describe('filter isActive = false', async () => { it('should return default set of validators', async () => { - const { body } = await client.get('/validators') + const { body } = await client.get('/validators?isActive=false') .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') assert.equal(body.pagination.page, 1) assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length) + assert.equal(body.pagination.total, inactiveValidators.length) assert.equal(body.resultSet.length, 10) - const expectedValidators = validators + const expectedValidators = inactiveValidators .slice(0, 10) .map(row => ({ proTxHash: row.pro_tx_hash, - proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - lastProposedBlockHeader: blocks - .filter((block) => block.validator === row.pro_tx_hash) - .map((block) => BlockHeader.fromRow(block)) - .map((blockHeader) => ({ - hash: blockHeader.hash, - height: blockHeader.height, - timestamp: blockHeader.timestamp.toISOString(), - blockVersion: blockHeader.blockVersion, - appVersion: blockHeader.appVersion, - l1LockedHeight: blockHeader.l1LockedHeight, - validator: blockHeader.validator - })) - .toReversed()[0] ?? null + isActive: false, + proposedBlocksAmount: 0, + lastProposedBlockHeader: null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return default set of validators order desc', async () => { - const { body } = await client.get('/validators?order=desc') + const { body } = await client.get('/validators?order=desc&isActive=false') .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') assert.equal(body.pagination.page, 1) assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length) + assert.equal(body.pagination.total, inactiveValidators.length) assert.equal(body.resultSet.length, 10) - const expectedValidators = validators - .slice(validators.length - 10, validators.length) - .sort((a, b) => b.id - a.id) + const expectedValidators = inactiveValidators + .toReversed() + .slice(0, 10) .map(row => ({ proTxHash: row.pro_tx_hash, - proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - lastProposedBlockHeader: blocks - .filter((block) => block.validator === row.pro_tx_hash) - .map((block) => BlockHeader.fromRow(block)) - .map((blockHeader) => ({ - hash: blockHeader.hash, - height: blockHeader.height, - timestamp: blockHeader.timestamp.toISOString(), - blockVersion: blockHeader.blockVersion, - appVersion: blockHeader.appVersion, - l1LockedHeight: blockHeader.l1LockedHeight, - validator: blockHeader.validator - })) - .toReversed()[0] ?? null + isActive: false, + proposedBlocksAmount: 0, + lastProposedBlockHeader: null })) assert.deepEqual(expectedValidators, body.resultSet) }) it('should be able to walk through pages', async () => { - const { body } = await client.get('/validators?page=2') + const { body } = await client.get('/validators?page=2&isActive=false') .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') assert.equal(body.pagination.page, 2) assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length) + assert.equal(body.pagination.total, inactiveValidators.length) assert.equal(body.resultSet.length, 10) - const expectedValidators = validators + const expectedValidators = inactiveValidators .slice(10, 20) .map(row => ({ proTxHash: row.pro_tx_hash, - proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - lastProposedBlockHeader: blocks - .filter((block) => block.validator === row.pro_tx_hash) - .map((block) => BlockHeader.fromRow(block)) - .map((blockHeader) => ({ - hash: blockHeader.hash, - height: blockHeader.height, - timestamp: blockHeader.timestamp.toISOString(), - blockVersion: blockHeader.blockVersion, - appVersion: blockHeader.appVersion, - l1LockedHeight: blockHeader.l1LockedHeight, - validator: blockHeader.validator - })) - .toReversed()[0] ?? null + isActive: false, + proposedBlocksAmount: 0, + lastProposedBlockHeader: null })) assert.deepEqual(expectedValidators, body.resultSet) }) it('should return custom page size', async () => { - const { body } = await client.get('/validators?limit=7') + const { body } = await client.get('/validators?limit=7&isActive=false') .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') assert.equal(body.pagination.page, 1) assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, validators.length) + assert.equal(body.pagination.total, inactiveValidators.length) assert.equal(body.resultSet.length, 7) - const expectedValidators = validators + const expectedValidators = inactiveValidators .slice(0, 7) .map(row => ({ proTxHash: row.pro_tx_hash, - proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - lastProposedBlockHeader: blocks - .filter((block) => block.validator === row.pro_tx_hash) - .map((block) => BlockHeader.fromRow(block)) - .map((blockHeader) => ({ - hash: blockHeader.hash, - height: blockHeader.height, - timestamp: blockHeader.timestamp.toISOString(), - blockVersion: blockHeader.blockVersion, - appVersion: blockHeader.appVersion, - l1LockedHeight: blockHeader.l1LockedHeight, - validator: blockHeader.validator - })) - .toReversed()[0] ?? null + isActive: false, + proposedBlocksAmount: 0, + lastProposedBlockHeader: null })) assert.deepEqual(expectedValidators, body.resultSet) }) it('should allow to walk through pages with custom page size', async () => { - const { body } = await client.get('/validators?limit=7&page=2') + const { body } = await client.get('/validators?limit=7&page=2&isActive=false') .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') assert.equal(body.pagination.page, 2) assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, validators.length) + assert.equal(body.pagination.total, inactiveValidators.length) assert.equal(body.resultSet.length, 7) - const expectedValidators = validators + const expectedValidators = inactiveValidators .slice(7, 14) .map(row => ({ proTxHash: row.pro_tx_hash, - proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - lastProposedBlockHeader: blocks - .filter((block) => block.validator === row.pro_tx_hash) - .map((block) => BlockHeader.fromRow(block)) - .map((blockHeader) => ({ - hash: blockHeader.hash, - height: blockHeader.height, - timestamp: blockHeader.timestamp.toISOString(), - blockVersion: blockHeader.blockVersion, - appVersion: blockHeader.appVersion, - l1LockedHeight: blockHeader.l1LockedHeight, - validator: blockHeader.validator - })) - .toReversed()[0] ?? null + isActive: false, + proposedBlocksAmount: 0, + lastProposedBlockHeader: null })) assert.deepEqual(expectedValidators, body.resultSet) }) it('should allow to walk through pages with custom page size desc', async () => { - const { body } = await client.get('/validators?limit=5&page=4&order=desc') + const { body } = await client.get('/validators?limit=5&page=4&order=desc&isActive=false') .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') assert.equal(body.pagination.page, 4) assert.equal(body.pagination.limit, 5) - assert.equal(body.pagination.total, validators.length) + assert.equal(body.pagination.total, inactiveValidators.length) assert.equal(body.resultSet.length, 5) - const expectedValidators = validators - .sort((a, b) => b.id - a.id) + const expectedValidators = inactiveValidators + .toReversed() .slice(15, 20) .map(row => ({ proTxHash: row.pro_tx_hash, + isActive: false, proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, lastProposedBlockHeader: blocks .filter((block) => block.validator === row.pro_tx_hash) @@ -305,19 +773,20 @@ describe('Validators routes', () => { }) it('should return less items when when it is out of bounds', async () => { - const { body } = await client.get('/validators?limit=10&page=3&order=desc') + const { body } = await client.get('/validators?limit=3&page=7&isActive=false') .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') - assert.equal(body.pagination.page, 3) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length) - assert.equal(body.resultSet.length, 5) + assert.equal(body.pagination.page, 7) + assert.equal(body.pagination.limit, 3) + assert.equal(body.pagination.total, inactiveValidators.length) + assert.equal(body.resultSet.length, 2) - const expectedValidators = validators - .slice(20, 25) + const expectedValidators = inactiveValidators + .slice(18, 20) .map(row => ({ proTxHash: row.pro_tx_hash, + isActive: false, proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, lastProposedBlockHeader: blocks .filter((block) => block.validator === row.pro_tx_hash) @@ -338,7 +807,7 @@ describe('Validators routes', () => { }) it('should return less items when there is none on the one bound', async () => { - const { body } = await client.get('/validators?limit=10&page=4&order=desc') + const { body } = await client.get('/validators?limit=10&page=4&isActive=false') .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') @@ -353,441 +822,4 @@ describe('Validators routes', () => { }) }) }) - - - describe('with isActive=true filter', async () => { - it('should return default set of validators', async () => { - const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 10) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?isActive=true') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, activeValidators.length) - assert.equal(body.resultSet.length, 10) - - const expectedValidators = validators - .sort((a, b) => a.id - b.id) - .slice(0, 10) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should return default set of validators order desc', async () => { - const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 10) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?order=desc&isActive=true') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, activeValidators.length) - assert.equal(body.resultSet.length, 10) - - const expectedValidators = validators - .sort((a, b) => b.id - a.id) - .slice(0, 10) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should be able to walk through pages', async () => { - const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 20) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?page=2&isActive=true') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 2) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, activeValidators.length) - assert.equal(body.resultSet.length, 10) - - const expectedValidators = validators - .sort((a, b) => a.id - b.id) - .slice(10, 20) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should return custom page size', async () => { - const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 14) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=7&isActive=true') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, activeValidators.length) - assert.equal(body.resultSet.length, 7) - - const expectedValidators = validators - .sort((a, b) => a.id - b.id) - .slice(0, 7) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should allow to walk through pages with custom page size', async () => { - const activeValidators = validators.slice(0, 14) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=7&page=2&isActive=true') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 2) - assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, activeValidators.length) - assert.equal(body.resultSet.length, 7) - - const expectedValidators = validators - .slice(7, 14) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should allow to walk through pages with custom page size desc', async () => { - const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 12) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=3&page=4&order=desc&isActive=true') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 4) - assert.equal(body.pagination.limit, 3) - assert.equal(body.pagination.total, activeValidators.length) - assert.equal(body.resultSet.length, 3) - - const expectedValidators = validators - .sort((a, b) => b.id - a.id) - .slice(9, 12) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should return less items when when it is out of bounds', async () => { - const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 14) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=4&page=4&order=desc&isActive=true') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 4) - assert.equal(body.pagination.limit, 4) - assert.equal(body.pagination.total, activeValidators.length) - assert.equal(body.resultSet.length, 2) - - const expectedValidators = validators - .sort((a, b) => b.id - a.id) - .slice(12, 14) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: activeValidators.some(activeValidator => activeValidator.pro_tx_hash === row.pro_tx_hash) - })) - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should return less items when there is none on the one bound', async () => { - const activeValidators = validators.slice(0, 3) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=10&page=4&order=desc&isActive=true') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 4) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, -1) - assert.equal(body.resultSet.length, 0) - - const expectedValidators = [] - - assert.deepEqual(expectedValidators, body.resultSet) - }) - }) - - describe('with isActive=false filter', async () => { - it('should return default set of validators', async () => { - const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 10) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?isActive=false') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length - activeValidators.length) - assert.equal(body.resultSet.length, 10) - - const expectedValidators = validators - .sort((a, b) => a.id - b.id) - .slice(10, 20) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: false - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should return default set of validators order desc', async () => { - const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 10) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?order=desc&isActive=false') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length - activeValidators.length) - assert.equal(body.resultSet.length, 10) - - const expectedValidators = validators - .sort((a, b) => b.id - a.id) - .slice(10, 20) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: false - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should be able to walk through pages', async () => { - const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 10) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?page=2&isActive=false') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 2) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, validators.length - activeValidators.length) - assert.equal(body.resultSet.length, 10) - - const expectedValidators = validators - .sort((a, b) => a.id - b.id) - .slice(20, 30) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: false - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should return custom page size', async () => { - const activeValidators = validators.sort((a, b) => a.id - b.id).slice(0, 10) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=7&isActive=false') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, validators.length - activeValidators.length) - assert.equal(body.resultSet.length, 7) - - const expectedValidators = validators - .sort((a, b) => a.id - b.id) - .slice(10, 17) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: false - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should allow to walk through pages with custom page size', async () => { - const activeValidators = validators.slice(0, 14) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=7&page=2&isActive=false') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 2) - assert.equal(body.pagination.limit, 7) - assert.equal(body.pagination.total, validators.length - activeValidators.length) - assert.equal(body.resultSet.length, 7) - - const expectedValidators = validators - .slice(21, 28) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: false - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should allow to walk through pages with custom page size desc', async () => { - const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 10) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=3&page=4&order=desc&isActive=false') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 4) - assert.equal(body.pagination.limit, 3) - assert.equal(body.pagination.total, validators.length - activeValidators.length) - assert.equal(body.resultSet.length, 3) - - const expectedValidators = validators - .sort((a, b) => b.id - a.id) - .slice(19, 22) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: false - })) - - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should return less items when when it is out of bounds', async () => { - const activeValidators = validators.sort((a, b) => b.id - a.id).slice(0, 10) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=30&page=1&order=desc&isActive=false') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 1) - assert.equal(body.pagination.limit, 30) - assert.equal(body.pagination.total, validators.length - activeValidators.length) - assert.equal(body.resultSet.length, 20) - - const expectedValidators = validators - .sort((a, b) => b.id - a.id) - .slice(10, 30) - .map(row => ({ - proTxHash: row.pro_tx_hash, - isActive: false - })) - assert.deepEqual(expectedValidators, body.resultSet) - }) - - it('should return less items when there is none on the one bound', async () => { - const activeValidators = validators.slice(0, 3) - - mock.method(tenderdashRpc, 'getValidators', - async () => - Promise.resolve(activeValidators.map(activeValidator => - ({ proTxHash: activeValidator.pro_tx_hash })))) - - const { body } = await client.get('/validators?limit=10&page=4&order=desc&isActive=false') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - - assert.equal(body.pagination.page, 4) - assert.equal(body.pagination.limit, 10) - assert.equal(body.pagination.total, -1) - assert.equal(body.resultSet.length, 0) - - const expectedValidators = [] - - assert.deepEqual(expectedValidators, body.resultSet) - }) - }) }) From 8f741694450a956ed8d75f9292244061c8e7f78e Mon Sep 17 00:00:00 2001 From: pshenmic Date: Thu, 4 Jul 2024 02:34:56 +0700 Subject: [PATCH 3/6] Update documentation --- packages/api/README.md | 43 +++++++++++++----------- packages/frontend/src/app/api/content.md | 43 +++++++++++++----------- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/packages/api/README.md b/packages/api/README.md index e65216777..5f729ecfc 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -177,22 +177,24 @@ GET /blocks ### Validators Return all validators with pagination info. * `lastProposedBlockHeader` field is nullable +* `?isActive=true` boolean can be supplied in the query params to filter by isActive field ``` GET /validators { resultSet: [ { - "proTxHash": "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0", - "proposedBlocksAmount": 5, - "lastProposedBlockHeader": { - "height": 5, - "timestamp": "2024-06-23T13:51:44.154Z", - "hash": "7253F441FF6AEAC847F9E03672B9386E35FC8CBCFC4A7CC67557FCA10E342904", - "l1LockedHeight": 1337, - "appVersion": 1, - "blockVersion": 13 - "validator": "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0" + proTxHash: "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0", + isActive: true, + proposedBlocksAmount: 5, + lastProposedBlockHeader: { + height: 5, + timestamp: "2024-06-23T13:51:44.154Z", + hash: "7253F441FF6AEAC847F9E03672B9386E35FC8CBCFC4A7CC67557FCA10E342904", + l1LockedHeight: 1337, + appVersion: 1, + blockVersion: 13 + validator: "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0" } }, ... ], @@ -211,16 +213,17 @@ Get validator by ProTxHash. GET /validator/F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0 { - "proTxHash": "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0", - "proposedBlocksAmount": 5, - "lastProposedBlockHeader": { - "height": 5, - "timestamp": "2024-06-23T13:51:44.154Z", - "hash": "7253F441FF6AEAC847F9E03672B9386E35FC8CBCFC4A7CC67557FCA10E342904", - "l1LockedHeight": 1337, - "appVersion": 1, - "blockVersion": 13, - "validator": "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0" + proTxHash: "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0", + isActive: true, + proposedBlocksAmount: 5, + lastProposedBlockHeader: { + height: 5, + timestamp: "2024-06-23T13:51:44.154Z", + hash: "7253F441FF6AEAC847F9E03672B9386E35FC8CBCFC4A7CC67557FCA10E342904", + l1LockedHeight: 1337, + appVersion: 1, + blockVersion: 13, + validator: "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0" } } ``` diff --git a/packages/frontend/src/app/api/content.md b/packages/frontend/src/app/api/content.md index ef2918332..da63e0105 100644 --- a/packages/frontend/src/app/api/content.md +++ b/packages/frontend/src/app/api/content.md @@ -146,22 +146,24 @@ GET /blocks ### Validators Return all validators with pagination info. * `lastProposedBlockHeader` field is nullable +* `?isActive=true` boolean can be supplied in the query params to filter by isActive field ``` GET /validators { resultSet: [ { - "proTxHash": "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0", - "proposedBlocksAmount": 5, - "lastProposedBlockHeader": { - "height": 5, - "timestamp": "2024-06-23T13:51:44.154Z", - "hash": "7253F441FF6AEAC847F9E03672B9386E35FC8CBCFC4A7CC67557FCA10E342904", - "l1LockedHeight": 1337, - "appVersion": 1, - "blockVersion": 13 - "validator": "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0" + proTxHash: "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0", + isActive: true, + proposedBlocksAmount: 5, + lastProposedBlockHeader: { + height: 5, + timestamp: "2024-06-23T13:51:44.154Z", + hash: "7253F441FF6AEAC847F9E03672B9386E35FC8CBCFC4A7CC67557FCA10E342904", + l1LockedHeight: 1337, + appVersion: 1, + blockVersion: 13 + validator: "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0" } }, ... ], @@ -180,16 +182,17 @@ Get validator by ProTxHash. GET /validator/F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0 { - "proTxHash": "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0", - "proposedBlocksAmount": 5, - "lastProposedBlockHeader": { - "height": 5, - "timestamp": "2024-06-23T13:51:44.154Z", - "hash": "7253F441FF6AEAC847F9E03672B9386E35FC8CBCFC4A7CC67557FCA10E342904", - "l1LockedHeight": 1337, - "appVersion": 1, - "blockVersion": 13, - "validator": "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0" + proTxHash: "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0", + isActive: true, + proposedBlocksAmount: 5, + lastProposedBlockHeader: { + height: 5, + timestamp: "2024-06-23T13:51:44.154Z", + hash: "7253F441FF6AEAC847F9E03672B9386E35FC8CBCFC4A7CC67557FCA10E342904", + l1LockedHeight: 1337, + appVersion: 1, + blockVersion: 13, + validator: "F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0" } } ``` From 876ea1ab4687cace92eb551983101af4b65ff27d Mon Sep 17 00:00:00 2001 From: pshenmic Date: Thu, 4 Jul 2024 02:36:59 +0700 Subject: [PATCH 4/6] Uncomment all test cases --- .../api/test/integration/validators.spec.js | 1132 ++++++++--------- 1 file changed, 566 insertions(+), 566 deletions(-) diff --git a/packages/api/test/integration/validators.spec.js b/packages/api/test/integration/validators.spec.js index c7b066336..eb24d6338 100644 --- a/packages/api/test/integration/validators.spec.js +++ b/packages/api/test/integration/validators.spec.js @@ -56,574 +56,574 @@ describe('Validators routes', () => { await knex.destroy() }) - // describe('getValidatorByProTxHash()', async () => { - // it('should return inactive validator by proTxHash', async () => { - // const [validator] = inactiveValidators - // - // const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // const expectedValidator = { - // proTxHash: validator.pro_tx_hash, - // isActive: false, - // proposedBlocksAmount: 0, - // lastProposedBlockHeader: null - // } - // - // assert.deepEqual(body, expectedValidator) - // }) - // - // it('should return active validator by proTxHash', async () => { - // const [validator] = activeValidators - // - // const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // const expectedValidator = { - // proTxHash: validator.pro_tx_hash, - // isActive: true, - // proposedBlocksAmount: blocks.filter((block) => block.validator === validator.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === validator.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // } - // - // assert.deepEqual(body, expectedValidator) - // }) - // - // it('should return 404 if validator not found', async () => { - // await client.get('/validator/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF') - // .expect(404) - // .expect('Content-Type', 'application/json; charset=utf-8') - // }) - // }) + describe('getValidatorByProTxHash()', async () => { + it('should return inactive validator by proTxHash', async () => { + const [validator] = inactiveValidators + + const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + const expectedValidator = { + proTxHash: validator.pro_tx_hash, + isActive: false, + proposedBlocksAmount: 0, + lastProposedBlockHeader: null + } + + assert.deepEqual(body, expectedValidator) + }) + + it('should return active validator by proTxHash', async () => { + const [validator] = activeValidators + + const { body } = await client.get(`/validator/${validator.pro_tx_hash}`) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + const expectedValidator = { + proTxHash: validator.pro_tx_hash, + isActive: true, + proposedBlocksAmount: blocks.filter((block) => block.validator === validator.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === validator.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + } + + assert.deepEqual(body, expectedValidator) + }) + + it('should return 404 if validator not found', async () => { + await client.get('/validator/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF') + .expect(404) + .expect('Content-Type', 'application/json; charset=utf-8') + }) + }) describe('getValidators()', async () => { - // describe('no filter', async () => { - // it('should return default set of validators', async () => { - // const { body } = await client.get('/validators') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 1) - // assert.equal(body.pagination.limit, 10) - // assert.equal(body.pagination.total, validators.length) - // assert.equal(body.resultSet.length, 10) - // - // const expectedValidators = validators - // .slice(0, 10) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(body.resultSet, expectedValidators) - // }) - // - // it('should return default set of validators order desc', async () => { - // const { body } = await client.get('/validators?order=desc') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 1) - // assert.equal(body.pagination.limit, 10) - // assert.equal(body.pagination.total, validators.length) - // assert.equal(body.resultSet.length, 10) - // - // const expectedValidators = validators - // .toReversed() - // .slice(0, 10) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should be able to walk through pages', async () => { - // const { body } = await client.get('/validators?page=2') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 2) - // assert.equal(body.pagination.limit, 10) - // assert.equal(body.pagination.total, validators.length) - // assert.equal(body.resultSet.length, 10) - // - // const expectedValidators = validators - // .slice(10, 20) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: validators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should return custom page size', async () => { - // const { body } = await client.get('/validators?limit=7') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 1) - // assert.equal(body.pagination.limit, 7) - // assert.equal(body.pagination.total, validators.length) - // assert.equal(body.resultSet.length, 7) - // - // const expectedValidators = validators - // .slice(0, 7) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: validators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should allow to walk through pages with custom page size', async () => { - // const { body } = await client.get('/validators?limit=7&page=2') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 2) - // assert.equal(body.pagination.limit, 7) - // assert.equal(body.pagination.total, validators.length) - // assert.equal(body.resultSet.length, 7) - // - // const expectedValidators = validators - // .slice(7, 14) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should allow to walk through pages with custom page size desc', async () => { - // const { body } = await client.get('/validators?limit=5&page=4&order=desc') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 4) - // assert.equal(body.pagination.limit, 5) - // assert.equal(body.pagination.total, validators.length) - // assert.equal(body.resultSet.length, 5) - // - // const expectedValidators = validators - // .toReversed() - // .slice(15, 20) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should return less items when when it is out of bounds', async () => { - // const { body } = await client.get('/validators?limit=6&page=9') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 9) - // assert.equal(body.pagination.limit, 6) - // assert.equal(body.pagination.total, validators.length) - // assert.equal(body.resultSet.length, 2) - // - // const expectedValidators = validators - // .slice(48, 50) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should return less items when there is none on the one bound', async () => { - // const { body } = await client.get('/validators?limit=10&page=6') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 6) - // assert.equal(body.pagination.limit, 10) - // assert.equal(body.pagination.total, -1) - // assert.equal(body.resultSet.length, 0) - // - // const expectedValidators = [] - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // }) - - // describe('filter isActive = true', async () => { - // it('should return default set of validators', async () => { - // const { body } = await client.get('/validators?isActive=true') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 1) - // assert.equal(body.pagination.limit, 10) - // assert.equal(body.pagination.total, activeValidators.length) - // assert.equal(body.resultSet.length, 10) - // - // const expectedValidators = activeValidators - // .slice(0, 10) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: true, - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(body.resultSet, expectedValidators) - // }) - // - // it('should return default set of validators order desc', async () => { - // const { body } = await client.get('/validators?order=desc&isActive=true') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 1) - // assert.equal(body.pagination.limit, 10) - // assert.equal(body.pagination.total, activeValidators.length) - // assert.equal(body.resultSet.length, 10) - // - // const expectedValidators = activeValidators - // .toReversed() - // .slice(0, 10) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: true, - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should be able to walk through pages', async () => { - // const { body } = await client.get('/validators?page=2&isActive=true') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 2) - // assert.equal(body.pagination.limit, 10) - // assert.equal(body.pagination.total, activeValidators.length) - // assert.equal(body.resultSet.length, 10) - // - // const expectedValidators = activeValidators - // .slice(10, 20) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: true, - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should return custom page size', async () => { - // const { body } = await client.get('/validators?limit=7&isActive=true') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 1) - // assert.equal(body.pagination.limit, 7) - // assert.equal(body.pagination.total, activeValidators.length) - // assert.equal(body.resultSet.length, 7) - // - // const expectedValidators = activeValidators - // .slice(0, 7) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: true, - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should allow to walk through pages with custom page size', async () => { - // const { body } = await client.get('/validators?limit=7&page=2&isActive=true') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 2) - // assert.equal(body.pagination.limit, 7) - // assert.equal(body.pagination.total, activeValidators.length) - // assert.equal(body.resultSet.length, 7) - // - // const expectedValidators = activeValidators - // .slice(7, 14) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: true, - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should allow to walk through pages with custom page size desc', async () => { - // const { body } = await client.get('/validators?limit=5&page=4&order=desc&isActive=true') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 4) - // assert.equal(body.pagination.limit, 5) - // assert.equal(body.pagination.total, activeValidators.length) - // assert.equal(body.resultSet.length, 5) - // - // const expectedValidators = activeValidators - // .toReversed() - // .slice(15, 20) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: true, - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should return less items when when it is out of bounds', async () => { - // const { body } = await client.get('/validators?limit=4&page=8&isActive=true') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 8) - // assert.equal(body.pagination.limit, 4) - // assert.equal(body.pagination.total, activeValidators.length) - // assert.equal(body.resultSet.length, 2) - // - // const expectedValidators = activeValidators - // .slice(28, 30) - // .map(row => ({ - // proTxHash: row.pro_tx_hash, - // isActive: true, - // proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, - // lastProposedBlockHeader: blocks - // .filter((block) => block.validator === row.pro_tx_hash) - // .map((block) => BlockHeader.fromRow(block)) - // .map((blockHeader) => ({ - // hash: blockHeader.hash, - // height: blockHeader.height, - // timestamp: blockHeader.timestamp.toISOString(), - // blockVersion: blockHeader.blockVersion, - // appVersion: blockHeader.appVersion, - // l1LockedHeight: blockHeader.l1LockedHeight, - // validator: blockHeader.validator - // })) - // .toReversed()[0] ?? null - // })) - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // - // it('should return less items when there is none on the one bound', async () => { - // const { body } = await client.get('/validators?limit=10&page=4&isActive=true') - // .expect(200) - // .expect('Content-Type', 'application/json; charset=utf-8') - // - // assert.equal(body.pagination.page, 4) - // assert.equal(body.pagination.limit, 10) - // assert.equal(body.pagination.total, -1) - // assert.equal(body.resultSet.length, 0) - // - // const expectedValidators = [] - // - // assert.deepEqual(expectedValidators, body.resultSet) - // }) - // }) + describe('no filter', async () => { + it('should return default set of validators', async () => { + const { body } = await client.get('/validators') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .slice(0, 10) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(body.resultSet, expectedValidators) + }) + + it('should return default set of validators order desc', async () => { + const { body } = await client.get('/validators?order=desc') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .toReversed() + .slice(0, 10) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should be able to walk through pages', async () => { + const { body } = await client.get('/validators?page=2') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = validators + .slice(10, 20) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: validators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return custom page size', async () => { + const { body } = await client.get('/validators?limit=7') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = validators + .slice(0, 7) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: validators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size', async () => { + const { body } = await client.get('/validators?limit=7&page=2') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = validators + .slice(7, 14) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size desc', async () => { + const { body } = await client.get('/validators?limit=5&page=4&order=desc') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 5) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 5) + + const expectedValidators = validators + .toReversed() + .slice(15, 20) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when when it is out of bounds', async () => { + const { body } = await client.get('/validators?limit=6&page=9') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 9) + assert.equal(body.pagination.limit, 6) + assert.equal(body.pagination.total, validators.length) + assert.equal(body.resultSet.length, 2) + + const expectedValidators = validators + .slice(48, 50) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash), + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when there is none on the one bound', async () => { + const { body } = await client.get('/validators?limit=10&page=6') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 6) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, -1) + assert.equal(body.resultSet.length, 0) + + const expectedValidators = [] + + assert.deepEqual(expectedValidators, body.resultSet) + }) + }) + + describe('filter isActive = true', async () => { + it('should return default set of validators', async () => { + const { body } = await client.get('/validators?isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = activeValidators + .slice(0, 10) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: true, + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(body.resultSet, expectedValidators) + }) + + it('should return default set of validators order desc', async () => { + const { body } = await client.get('/validators?order=desc&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = activeValidators + .toReversed() + .slice(0, 10) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: true, + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should be able to walk through pages', async () => { + const { body } = await client.get('/validators?page=2&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 10) + + const expectedValidators = activeValidators + .slice(10, 20) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: true, + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return custom page size', async () => { + const { body } = await client.get('/validators?limit=7&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = activeValidators + .slice(0, 7) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: true, + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size', async () => { + const { body } = await client.get('/validators?limit=7&page=2&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 2) + assert.equal(body.pagination.limit, 7) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 7) + + const expectedValidators = activeValidators + .slice(7, 14) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: true, + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should allow to walk through pages with custom page size desc', async () => { + const { body } = await client.get('/validators?limit=5&page=4&order=desc&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 5) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 5) + + const expectedValidators = activeValidators + .toReversed() + .slice(15, 20) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: true, + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when when it is out of bounds', async () => { + const { body } = await client.get('/validators?limit=4&page=8&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 8) + assert.equal(body.pagination.limit, 4) + assert.equal(body.pagination.total, activeValidators.length) + assert.equal(body.resultSet.length, 2) + + const expectedValidators = activeValidators + .slice(28, 30) + .map(row => ({ + proTxHash: row.pro_tx_hash, + isActive: true, + proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length, + lastProposedBlockHeader: blocks + .filter((block) => block.validator === row.pro_tx_hash) + .map((block) => BlockHeader.fromRow(block)) + .map((blockHeader) => ({ + hash: blockHeader.hash, + height: blockHeader.height, + timestamp: blockHeader.timestamp.toISOString(), + blockVersion: blockHeader.blockVersion, + appVersion: blockHeader.appVersion, + l1LockedHeight: blockHeader.l1LockedHeight, + validator: blockHeader.validator + })) + .toReversed()[0] ?? null + })) + + assert.deepEqual(expectedValidators, body.resultSet) + }) + + it('should return less items when there is none on the one bound', async () => { + const { body } = await client.get('/validators?limit=10&page=4&isActive=true') + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.pagination.page, 4) + assert.equal(body.pagination.limit, 10) + assert.equal(body.pagination.total, -1) + assert.equal(body.resultSet.length, 0) + + const expectedValidators = [] + + assert.deepEqual(expectedValidators, body.resultSet) + }) + }) describe('filter isActive = false', async () => { it('should return default set of validators', async () => { From 5ac3469a991090f3dc86c2be240927fcc7a318d9 Mon Sep 17 00:00:00 2001 From: pshenmic Date: Thu, 4 Jul 2024 02:37:50 +0700 Subject: [PATCH 5/6] Remove redundant jsdoc --- packages/api/src/dao/ValidatorsDAO.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/api/src/dao/ValidatorsDAO.js b/packages/api/src/dao/ValidatorsDAO.js index 1a61d1406..60459fbe2 100644 --- a/packages/api/src/dao/ValidatorsDAO.js +++ b/packages/api/src/dao/ValidatorsDAO.js @@ -61,17 +61,6 @@ module.exports = class ValidatorsDAO { return Validator.fromRow(row) } - /** - * Get all active / non - * - * @param page {number} - * @param limit {number} - * @param order {string} - * @param isActive {undefined | boolean} - * @param validators {[{}]} validators (from Tenderdash RPC) - * - * @returns {Promise} - */ getValidators = async (page, limit, order, isActive, validators) => { const fromRank = ((page - 1) * limit) + 1 const toRank = fromRank + limit - 1 From 99b5df8435134325c8c8f75447b7084e38f9bf99 Mon Sep 17 00:00:00 2001 From: pshenmic Date: Thu, 4 Jul 2024 02:45:50 +0700 Subject: [PATCH 6/6] Fix deepEqual arguments --- .../api/test/integration/validators.spec.js | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/api/test/integration/validators.spec.js b/packages/api/test/integration/validators.spec.js index eb24d6338..3e916d703 100644 --- a/packages/api/test/integration/validators.spec.js +++ b/packages/api/test/integration/validators.spec.js @@ -178,7 +178,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should be able to walk through pages', async () => { @@ -212,7 +212,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return custom page size', async () => { @@ -246,7 +246,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should allow to walk through pages with custom page size', async () => { @@ -280,7 +280,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should allow to walk through pages with custom page size desc', async () => { @@ -315,7 +315,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return less items when when it is out of bounds', async () => { @@ -349,7 +349,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return less items when there is none on the one bound', async () => { @@ -364,7 +364,7 @@ describe('Validators routes', () => { const expectedValidators = [] - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) }) @@ -435,7 +435,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should be able to walk through pages', async () => { @@ -469,7 +469,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return custom page size', async () => { @@ -503,7 +503,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should allow to walk through pages with custom page size', async () => { @@ -537,7 +537,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should allow to walk through pages with custom page size desc', async () => { @@ -572,7 +572,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return less items when when it is out of bounds', async () => { @@ -606,7 +606,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return less items when there is none on the one bound', async () => { @@ -621,7 +621,7 @@ describe('Validators routes', () => { const expectedValidators = [] - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) }) @@ -668,7 +668,7 @@ describe('Validators routes', () => { lastProposedBlockHeader: null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should be able to walk through pages', async () => { @@ -690,7 +690,7 @@ describe('Validators routes', () => { lastProposedBlockHeader: null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return custom page size', async () => { @@ -712,7 +712,7 @@ describe('Validators routes', () => { lastProposedBlockHeader: null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should allow to walk through pages with custom page size', async () => { @@ -734,7 +734,7 @@ describe('Validators routes', () => { lastProposedBlockHeader: null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should allow to walk through pages with custom page size desc', async () => { @@ -769,7 +769,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return less items when when it is out of bounds', async () => { @@ -803,7 +803,7 @@ describe('Validators routes', () => { .toReversed()[0] ?? null })) - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) it('should return less items when there is none on the one bound', async () => { @@ -818,7 +818,7 @@ describe('Validators routes', () => { const expectedValidators = [] - assert.deepEqual(expectedValidators, body.resultSet) + assert.deepEqual(body.resultSet, expectedValidators) }) }) })