Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@ To update the schema, use the following command:
npm run db:generate-schema
```

## Usage

### Projects

To add a new project, use the following command:

```bash
node index.js project add [--name <name>] [--github-urls <urls...>] [--category <category>]
```

For example, to add a project named "express" with GitHub URLs:

```bash
node index.js project add --name express --github-urls https://github.com/expressjs https://github.com/pillarjs https://github.com/jshttp --category impact
```

## Debug mode

This project uses the [debug library](https://www.npmjs.com/package/debug), so you can always use the environmental variable `DEBUG=*` to print more detailed information of the execution.


## Linting

To lint the files, use the following command:
Expand Down
134 changes: 134 additions & 0 deletions __tests__/cli.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const inquirer = require('inquirer').default
const knexInit = require('knex')
const { getConfig } = require('../src/config/')
const { runAddProjectCommand } = require('../src/cli')
const { resetDatabase, getAllProjects } = require('./utils')

const { dbSettings } = getConfig('test')

// Mock inquirer for testing
jest.spyOn(inquirer, 'prompt').mockImplementation(async (questions) => {
const questionMap = {
'What is the name of the project?': 'eslint',
'Enter the GitHub URLs (comma-separated):': 'https://github.com/eslint',
'Select a category:': 'impact'
}
return questions.reduce((acc, question) => {
acc[question.name] = questionMap[question.message]
return acc
}, {})
})

let knex

beforeAll(() => {
knex = knexInit(dbSettings)
})
beforeEach(() => resetDatabase(knex))
afterEach(jest.clearAllMocks)
afterAll(async () => {
await resetDatabase(knex)
await knex.destroy()
})

describe('Interactive Mode', () => {
test('Add a project with name, GitHub URLs, and category', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
await runAddProjectCommand(knex, {})
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
expect(projects[0].name).toBe('eslint')
expect(projects[0].category).toBe('impact')
// @TODO: Add test for githubUrls when it is implemented
})

test('Prevent to add a project that already exists', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
await runAddProjectCommand(knex, {})
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
await expect(runAddProjectCommand(knex, {}))
.rejects
.toThrow('Project with name eslint already exists')
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
})
})

describe('Non-Interactive Mode', () => {
test('Add a project with name, GitHub URLs, and category', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
await runAddProjectCommand(knex, { name: 'eslint', githubUrls: ['https://github.com/eslint'], category: 'impact' })
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
expect(projects[0].name).toBe('eslint')
expect(projects[0].category).toBe('impact')
// @TODO: Add test for githubUrls when it is implemented
})

test('Prevent to add a project that already exists', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
await runAddProjectCommand(knex, { name: 'eslint', githubUrls: ['https://github.com/eslint'], category: 'impact' })
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
await expect(runAddProjectCommand(knex, { name: 'eslint', githubUrls: ['https://github.com/eslint'], category: 'impact' }))
.rejects
.toThrow('Project with name eslint already exists')
projects = await getAllProjects(knex)
expect(projects.length).toBe(1)
})

test('Error when no name is provided', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
await expect(runAddProjectCommand(knex, { githubUrls: ['https://github.com/eslint'], category: 'impact' }))
.rejects
.toThrow('Project name is required')
projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
})

test('Error when no GitHub URLs are provided', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
await expect(runAddProjectCommand(knex, { name: 'eslint', category: 'impact' }))
.rejects
.toThrow('GitHub URLs are required')
projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
})

test('Error when no category is provided', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
await expect(runAddProjectCommand(knex, { name: 'eslint', githubUrls: ['https://github.com/eslint'] }))
.rejects
.toThrow('Category is required')
projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
})

test('Error when invalid category is provided', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
await expect(runAddProjectCommand(knex, { name: 'eslint', githubUrls: ['https://github.com/eslint'], category: 'invalid' }))
.rejects
.toThrow('Invalid category, use one of the following')
projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
})

test('Error when invalid GitHub URLs are provided', async () => {
let projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
await expect(runAddProjectCommand(knex, { name: 'eslint', githubUrls: ['invalid-url'], category: 'impact' }))
.rejects
.toThrow('Invalid URL: invalid-url. Please enter valid GitHub URLs.')
projects = await getAllProjects(knex)
expect(projects.length).toBe(0)
})
})
23 changes: 23 additions & 0 deletions __tests__/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { validateGithubUrl } = require('../src/utils/index')

describe('validateGithubUrl', () => {
it('should return true for a valid GitHub URL', () => {
const url = 'https://github.com/user/repo'
expect(validateGithubUrl(url)).toBe(true)
})

it('should return false for a URL without https protocol', () => {
const url = 'http://github.com/user/repo'
expect(validateGithubUrl(url)).toBe(false)
})

it('should return false for a URL not containing github.com', () => {
const url = 'https://example.com/user/repo'
expect(validateGithubUrl(url)).toBe(false)
})

it('should return false for an invalid URL', () => {
const url = 'not-a-valid-url'
expect(validateGithubUrl(url)).toBe(false)
})
})
7 changes: 7 additions & 0 deletions __tests__/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const resetDatabase = (knex) => knex('projects').del()
const getAllProjects = (knex) => knex('projects').select('*')

module.exports = {
resetDatabase,
getAllProjects
}
29 changes: 29 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const { Command } = require('commander')
const { getConfig } = require('./src/config')
const { projectCategories, dbSettings } = getConfig()
const { runAddProjectCommand } = require('./src/cli')
const knex = require('knex')(dbSettings)

const program = new Command()

const project = program.command('project').description('Manage projects')

// Project commands
project
.command('add')
.description('Add a new project')
.option('--name <name>', 'Name of the project')
.option('--github-urls <urls...>', 'GitHub URLs of the project')
.option('--category <category>', `Category of the project. Choose from: ${projectCategories.join(', ')}`)
.action(async (options) => {
try {
await runAddProjectCommand(knex, options)
} catch (error) {
console.error('Error adding project:', error.message)
process.exit(1)
} finally {
await knex.destroy()
}
})

program.parse(process.argv)
16 changes: 16 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default {
collectCoverageFrom: [
'src/**/*.js'
],
coveragePathIgnorePatterns: [
'<rootDir>/__tests__/',
'<rootDir>/src/database/migrations/',
'<rootDir>/src/config/'
],
transformIgnorePatterns: [
'/node_modules/(?!octokit).+\\.js$'
],
modulePathIgnorePatterns: [
'<rootDir>/__tests__/utils/'
]
}
24 changes: 3 additions & 21 deletions knexfile.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,6 @@
const { normalizeBoolean } = require('@ulisesgascon/normalize-boolean')
const dbSettings = {
client: 'pg',
connection: {
host: process.env.DB_HOST || '0.0.0.0',
user: process.env.DB_USER || 'openjs',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'dashboard'
},
ssl: normalizeBoolean(process.env.DB_SSL),
pool: {
min: 2,
max: 10
},
migrations: {
directory: './src/database/migrations'
},
seeds: {
directory: './src/database/seeds'
}
}
const { getConfig } = require('./src/config')

const { dbSettings } = getConfig()

module.exports = {
development: dbSettings
Expand Down
Loading