@govtechsg/oobee 0.10.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +22 -0
- package/.github/pull_request_template.md +11 -0
- package/.github/workflows/docker-test.yml +54 -0
- package/.github/workflows/image.yml +107 -0
- package/.github/workflows/publish.yml +18 -0
- package/.idea/modules.xml +8 -0
- package/.idea/purple-a11y.iml +9 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierrc.json +12 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +10 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/DETAILS.md +163 -0
- package/Dockerfile +60 -0
- package/INSTALLATION.md +146 -0
- package/INTEGRATION.md +785 -0
- package/LICENSE +22 -0
- package/README.md +587 -0
- package/SECURITY.md +5 -0
- package/__mocks__/mock-report.html +1431 -0
- package/__mocks__/mockFunctions.ts +32 -0
- package/__mocks__/mockIssues.ts +64 -0
- package/__mocks__/mock_all_issues/000000001.json +64 -0
- package/__mocks__/mock_all_issues/000000002.json +53 -0
- package/__mocks__/mock_all_issues/fake-file.txt +0 -0
- package/__tests__/logs.test.ts +25 -0
- package/__tests__/mergeAxeResults.test.ts +278 -0
- package/__tests__/utils.test.ts +118 -0
- package/a11y-scan-results.zip +0 -0
- package/eslint.config.js +53 -0
- package/exclusions.txt +2 -0
- package/gitlab-pipeline-template.yml +54 -0
- package/jest.config.js +1 -0
- package/package.json +96 -0
- package/scripts/copyFiles.js +44 -0
- package/scripts/install_oobee_dependencies.cmd +13 -0
- package/scripts/install_oobee_dependencies.command +101 -0
- package/scripts/install_oobee_dependencies.ps1 +110 -0
- package/scripts/oobee_shell.cmd +13 -0
- package/scripts/oobee_shell.command +11 -0
- package/scripts/oobee_shell.sh +55 -0
- package/scripts/oobee_shell_ps.ps1 +54 -0
- package/src/cli.ts +401 -0
- package/src/combine.ts +240 -0
- package/src/constants/__tests__/common.test.ts +44 -0
- package/src/constants/cliFunctions.ts +305 -0
- package/src/constants/common.ts +1840 -0
- package/src/constants/constants.ts +443 -0
- package/src/constants/errorMeta.json +319 -0
- package/src/constants/itemTypeDescription.ts +11 -0
- package/src/constants/oobeeAi.ts +141 -0
- package/src/constants/questions.ts +181 -0
- package/src/constants/sampleData.ts +187 -0
- package/src/crawlers/__tests__/commonCrawlerFunc.test.ts +51 -0
- package/src/crawlers/commonCrawlerFunc.ts +656 -0
- package/src/crawlers/crawlDomain.ts +877 -0
- package/src/crawlers/crawlIntelligentSitemap.ts +156 -0
- package/src/crawlers/crawlLocalFile.ts +193 -0
- package/src/crawlers/crawlSitemap.ts +356 -0
- package/src/crawlers/custom/extractAndGradeText.ts +57 -0
- package/src/crawlers/custom/flagUnlabelledClickableElements.ts +964 -0
- package/src/crawlers/custom/utils.ts +486 -0
- package/src/crawlers/customAxeFunctions.ts +82 -0
- package/src/crawlers/pdfScanFunc.ts +468 -0
- package/src/crawlers/runCustom.ts +117 -0
- package/src/index.ts +173 -0
- package/src/logs.ts +66 -0
- package/src/mergeAxeResults.ts +964 -0
- package/src/npmIndex.ts +284 -0
- package/src/screenshotFunc/htmlScreenshotFunc.ts +411 -0
- package/src/screenshotFunc/pdfScreenshotFunc.ts +762 -0
- package/src/static/ejs/partials/components/categorySelector.ejs +4 -0
- package/src/static/ejs/partials/components/categorySelectorDropdown.ejs +57 -0
- package/src/static/ejs/partials/components/pagesScannedModal.ejs +70 -0
- package/src/static/ejs/partials/components/reportSearch.ejs +47 -0
- package/src/static/ejs/partials/components/ruleOffcanvas.ejs +105 -0
- package/src/static/ejs/partials/components/scanAbout.ejs +263 -0
- package/src/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
- package/src/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
- package/src/static/ejs/partials/components/summaryScanResults.ejs +16 -0
- package/src/static/ejs/partials/components/summaryTable.ejs +20 -0
- package/src/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
- package/src/static/ejs/partials/components/topFive.ejs +6 -0
- package/src/static/ejs/partials/components/wcagCompliance.ejs +70 -0
- package/src/static/ejs/partials/footer.ejs +21 -0
- package/src/static/ejs/partials/header.ejs +230 -0
- package/src/static/ejs/partials/main.ejs +40 -0
- package/src/static/ejs/partials/scripts/bootstrap.ejs +8 -0
- package/src/static/ejs/partials/scripts/categorySelectorDropdownScript.ejs +190 -0
- package/src/static/ejs/partials/scripts/categorySummary.ejs +141 -0
- package/src/static/ejs/partials/scripts/highlightjs.ejs +335 -0
- package/src/static/ejs/partials/scripts/popper.ejs +7 -0
- package/src/static/ejs/partials/scripts/reportSearch.ejs +248 -0
- package/src/static/ejs/partials/scripts/ruleOffcanvas.ejs +801 -0
- package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +71 -0
- package/src/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
- package/src/static/ejs/partials/scripts/summaryTable.ejs +78 -0
- package/src/static/ejs/partials/scripts/utils.ejs +441 -0
- package/src/static/ejs/partials/styles/bootstrap.ejs +12375 -0
- package/src/static/ejs/partials/styles/highlightjs.ejs +54 -0
- package/src/static/ejs/partials/styles/styles.ejs +1843 -0
- package/src/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
- package/src/static/ejs/partials/summaryHeader.ejs +70 -0
- package/src/static/ejs/partials/summaryMain.ejs +75 -0
- package/src/static/ejs/report.ejs +420 -0
- package/src/static/ejs/summary.ejs +47 -0
- package/src/static/mustache/.prettierrc +4 -0
- package/src/static/mustache/Attention Deficit.mustache +11 -0
- package/src/static/mustache/Blind.mustache +11 -0
- package/src/static/mustache/Cognitive.mustache +7 -0
- package/src/static/mustache/Colorblindness.mustache +20 -0
- package/src/static/mustache/Deaf.mustache +12 -0
- package/src/static/mustache/Deafblind.mustache +7 -0
- package/src/static/mustache/Dyslexia.mustache +14 -0
- package/src/static/mustache/Low Vision.mustache +7 -0
- package/src/static/mustache/Mobility.mustache +15 -0
- package/src/static/mustache/Sighted Keyboard Users.mustache +42 -0
- package/src/static/mustache/report.mustache +1709 -0
- package/src/types/print-message.d.ts +28 -0
- package/src/types/types.ts +46 -0
- package/src/types/xpath-to-css.d.ts +3 -0
- package/src/utils.ts +332 -0
- package/tsconfig.json +15 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
/* eslint-disable no-undef */
|
2
|
+
const actualFs = jest.requireActual('fs-extra');
|
3
|
+
const actualPath = jest.requireActual('path');
|
4
|
+
|
5
|
+
export const createIssueCountMap = jest.fn((critical, serious, moderate, minor) => {
|
6
|
+
const totalCount = critical + serious + moderate + minor;
|
7
|
+
return [
|
8
|
+
`Total Issue Count: ${totalCount}`,
|
9
|
+
`Issue Breakdown`,
|
10
|
+
`Critical: ${critical}`,
|
11
|
+
`Serious: ${serious}`,
|
12
|
+
`Moderate: ${moderate}`,
|
13
|
+
`Minor: ${minor}`,
|
14
|
+
];
|
15
|
+
});
|
16
|
+
|
17
|
+
export const createAlertMessage = jest.fn(warnlevel => [
|
18
|
+
[
|
19
|
+
`Issues with impact level - ${warnlevel} found in your project. Please review the accessibility issues.`,
|
20
|
+
],
|
21
|
+
{ border: true, borderColor: 'red' },
|
22
|
+
]);
|
23
|
+
|
24
|
+
export const createFinalResultsInJson = jest.fn((allissues, dateTimeStamp) => {
|
25
|
+
const finalResultsInJson = JSON.stringify(
|
26
|
+
{ startTime: dateTimeStamp, count: allissues.length, allissues },
|
27
|
+
null,
|
28
|
+
4,
|
29
|
+
);
|
30
|
+
|
31
|
+
return finalResultsInJson;
|
32
|
+
});
|
@@ -0,0 +1,64 @@
|
|
1
|
+
// Issues with all warn level except none
|
2
|
+
// 0: critical, 1: serious, 2: moderate, 3: minor
|
3
|
+
export const allIssues = [
|
4
|
+
{
|
5
|
+
url: 'https://www.isomer.gov.sg/terms-of-use/',
|
6
|
+
page: '/terms-of-use/',
|
7
|
+
description: 'Buttons must have discernible text',
|
8
|
+
helpUrl: 'https://dequeuniversity.com/rules/axe/4.2/button-name?application=oobee',
|
9
|
+
htmlElement:
|
10
|
+
"<button class='bp-button'><i class='sgds-icon sgds-icon-mail is-size-4'></i>/button>",
|
11
|
+
order: 3,
|
12
|
+
wcagLinks: [['https://www.w3.org/WAI/WCAG21/Understanding/text-alternatives']],
|
13
|
+
impact: 'critical',
|
14
|
+
disabilities: ['disabilities'],
|
15
|
+
},
|
16
|
+
{
|
17
|
+
url: 'https://www.isomer.gov.sg/terms-of-use/',
|
18
|
+
page: '/terms-of-use/',
|
19
|
+
description: 'Buttons must have discernible text',
|
20
|
+
helpUrl: 'https://dequeuniversity.com/rules/axe/4.2/button-name?application=oobee',
|
21
|
+
htmlElement:
|
22
|
+
"<button class='bp-button'><i class='sgds-icon sgds-icon-mail is-size-4'></i>/button>",
|
23
|
+
order: 3,
|
24
|
+
wcagLinks: [['https://www.w3.org/WAI/WCAG21/Understanding/text-alternatives']],
|
25
|
+
impact: 'serious',
|
26
|
+
disabilities: ['disabilities'],
|
27
|
+
},
|
28
|
+
{
|
29
|
+
url: 'https://www.isomer.gov.sg/terms-of-use/',
|
30
|
+
page: '/terms-of-use/',
|
31
|
+
description: 'Buttons must have discernible text',
|
32
|
+
helpUrl: 'https://dequeuniversity.com/rules/axe/4.2/button-name?application=oobee',
|
33
|
+
htmlElement:
|
34
|
+
"<button class='bp-button'><i class='sgds-icon sgds-icon-mail is-size-4'></i>/button>",
|
35
|
+
order: 3,
|
36
|
+
wcagLinks: [['https://www.w3.org/WAI/WCAG21/Understanding/text-alternatives']],
|
37
|
+
impact: 'moderate',
|
38
|
+
disabilities: ['disabilities'],
|
39
|
+
},
|
40
|
+
{
|
41
|
+
url: 'https://www.isomer.gov.sg/terms-of-use/',
|
42
|
+
page: '/terms-of-use/',
|
43
|
+
description: 'Buttons must have discernible text',
|
44
|
+
helpUrl: 'https://dequeuniversity.com/rules/axe/4.2/button-name?application=oobee',
|
45
|
+
htmlElement:
|
46
|
+
"<button class='bp-button'><i class='sgds-icon sgds-icon-mail is-size-4'></i>/button>",
|
47
|
+
order: 3,
|
48
|
+
wcagLinks: [['https://www.w3.org/WAI/WCAG21/Understanding/text-alternatives']],
|
49
|
+
impact: 'minor',
|
50
|
+
disabilities: ['disabilities'],
|
51
|
+
},
|
52
|
+
];
|
53
|
+
|
54
|
+
// All Except Target Warn Level Issues
|
55
|
+
export const allExceptCritical = [allIssues[1], allIssues[2], allIssues[3]];
|
56
|
+
export const allExceptSerious = [allIssues[0], allIssues[2], allIssues[3]];
|
57
|
+
export const allExceptModerate = [allIssues[0], allIssues[1], allIssues[3]];
|
58
|
+
export const allExceptMinor = [allIssues[0], allIssues[1], allIssues[2]];
|
59
|
+
|
60
|
+
// Individual Target Warn Level Issues
|
61
|
+
export const criticalOnly = [allIssues[0]];
|
62
|
+
export const seriousOnly = [allIssues[1]];
|
63
|
+
export const moderateOnly = [allIssues[2]];
|
64
|
+
export const minorOnly = [allIssues[3]];
|
@@ -0,0 +1,64 @@
|
|
1
|
+
{
|
2
|
+
"url": "https://www.sitemaps.org/",
|
3
|
+
"page": "/",
|
4
|
+
"errors": [
|
5
|
+
{
|
6
|
+
"id": "color-contrast",
|
7
|
+
"description": "Elements must have sufficient color contrast",
|
8
|
+
"impact": "serious",
|
9
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.2/color-contrast?application=oobee",
|
10
|
+
"fixes": [
|
11
|
+
{
|
12
|
+
"htmlElement": "<p class=\"date\">\n\n Last Updated: 17 April 2020\n\n </p>"
|
13
|
+
}
|
14
|
+
]
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"id": "html-has-lang",
|
18
|
+
"description": "<html> element must have a lang attribute",
|
19
|
+
"impact": "serious",
|
20
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.2/html-has-lang?application=oobee",
|
21
|
+
"fixes": [
|
22
|
+
{
|
23
|
+
"htmlElement": "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">"
|
24
|
+
}
|
25
|
+
]
|
26
|
+
},
|
27
|
+
{
|
28
|
+
"id": "landmark-one-main",
|
29
|
+
"description": "Document should have one main landmark",
|
30
|
+
"impact": "moderate",
|
31
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.2/landmark-one-main?application=oobee",
|
32
|
+
"fixes": [
|
33
|
+
{
|
34
|
+
"htmlElement": "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">"
|
35
|
+
}
|
36
|
+
]
|
37
|
+
},
|
38
|
+
{
|
39
|
+
"id": "region",
|
40
|
+
"description": "All page content should be contained by landmarks",
|
41
|
+
"impact": "moderate",
|
42
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.2/region?application=oobee",
|
43
|
+
"fixes": [
|
44
|
+
{
|
45
|
+
"htmlElement": "<div id=\"container\">"
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"htmlElement": "<div id=\"footer\">\n\n <p>\n\n <a href=\"terms.php\">Terms and conditions</a></p>\n\n </div>"
|
49
|
+
}
|
50
|
+
]
|
51
|
+
},
|
52
|
+
{
|
53
|
+
"id": "select-name",
|
54
|
+
"description": "Select element must have an accessible name",
|
55
|
+
"impact": "critical",
|
56
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.2/select-name?application=oobee",
|
57
|
+
"fixes": [
|
58
|
+
{
|
59
|
+
"htmlElement": "<select id=\"lang\" name=\"lang\" onchange=\"return onLangChange(this)\" style=\"font-size: 10px;\">"
|
60
|
+
}
|
61
|
+
]
|
62
|
+
}
|
63
|
+
]
|
64
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
{
|
2
|
+
"url": "https://www.sitemaps.org/faq.html",
|
3
|
+
"page": "/faq.html",
|
4
|
+
"errors": [
|
5
|
+
{
|
6
|
+
"id": "color-contrast",
|
7
|
+
"description": "Elements must have sufficient color contrast",
|
8
|
+
"impact": "serious",
|
9
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.2/color-contrast?application=oobee",
|
10
|
+
"fixes": [
|
11
|
+
{
|
12
|
+
"htmlElement": "<p class=\"date\">\n\n Last Updated: Monday, November 21, 2016\n\n </p>"
|
13
|
+
}
|
14
|
+
]
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"id": "html-has-lang",
|
18
|
+
"description": "<html> element must have a lang attribute",
|
19
|
+
"impact": "serious",
|
20
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.2/html-has-lang?application=oobee",
|
21
|
+
"fixes": [
|
22
|
+
{
|
23
|
+
"htmlElement": "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" id=\"sitemaps_org\">"
|
24
|
+
}
|
25
|
+
]
|
26
|
+
},
|
27
|
+
{
|
28
|
+
"id": "landmark-one-main",
|
29
|
+
"description": "Document should have one main landmark",
|
30
|
+
"impact": "moderate",
|
31
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.2/landmark-one-main?application=oobee",
|
32
|
+
"fixes": [
|
33
|
+
{
|
34
|
+
"htmlElement": "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" id=\"sitemaps_org\">"
|
35
|
+
}
|
36
|
+
]
|
37
|
+
},
|
38
|
+
{
|
39
|
+
"id": "region",
|
40
|
+
"description": "All page content should be contained by landmarks",
|
41
|
+
"impact": "moderate",
|
42
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.2/region?application=oobee",
|
43
|
+
"fixes": [
|
44
|
+
{
|
45
|
+
"htmlElement": "<div id=\"container\">"
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"htmlElement": "<div id=\"footer\">\n\n <p>\n\n <a href=\"terms.php\">Terms and conditions</a></p>\n\n </div>"
|
49
|
+
}
|
50
|
+
]
|
51
|
+
}
|
52
|
+
]
|
53
|
+
}
|
File without changes
|
@@ -0,0 +1,25 @@
|
|
1
|
+
/* eslint-disable no-undef */
|
2
|
+
import {jest} from '@jest/globals'
|
3
|
+
import winston from 'winston';
|
4
|
+
import { logFormat } from '../logs.js';
|
5
|
+
|
6
|
+
describe('test log format', () => {
|
7
|
+
afterEach(() => {
|
8
|
+
jest.resetModules();
|
9
|
+
});
|
10
|
+
|
11
|
+
test('should return expected log format', () => {
|
12
|
+
const mockFormatInfo = {
|
13
|
+
level: 'info',
|
14
|
+
message: `unit-test-message`,
|
15
|
+
};
|
16
|
+
|
17
|
+
const result = winston.format.printf(logFormat.template(mockFormatInfo));
|
18
|
+
expect(typeof result).toBe('object');
|
19
|
+
expect(JSON.parse(result.template).level).toEqual('info');
|
20
|
+
expect(JSON.parse(result.template).message).toEqual('unit-test-message');
|
21
|
+
expect(
|
22
|
+
Object.prototype.hasOwnProperty.call(JSON.parse(result.template), 'timestamp'),
|
23
|
+
).toBeTruthy();
|
24
|
+
});
|
25
|
+
});
|
@@ -0,0 +1,278 @@
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
2
|
+
/* eslint-disable no-undef */
|
3
|
+
import { jest } from '@jest/globals';
|
4
|
+
import printMessage from 'print-message';
|
5
|
+
import fs from 'fs-extra';
|
6
|
+
import path from 'path';
|
7
|
+
import Mustache from 'mustache';
|
8
|
+
|
9
|
+
import privateFuncs from '../mergeAxeResults.js';
|
10
|
+
|
11
|
+
const issueCountMap = privateFuncs.__get__('issueCountMap');
|
12
|
+
const thresholdLimitCheck = privateFuncs.__get__('thresholdLimitCheck');
|
13
|
+
const extractFileNames = privateFuncs.__get__('extractFileNames');
|
14
|
+
const parseContentToJson = privateFuncs.__get__('parseContentToJson');
|
15
|
+
const granularReporting = privateFuncs.__get__('granularReporting');
|
16
|
+
const writeHTML = privateFuncs.__get__('writeHTML');
|
17
|
+
const writeResults = privateFuncs.__get__('writeResults');
|
18
|
+
|
19
|
+
const {
|
20
|
+
allIssues,
|
21
|
+
allExceptCritical,
|
22
|
+
allExceptModerate,
|
23
|
+
allExceptSerious,
|
24
|
+
allExceptMinor,
|
25
|
+
criticalOnly,
|
26
|
+
seriousOnly,
|
27
|
+
moderateOnly,
|
28
|
+
minorOnly,
|
29
|
+
} = require('../__mocks__/mockIssues');
|
30
|
+
|
31
|
+
const {
|
32
|
+
createIssueCountMap,
|
33
|
+
createAlertMessage,
|
34
|
+
createFilenames,
|
35
|
+
createFinalResultsInJson,
|
36
|
+
} = require('../__mocks__/mockFunctions');
|
37
|
+
const { getCurrentDate, getFormattedTime } = require('../utils');
|
38
|
+
const { consoleLogger, silentLogger } = require('../logs');
|
39
|
+
|
40
|
+
let randomToken;
|
41
|
+
let currentDate;
|
42
|
+
let expectedStoragePath;
|
43
|
+
let expectedJsonFilename;
|
44
|
+
let expectedHTMLFilename;
|
45
|
+
let htmlFilename;
|
46
|
+
let jsonFilename;
|
47
|
+
|
48
|
+
jest.mock('print-message');
|
49
|
+
jest.mock('fs-extra');
|
50
|
+
jest.mock('mustache');
|
51
|
+
|
52
|
+
beforeEach(() => {
|
53
|
+
randomToken = '162282454060d4c8470d';
|
54
|
+
currentDate = getCurrentDate();
|
55
|
+
expectedStoragePath = `results/${currentDate}/${randomToken}`;
|
56
|
+
|
57
|
+
// Reports storagePath, expected report and compiled result files
|
58
|
+
htmlFilename = 'report';
|
59
|
+
expectedHTMLFilename = `${expectedStoragePath}/${htmlFilename}.html`;
|
60
|
+
|
61
|
+
// Mock the JSON result generated from the issues
|
62
|
+
dateTimeStamp = getFormattedTime();
|
63
|
+
jsonOutput = createFinalResultsInJson(allIssues, dateTimeStamp);
|
64
|
+
});
|
65
|
+
|
66
|
+
// Test issueCountMap
|
67
|
+
describe('test breakdown of issue counts', () => {
|
68
|
+
test('should return map of 0 for no issues', () => {
|
69
|
+
const result = issueCountMap([]);
|
70
|
+
expect(result).toEqual(
|
71
|
+
new Map([
|
72
|
+
['critical', 0],
|
73
|
+
['serious', 0],
|
74
|
+
['moderate', 0],
|
75
|
+
['minor', 0],
|
76
|
+
['total', 0],
|
77
|
+
]),
|
78
|
+
);
|
79
|
+
});
|
80
|
+
|
81
|
+
test('should return map of issue counts', () => {
|
82
|
+
const result = issueCountMap(allIssues);
|
83
|
+
expect(result).toEqual(
|
84
|
+
new Map([
|
85
|
+
['critical', 1],
|
86
|
+
['serious', 1],
|
87
|
+
['moderate', 1],
|
88
|
+
['minor', 1],
|
89
|
+
['total', 4],
|
90
|
+
]),
|
91
|
+
);
|
92
|
+
});
|
93
|
+
});
|
94
|
+
|
95
|
+
// Test thresholdLimitCheck
|
96
|
+
describe('test threshold limit check', () => {
|
97
|
+
const mockIssueCountMap = mockData => {
|
98
|
+
privateFuncs.__set__('issueCountMap', mockData)();
|
99
|
+
};
|
100
|
+
|
101
|
+
beforeEach(() => {
|
102
|
+
warnlevel = 'none';
|
103
|
+
expectedIssueCountMap = [];
|
104
|
+
});
|
105
|
+
|
106
|
+
afterEach(() => {
|
107
|
+
printMessage.mockReset();
|
108
|
+
});
|
109
|
+
|
110
|
+
test('should print issue count with no trigger if warnlevel is none', async () => {
|
111
|
+
expectedIssueCountMap = createIssueCountMap(1, 1, 1, 1);
|
112
|
+
mockIssueCountMap(expectedIssueCountMap);
|
113
|
+
|
114
|
+
await thresholdLimitCheck(warnlevel, allIssues);
|
115
|
+
expect(printMessage).toHaveBeenCalled();
|
116
|
+
expect(printMessage.mock.calls.length).toEqual(1);
|
117
|
+
expect(printMessage.mock.calls[0][0]).toEqual(expectedIssueCountMap);
|
118
|
+
});
|
119
|
+
|
120
|
+
test.each([
|
121
|
+
['critical', allExceptCritical, createIssueCountMap(0, 1, 1, 1)],
|
122
|
+
['serious', allExceptSerious, createIssueCountMap(1, 0, 1, 1)],
|
123
|
+
['moderate', allExceptModerate, createIssueCountMap(1, 1, 0, 1)],
|
124
|
+
['minor', allExceptMinor, createIssueCountMap(1, 1, 1, 0)],
|
125
|
+
])(
|
126
|
+
'should print issue count only if all issues except %s issues are present',
|
127
|
+
async (warnlevel, data, expected) => {
|
128
|
+
mockIssueCountMap(data);
|
129
|
+
|
130
|
+
await thresholdLimitCheck(warnlevel, data);
|
131
|
+
expect(printMessage).toHaveBeenCalled();
|
132
|
+
expect(printMessage.mock.calls.length).toEqual(1);
|
133
|
+
expect(printMessage.mock.calls[0][0]).toEqual(expected);
|
134
|
+
},
|
135
|
+
);
|
136
|
+
|
137
|
+
test.each([
|
138
|
+
['critical', criticalOnly, createIssueCountMap(1, 0, 0, 0)],
|
139
|
+
['serious', seriousOnly, createIssueCountMap(0, 1, 0, 0)],
|
140
|
+
['moderate', moderateOnly, createIssueCountMap(0, 0, 1, 0)],
|
141
|
+
['minor', minorOnly, createIssueCountMap(0, 0, 0, 1)],
|
142
|
+
])(
|
143
|
+
'should print issue count and alert message if %s issues are present',
|
144
|
+
async (warnlevel, data, expected) => {
|
145
|
+
const expectedAlertMessage = createAlertMessage(warnlevel);
|
146
|
+
mockIssueCountMap(data);
|
147
|
+
|
148
|
+
await thresholdLimitCheck(warnlevel, data);
|
149
|
+
expect(printMessage).toHaveBeenCalled();
|
150
|
+
expect(printMessage.mock.calls.length).toEqual(2);
|
151
|
+
expect(printMessage.mock.calls[0][0]).toEqual(expected);
|
152
|
+
expect(printMessage.mock.calls[1]).toEqual(expectedAlertMessage);
|
153
|
+
},
|
154
|
+
);
|
155
|
+
});
|
156
|
+
|
157
|
+
describe('test parsing content to json', () => {
|
158
|
+
test('should return JSON', async () => {
|
159
|
+
fs.readFile.mockResolvedValue('{ "a": 1 }');
|
160
|
+
const expected = { a: 1 };
|
161
|
+
const result = await parseContentToJson('path');
|
162
|
+
expect(result).toMatchObject(expected);
|
163
|
+
});
|
164
|
+
|
165
|
+
test('should print error message when unable to parse content', async () => {
|
166
|
+
fs.readFile.mockResolvedValue(undefined);
|
167
|
+
await parseContentToJson('path');
|
168
|
+
const spyConsoleLogger = jest.spyOn(consoleLogger, 'info').mockImplementation();
|
169
|
+
const spySilentLogger = jest.spyOn(silentLogger, 'error').mockImplementation();
|
170
|
+
|
171
|
+
expect(spyConsoleLogger.mock.calls[0][0]).toEqual(
|
172
|
+
'An error has occurred when parsing the content, please try again.',
|
173
|
+
);
|
174
|
+
|
175
|
+
expect(spySilentLogger.mock.calls[0][0].toString()).toMatch(
|
176
|
+
new RegExp(/SyntaxError\:[\s]+Unexpected token.*JSON/),
|
177
|
+
);
|
178
|
+
});
|
179
|
+
});
|
180
|
+
|
181
|
+
describe('test write results', () => {
|
182
|
+
afterEach(() => {
|
183
|
+
fs.writeFile.mockReset();
|
184
|
+
});
|
185
|
+
|
186
|
+
test('should create compiled json file with intended filename', async () => {
|
187
|
+
fs.writeFile.mockResolvedValue();
|
188
|
+
await writeResults(allIssues, expectedStoragePath, jsonFilename);
|
189
|
+
|
190
|
+
expect(fs.writeFile).toHaveBeenCalled();
|
191
|
+
expect(fs.writeFile.mock.calls[0][0]).toEqual(expectedJsonFilename);
|
192
|
+
expect(fs.writeFile.mock.calls[0][1]).toEqual(jsonOutput);
|
193
|
+
});
|
194
|
+
|
195
|
+
test('should fail if there is error writing to JSON file', async () => {
|
196
|
+
fs.writeFile.mockImplementation(() => {
|
197
|
+
throw new Error();
|
198
|
+
});
|
199
|
+
|
200
|
+
const spyConsoleLogger = jest.spyOn(consoleLogger, 'info').mockImplementation();
|
201
|
+
const spySilentLogger = jest.spyOn(silentLogger, 'error').mockImplementation();
|
202
|
+
|
203
|
+
await writeResults(allIssues, expectedStoragePath, jsonFilename);
|
204
|
+
expect(fs.writeFile).toThrowError();
|
205
|
+
expect(spyConsoleLogger.mock.calls[0][0]).toEqual(
|
206
|
+
'An error has occurred when compiling the results into the report, please try again.',
|
207
|
+
);
|
208
|
+
expect(spySilentLogger.mock.calls[0][0].toString()).toEqual('(writeResults) - Error');
|
209
|
+
});
|
210
|
+
});
|
211
|
+
|
212
|
+
describe('test write results into HTML report', () => {
|
213
|
+
beforeAll(() => {
|
214
|
+
// Reports Templates and Content
|
215
|
+
const originalFs = jest.requireActual('fs-extra');
|
216
|
+
const mustacheReportfile = path.resolve('./static/report.mustache');
|
217
|
+
const mockReportFile = path.resolve('./__mocks__/mock-report.html');
|
218
|
+
|
219
|
+
mustacheTemp = originalFs.readFileSync(mustacheReportfile, 'utf8', data => data);
|
220
|
+
mockReportContent = originalFs.readFileSync(mockReportFile, 'utf8', data => data);
|
221
|
+
});
|
222
|
+
|
223
|
+
afterEach(() => {
|
224
|
+
fs.readFile.mockReset();
|
225
|
+
fs.writeFile.mockReset();
|
226
|
+
Mustache.render.mockReset();
|
227
|
+
});
|
228
|
+
|
229
|
+
test('should create HTML report with intended filename', async () => {
|
230
|
+
fs.readFile.mockResolvedValue(mustacheTemp);
|
231
|
+
Mustache.render.mockResolvedValue(mockReportContent);
|
232
|
+
await writeHTML(allIssues, expectedStoragePath, htmlFilename);
|
233
|
+
|
234
|
+
expect(fs.readFile).toHaveBeenCalled();
|
235
|
+
expect(Mustache.render).toHaveBeenCalled();
|
236
|
+
expect(fs.writeFile).toHaveBeenCalled();
|
237
|
+
|
238
|
+
expect(fs.readFile.mock.results[0].value).resolves.toEqual(mustacheTemp);
|
239
|
+
expect(Mustache.render.mock.results[0].value).resolves.toEqual(mockReportContent);
|
240
|
+
expect(fs.writeFile.mock.calls[0][0]).toEqual(expectedHTMLFilename);
|
241
|
+
expect(fs.writeFile.mock.calls[0][1]).resolves.toEqual(mockReportContent);
|
242
|
+
});
|
243
|
+
|
244
|
+
test('should fail if unable to read the template file', async () => {
|
245
|
+
fs.readFile.mockImplementation(() => {
|
246
|
+
throw new Error();
|
247
|
+
});
|
248
|
+
|
249
|
+
const spyConsoleLogger = jest.spyOn(consoleLogger, 'info').mockImplementation();
|
250
|
+
const spySilentLogger = jest.spyOn(silentLogger, 'error').mockImplementation();
|
251
|
+
await writeHTML(allIssues, expectedStoragePath, htmlFilename);
|
252
|
+
expect(fs.readFile).toThrowError();
|
253
|
+
expect(spyConsoleLogger.mock.calls[0][0]).toEqual(
|
254
|
+
'An error has occurred when generating the report, please try again.',
|
255
|
+
);
|
256
|
+
expect(spySilentLogger.mock.calls[0][0].toString()).toEqual('(writeHTML) - Error');
|
257
|
+
});
|
258
|
+
});
|
259
|
+
|
260
|
+
describe('test granular reporting feature', () => {
|
261
|
+
afterEach(() => {
|
262
|
+
privateFuncs.__get__('writeHTML');
|
263
|
+
privateFuncs.__get__('writeResults');
|
264
|
+
});
|
265
|
+
|
266
|
+
test('should return false when there are no issues', async () => {
|
267
|
+
const noIssues = [];
|
268
|
+
const result = await granularReporting(randomToken, noIssues);
|
269
|
+
expect(result).toBe(false);
|
270
|
+
});
|
271
|
+
|
272
|
+
test('should return true and generate the graular reports along with main reports', async () => {
|
273
|
+
privateFuncs.__set__('writeResults', jest.fn());
|
274
|
+
privateFuncs.__set__('writeHTML', jest.fn());
|
275
|
+
const result = await granularReporting(randomToken, allIssues);
|
276
|
+
expect(result).toBe(true);
|
277
|
+
});
|
278
|
+
});
|
@@ -0,0 +1,118 @@
|
|
1
|
+
/* eslint-disable no-undef */
|
2
|
+
import {jest} from '@jest/globals'
|
3
|
+
import {
|
4
|
+
setThresholdLimits,
|
5
|
+
getHost,
|
6
|
+
getCurrentDate,
|
7
|
+
validateUrl,
|
8
|
+
getStoragePath,
|
9
|
+
setHeadlessMode,
|
10
|
+
} from '../utils.js';
|
11
|
+
|
12
|
+
describe('test setting of threshold warn level', () => {
|
13
|
+
const OLD_ENV = process.env;
|
14
|
+
|
15
|
+
beforeEach(() => {
|
16
|
+
jest.resetModules();
|
17
|
+
process.env = { ...OLD_ENV };
|
18
|
+
});
|
19
|
+
|
20
|
+
afterAll(() => {
|
21
|
+
process.env = OLD_ENV;
|
22
|
+
});
|
23
|
+
|
24
|
+
test.each([
|
25
|
+
['critical', 'critical'],
|
26
|
+
['serious', 'serious'],
|
27
|
+
['moderate', 'moderate'],
|
28
|
+
['minor', 'minor'],
|
29
|
+
['none', 'none'],
|
30
|
+
])('should set warn level as %s', (warnLevel, expected) => {
|
31
|
+
setThresholdLimits(warnLevel);
|
32
|
+
expect(process.env.WARN_LEVEL).toBe(expected);
|
33
|
+
});
|
34
|
+
});
|
35
|
+
|
36
|
+
describe('test getHost', () => {
|
37
|
+
test('should retrieve the hostnames accordingly', () => {
|
38
|
+
expect(getHost('https://www.bbc.com/news')).toEqual('www.bbc.com');
|
39
|
+
expect(getHost('https://www.isomer.gov.sg/')).toEqual('www.isomer.gov.sg');
|
40
|
+
expect(getHost('https://fontawesome.com/sessions/sign-in')).toEqual(
|
41
|
+
'fontawesome.com',
|
42
|
+
);
|
43
|
+
// port number will be excluded since it is the default port for HTTPS
|
44
|
+
expect(getHost('https://www.crowdtask.gov.sg:443')).toEqual('www.crowdtask.gov.sg');
|
45
|
+
expect(getHost('http://localhost:5000/about/me')).toEqual('localhost:5000');
|
46
|
+
});
|
47
|
+
});
|
48
|
+
|
49
|
+
describe('test getCurrentDate', () => {
|
50
|
+
const mockDate = new Date('December 17, 1995');
|
51
|
+
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
52
|
+
|
53
|
+
test('should return date in string "1995-12-17"', () => {
|
54
|
+
expect(getCurrentDate()).toEqual('1995-12-17');
|
55
|
+
});
|
56
|
+
});
|
57
|
+
|
58
|
+
describe('test validateUrl', () => {
|
59
|
+
test('urls should pass', () => {
|
60
|
+
expect(validateUrl('https://www.bbc.com/news')).toEqual(true);
|
61
|
+
expect(validateUrl('https://www.isomer.gov.sg/')).toEqual(true);
|
62
|
+
expect(validateUrl('https://www.bbc.com/')).toEqual(true);
|
63
|
+
});
|
64
|
+
|
65
|
+
test('urls should fail', () => {
|
66
|
+
expect(validateUrl('https://www.bbc.gif')).toEqual(false);
|
67
|
+
expect(validateUrl('https://www.bbc.jpg')).toEqual(false);
|
68
|
+
expect(validateUrl('https://www.bbc.jpeg')).toEqual(false);
|
69
|
+
expect(validateUrl('https://www.bbc.png')).toEqual(false);
|
70
|
+
expect(validateUrl('https://www.bbc.pdf')).toEqual(false);
|
71
|
+
expect(validateUrl('https://www.bbc.doc')).toEqual(false);
|
72
|
+
expect(validateUrl('https://www.bbc.css')).toEqual(false);
|
73
|
+
expect(validateUrl('https://www.bbc.svg')).toEqual(false);
|
74
|
+
expect(validateUrl('https://www.bbc.js')).toEqual(false);
|
75
|
+
expect(validateUrl('https://www.bbc.ts')).toEqual(false);
|
76
|
+
expect(validateUrl('https://www.bbc.xml')).toEqual(false);
|
77
|
+
expect(validateUrl('https://www.bbc.csv')).toEqual(false);
|
78
|
+
expect(validateUrl('https://www.bbc.tgz')).toEqual(false);
|
79
|
+
expect(validateUrl('https://www.bbc.zip')).toEqual(false);
|
80
|
+
expect(validateUrl('https://www.bbc.xls')).toEqual(false);
|
81
|
+
expect(validateUrl('https://www.bbc.ppt')).toEqual(false);
|
82
|
+
expect(validateUrl('https://www.bbc.ico')).toEqual(false);
|
83
|
+
expect(validateUrl('https://www.bbc.woff')).toEqual(false);
|
84
|
+
expect(validateURL('https://www.bbc.webp')).toEqual(false);
|
85
|
+
});
|
86
|
+
});
|
87
|
+
|
88
|
+
describe('test getStoragePath', () => {
|
89
|
+
jest.mock('../utils', () => ({
|
90
|
+
getCurrentDate: '1995-12-17',
|
91
|
+
}));
|
92
|
+
const mockRandomToken = 'token123';
|
93
|
+
test('should return "results/1995-12-17/token123"', () => {
|
94
|
+
expect(getStoragePath(mockRandomToken)).toEqual(`results/1995-12-17/${mockRandomToken}`);
|
95
|
+
});
|
96
|
+
});
|
97
|
+
|
98
|
+
describe('test setHeadlessMode', () => {
|
99
|
+
test('should headlessMode is true', () => {
|
100
|
+
setHeadlessMode(true);
|
101
|
+
expect(process.env.CRAWLEE_HEADLESS).toEqual('1');
|
102
|
+
});
|
103
|
+
test('should headlessMode is false', () => {
|
104
|
+
setHeadlessMode(false);
|
105
|
+
expect(process.env.CRAWLEE_HEADLESS).toEqual('0');
|
106
|
+
});
|
107
|
+
});
|
108
|
+
|
109
|
+
describe('test setHeadlessMode', () => {
|
110
|
+
test('should headlessMode is true', () => {
|
111
|
+
setHeadlessMode(true);
|
112
|
+
expect(process.env.CRAWLEE_HEADLESS).toEqual('1');
|
113
|
+
});
|
114
|
+
test('should headlessMode is false', () => {
|
115
|
+
setHeadlessMode(false);
|
116
|
+
expect(process.env.CRAWLEE_HEADLESS).toEqual('0');
|
117
|
+
});
|
118
|
+
});
|
Binary file
|