From 140a68847ebbdd0c1123df58ac65661f7d0313af Mon Sep 17 00:00:00 2001 From: randomcascade Date: Wed, 25 Sep 2024 13:49:51 -0500 Subject: [PATCH 1/2] feat: add unarchiving plugin --- lib/plugins/archive.js | 86 +++++++++++++++++++++++++++ lib/settings.js | 65 ++++++++++---------- test/unit/lib/plugins/archive.test.js | 55 +++++++++++++++++ 3 files changed, 176 insertions(+), 30 deletions(-) create mode 100644 lib/plugins/archive.js create mode 100644 test/unit/lib/plugins/archive.test.js diff --git a/lib/plugins/archive.js b/lib/plugins/archive.js new file mode 100644 index 00000000..6f0d76b5 --- /dev/null +++ b/lib/plugins/archive.js @@ -0,0 +1,86 @@ +const NopCommand = require('../nopcommand'); + +function returnValue(shouldContinue, nop) { + return { shouldContinue, nopCommands: nop }; +} + +module.exports = class Archive { + constructor(nop, github, repo, settings, log) { + this.github = github; + this.repo = repo; + this.settings = settings; + this.log = log; + this.nop = nop; + } + + // Returns true if plugin application should continue, false otherwise + async sync() { + // Fetch repository details using REST API + const { data: repoDetails } = await this.github.repos.get({ + owner: this.repo.owner, + repo: this.repo.repo + }); + if (typeof this.settings?.archived !== 'undefined') { + this.log.debug(`Checking if ${this.repo.owner}/${this.repo.repo} is archived`); + + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is ${repoDetails.archived ? 'archived' : 'not archived'}`); + + if (repoDetails.archived) { + if (this.settings.archived) { + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} already archived, inform other plugins should not run.`); + return returnValue(false); + } + else { + this.log.debug(`Unarchiving ${this.repo.owner}/${this.repo.repo}`); + if (this.nop) { + return returnValue(true, [new NopCommand(this.constructor.name, this.repo, this.github.repos.update.endpoint(this.settings), 'will unarchive')]); + } + else { + // Unarchive the repository using REST API + const updateResponse = await this.github.repos.update({ + owner: this.repo.owner, + repo: this.repo.repo, + archived: false + }); + this.log.debug(`Unarchive result ${JSON.stringify(updateResponse)}`); + + return returnValue(true); + } + } + } + else { + if (this.settings.archived) { + this.log.debug(`Archiving ${this.repo.owner}/${this.repo.repo}`); + if (this.nop) { + return returnValue(false, [new NopCommand(this.constructor.name, this.repo, this.github.repos.update.endpoint(this.settings), 'will archive')]); + } + else { + // Archive the repository using REST API + const updateResponse = await this.github.repos.update({ + owner: this.repo.owner, + repo: this.repo.repo, + archived: true + }); + this.log.debug(`Archive result ${JSON.stringify(updateResponse)}`); + + return returnValue(false); + } + } + else { + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is not archived, ignoring.`); + return returnValue(true); + } + } + } + else { + if (repoDetails.archived) { + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is archived, ignoring.`); + return returnValue(false); + } + else { + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is not archived, proceed as usual.`); + return returnValue(true); + } + } + } +}; \ No newline at end of file diff --git a/lib/settings.js b/lib/settings.js index 961aa4c6..e8573a7b 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -5,6 +5,7 @@ const errorTemplate = require('./error') const Glob = require('./glob') const NopCommand = require('./nopcommand') const MergeDeep = require('./mergeDeep') +const Archive = require('./plugins/archive') const env = require('./env') const CONFIG_PATH = env.CONFIG_PATH const eta = new Eta({ views: path.join(__dirname) }) @@ -318,38 +319,42 @@ ${this.results.reduce((x, y) => { if (overrideRepoConfig) { repoConfig = this.mergeDeep.mergeDeep({}, repoConfig, overrideRepoConfig) } - if (repoConfig) { - try { - this.log.debug(`found a matching repoconfig for this repo ${JSON.stringify(repoConfig)}`) - const childPlugins = this.childPluginsList(repo) - const RepoPlugin = Settings.PLUGINS.repository - return new RepoPlugin(this.nop, this.github, repo, repoConfig, this.installation_id, this.log, this.errors).sync().then(res => { - this.appendToResults(res) - return Promise.all( - childPlugins.map(([Plugin, config]) => { - return new Plugin(this.nop, this.github, repo, config, this.log, this.errors).sync() - })) - }).then(res => { - this.appendToResults(res) - }) - } catch (e) { - if (this.nop) { - const nopcommand = new NopCommand(this.constructor.name, this.repo, null, `${e}`, 'ERROR') - this.log.error(`NOPCOMMAND ${JSON.stringify(nopcommand)}`) - this.appendToResults([nopcommand]) - // throw e - } else { - throw e + const {shouldContinue, nopCommands} = await new Archive(this.nop, this.github, repo, repoConfig, this.log).sync() + if (nopCommands) this.appendToResults(nopCommands) + if (shouldContinue) { + if (repoConfig) { + try { + this.log.debug(`found a matching repoconfig for this repo ${JSON.stringify(repoConfig)}`) + const childPlugins = this.childPluginsList(repo) + const RepoPlugin = Settings.PLUGINS.repository + return new RepoPlugin(this.nop, this.github, repo, repoConfig, this.installation_id, this.log, this.errors).sync().then(res => { + this.appendToResults(res) + return Promise.all( + childPlugins.map(([Plugin, config]) => { + return new Plugin(this.nop, this.github, repo, config, this.log, this.errors).sync() + })) + }).then(res => { + this.appendToResults(res) + }) + } catch (e) { + if (this.nop) { + const nopcommand = new NopCommand(this.constructor.name, this.repo, null, `${e}`, 'ERROR') + this.log.error(`NOPCOMMAND ${JSON.stringify(nopcommand)}`) + this.appendToResults([nopcommand]) + // throw e + } else { + throw e + } } + } else { + this.log.debug(`Didnt find any a matching repoconfig for this repo ${JSON.stringify(repo)} in ${JSON.stringify(this.repoConfigs)}`) + const childPlugins = this.childPluginsList(repo) + return Promise.all(childPlugins.map(([Plugin, config]) => { + return new Plugin(this.nop, this.github, repo, config, this.log, this.errors).sync().then(res => { + this.appendToResults(res) + }) + })) } - } else { - this.log.debug(`Didnt find any a matching repoconfig for this repo ${JSON.stringify(repo)} in ${JSON.stringify(this.repoConfigs)}`) - const childPlugins = this.childPluginsList(repo) - return Promise.all(childPlugins.map(([Plugin, config]) => { - return new Plugin(this.nop, this.github, repo, config, this.log, this.errors).sync().then(res => { - this.appendToResults(res) - }) - })) } } diff --git a/test/unit/lib/plugins/archive.test.js b/test/unit/lib/plugins/archive.test.js new file mode 100644 index 00000000..49e528a0 --- /dev/null +++ b/test/unit/lib/plugins/archive.test.js @@ -0,0 +1,55 @@ +const Archive = require('../../../../lib/plugins/archive'); +const NopCommand = require('../../../../lib/nopcommand'); + +describe('Archive Plugin', () => { + let github; + let log; + let repo; + let settings; + let nop; + + beforeEach(() => { + github = { + repos: { + get: jest.fn() + } + }; + log = { + debug: jest.fn(), + error: jest.fn() + }; + repo = { owner: 'test-owner', repo: 'test-repo' }; + settings = {}; + nop = new NopCommand(); + }); + + it('should return false if the repository is archived', async () => { + github.repos.get.mockResolvedValue({ data: { archived: true } }); + + const archive = new Archive(nop, github, repo, settings, log); + const result = await archive.sync(); + + expect(result.shouldContinue).toBe(false); + expect(log.debug).toHaveBeenCalledWith('Repo test-owner/test-repo is archived, ignoring.'); + }); + + it('should return true if the repository is not archived', async () => { + github.repos.get.mockResolvedValue({ data: { archived: false } }); + + const archive = new Archive(nop, github, repo, settings, log); + const result = await archive.sync(); + + expect(result.shouldContinue).toBe(true); + expect(log.debug).toHaveBeenCalledWith('Repo test-owner/test-repo is not archived, proceed as usual.'); + }); + + it('should handle errors gracefully', async () => { + github.repos.get.mockRejectedValue(new Error('API error')); + + const archive = new Archive(nop, github, repo, settings, log); + const result = await archive.sync(); + + expect(result.shouldContinue).toBe(true); + expect(log.error).toHaveBeenCalledWith('Error fetching repository details: Error: API error'); + }); +}); \ No newline at end of file From aadac7622639a5f0192d46c7328c13182dbe7e86 Mon Sep 17 00:00:00 2001 From: randomcascade Date: Wed, 25 Sep 2024 15:48:41 -0500 Subject: [PATCH 2/2] fix: fixed some broken test cases and added trailing newling --- lib/plugins/archive.js | 2 +- test/unit/lib/plugins/archive.test.js | 35 ++++++++++++++++++--------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/plugins/archive.js b/lib/plugins/archive.js index 6f0d76b5..a481029c 100644 --- a/lib/plugins/archive.js +++ b/lib/plugins/archive.js @@ -83,4 +83,4 @@ module.exports = class Archive { } } } -}; \ No newline at end of file +}; diff --git a/test/unit/lib/plugins/archive.test.js b/test/unit/lib/plugins/archive.test.js index 49e528a0..9f5804f0 100644 --- a/test/unit/lib/plugins/archive.test.js +++ b/test/unit/lib/plugins/archive.test.js @@ -11,45 +11,58 @@ describe('Archive Plugin', () => { beforeEach(() => { github = { repos: { - get: jest.fn() + get: jest.fn(), + update: jest.fn() } }; log = { debug: jest.fn(), - error: jest.fn() }; repo = { owner: 'test-owner', repo: 'test-repo' }; settings = {}; - nop = new NopCommand(); + nop = false; }); - it('should return false if the repository is archived', async () => { + it('should return false if the repository is archived and settings.archived is true', async () => { github.repos.get.mockResolvedValue({ data: { archived: true } }); + settings.archived = true; const archive = new Archive(nop, github, repo, settings, log); const result = await archive.sync(); expect(result.shouldContinue).toBe(false); - expect(log.debug).toHaveBeenCalledWith('Repo test-owner/test-repo is archived, ignoring.'); }); - it('should return true if the repository is not archived', async () => { - github.repos.get.mockResolvedValue({ data: { archived: false } }); + it('should return true if the repository is archived and settings.archived is false', async () => { + github.repos.get.mockResolvedValue({ data: { archived: true } }); + settings.archived = false; const archive = new Archive(nop, github, repo, settings, log); const result = await archive.sync(); expect(result.shouldContinue).toBe(true); - expect(log.debug).toHaveBeenCalledWith('Repo test-owner/test-repo is not archived, proceed as usual.'); + expect(log.debug).toHaveBeenCalledWith('Unarchiving test-owner/test-repo'); }); - it('should handle errors gracefully', async () => { - github.repos.get.mockRejectedValue(new Error('API error')); + it('should return false if the repository is not archived and settings.archived is true', async () => { + github.repos.get.mockResolvedValue({ data: { archived: false } }); + settings.archived = true; + + const archive = new Archive(nop, github, repo, settings, log); + const result = await archive.sync(); + + expect(result.shouldContinue).toBe(false); + expect(log.debug).toHaveBeenCalledWith('Archiving test-owner/test-repo'); + }); + + it('should return true if the repository is not archived and settings.archived is false', async () => { + github.repos.get.mockResolvedValue({ data: { archived: false } }); + settings.archived = false; const archive = new Archive(nop, github, repo, settings, log); const result = await archive.sync(); expect(result.shouldContinue).toBe(true); - expect(log.error).toHaveBeenCalledWith('Error fetching repository details: Error: API error'); + expect(log.debug).toHaveBeenCalledWith('Repo test-owner/test-repo is not archived, ignoring.'); }); }); \ No newline at end of file