Skip to content

Commit 9253baf

Browse files
Matej JellusMatej Jellus
authored andcommitted
Add SEO tests
1 parent 7dfb0cd commit 9253baf

File tree

11 files changed

+241
-1
lines changed

11 files changed

+241
-1
lines changed

src/Pentest.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
import Security from './security';
22
import HTML from './html';
3+
import SEO from './seo';
34
import WordPress from './wordpress';
45
import { Result } from './Test';
56

67
interface PentestResult {
78
security: Result;
89
html: Result;
10+
seo: Result;
911
wordpress: Result;
1012
}
1113

1214
class Pentest {
1315
public async run(url: string): Promise<PentestResult> {
1416
const security = new Security();
1517
const html = new HTML();
18+
const seo = new SEO();
1619
const wordPress = new WordPress();
1720
const securityResult = <Result> await security.run({ url });
1821
const htmlResult = <Result> await html.run({ url });
22+
const seoResult = <Result> await seo.run({ url });
1923
const wordPressResult = <Result> await wordPress.run({ url });
2024

2125
return {
2226
security: securityResult,
2327
html: htmlResult,
28+
seo: seoResult,
2429
wordpress: wordPressResult,
2530
};
2631
}

src/functions/getHeading.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { URL } from 'url';
2+
import getObject from './getObject';
3+
4+
const getHeading = (result: any): string | string[] => {
5+
const titles = getObject(result.html, 'name', 'h1')
6+
.map((title: any) => {
7+
return title.children[0].data;
8+
});
9+
10+
return titles.length > 1 ? titles : titles.shift();
11+
};
12+
13+
export default getHeading;

src/functions/getTitle.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { URL } from 'url';
2+
import getObject from './getObject';
3+
4+
const getTitle = (result: any): string | string[] => {
5+
const titles = getObject(result.html, 'name', 'title')
6+
.map((title: any) => {
7+
return title.children[0].data;
8+
});
9+
10+
return titles.length > 1 ? titles : titles.shift();
11+
};
12+
13+
export default getTitle;

src/functions/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ export { default as getGenerator } from './getGenerator';
99
export { default as parseHtml } from './parseHtml';
1010
export { default as parseSitemap } from './parseSitemap';
1111
export { default as parseXml } from './parseXml';
12+
export { default as getTitle } from './getTitle';
13+
export { default as getHeading } from './getHeading';

src/functions/parseXml.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import parser from 'xml2js';
33
export default function(result) {
44
return new Promise((resolve, reject) => {
55
parser.parseString(result.body, (err, r) => {
6+
if (err) {
7+
reject(err);
8+
}
9+
610
resolve(r);
711
});
812
});

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ program
4848
const results = await pentest.run(url);
4949

5050
const report = Report.get(config.report.format);
51-
report.write([ results.security, results.html, results.wordpress ]);
51+
report.write([ results.security, results.html, results.seo, results.wordpress ]);
5252
});
5353

5454
program

src/seo/Heading.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Test, { TestParameters, Result } from '../Test';
2+
import request from '../request';
3+
import logger from '../logger';
4+
import { getHeading, parseHtml } from '../functions';
5+
6+
class Heading extends Test {
7+
public name = 'Heading';
8+
9+
public async test({ url }: TestParameters): Promise<Result> {
10+
logger.info(`Starting ${this.constructor.name} test...`);
11+
12+
const response = await request.get(url);
13+
const html = await parseHtml(response);
14+
const heading = getHeading(html);
15+
const subTests = this.checkHeading(heading);
16+
17+
return {
18+
status: this.getStatus(subTests.map(test => test.status)),
19+
title: this.constructor.name,
20+
description: '',
21+
results: subTests,
22+
};
23+
}
24+
25+
private checkHeading(title: string | string[]): Array<Result> {
26+
const results = [];
27+
28+
results.push({
29+
status: typeof title !== undefined && title.length > 0 ? 'SUCCESS' : 'WARNING',
30+
title: 'H1 tag',
31+
});
32+
33+
results.push({
34+
status: Array.isArray(title) ? 'ERROR' : 'SUCCESS',
35+
title: 'Duplicate H1 tag',
36+
description: `HTML should contain just one title tag.`,
37+
});
38+
39+
results.push({
40+
status: title.length <= 60 ? 'SUCCESS' : 'WARNING',
41+
title: 'Title length',
42+
description: `Title length should be under 60 characters and it is ${title.length}.`,
43+
});
44+
45+
return results;
46+
}
47+
}
48+
49+
export default Heading;

src/seo/Robots.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Test, { TestParameters, Result } from '../Test';
2+
import request from '../request';
3+
import logger from '../logger';
4+
5+
class Robots extends Test {
6+
public name = 'Robots';
7+
8+
public async test({ url }: TestParameters): Promise<Result> {
9+
logger.info(`Starting ${this.constructor.name} test...`);
10+
11+
const response = await request.get(`${url}/robots.txt`);
12+
13+
return {
14+
status: Math.floor(response.statusCode / 100) === 2 ? 'SUCCESS' : 'WARNING',
15+
title: 'Robots.txt',
16+
description: '',
17+
};
18+
}
19+
}
20+
21+
export default Robots;

src/seo/Sitemap.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Test, { TestParameters, Result } from '../Test';
2+
import request from '../request';
3+
import logger from '../logger';
4+
import { parseXml } from '../functions';
5+
6+
class Sitemap extends Test {
7+
public name = 'Sitemap';
8+
9+
public async test({ url }: TestParameters): Promise<Result> {
10+
logger.info(`Starting ${this.constructor.name} test...`);
11+
12+
const robotsResponse = await request.get(`${url}/robots.txt`);
13+
14+
let sitemapUrl = `${url}/sitemap.xml`;
15+
16+
if (Math.floor(robotsResponse.statusCode / 100) === 2) {
17+
const lines = robotsResponse.body.split(/\r?\n/);
18+
19+
const sitemap = lines.find(line => line.startsWith('Sitemap'));
20+
21+
if (typeof sitemap !== 'undefined') {
22+
sitemapUrl = sitemap.split(' ')[1];
23+
}
24+
}
25+
26+
const response = await request.get(sitemapUrl);
27+
const xml = await parseXml(response) as object;
28+
29+
return {
30+
status: 'sitemapindex' in xml || 'urlset' in xml ? 'SUCCESS' : 'WARNING',
31+
title: this.constructor.name,
32+
description: '',
33+
};
34+
}
35+
}
36+
37+
export default Sitemap;

src/seo/Title.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Test, { TestParameters, Result } from '../Test';
2+
import request from '../request';
3+
import logger from '../logger';
4+
import { getTitle, parseHtml } from '../functions';
5+
6+
class Title extends Test {
7+
public name = 'Title';
8+
9+
public async test({ url }: TestParameters): Promise<Result> {
10+
logger.info(`Starting ${this.constructor.name} test...`);
11+
12+
const response = await request.get(url);
13+
const html = await parseHtml(response);
14+
const title = getTitle(html);
15+
const subTests = this.checkTitle(title);
16+
17+
return {
18+
status: this.getStatus(subTests.map(test => test.status)),
19+
title: this.constructor.name,
20+
description: '',
21+
results: subTests,
22+
};
23+
}
24+
25+
private checkTitle(title: string | string[]): Array<Result> {
26+
const results = [];
27+
28+
results.push({
29+
status: typeof title !== undefined && title.length > 0 ? 'SUCCESS' : 'WARNING',
30+
title: 'Title tag',
31+
});
32+
33+
results.push({
34+
status: Array.isArray(title) ? 'ERROR' : 'SUCCESS',
35+
title: 'Duplicate title tag',
36+
description: `HTML should contain just one title tag.`,
37+
});
38+
39+
results.push({
40+
status: title.length <= 60 ? 'SUCCESS' : 'WARNING',
41+
title: 'Title length',
42+
description: `Title length should be under 60 characters and it is ${title.length}.`,
43+
});
44+
45+
return results;
46+
}
47+
}
48+
49+
export default Title;

0 commit comments

Comments
 (0)