Skip to content

Commit 40a956e

Browse files
authored
Merge pull request #32 from secure-dashboards/feat/add-repositories
2 parents c96c7b6 + fe00c8b commit 40a956e

File tree

9 files changed

+468
-24
lines changed

9 files changed

+468
-24
lines changed

__tests__/cli/__snapshots__/workflows.test.js.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ exports[`list - Non-Interactive Mode Should provide a list of available workflow
55
{
66
"description": "Check the organizations stored and update the information with the GitHub API.",
77
"name": "update-github-orgs",
8+
"workflow": [Function],
9+
},
10+
{
11+
"description": "Check the organizations stored and update/create the information related to the repositories with the GitHub API.",
12+
"name": "upsert-github-repositories",
13+
"workflow": [Function],
814
},
915
]
1016
`;

__tests__/cli/workflows.test.js

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
const inquirer = require('inquirer').default
22
const knexInit = require('knex')
3+
const { simplifyObject } = require('@ulisesgascon/simplify-object')
34
const { getConfig } = require('../../src/config')
45
const { runWorkflowCommand, listWorkflowCommand } = require('../../src/cli')
5-
const { resetDatabase, getAllProjects, getAllGithubOrgs, addGithubOrg, addProject } = require('../../__utils__')
6+
const { resetDatabase, getAllProjects, getAllGithubOrgs, addGithubOrg, addProject, getAllGithubRepos, addGithubRepo } = require('../../__utils__')
67
const { github } = require('../../src/providers')
7-
const { sampleGithubOrg } = require('../../__fixtures__')
8+
const { sampleGithubOrg, sampleGithubListOrgRepos, sampleGithubRepository } = require('../../__fixtures__')
89

910
const { dbSettings } = getConfig('test')
1011

@@ -47,23 +48,23 @@ describe('run GENERIC - Non-Interactive Mode', () => {
4748
})
4849

4950
describe('run update-github-orgs', () => {
50-
// Mock inquirer for testing
51-
jest.spyOn(inquirer, 'prompt').mockImplementation(async (questions) => {
52-
const questionMap = {
53-
'What is the name of the workflow?': 'update-github-orgs'
54-
}
55-
return questions.reduce((acc, question) => {
56-
acc[question.name] = questionMap[question.message]
57-
return acc
58-
}, {})
59-
})
51+
// // Mock inquirer for testing
52+
// jest.spyOn(inquirer, 'prompt').mockImplementation(async (questions) => {
53+
// const questionMap = {
54+
// 'What is the name of the workflow?': 'update-github-orgs'
55+
// }
56+
// return questions.reduce((acc, question) => {
57+
// acc[question.name] = questionMap[question.message]
58+
// return acc
59+
// }, {})
60+
// })
6061

6162
test('Should throw an error when no Github orgs are stored in the database', async () => {
6263
const projects = await getAllProjects(knex)
6364
expect(projects.length).toBe(0)
6465
const githubOrgs = await getAllGithubOrgs(knex)
6566
expect(githubOrgs.length).toBe(0)
66-
await expect(runWorkflowCommand(knex, {}))
67+
await expect(runWorkflowCommand(knex, { name: 'update-github-orgs' }))
6768
.rejects
6869
.toThrow('No organizations found. Please add organizations/projects before running this workflow.')
6970
})
@@ -79,7 +80,7 @@ describe('run update-github-orgs', () => {
7980
expect(githubOrgs[0].description).toBe(null)
8081
// Mock the fetchOrgByLogin method
8182
jest.spyOn(github, 'fetchOrgByLogin').mockResolvedValue(sampleGithubOrg)
82-
await runWorkflowCommand(knex, {})
83+
await runWorkflowCommand(knex, { name: 'update-github-orgs' })
8384
// Check the database changes
8485
githubOrgs = await getAllGithubOrgs(knex)
8586
expect(githubOrgs.length).toBe(1)
@@ -88,3 +89,61 @@ describe('run update-github-orgs', () => {
8889

8990
test.todo('Should throw an error when the Github API is not available')
9091
})
92+
93+
describe('run upsert-github-repositories', () => {
94+
test('Should throw an error when no Github orgs are stored in the database', async () => {
95+
const projects = await getAllProjects(knex)
96+
expect(projects.length).toBe(0)
97+
const githubOrgs = await getAllGithubOrgs(knex)
98+
expect(githubOrgs.length).toBe(0)
99+
await expect(runWorkflowCommand(knex, { name: 'upsert-github-repositories' }))
100+
.rejects
101+
.toThrow('No organizations found. Please add organizations/projects before running this workflow.')
102+
})
103+
test('Should add the repositories related to the organization', async () => {
104+
// Prepare the database
105+
const project = await addProject(knex, { name: sampleGithubOrg.login, category: 'impact' })
106+
await addGithubOrg(knex, { login: sampleGithubOrg.login, html_url: sampleGithubOrg.html_url, project_id: project.id })
107+
const projects = await getAllProjects(knex)
108+
expect(projects.length).toBe(1)
109+
const githubOrgs = await getAllGithubOrgs(knex)
110+
expect(githubOrgs.length).toBe(1)
111+
let githubRepos = await getAllGithubRepos(knex)
112+
expect(githubRepos.length).toBe(0)
113+
// Mock the github methods used
114+
jest.spyOn(github, 'fetchOrgReposListByLogin').mockResolvedValue(sampleGithubListOrgRepos)
115+
jest.spyOn(github, 'fetchRepoByFullName').mockResolvedValue(sampleGithubRepository)
116+
await runWorkflowCommand(knex, { name: 'upsert-github-repositories' })
117+
// Check the database changes
118+
githubRepos = await getAllGithubRepos(knex)
119+
expect(githubRepos.length).toBe(1)
120+
expect(githubRepos[0].description).toBe(sampleGithubRepository.description)
121+
})
122+
test('Should update the repositories related to the organization', async () => {
123+
// Prepare the database
124+
const project = await addProject(knex, { name: sampleGithubOrg.login, category: 'impact' })
125+
const org = await addGithubOrg(knex, { login: sampleGithubOrg.login, html_url: sampleGithubOrg.html_url, project_id: project.id })
126+
const githubRepoData = simplifyObject(sampleGithubRepository, {
127+
include: ['node_id', 'name', 'full_name', 'html_url', 'url', 'git_url', 'ssh_url', 'clone_url', 'visibility', 'default_branch']
128+
})
129+
githubRepoData.github_organization_id = org.id
130+
githubRepoData.description = 'existing data'
131+
await addGithubRepo(knex, githubRepoData)
132+
const projects = await getAllProjects(knex)
133+
expect(projects.length).toBe(1)
134+
const githubOrgs = await getAllGithubOrgs(knex)
135+
expect(githubOrgs.length).toBe(1)
136+
let githubRepos = await getAllGithubRepos(knex)
137+
expect(githubRepos.length).toBe(1)
138+
expect(githubRepos[0].description).toBe('existing data')
139+
// Mock the github methods used
140+
jest.spyOn(github, 'fetchOrgReposListByLogin').mockResolvedValue(sampleGithubListOrgRepos)
141+
jest.spyOn(github, 'fetchRepoByFullName').mockResolvedValue(sampleGithubRepository)
142+
await runWorkflowCommand(knex, { name: 'upsert-github-repositories' })
143+
// Check the database changes
144+
githubRepos = await getAllGithubRepos(knex)
145+
expect(githubRepos.length).toBe(1)
146+
expect(githubRepos[0].description).toBe(sampleGithubRepository.description)
147+
})
148+
test.todo('Should throw an error when the Github API is not available')
149+
})

__utils__/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const resetDatabase = async (knex) => {
2+
await knex.raw('TRUNCATE TABLE github_repositories RESTART IDENTITY CASCADE')
23
await knex.raw('TRUNCATE TABLE github_organizations RESTART IDENTITY CASCADE')
34
await knex.raw('TRUNCATE TABLE projects RESTART IDENTITY CASCADE')
45
}
@@ -16,10 +17,18 @@ const addGithubOrg = async (knex, data) => {
1617
return githubOrg
1718
}
1819

20+
const getAllGithubRepos = (knex) => knex('github_repositories').select('*')
21+
const addGithubRepo = async (knex, data) => {
22+
const [githubRepo] = await knex('github_repositories').insert(data).returning('*')
23+
return githubRepo
24+
}
25+
1926
module.exports = {
2027
resetDatabase,
2128
getAllProjects,
2229
getAllGithubOrgs,
2330
addProject,
24-
addGithubOrg
31+
addGithubOrg,
32+
getAllGithubRepos,
33+
addGithubRepo
2534
}

src/cli/workflows.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
const inquirer = require('inquirer').default
2-
const { updateGithubOrgs } = require('../workflows')
2+
const debug = require('debug')('cli:workflows')
3+
const { updateGithubOrgs, upsertGithubRepositories } = require('../workflows')
34
const { logger } = require('../utils')
45

56
const commandList = [{
67
name: 'update-github-orgs',
7-
description: 'Check the organizations stored and update the information with the GitHub API.'
8+
description: 'Check the organizations stored and update the information with the GitHub API.',
9+
workflow: updateGithubOrgs
10+
}, {
11+
name: 'upsert-github-repositories',
12+
description: 'Check the organizations stored and update/create the information related to the repositories with the GitHub API.',
13+
workflow: upsertGithubRepositories
814
}]
915

1016
const validCommandNames = commandList.map(({ name }) => name)
@@ -32,9 +38,9 @@ async function runWorkflowCommand (knex, options = {}) {
3238
}
3339
])
3440

35-
if (answers.name === 'update-github-orgs') {
36-
await updateGithubOrgs(knex)
37-
}
41+
const command = commandList.find(({ name }) => name === answers.name)
42+
debug(`Running workflow: ${command.name}`)
43+
await command.workflow(knex)
3844

3945
return answers
4046
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
exports.up = async (knex) => {
2+
// Create 'github_repositories' table
3+
await knex.schema.createTable('github_repositories', (table) => {
4+
table.increments('id').primary() // Primary key
5+
table.string('node_id').unique().notNullable()
6+
table.string('name').notNullable()
7+
table.string('full_name').notNullable()
8+
table.string('html_url').notNullable()
9+
table.text('description')
10+
table.boolean('fork')
11+
table.string('url').notNullable()
12+
table.string('git_url').notNullable()
13+
table.string('ssh_url').notNullable()
14+
table.string('clone_url').notNullable()
15+
table.string('svn_url')
16+
table.string('homepage')
17+
table.integer('size')
18+
table.integer('stargazers_count')
19+
table.integer('watchers_count')
20+
table.string('language')
21+
table.boolean('has_issues')
22+
table.boolean('has_projects')
23+
table.boolean('has_downloads')
24+
table.boolean('has_wiki')
25+
table.boolean('has_pages')
26+
table.boolean('has_discussions')
27+
table.integer('forks_count')
28+
table.string('mirror_url')
29+
table.boolean('archived')
30+
table.boolean('disabled')
31+
table.integer('open_issues_count')
32+
table.boolean('allow_forking')
33+
table.boolean('is_template')
34+
table.boolean('web_commit_signoff_required')
35+
table.specificType('topics', 'text[]') // Array of strings
36+
table.enu('visibility', ['public', 'private', 'internal']).notNullable()
37+
table.string('default_branch').notNullable()
38+
table.boolean('allow_squash_merge')
39+
table.boolean('allow_merge_commit')
40+
table.boolean('allow_rebase_merge')
41+
table.boolean('allow_auto_merge')
42+
table.boolean('delete_branch_on_merge')
43+
table.boolean('allow_update_branch')
44+
table.boolean('use_squash_pr_title_as_default')
45+
table.string('squash_merge_commit_message')
46+
table.string('squash_merge_commit_title')
47+
table.string('merge_commit_message')
48+
table.string('merge_commit_title')
49+
table.integer('network_count')
50+
table.integer('subscribers_count')
51+
table.integer('github_repo_id').unique()
52+
table.timestamp('github_created_at')
53+
table.timestamp('github_updated_at')
54+
table.timestamp('github_archived_at')
55+
table.string('license_key')
56+
table.string('license_name')
57+
table.string('license_spdx_id')
58+
table.string('license_url')
59+
table.string('license_node_id')
60+
table.enu('secret_scanning_status', ['enabled', 'disabled']).defaultTo('disabled')
61+
table.enu('secret_scanning_push_protection_status', ['enabled', 'disabled']).defaultTo('disabled')
62+
table.enu('dependabot_security_updates_status', ['enabled', 'disabled']).defaultTo('disabled')
63+
table.enu('secret_scanning_non_provider_patterns_status', ['enabled', 'disabled']).defaultTo('disabled')
64+
table.enu('secret_scanning_validity_checks_status', ['enabled', 'disabled']).defaultTo('disabled')
65+
66+
// Foreign key to 'github_organizations' table
67+
table
68+
.integer('github_organization_id')
69+
.unsigned()
70+
.references('id')
71+
.inTable('github_organizations')
72+
.onDelete('CASCADE') // Deletes repository if the organization is deleted
73+
.onUpdate('CASCADE') // Updates repository if the organization ID is updated
74+
75+
// Timestamps
76+
table.timestamp('created_at').defaultTo(knex.fn.now()).notNullable()
77+
table.timestamp('updated_at').defaultTo(knex.fn.now()).notNullable()
78+
})
79+
80+
// Add trigger to 'github_repositories' table
81+
await knex.raw(`
82+
CREATE TRIGGER set_updated_at_github_repositories
83+
BEFORE UPDATE ON github_repositories
84+
FOR EACH ROW
85+
EXECUTE FUNCTION update_updated_at_column();
86+
`)
87+
}
88+
89+
exports.down = async (knex) => {
90+
// Drop trigger
91+
await knex.raw('DROP TRIGGER IF EXISTS set_updated_at_github_repositories ON github_repositories;')
92+
// Drop table
93+
await knex.schema.dropTableIfExists('github_repositories')
94+
}

0 commit comments

Comments
 (0)