Skip to content

Commit

Permalink
add tests and docs for extends feature
Browse files Browse the repository at this point in the history
  • Loading branch information
willnorris committed Apr 8, 2021
1 parent dfed5bb commit 43b95b6
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 0 deletions.
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,56 @@ rules:
...
```

### Extending Rulesets

A ruleset can extend another ruleset, in which case the two files will be
recursively merged. Extended rulesets can themselves extend additional rulesets
up to 20 rulesets deep.

Extend a ruleset by including an "extends" top-level key which identifies a URL
or file path:

```JavaScript
{
"extends": "https://raw.githubusercontent.com/todogroup/repolinter/master/rulesets/default.json"
"rules": {
# disable CI check
"integrates-with-ci": {
"level": "off"
}
}
}
```

```YAML
extends: https://raw.githubusercontent.com/todogroup/repolinter/master/rulesets/default.json
rules:
# disable CI check
integrates-with-ci
level: off
...
```

Relative paths are resolved relative to the location used to access the
extending file. For example, if repolinter is invoked as:

```
repolinter -u http://example.com/custom-rules.yaml
```

And that ruleset includes `extends: "./default.yaml"`, the path will be resolved
relative to the original URL as `http://example.com/default.yaml`. If instead
repolinter is invoked as:

```
repolinter -r /etc/repolinter/custom-rules.yaml
```

And that ruleset includes `extends: "./default.yaml"`, the path will be resolved
relative to the original file path as `/etc/repolinter/default.yaml`.

YAML and JSON rulesets can be extended from either format.

## API

Repolinter also includes an extensible JavaScript API:
Expand Down
6 changes: 6 additions & 0 deletions tests/lib/absolute-override.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
$schema: "../../rulesets/schema.json"
extends: "http://localhost:9000/default.json"
version: 2
rules:
test-file-exists:
level: off
129 changes: 129 additions & 0 deletions tests/lib/config_tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2017 TODO Group. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const expect = chai.expect
const path = require('path')
const fs = require('fs')
const ServerMock = require('mock-http-server')

chai.use(chaiAsPromised)

describe('lib', () => {
describe('config', function () {
const Config = require('../../lib/config')

this.timeout(10000)

describe('isAbsoluteURL', () => {
it('should identify absolute URLs', async () => {
expect(Config.isAbsoluteURL('http://example.com/')).to.equals(true)
expect(Config.isAbsoluteURL('https://example.com/')).to.equals(true)
expect(Config.isAbsoluteURL('ftp://example.com/')).to.equals(true)
})

it('should identify relative URLs', async () => {
expect(Config.isAbsoluteURL('foo')).to.equals(false)
expect(Config.isAbsoluteURL('/foo')).to.equals(false)
expect(Config.isAbsoluteURL('file:/foo')).to.equals(false)
expect(Config.isAbsoluteURL('file:///foo')).to.equals(false)
expect(Config.isAbsoluteURL('c:\\foo')).to.equals(false)
})
})

describe('findConfig', () => {
it('should find config file in directory', async () => {
const localConfig = path.join(__dirname, 'repolinter.yaml')
expect(Config.findConfig(__dirname)).to.equals(localConfig)
})
it('should return default file when no config present', async () => {
const parent = path.join(__dirname, '..')
const defaultConfig = path.join(
__dirname,
'../../rulesets/default.json'
)
expect(Config.findConfig(parent)).to.equals(defaultConfig)
})
})

describe('loadConfig', async () => {
const server = new ServerMock({ host: 'localhost', port: 9000 }, {})
const serveDirectory = dir => ({
method: 'GET',
path: '*',
reply: {
status: 200,
body: request =>
fs.readFileSync(path.resolve(dir, request.pathname.substring(1)))
}
})
beforeEach(done => server.start(done))
afterEach(done => server.stop(done))

it('should load local config file', async () => {
const actual = await Config.loadConfig(
path.join(__dirname, 'default.json')
)
expect(actual.rules).to.have.property('test-file-exists')
expect(actual.rules['test-file-exists'].level).to.equals('error')
})

it('should load URL config file', async () => {
server.on(serveDirectory(__dirname))
const actual = await Config.loadConfig(
'http://localhost:9000/default.json'
)
expect(actual.rules).to.have.property('test-file-exists')
expect(actual.rules['test-file-exists'].level).to.equals('error')
})

it('should handle relative file extends', async () => {
const actual = await Config.loadConfig(
path.join(__dirname, 'repolinter.yaml')
)
expect(actual.rules).to.have.property('test-file-exists')
expect(actual.rules['test-file-exists'].level).to.equals('error')
})

it('should handle relative URL extends', async () => {
server.on(serveDirectory(__dirname))
const actual = await Config.loadConfig(
'http://localhost:9000/repolinter.yaml'
)
expect(actual.rules).to.have.property('test-file-exists')
expect(actual.rules['test-file-exists'].level).to.equals('error')
})

it('should handle absolute URL extends', async () => {
server.on(serveDirectory(__dirname))
const actual = await Config.loadConfig(
path.join(__dirname, 'absolute-override.yaml')
)
expect(actual.rules).to.have.property('test-file-exists')
expect(actual.rules['test-file-exists'].level).to.equals('off')
})

it('should throw error on non existant file', async () => {
expect(Config.loadConfig('/does-not-exist')).to.eventually.throw(
'ENOENT'
)
})

it('should throw error on non existant URL', async () => {
server.on(serveDirectory(__dirname))
expect(
Config.loadConfig('http://localhost:9000/404')
).to.eventually.throw('404')
})
})

describe('validateConfig', () => {
// already tested as part of the repolinter api tests in tests/api
})

describe('parseConfig', () => {
// already tested as part of the repolinter api tests in tests/api
})
})
})
16 changes: 16 additions & 0 deletions tests/lib/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "../../rulesets/schema.json",
"version": 2,
"axioms": {},
"rules": {
"test-file-exists": {
"level": "error",
"rule": {
"type": "file-existence",
"options": {
"globsAny": ["text_file_for_test.txt"]
}
}
}
}
}
3 changes: 3 additions & 0 deletions tests/lib/repolinter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
$schema: "../../rulesets/schema.json"
extends: "./default.json"
version: 2

0 comments on commit 43b95b6

Please sign in to comment.