Skip to content

Commit 73d4758

Browse files
authored
Merge pull request #134 from green-code-initiative/133-add-a-getpriorityrule-core-service
feat(core-services): add priority rule + tests
2 parents 7ee8be7 + 172859a commit 73d4758

File tree

5 files changed

+194
-1
lines changed

5 files changed

+194
-1
lines changed

shared/core-services/src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import api from './adapter.js'
22
import settings from './settings.service.js'
3+
4+
import * as priorityRule from './priority-rule.service.js'
35
import * as score from './score.service.js'
46

57
export default {
68
api,
79
settings,
10+
...priorityRule,
811
...score
912
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* @module priority-rule.services
3+
* @description This module provides functions to recommend a priority rule to fix.
4+
*/
5+
6+
import api from './adapter.js';
7+
8+
/**
9+
* @typedef {Object} DescriptionSection
10+
* @property {string} key
11+
* @property {string} content
12+
* @property {string} [context]
13+
*/
14+
15+
/**
16+
* @typedef {Object} RuleDetails
17+
* @property {string} key
18+
* @property {string} name
19+
* @property {string} htmlDesc
20+
* @property {'INFO'|'MINOR'|'MAJOR'|'CRITICAL'|'BLOCKER'} severity
21+
* @property {string[]} tags
22+
* @property {string} langName
23+
* @property {DescriptionSection[]} descriptionSections
24+
*/
25+
26+
/**
27+
* @param {Object} ruleFacet
28+
* @returns {string}
29+
*/
30+
function getMostIssuesRule(ruleFacet) {
31+
let ruleKey = null
32+
let max = 0
33+
for (let [key, value] of Object.entries(ruleFacet)) {
34+
if (value > max) {
35+
max = value
36+
ruleKey = key
37+
}
38+
}
39+
return ruleKey
40+
}
41+
42+
const SEVERITY_LEVELS = [
43+
'BLOCKER',
44+
'CRITICAL',
45+
'MAJOR',
46+
'MINOR,INFO'
47+
]
48+
49+
/**
50+
* Select a PriorityRule based the score calculation algorithm
51+
*
52+
* @param {Object} config
53+
* @param {string} config.project key of the project
54+
* @param {string} config.branch
55+
* @returns {Promise<RuleDetails|null>}
56+
*/
57+
export async function getPriorityRule(config) {
58+
let ruleKey;
59+
60+
for (let severity of SEVERITY_LEVELS) {
61+
const ruleFacet = await api.services.getIssuesFacet('rules', { ...config, severity });
62+
ruleKey = getMostIssuesRule(ruleFacet)
63+
if (ruleKey) {
64+
break
65+
}
66+
}
67+
68+
const ruleDetails = ruleKey ?
69+
await api.services.getRuleDetails(ruleKey) :
70+
null
71+
return ruleDetails;
72+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { vi, describe, expect, test } from 'vitest'
2+
3+
import api from './adapter'
4+
import { getPriorityRule } from './priority-rule.service'
5+
6+
vi.mock('./adapter', async () => ({ default: { services: {
7+
getIssuesFacet: vi.fn(),
8+
getRuleDetails: vi.fn().mockImplementation(key => ({
9+
'blocker:key': { key },
10+
'critical:key': { key },
11+
'major:key': { key },
12+
'minor:key': { key },
13+
'info:key': { key },
14+
})[key])
15+
}}}))
16+
17+
const mockConfig = { project: 'foo' }
18+
19+
function mockGetIssuesFacet(data) {
20+
return (facet, config) => {
21+
expect(config).toHaveProperty('severity')
22+
expect(config).toHaveProperty('project', 'foo')
23+
expect(facet).toBe('rules')
24+
return data[config.severity] || {}
25+
}
26+
}
27+
28+
const BLOCKER = { 'blocker:key': 4, 'blocker:key2': 3 }
29+
const CRITICAL = { 'critical:key': 7, 'critical:key2': 4 }
30+
const MAJOR = { 'major:key': 12, 'critical:key2': 4 }
31+
const MINOR_OR_INFO = { 'minor:key': 31, 'info:key': 12, 'minor:key2': 4 }
32+
33+
describe('getPriorityRule()', () => {
34+
35+
test('Should return Blocker issue related rule if one exist', async () => {
36+
const mock = mockGetIssuesFacet({ BLOCKER, CRITICAL, MAJOR })
37+
api.services.getIssuesFacet.mockImplementation(mock);
38+
const rule = await getPriorityRule(mockConfig)
39+
expect(rule).toHaveProperty('key', 'blocker:key')
40+
})
41+
42+
test('Should return Critical issue related rule if one exist and no blocker exist', async () => {
43+
const mock = mockGetIssuesFacet({ CRITICAL, MAJOR })
44+
api.services.getIssuesFacet.mockImplementation(mock);
45+
const rule = await getPriorityRule(mockConfig)
46+
expect(rule).toHaveProperty('key', 'critical:key')
47+
})
48+
49+
test('Should return major issue related rule if one exist and no blocker or critical exist', async () => {
50+
const mock = mockGetIssuesFacet({ MAJOR, 'MINOR,INFO': MINOR_OR_INFO })
51+
api.services.getIssuesFacet.mockImplementation(mock);
52+
const rule = await getPriorityRule(mockConfig)
53+
expect(rule).toHaveProperty('key', 'major:key')
54+
})
55+
56+
test('Should return minor issue related rule if one exist and no blocker, critical or major exist', async () => {
57+
const mock = mockGetIssuesFacet({ 'MINOR,INFO': MINOR_OR_INFO })
58+
api.services.getIssuesFacet.mockImplementation(mock);
59+
const rule = await getPriorityRule(mockConfig)
60+
expect(rule).toHaveProperty('key', 'minor:key')
61+
})
62+
63+
test('Should return null if no issue exist', async () => {
64+
const mock = mockGetIssuesFacet({ })
65+
api.services.getIssuesFacet.mockImplementation(mock);
66+
const rule = await getPriorityRule(mockConfig)
67+
expect(rule).toBe(null)
68+
})
69+
70+
})

shared/core-services/src/score.service.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ beforeAll(() => {
1515
});
1616

1717
afterAll(() => {
18-
// Mock the API calls
18+
// Clean the API calls
1919
});
2020

2121
describe('calculateProjectScore', () => {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { beforeEach, describe, expect, test } from 'vitest'
2+
3+
import settings from './settings.service'
4+
5+
let store;
6+
7+
const storageApi = {
8+
set: (key, value) => store[key] = value,
9+
get: (key) => store[key],
10+
};
11+
12+
beforeEach(()=> {
13+
store = {};
14+
settings.resetSettings()
15+
})
16+
17+
describe('Settings service', () => {
18+
19+
describe('initStorage', () => {
20+
test('apply the given store', async () => {
21+
settings.initStorage(storageApi)
22+
expect(settings.get('foo')).toBe(undefined)
23+
settings.set('foo', 'bar')
24+
expect(settings.get('foo')).toBe('bar')
25+
})
26+
27+
})
28+
29+
describe('storageReady', () => {
30+
test('return false if not initialized', async () => {
31+
expect(settings.storageReady).toBe(false)
32+
})
33+
test('return true if initialized', async () => {
34+
settings.initStorage(storageApi)
35+
expect(settings.storageReady).toBe(true)
36+
})
37+
})
38+
39+
describe('resetSettings', () => {
40+
test('storageReady return false after initialized and reset', async () => {
41+
settings.initStorage(storageApi)
42+
settings.resetSettings()
43+
expect(settings.storageReady).toBe(false)
44+
})
45+
46+
})
47+
48+
})

0 commit comments

Comments
 (0)