Skip to content

Commit

Permalink
👷 Add github action to test rules
Browse files Browse the repository at this point in the history
  • Loading branch information
ouvreboite committed Jun 12, 2024
1 parent 28af64d commit e1cc87c
Show file tree
Hide file tree
Showing 13 changed files with 66 additions and 28 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/poltergust.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Run Poltergust Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v2

- name: Use Node.js
uses: actions/setup-node@v2

- name: Install poltergust
run: npm ci

- name: Run Poltergust tests
run: npx poltergust test ./example
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
poltergust/node_modules/*
node_modules/*
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ An npm CLI to extract Spectral rules from .md files, merge them and test them.
## Install the poltergust CLI

```sh
cd poltergust
npm install
npm link
```
Expand All @@ -18,13 +17,14 @@ npm link

```sh
cd ..
poltergust test ./rules
poltergust merge ./rules
poltergust test ./example
poltergust merge ./example
```

### Example of a test output

```
$ poltergust merge ./example
🔎 Testing the spectral rules from the .md files in the directory: ./rules
👻 base-path-must-start-with-slash (rules\api.md:42)
✅ Test OK (rules\api.md:17)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
50 changes: 27 additions & 23 deletions poltergust/functions.mjs → functions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,19 @@ function extractTestCaseBlock(block, filePath) {


/**
* @param {Object.<string, {content:string}>} spectralRules
* @param {string[]} spectralRulesContents
* @param {string} rulesDir
* @returns {string} the content of the spectral.yaml file
*/
export function generateRuleFileContent(spectralRules, rulesDir){
export function generateRuleFileContent(spectralRulesContents, rulesDir){
const baseFile = path.join(rulesDir, 'spectral.base.yaml');
if (!fs.existsSync(baseFile)) {
console.error(`❌ The file ${baseFile} does not exist.`);
}
let ruleFileContent = fs.readFileSync(baseFile, 'utf8');

for (let ruleName in spectralRules) {
const rule = spectralRules[ruleName];
const indentedRule = rule.content.split('\n').map(line => ' ' + line).join('\n')
let ruleFileContent = fs.readFileSync(baseFile, 'utf8');
for (let rule of spectralRulesContents) {
const indentedRule = rule.split('\n').map(line => ' ' + line).join('\n')
ruleFileContent += '\n'+indentedRule;
}

Expand All @@ -168,16 +167,15 @@ export function generateRuleFileContent(spectralRules, rulesDir){
* @param {{name: string, content: string, line: number, filePath: string}} rule
* @param {{content: string, assertions: {ruleName: string, line: number, shouldFail: boolean}[], line: number, filePath: string}} testCase
* @param {string} rulesDir
* @return {boolean} success
*/
export async function runTestCase(rule, testCase, rulesDir){
//write rule file to temp dir
const tempDir = await mkdtemp(rule.name+"-test-");
const tempRuleFilePath = path.join(tempDir, '.spectral.yaml');
const functionsDirPath = path.join(rulesDir, 'functions');
const tempFunctionsDirPath = path.join(tempDir, 'functions')
let ruleFile = generateRuleFileContent({ruleName: rule}, rulesDir);
//append ruleFile: aaa to the rulefile
//ruleFile += `\nruleFile: ${tempFunctionsDirPath}`;
let ruleFile = generateRuleFileContent([rule.content], rulesDir);
fs.writeFileSync(tempRuleFilePath, ruleFile);

//copy the functions folder into the temp dir
Expand All @@ -188,37 +186,40 @@ export async function runTestCase(rule, testCase, rulesDir){
fs.copyFileSync(sourceFile, destFile);
});

let ok = true;

try{
//run test case against the rule
const spectral = new Spectral();
spectral.setRuleset(await bundleAndLoadRuleset(path.resolve(tempRuleFilePath), { fs, fetch }));
let errors = await spectral.run(testCase.content);
const errors = await spectral.run(testCase.content);

//check each assertions
let ok = true;
errors = errors.filter(e => e.code === rule.name);
for (let assertion of testCase.assertions) {
const ruleErrors = errors.filter(e => e.code === rule.name);
const ruleAssertions = testCase.assertions.filter(a => a.ruleName === rule.name);

for (let assertion of ruleAssertions) {
//should-not-fail-anywhere
if(!assertion.shouldFail && assertion.line === undefined){
if(errors.length > 0){
if(ruleErrors.length > 0){
console.error(` ❌ Was not expecting to fail rule anywhere ${assertion.ruleName} in test (${testCase.filePath}:${testCase.line})`);
console.error(` But failed there instead:`);
errors.forEach(e => console.error(' ', {start: e.range.start.line, end: e.range.end.line}, `(${testCase.filePath}:${testCase.line+e.range.start.line+1})`));
ruleErrors.forEach(e => console.error(' ', {start: e.range.start.line, end: e.range.end.line}, `(${testCase.filePath}:${testCase.line+e.range.start.line+1})`));
ok = false;
}
}

//should-fail-anywhere
if(assertion.shouldFail && assertion.line === undefined){
if(errors.length === 0){
if(ruleErrors.length === 0){
console.error(` ❌ Was expecting to fail rule anywhere ${assertion.ruleName} in test (${testCase.filePath}:${testCase.line})`);
ok = false;
}
}

//should-not-fail-here
if(!assertion.shouldFail && assertion.line !== undefined){
let matchingErrors = errors.filter(e => e.range.start.line <= assertion.line && e.range.end.line >= assertion.line);
let matchingErrors = ruleErrors.filter(e => e.range.start.line <= assertion.line && e.range.end.line >= assertion.line);
if(matchingErrors.length > 0){
console.error(` ❌ Was not expecting to fail rule ${assertion.ruleName} at line ${assertion.line} in test (${testCase.filePath}:${testCase.line+assertion.line+1})`);
ok = false;
Expand All @@ -227,21 +228,24 @@ export async function runTestCase(rule, testCase, rulesDir){

//should-fail-here
if(assertion.shouldFail && assertion.line !== undefined){
let matchingErrors = errors.filter(e => e.range.start.line <= assertion.line && e.range.end.line >= assertion.line);
let matchingErrors = ruleErrors.filter(e => e.range.start.line <= assertion.line && e.range.end.line >= assertion.line);
if(matchingErrors.length === 0){
console.error(` ❌ Was expecting to fail rule ${assertion.ruleName} at line ${assertion.line} in test (${testCase.filePath}:${testCase.line+assertion.line+1})`);
console.error(` But failed there instead:`);
errors.forEach(e => console.error(' ', {start: e.range.start.line, end: e.range.end.line}, `(${testCase.filePath}:${testCase.line+e.range.start.line+1})`));
ruleErrors.forEach(e => console.error(' ', {start: e.range.start.line, end: e.range.end.line}, `(${testCase.filePath}:${testCase.line+e.range.start.line+1})`));
ok = false;
}
}

}

if(ok){
console.log(` ✅ Test OK (${testCase.filePath}:${testCase.line})`);
}
}finally{
fs.rmSync(tempDir, {recursive: true});
}

if(ok){
console.log(` ✅ Test OK (${testCase.filePath}:${testCase.line})`);
return true;
}else{
return false;
}
}
4 changes: 3 additions & 1 deletion poltergust/index.mjs → index.mjs
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ if (args[0] === 'merge') {
const rulesDir = args[1];
console.log("🔎 Extracting and merging spectral rules from the .md files in the directory: " + rulesDir);
let rulesAndTestCases = extractAllRulesAndTestCases(rulesDir);
let rules = Object.values(rulesAndTestCases).map((ruleAndTestCases) => ruleAndTestCases.rule).filter((rule) => rule);

for(let ruleName in rulesAndTestCases){
const rule = rulesAndTestCases[ruleName].rule;
console.log(`👻 ${ruleName} (${rule.filePath}:${rule.line})`);
}
let rules = Object.values(rulesAndTestCases).filter((ruleAndTestCases) => ruleAndTestCases.rule).map((ruleAndTestCases) => ruleAndTestCases.rule.content);
const ruleFileContent = generateRuleFileContent(rules, rulesDir);
const ruleFile = path.join(rulesDir, 'spectral.yaml');
fs.writeFileSync(ruleFile, ruleFileContent, 'utf8');
Expand Down Expand Up @@ -45,4 +46,5 @@ if (args[0] === 'merge') {
await runTestCase(rule, testCase, rulesDir);
}
}
process.exit(1);
}
8 changes: 8 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"module": "ES2022",
"target": "es2022",
"checkJs": true
},
"exclude": ["node_modules"]
}
File renamed without changes.
File renamed without changes.

0 comments on commit e1cc87c

Please sign in to comment.