Skip to content

Commit a680b0b

Browse files
madkoodecyjphr
andauthored
Bug/archived repo (#991)
* fix: apply org-level settings before loading repository configurations * fix: enhance descriptions and add new properties for security features in settings.json * fix: update description for deprecated squash-merge commit title property in settings.json * fix issue with archived repos --------- Co-authored-by: Yadhav Jayaraman <57544838+decyjphr@users.noreply.github.com>
1 parent db9d78c commit a680b0b

4 files changed

Lines changed: 164 additions & 1 deletion

File tree

lib/settings.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,14 +370,19 @@ ${this.results.reduce((x, y) => {
370370
const RepoPlugin = Settings.PLUGINS.repository
371371

372372
const archivePlugin = new Archive(this.nop, this.github, repo, repoConfig, this.log)
373-
const { shouldArchive, shouldUnarchive } = await archivePlugin.getState()
373+
const { isArchived, shouldArchive, shouldUnarchive } = await archivePlugin.getState()
374374

375375
if (shouldUnarchive) {
376376
this.log.debug(`Unarchiving repo ${repo.repo}`)
377377
const unArchiveResults = await archivePlugin.sync()
378378
this.appendToResults(unArchiveResults)
379379
}
380380

381+
if (isArchived && !shouldUnarchive) {
382+
this.log.debug(`Skipping repo/child plugin updates for archived repo ${repo.repo}`)
383+
return
384+
}
385+
381386
const repoResults = await new RepoPlugin(this.nop, this.github, repo, repoConfig, this.installation_id, this.log, this.errors).sync()
382387
this.appendToResults(repoResults)
383388

schema/dereferenced/settings.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@
5050
}
5151
}
5252
},
53+
"code_security": {
54+
"type": "object",
55+
"description": "Use the `status` property to enable or disable GitHub Code Security for this repository.",
56+
"description": "Use the `status` property to enable or disable GitHub Advanced Security for this repository.\nFor more information, see \"[About GitHub Advanced\nSecurity](/github/getting-started-with-github/learning-about-github/about-github-advanced-security).\"\n\nFor standalone Code Scanning or Secret Protection products, this parameter cannot be used.",
57+
"properties": {
58+
"status": {
59+
"type": "string",
60+
"description": "Can be `enabled` or `disabled`."
61+
}
62+
}
63+
},
5364
"code_security": {
5465
"type": "object",
5566
"description": "Use the `status` property to enable or disable GitHub Code Security for this repository.",
@@ -90,6 +101,16 @@
90101
}
91102
}
92103
},
104+
"secret_scanning_ai_detection": {
105+
"type": "object",
106+
"description": "Use the `status` property to enable or disable secret scanning AI detection for this repository. For more information, see \"[Responsible detection of generic secrets with AI](https://docs.github.com/code-security/secret-scanning/using-advanced-secret-scanning-and-push-protection-features/generic-secret-detection/responsible-ai-generic-secrets).\"",
107+
"properties": {
108+
"status": {
109+
"type": "string",
110+
"description": "Can be `enabled` or `disabled`."
111+
}
112+
}
113+
},
93114
"secret_scanning_non_provider_patterns": {
94115
"type": "object",
95116
"description": "Use the `status` property to enable or disable secret scanning non-provider patterns for this repository. For more information, see \"[Supported secret scanning patterns](/code-security/secret-scanning/introduction/supported-secret-scanning-patterns#supported-secrets).\"",

test/unit/lib/plugins/archive.test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,56 @@ describe('Archive Plugin', () => {
9393
})
9494
})
9595

96+
describe('getState', () => {
97+
it('getState when repo is already archived and desired state is not set returns isArchived true shouldArchive false shouldUnarchive false', async () => {
98+
// Arrange
99+
github.rest.repos.get.mockResolvedValue({ data: { archived: true } })
100+
archive = new Archive(false, github, repo, {}, log)
101+
102+
// Act
103+
const result = await archive.getState()
104+
105+
// Assert
106+
expect(result).toEqual({
107+
isArchived: true,
108+
shouldArchive: false,
109+
shouldUnarchive: false
110+
})
111+
})
112+
113+
it('getState when repo is not archived and desired state is not set returns isArchived false shouldArchive false shouldUnarchive false', async () => {
114+
// Arrange
115+
github.rest.repos.get.mockResolvedValue({ data: { archived: false } })
116+
archive = new Archive(false, github, repo, {}, log)
117+
118+
// Act
119+
const result = await archive.getState()
120+
121+
// Assert
122+
expect(result).toEqual({
123+
isArchived: false,
124+
shouldArchive: false,
125+
shouldUnarchive: false
126+
})
127+
})
128+
129+
it('getState when repo is archived and desired state is false returns isArchived true shouldArchive false shouldUnarchive true', async () => {
130+
// Arrange
131+
github.rest.repos.get.mockResolvedValue({ data: { archived: true } })
132+
archive = new Archive(false, github, repo, { archived: false }, log)
133+
134+
// Act
135+
const result = await archive.getState()
136+
137+
// Assert
138+
expect(result).toEqual({
139+
isArchived: true,
140+
shouldArchive: false,
141+
shouldUnarchive: true
142+
})
143+
})
144+
})
145+
96146
describe('sync', () => {
97147
beforeEach(() => {
98148
archive = new Archive(false, github, repo, settings, log)

test/unit/lib/settings.test.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,4 +462,91 @@ repository:
462462
);
463463
});
464464
});
465+
466+
describe('updateRepos - archived repo skipping', () => {
467+
const Archive = require('../../../lib/plugins/archive')
468+
469+
let settings
470+
let mockRepoSync
471+
let originalRepoPlugin
472+
473+
beforeEach(() => {
474+
// Preserve the original RepoPlugin so it can be restored after each test
475+
originalRepoPlugin = Settings.PLUGINS.repository
476+
477+
// Replace RepoPlugin with a mock constructor whose sync() we can assert on
478+
mockRepoSync = jest.fn().mockResolvedValue([])
479+
Settings.PLUGINS.repository = jest.fn().mockImplementation(() => ({
480+
sync: mockRepoSync
481+
}))
482+
483+
// Build a Settings instance that will enter the `if (repoConfig)` branch:
484+
// config.repository must be defined so repoConfig is truthy
485+
settings = new Settings(
486+
false,
487+
stubContext,
488+
{ owner: 'test-org', repo: 'test-repo' },
489+
{ repository: { name: 'test-repo' } },
490+
'main'
491+
)
492+
493+
// Pre-set subOrgConfigs so updateRepos() does not call the async getSubOrgConfigs()
494+
settings.subOrgConfigs = {}
495+
496+
// Pre-set repoConfigs so getRepoOverrideConfig() does not throw on undefined
497+
settings.repoConfigs = {}
498+
})
499+
500+
afterEach(() => {
501+
// Restore the real RepoPlugin and all prototype spies
502+
Settings.PLUGINS.repository = originalRepoPlugin
503+
jest.restoreAllMocks()
504+
})
505+
506+
it('updateRepos when repo is already archived and not being unarchived does not call RepoPlugin sync', async () => {
507+
// Arrange
508+
jest.spyOn(Archive.prototype, 'getState').mockResolvedValue({
509+
isArchived: true,
510+
shouldArchive: false,
511+
shouldUnarchive: false
512+
})
513+
514+
// Act
515+
await settings.updateRepos({ owner: 'test-org', repo: 'test-repo' })
516+
517+
// Assert
518+
expect(mockRepoSync).not.toHaveBeenCalled()
519+
})
520+
521+
it('updateRepos when repo is archived but is being unarchived calls RepoPlugin sync', async () => {
522+
// Arrange
523+
jest.spyOn(Archive.prototype, 'getState').mockResolvedValue({
524+
isArchived: true,
525+
shouldArchive: false,
526+
shouldUnarchive: true
527+
})
528+
jest.spyOn(Archive.prototype, 'sync').mockResolvedValue([])
529+
530+
// Act
531+
await settings.updateRepos({ owner: 'test-org', repo: 'test-repo' })
532+
533+
// Assert
534+
expect(mockRepoSync).toHaveBeenCalledTimes(1)
535+
})
536+
537+
it('updateRepos when repo is not archived calls RepoPlugin sync', async () => {
538+
// Arrange
539+
jest.spyOn(Archive.prototype, 'getState').mockResolvedValue({
540+
isArchived: false,
541+
shouldArchive: false,
542+
shouldUnarchive: false
543+
})
544+
545+
// Act
546+
await settings.updateRepos({ owner: 'test-org', repo: 'test-repo' })
547+
548+
// Assert
549+
expect(mockRepoSync).toHaveBeenCalledTimes(1)
550+
})
551+
}) // updateRepos - archived repo skipping
465552
}) // Settings Tests

0 commit comments

Comments
 (0)