diff --git a/lib/settings.js b/lib/settings.js index 919e01854..13c5f2d1c 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -362,6 +362,32 @@ ${this.results.reduce((x, y) => { if (overrideRepoConfig) { repoConfig = this.mergeDeep.mergeDeep({}, repoConfig, overrideRepoConfig) } + + const archivePlugin = new Archive(this.nop, this.github, repo, repoConfig, this.log) + let isArchived, shouldArchive, shouldUnarchive + try { + ({ isArchived, shouldArchive, shouldUnarchive } = await archivePlugin.getState()) + + if (shouldUnarchive) { + this.log.debug(`Unarchiving repo ${repo.repo}`) + const unArchiveResults = await archivePlugin.sync() + this.appendToResults(unArchiveResults) + } else if (isArchived) { + // Archived repos are read-only on GitHub - any other plugin call would just 403/422. + this.log.debug(`Repo ${repo.repo} is archived, skipping settings sync`) + return + } + } 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]) + return + } else { + throw e + } + } + if (repoConfig) { try { this.log.debug(`found a matching repoconfig for this repo ${JSON.stringify(repoConfig)}`) @@ -369,15 +395,6 @@ ${this.results.reduce((x, y) => { const childPlugins = this.childPluginsList(repo) const RepoPlugin = Settings.PLUGINS.repository - const archivePlugin = new Archive(this.nop, this.github, repo, repoConfig, this.log) - const { shouldArchive, shouldUnarchive } = await archivePlugin.getState() - - if (shouldUnarchive) { - this.log.debug(`Unarchiving repo ${repo.repo}`) - const unArchiveResults = await archivePlugin.sync() - this.appendToResults(unArchiveResults) - } - const repoResults = await new RepoPlugin(this.nop, this.github, repo, repoConfig, this.installation_id, this.log, this.errors).sync() this.appendToResults(repoResults) diff --git a/test/unit/lib/settings.test.js b/test/unit/lib/settings.test.js index b36106518..9a2f58cc3 100644 --- a/test/unit/lib/settings.test.js +++ b/test/unit/lib/settings.test.js @@ -305,6 +305,89 @@ repository: }) }) // loadConfigs + describe('updateRepos', () => { + let settings + + beforeEach(() => { + mockSubOrg = undefined + stubConfig = { + restrictedRepos: {}, + repository: { topics: ['frontend'] } + } + settings = createSettings(stubConfig) + settings.subOrgConfigs = {} + settings.repoConfigs = {} + jest.spyOn(settings, 'childPluginsList').mockReturnValue([]) + }) + + it('skips RepoPlugin and child plugin sync for a repo that is already archived', async () => { + settings.github.rest.repos.get = jest.fn().mockResolvedValue({ data: { archived: true } }) + const RepoPlugin = Settings.PLUGINS.repository + const syncSpy = jest.spyOn(RepoPlugin.prototype, 'sync').mockResolvedValue([]) + + await settings.updateRepos(mockRepo) + + expect(syncSpy).not.toHaveBeenCalled() + syncSpy.mockRestore() + }) + + it('unarchives then runs plugin sync when the repo should be unarchived', async () => { + stubConfig.repository = { topics: ['frontend'], archived: false } + settings = createSettings(stubConfig) + settings.subOrgConfigs = {} + settings.repoConfigs = {} + jest.spyOn(settings, 'childPluginsList').mockReturnValue([]) + + settings.github.rest.repos.get = jest.fn().mockResolvedValue({ data: { archived: true } }) + settings.github.rest.repos.update = jest.fn().mockResolvedValue({ data: { archived: false } }) + const RepoPlugin = Settings.PLUGINS.repository + const syncSpy = jest.spyOn(RepoPlugin.prototype, 'sync').mockResolvedValue([]) + + await settings.updateRepos(mockRepo) + + expect(settings.github.rest.repos.update).toHaveBeenCalledWith( + expect.objectContaining({ archived: false }) + ) + expect(syncSpy).toHaveBeenCalled() + syncSpy.mockRestore() + }) + + it('runs RepoPlugin and child plugin sync for a repo that is not archived', async () => { + settings.github.rest.repos.get = jest.fn().mockResolvedValue({ data: { archived: false } }) + const RepoPlugin = Settings.PLUGINS.repository + const syncSpy = jest.spyOn(RepoPlugin.prototype, 'sync').mockResolvedValue([]) + + await settings.updateRepos(mockRepo) + + expect(syncSpy).toHaveBeenCalled() + syncSpy.mockRestore() + }) + + it('appends an ERROR NopCommand and does not throw when the archive check fails in nop mode', async () => { + settings.nop = true + settings.github.rest.repos.get = jest.fn().mockRejectedValue(new Error('boom')) + const RepoPlugin = Settings.PLUGINS.repository + const syncSpy = jest.spyOn(RepoPlugin.prototype, 'sync').mockResolvedValue([]) + jest.spyOn(settings, 'appendToResults') + + await expect(settings.updateRepos(mockRepo)).resolves.toBeUndefined() + + expect(settings.appendToResults).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ type: 'ERROR' }) + ]) + ) + expect(syncSpy).not.toHaveBeenCalled() + syncSpy.mockRestore() + }) + + it('rethrows when the archive check fails outside of nop mode', async () => { + settings.github.rest.repos.get = jest.fn().mockRejectedValue(new Error('boom')) + + await expect(settings.updateRepos(mockRepo)).rejects.toThrow('boom') + }) + }) // updateRepos + describe('loadYaml', () => { let settings;