From 3ca950f2ab79edb7b4295e3a4c5b7ba73be22bb4 Mon Sep 17 00:00:00 2001 From: Luis Videla Date: Tue, 22 Oct 2024 09:25:35 -0300 Subject: [PATCH 1/5] refactor: Add Projects class and tasks for ethernaut-optigov plugin --- .../src/internal/agora/Projects.js | 59 ++++++++++++ .../ethernaut-optigov/src/tasks/Projects.js | 96 +++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 packages/ethernaut-optigov/src/internal/agora/Projects.js create mode 100644 packages/ethernaut-optigov/src/tasks/Projects.js diff --git a/packages/ethernaut-optigov/src/internal/agora/Projects.js b/packages/ethernaut-optigov/src/internal/agora/Projects.js new file mode 100644 index 0000000..df8bf14 --- /dev/null +++ b/packages/ethernaut-optigov/src/internal/agora/Projects.js @@ -0,0 +1,59 @@ +const axios = require('axios') +// const debug = require('ethernaut-common/src/ui/debug') +const EthernautCliError = require('ethernaut-common/src/error/error') +const API_BASE_URL = 'https://vote.optimism.io/api/v1' + +class Projects { + // constructor(agora) { + // this.agora = agora + // } + + constructor() { + this.apiKey = process.env.AGORA_API_KEY + } + + async projects({ limit, offset } = { limit: 10, offset: 0 }) { + try { + const response = await axios.get( + `${API_BASE_URL}/projects?limit=${limit}&?offset=${offset}`, + { + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, + }, + ) + + // debug.log(`Nonce: ${response.data.nonce}`, 'ethernaut-optigov') + return response.data.data + } catch (error) { + throw new EthernautCliError( + 'ethernaut-optigov', + `Http status error: ${error.message}`, + ) + } + } + + async roundProjects({ roundId, limit = 10, offset = 0 }) { + try { + const response = await axios.get( + `${API_BASE_URL}/retrofunding/rounds/${roundId}/projects?limit=${limit}&offset=${offset}`, + { + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, + }, + ) + + // debug.log(`Nonce: ${response.data.nonce}`, 'ethernaut-optigov') + // console.log('roundProjects: ', response.data) + return response.data.data + } catch (error) { + throw new EthernautCliError( + 'ethernaut-optigov', + `Http status error: ${error.message}`, + ) + } + } +} + +module.exports = Projects diff --git a/packages/ethernaut-optigov/src/tasks/Projects.js b/packages/ethernaut-optigov/src/tasks/Projects.js new file mode 100644 index 0000000..eb6c96f --- /dev/null +++ b/packages/ethernaut-optigov/src/tasks/Projects.js @@ -0,0 +1,96 @@ +const types = require('ethernaut-common/src/validation/types') +const output = require('ethernaut-common/src/ui/output') +const Projects = require('../internal/agora/Projects') +// const Agora = require('../internal/agora/Agora') +// const { getLatestRound } = require('../internal/agora/utils/latest-round') + +require('../scopes/optigov') + .task( + 'projects', + 'Prints a list of projects registered in RetroPGF, given specified filters', + ) + .addParam( + 'round', + 'The round number to query. Defaults to "latest". Can also be "any" to query all rounds.', + 'latest', + types.string, + ) + .addOptionalParam( + 'name', + 'A filter to apply to the project names', + undefined, + types.string, + ) + .addOptionalParam( + 'category', + 'A filter to apply to the project category', + undefined, + types.string, + ) + .setAction(async ({ round, name, category }) => { + console.log('params: ', { round, name, category }) + + try { + let roundId + if (round === 'latest') + roundId = 5 // await getLatestRound() + else if (round === 'any') roundId = undefined + + let projects = await getProjects(roundId) + + projects = filterProjects(projects, name, category) + + return output.resultBox(printProjects(projects), 'Projects') + } catch (err) { + return output.errorBox(err) + } + }) + +function filterProjects(projects, name, category) { + const nameLower = name?.toLowerCase() + const categoryLower = category?.toLowerCase() + + return projects.filter((p) => { + // Filter by name if it's provided + const matchesName = nameLower + ? p.name.toLowerCase().includes(nameLower) + : true + + // Filter by category if it's provided + const matchesCategory = categoryLower + ? p.category.toLowerCase() === categoryLower + : true + + return matchesName && matchesCategory + }) +} + +function printProjects(projects) { + const strs = [] + + for (const project of projects) { + strs.push(` - ${project.name}: ${project.category}: ${project.description}`) + } + + return strs.join('\n\n') +} + +async function getProjects(roundId) { + const agora = new Projects() + + if (roundId === undefined) { + return await agora.projects() + } + + return await agora.roundProjects({ roundId }) +} + +// async function getProjects(roundId) { +// const agora = new Agora() + +// if (roundId === 'any') { +// return await agora.retro.projects() +// } + +// return await agora.retro.roundProjects({ roundId }) +// } From d3677ca6f014ce603d64f20622479288900251be Mon Sep 17 00:00:00 2001 From: Luis Videla Date: Tue, 22 Oct 2024 10:18:17 -0300 Subject: [PATCH 2/5] refactor: Add Agora class and related modules for ethernaut-optigov plugin --- .../src/internal/agora/Agora.js | 35 +++++++++++++++++++ .../src/internal/agora/Auth.js | 9 ++--- .../src/internal/agora/Projects.js | 25 +++++++------ .../ethernaut-optigov/src/tasks/Projects.js | 31 +++++----------- 4 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 packages/ethernaut-optigov/src/internal/agora/Agora.js diff --git a/packages/ethernaut-optigov/src/internal/agora/Agora.js b/packages/ethernaut-optigov/src/internal/agora/Agora.js new file mode 100644 index 0000000..e3430f0 --- /dev/null +++ b/packages/ethernaut-optigov/src/internal/agora/Agora.js @@ -0,0 +1,35 @@ +const axios = require('axios') +const debug = require('ethernaut-common/src/ui/debug') +const EthernautCliError = require('ethernaut-common/src/error/error') +const Auth = require('./Auth') +const Projects = require('./Projects') + +const API_BASE_URL = 'https://vote.optimism.io/api/v1' +const AGORA_API_KEY = process.env.AGORA_API_KEY + +class Agora { + constructor() { + this.auth = new Auth(this) + this.projects = new Projects(this) + } + + async getSpec() { + try { + const response = await axios.get(`${API_BASE_URL}/spec`, { + headers: { + Authorization: `Bearer ${AGORA_API_KEY}`, + }, + }) + + debug.log(`Spec: ${response.data}`, 'ethernaut-optigov') + return response.data + } catch (error) { + throw new EthernautCliError( + 'ethernaut-optigov', + `Http status error: ${error.message}`, + ) + } + } +} + +module.exports = Agora diff --git a/packages/ethernaut-optigov/src/internal/agora/Auth.js b/packages/ethernaut-optigov/src/internal/agora/Auth.js index 95fbb81..a25d4e8 100644 --- a/packages/ethernaut-optigov/src/internal/agora/Auth.js +++ b/packages/ethernaut-optigov/src/internal/agora/Auth.js @@ -1,15 +1,16 @@ const axios = require('axios') const debug = require('ethernaut-common/src/ui/debug') const EthernautCliError = require('ethernaut-common/src/error/error') -const API_BASE_URL = 'https://vote.optimism.io/api/v1' class Auth { - constructor() {} + constructor(agora) { + this.agora = agora + } // Get a nonce from the Agora API async getNonce() { try { - const response = await axios.get(`${API_BASE_URL}/auth/nonce`) + const response = await axios.get(`${this.agora.API_BASE_URL}/auth/nonce`) debug.log(`Nonce: ${response.data.nonce}`, 'ethernaut-optigov') return response.data @@ -25,7 +26,7 @@ class Auth { async authenticateWithAgora(message, signature, nonce) { try { const response = await axios.post( - `${API_BASE_URL}/auth/verify`, + `${this.agora.API_BASE_URL}/auth/verify`, { message, signature, diff --git a/packages/ethernaut-optigov/src/internal/agora/Projects.js b/packages/ethernaut-optigov/src/internal/agora/Projects.js index df8bf14..53544f8 100644 --- a/packages/ethernaut-optigov/src/internal/agora/Projects.js +++ b/packages/ethernaut-optigov/src/internal/agora/Projects.js @@ -1,29 +1,29 @@ const axios = require('axios') -// const debug = require('ethernaut-common/src/ui/debug') +const debug = require('ethernaut-common/src/ui/debug') const EthernautCliError = require('ethernaut-common/src/error/error') -const API_BASE_URL = 'https://vote.optimism.io/api/v1' class Projects { - // constructor(agora) { - // this.agora = agora - // } + constructor(agora) { + this.agora = agora + } - constructor() { - this.apiKey = process.env.AGORA_API_KEY + async getLatestRound() { + // TODO: Implement this + return 5 } async projects({ limit, offset } = { limit: 10, offset: 0 }) { try { const response = await axios.get( - `${API_BASE_URL}/projects?limit=${limit}&?offset=${offset}`, + `${this.agora.API_BASE_URL}/projects?limit=${limit}&?offset=${offset}`, { headers: { - Authorization: `Bearer ${this.apiKey}`, + Authorization: `Bearer ${this.agora.AGORA_API_KEY}`, }, }, ) - // debug.log(`Nonce: ${response.data.nonce}`, 'ethernaut-optigov') + debug.log(`Projects: ${response.data}`, 'ethernaut-optigov') return response.data.data } catch (error) { throw new EthernautCliError( @@ -36,7 +36,7 @@ class Projects { async roundProjects({ roundId, limit = 10, offset = 0 }) { try { const response = await axios.get( - `${API_BASE_URL}/retrofunding/rounds/${roundId}/projects?limit=${limit}&offset=${offset}`, + `${this.agora.API_BASE_URL}/retrofunding/rounds/${roundId}/projects?limit=${limit}&offset=${offset}`, { headers: { Authorization: `Bearer ${this.apiKey}`, @@ -44,8 +44,7 @@ class Projects { }, ) - // debug.log(`Nonce: ${response.data.nonce}`, 'ethernaut-optigov') - // console.log('roundProjects: ', response.data) + debug.log(`Round Projects: ${response.data}`, 'ethernaut-optigov') return response.data.data } catch (error) { throw new EthernautCliError( diff --git a/packages/ethernaut-optigov/src/tasks/Projects.js b/packages/ethernaut-optigov/src/tasks/Projects.js index eb6c96f..3356737 100644 --- a/packages/ethernaut-optigov/src/tasks/Projects.js +++ b/packages/ethernaut-optigov/src/tasks/Projects.js @@ -1,8 +1,6 @@ const types = require('ethernaut-common/src/validation/types') const output = require('ethernaut-common/src/ui/output') -const Projects = require('../internal/agora/Projects') -// const Agora = require('../internal/agora/Agora') -// const { getLatestRound } = require('../internal/agora/utils/latest-round') +const Agora = require('../internal/agora/Agora') require('../scopes/optigov') .task( @@ -28,15 +26,14 @@ require('../scopes/optigov') types.string, ) .setAction(async ({ round, name, category }) => { - console.log('params: ', { round, name, category }) - try { + const agora = new Agora() + let roundId - if (round === 'latest') - roundId = 5 // await getLatestRound() + if (round === 'latest') roundId = agora.projects.getLatestRound() else if (round === 'any') roundId = undefined - let projects = await getProjects(roundId) + let projects = await getProjects(agora, roundId) projects = filterProjects(projects, name, category) @@ -75,22 +72,10 @@ function printProjects(projects) { return strs.join('\n\n') } -async function getProjects(roundId) { - const agora = new Projects() - +async function getProjects(agora, roundId) { if (roundId === undefined) { - return await agora.projects() + return await agora.projects.projects() } - return await agora.roundProjects({ roundId }) + return await agora.projects.roundProjects({ roundId }) } - -// async function getProjects(roundId) { -// const agora = new Agora() - -// if (roundId === 'any') { -// return await agora.retro.projects() -// } - -// return await agora.retro.roundProjects({ roundId }) -// } From 591c7691e3cc83f5cedc0254aa1e5c9ae01c24dc Mon Sep 17 00:00:00 2001 From: Luis Videla Date: Thu, 24 Oct 2024 07:55:54 -0300 Subject: [PATCH 3/5] refactor: Update Agora class and related modules Auth & Projects --- .../src/internal/agora/Agora.js | 57 ++++++++++++++----- .../src/internal/agora/Auth.js | 54 +++++------------- .../src/internal/agora/Projects.js | 40 +++++-------- .../ethernaut-optigov/src/tasks/Projects.js | 30 +++++----- packages/ethernaut-optigov/src/tasks/login.js | 6 +- 5 files changed, 91 insertions(+), 96 deletions(-) diff --git a/packages/ethernaut-optigov/src/internal/agora/Agora.js b/packages/ethernaut-optigov/src/internal/agora/Agora.js index e3430f0..985259d 100644 --- a/packages/ethernaut-optigov/src/internal/agora/Agora.js +++ b/packages/ethernaut-optigov/src/internal/agora/Agora.js @@ -1,35 +1,62 @@ const axios = require('axios') -const debug = require('ethernaut-common/src/ui/debug') const EthernautCliError = require('ethernaut-common/src/error/error') -const Auth = require('./Auth') -const Projects = require('./Projects') +const debug = require('ethernaut-common/src/ui/debug') const API_BASE_URL = 'https://vote.optimism.io/api/v1' const AGORA_API_KEY = process.env.AGORA_API_KEY class Agora { constructor() { - this.auth = new Auth(this) - this.projects = new Projects(this) + this.apiKey = AGORA_API_KEY + this.apiBaseUrl = API_BASE_URL + this.bearerToken = null } - async getSpec() { - try { - const response = await axios.get(`${API_BASE_URL}/spec`, { - headers: { - Authorization: `Bearer ${AGORA_API_KEY}`, - }, - }) + // Axios instance setup + createAxiosInstance() { + const headers = { + Authorization: this.bearerToken + ? `Bearer ${this.bearerToken}` + : `Bearer ${this.apiKey}`, + } - debug.log(`Spec: ${response.data}`, 'ethernaut-optigov') - return response.data - } catch (error) { + return axios.create({ + baseURL: this.apiBaseUrl, + headers, + }) + } + + // Handle common API errors + handleError(error) { + if (error.response) { + throw new EthernautCliError( + 'ethernaut-optigov', + `Http status error: ${error.response.data}`, + ) + } else { throw new EthernautCliError( 'ethernaut-optigov', `Http status error: ${error.message}`, ) } } + + // common method for getting API spec + async getSpec() { + try { + const axiosInstance = this.createAxiosInstance() + const response = await axiosInstance.get('/spec') + debug.log(`Spec: ${response.data}`, 'ethernaut-optigov') + return response.data + } catch (error) { + this.handleError(error) + } + } + + // Set the bearer token after authentication + setBearerToken(token) { + this.bearerToken = token + } } module.exports = Agora diff --git a/packages/ethernaut-optigov/src/internal/agora/Auth.js b/packages/ethernaut-optigov/src/internal/agora/Auth.js index a25d4e8..fdfe73a 100644 --- a/packages/ethernaut-optigov/src/internal/agora/Auth.js +++ b/packages/ethernaut-optigov/src/internal/agora/Auth.js @@ -1,62 +1,38 @@ -const axios = require('axios') const debug = require('ethernaut-common/src/ui/debug') -const EthernautCliError = require('ethernaut-common/src/error/error') class Auth { constructor(agora) { this.agora = agora } - // Get a nonce from the Agora API async getNonce() { try { - const response = await axios.get(`${this.agora.API_BASE_URL}/auth/nonce`) - + const axiosInstance = this.agora.createAxiosInstance() + const response = await axiosInstance.get('/auth/nonce') debug.log(`Nonce: ${response.data.nonce}`, 'ethernaut-optigov') - return response.data + return response.data.nonce } catch (error) { - throw new EthernautCliError( - 'ethernaut-optigov', - `Http status error: ${error.message}`, - ) + this.agora.handleError(error) } } - // Authenticate with the Agora API async authenticateWithAgora(message, signature, nonce) { try { - const response = await axios.post( - `${this.agora.API_BASE_URL}/auth/verify`, - { - message, - signature, - nonce, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ) + const axiosInstance = this.agora.createAxiosInstance() + const response = await axiosInstance.post('/auth/verify', { + message, + signature, + nonce, + }) debug.log('Auth Response Status:', response.status) - // save the Bearer token - this.bearerToken = response.data.access_token + + // Set the Bearer token for future requests + this.agora.setBearerToken(response.data.access_token) + return response.data.access_token } catch (error) { - if (error.response) { - // Server responded with a status other than 2xx - throw new EthernautCliError( - 'ethernaut-optigov', - `Http status error: ${error.response.data}`, - ) - } else { - // Other errors (e.g., network issue) - throw new EthernautCliError( - 'ethernaut-optigov', - `Http status error: ${error.message}`, - ) - } + this.agora.handleError(error) } } } diff --git a/packages/ethernaut-optigov/src/internal/agora/Projects.js b/packages/ethernaut-optigov/src/internal/agora/Projects.js index 53544f8..c068122 100644 --- a/packages/ethernaut-optigov/src/internal/agora/Projects.js +++ b/packages/ethernaut-optigov/src/internal/agora/Projects.js @@ -1,6 +1,4 @@ -const axios = require('axios') const debug = require('ethernaut-common/src/ui/debug') -const EthernautCliError = require('ethernaut-common/src/error/error') class Projects { constructor(agora) { @@ -8,49 +6,37 @@ class Projects { } async getLatestRound() { - // TODO: Implement this - return 5 + return 5 // Placeholder, you can implement this logic } - async projects({ limit, offset } = { limit: 10, offset: 0 }) { + async getProjects({ limit = 10, offset = 0 } = {}) { try { - const response = await axios.get( - `${this.agora.API_BASE_URL}/projects?limit=${limit}&?offset=${offset}`, - { - headers: { - Authorization: `Bearer ${this.agora.AGORA_API_KEY}`, - }, - }, - ) + const axiosInstance = this.agora.createAxiosInstance() + const response = await axiosInstance.get('/projects', { + params: { limit, offset }, + }) debug.log(`Projects: ${response.data}`, 'ethernaut-optigov') return response.data.data } catch (error) { - throw new EthernautCliError( - 'ethernaut-optigov', - `Http status error: ${error.message}`, - ) + this.agora.handleError(error) } } - async roundProjects({ roundId, limit = 10, offset = 0 }) { + async getRoundProjects({ roundId, limit = 10, offset = 0 }) { try { - const response = await axios.get( - `${this.agora.API_BASE_URL}/retrofunding/rounds/${roundId}/projects?limit=${limit}&offset=${offset}`, + const axiosInstance = this.agora.createAxiosInstance() + const response = await axiosInstance.get( + `/retrofunding/rounds/${roundId}/projects`, { - headers: { - Authorization: `Bearer ${this.apiKey}`, - }, + params: { limit, offset }, }, ) debug.log(`Round Projects: ${response.data}`, 'ethernaut-optigov') return response.data.data } catch (error) { - throw new EthernautCliError( - 'ethernaut-optigov', - `Http status error: ${error.message}`, - ) + this.agora.handleError(error) } } } diff --git a/packages/ethernaut-optigov/src/tasks/Projects.js b/packages/ethernaut-optigov/src/tasks/Projects.js index 3356737..e4feaaf 100644 --- a/packages/ethernaut-optigov/src/tasks/Projects.js +++ b/packages/ethernaut-optigov/src/tasks/Projects.js @@ -1,5 +1,6 @@ const types = require('ethernaut-common/src/validation/types') const output = require('ethernaut-common/src/ui/output') +const Projects = require('../internal/agora/Projects') const Agora = require('../internal/agora/Agora') require('../scopes/optigov') @@ -27,17 +28,11 @@ require('../scopes/optigov') ) .setAction(async ({ round, name, category }) => { try { - const agora = new Agora() + const projects = await getProjects(round) - let roundId - if (round === 'latest') roundId = agora.projects.getLatestRound() - else if (round === 'any') roundId = undefined + const filteredProjects = filterProjects(projects, name, category) - let projects = await getProjects(agora, roundId) - - projects = filterProjects(projects, name, category) - - return output.resultBox(printProjects(projects), 'Projects') + return output.resultBox(printProjects(filteredProjects), 'Projects') } catch (err) { return output.errorBox(err) } @@ -72,10 +67,19 @@ function printProjects(projects) { return strs.join('\n\n') } -async function getProjects(agora, roundId) { - if (roundId === undefined) { - return await agora.projects.projects() +async function getProjects(round) { + const agora = new Agora() + const projects = new Projects(agora) + + let roundId + + if (round === 'latest') { + roundId = await projects.getLatestRound() + } + + if (!roundId) { + return await projects.getProjects() } - return await agora.projects.roundProjects({ roundId }) + return await projects.getRoundProjects({ roundId }) } diff --git a/packages/ethernaut-optigov/src/tasks/login.js b/packages/ethernaut-optigov/src/tasks/login.js index 9674c3c..b45d22f 100644 --- a/packages/ethernaut-optigov/src/tasks/login.js +++ b/packages/ethernaut-optigov/src/tasks/login.js @@ -1,7 +1,8 @@ -const Auth = require('../internal/agora/Auth') const output = require('ethernaut-common/src/ui/output') const { createSiweMessage } = require('../internal/Siwe') const EthernautCliError = require('ethernaut-common/src/error/error') +const Auth = require('../internal/agora/Auth') +const Agora = require('../internal/agora/Agora') require('../scopes/optigov') .task( @@ -24,7 +25,8 @@ require('../scopes/optigov') 'ethernaut-optigov', ) - const auth = new Auth() + const agora = new Agora() + const auth = new Auth(agora) const statement = 'Log in to Agoras RetroPGF API with SIWE.' const nonce = await auth.getNonce() From 71043156fbaffb5be8614411d04b79d1ce11b53d Mon Sep 17 00:00:00 2001 From: Luis Videla Date: Sat, 26 Oct 2024 11:44:29 -0300 Subject: [PATCH 4/5] refactor: add pagination for projects --- .../ethernaut-optigov/src/tasks/Projects.js | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/ethernaut-optigov/src/tasks/Projects.js b/packages/ethernaut-optigov/src/tasks/Projects.js index e4feaaf..16c0677 100644 --- a/packages/ethernaut-optigov/src/tasks/Projects.js +++ b/packages/ethernaut-optigov/src/tasks/Projects.js @@ -8,6 +8,18 @@ require('../scopes/optigov') 'projects', 'Prints a list of projects registered in RetroPGF, given specified filters', ) + .addOptionalParam( + 'limit', + 'The maximum number of proposals to fetch. Defaults to 10.', + 10, + types.int, + ) + .addOptionalParam( + 'offset', + 'The number of proposals to skip before starting to fetch. Defaults to 0.', + 0, + types.int, + ) .addParam( 'round', 'The round number to query. Defaults to "latest". Can also be "any" to query all rounds.', @@ -26,9 +38,9 @@ require('../scopes/optigov') undefined, types.string, ) - .setAction(async ({ round, name, category }) => { + .setAction(async ({ limit, offset, round, name, category }) => { try { - const projects = await getProjects(round) + const projects = await getProjects(limit, offset, round) const filteredProjects = filterProjects(projects, name, category) @@ -67,7 +79,7 @@ function printProjects(projects) { return strs.join('\n\n') } -async function getProjects(round) { +async function getProjects(limit, offset, round) { const agora = new Agora() const projects = new Projects(agora) @@ -78,8 +90,8 @@ async function getProjects(round) { } if (!roundId) { - return await projects.getProjects() + return await projects.getProjects(limit, offset) } - return await projects.getRoundProjects({ roundId }) + return await projects.getRoundProjects({ roundId, limit, offset }) } From 7a94efc75ee89d71c0ce843c9c337fc8a8d7fdae Mon Sep 17 00:00:00 2001 From: Luis Videla Date: Sat, 26 Oct 2024 11:45:00 -0300 Subject: [PATCH 5/5] test: add proejcts tests --- .../test/tasks/projects.test.js | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 packages/ethernaut-optigov/test/tasks/projects.test.js diff --git a/packages/ethernaut-optigov/test/tasks/projects.test.js b/packages/ethernaut-optigov/test/tasks/projects.test.js new file mode 100644 index 0000000..9a154c6 --- /dev/null +++ b/packages/ethernaut-optigov/test/tasks/projects.test.js @@ -0,0 +1,123 @@ +const assert = require('assert') +const Projects = require('../../src/internal/agora/Projects') +const hre = require('hardhat') +const output = require('ethernaut-common/src/ui/output') + +describe('projects task', function () { + let originalGetProjects, + originalGetRoundProjects, + originalGetLatestRound, + originalOutputResultBox, + originalOutputErrorBox + + beforeEach(function () { + // Mock Projects class methods + originalGetProjects = Projects.prototype.getProjects + originalGetRoundProjects = Projects.prototype.getRoundProjects + originalGetLatestRound = Projects.prototype.getLatestRound + + Projects.prototype.getProjects = async function ({ + limit: _limit, + offset: _offset, + }) { + return [ + { + name: 'Project 1', + category: 'Category A', + description: 'Description 1', + }, + { + name: 'Project 2', + category: 'Category B', + description: 'Description 2', + }, + ] + } + + Projects.prototype.getRoundProjects = async function ({ + roundId, + limit: _limit, + offset: _offset, + }) { + return [ + { + name: `Project for Round ${roundId}`, + category: 'Category A', + description: 'Description for round project', + }, + ] + } + + Projects.prototype.getLatestRound = async function () { + return 5 + } + + // Mock the output methods + originalOutputResultBox = output.resultBox + originalOutputErrorBox = output.errorBox + + output.resultBox = (content, title) => `${title}: ${content}` + output.errorBox = (error) => `Error: ${error.message}` + }) + + afterEach(function () { + // Restore the original methods after each test + Projects.prototype.getProjects = originalGetProjects + Projects.prototype.getRoundProjects = originalGetRoundProjects + Projects.prototype.getLatestRound = originalGetLatestRound + + output.resultBox = originalOutputResultBox + output.errorBox = originalOutputErrorBox + }) + + it('fetches a list of projects with limit and offset', async function () { + const result = await hre.run( + { scope: 'optigov', task: 'projects' }, + { limit: 2, offset: 0, round: 'any' }, + ) + assert.equal( + result, + 'Projects: - Project 1: Category A: Description 1\n\n - Project 2: Category B: Description 2', + ) + }) + + it('fetches projects for the latest round', async function () { + const result = await hre.run( + { scope: 'optigov', task: 'projects' }, + { round: 'latest', limit: 2, offset: 0 }, + ) + + assert.equal( + result, + 'Projects: - Project for Round 5: Category A: Description for round project', + ) + }) + + it('fetches projects filtered by name and category', async function () { + const result = await hre.run( + { scope: 'optigov', task: 'projects' }, + { + round: 'any', + limit: 2, + offset: 0, + name: 'Project 1', + category: 'Category A', + }, + ) + + assert.equal(result, 'Projects: - Project 1: Category A: Description 1') + }) + + it('handles errors gracefully when fetching projects fails', async function () { + Projects.prototype.getProjects = async function () { + throw new Error('Failed to fetch projects') + } + + const result = await hre.run( + { scope: 'optigov', task: 'projects' }, + { limit: 2, offset: 0, round: 'any' }, + ) + + assert.equal(result, 'Error: Failed to fetch projects') + }) +})