Skip to content

Commit

Permalink
feat: adding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreiAlexandruParaschiv committed Aug 7, 2024
1 parent 9a5c102 commit e1e4f6f
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 27 deletions.
8 changes: 4 additions & 4 deletions .nycrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"lcov",
"text"
],
"check-coverage": false,
"lines": 100,
"branches": 100,
"statements": 100,
"check-coverage": true,
"lines": 90,
"branches": 90,
"statements": 90,
"all": true,
"include": [
"src/**/*.js"
Expand Down
35 changes: 12 additions & 23 deletions src/canonical/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const CANONICAL_CHECKS = Object.freeze({
* @param {Object} context.log - The logging object to log information.
* @returns {Promise<Array<Object>>} A promise that resolves to an array of top pages.
*/
async function getTopPagesForSiteId(dataAccess, siteId, context, log) {
export async function getTopPagesForSiteId(dataAccess, siteId, context, log) {
try {
const result = await dataAccess.getTopPagesForSite(siteId, 'ahrefs', 'global');
log.info('Received top pages response:', JSON.stringify(result, null, 2));
Expand All @@ -127,7 +127,7 @@ async function getTopPagesForSiteId(dataAccess, siteId, context, log) {
* @param {Object} log - The logging object to log information.
* @returns {Promise<Object>} An object containing the canonical URL and an array of checks.
*/
async function validateCanonicalTag(url, log) {
export async function validateCanonicalTag(url, log) {
// in case of undefined or null URL in the 200 top pages list
if (!url) {
const errorMessage = 'URL is undefined or null';
Expand All @@ -146,8 +146,6 @@ async function validateCanonicalTag(url, log) {
log.info(`Fetching URL: ${url}`);
const response = await fetch(url);
const html = await response.text();
log.info(`Fetched HTML content for URL: ${url}`);

const dom = new JSDOM(html);
const { head } = dom.window.document;
const canonicalLinks = head.querySelectorAll('link[rel="canonical"]');
Expand Down Expand Up @@ -183,8 +181,6 @@ async function validateCanonicalTag(url, log) {
log.info(`Multiple canonical tags found for URL: ${url}`);
} else if (canonicalLinks.length === 1) {
const canonicalLink = canonicalLinks[0];
log.info(`Canonical link element: ${JSON.stringify(canonicalLink.outerHTML)}`);

const href = canonicalLink.getAttribute('href');
if (!href) {
checks.push({
Expand All @@ -200,21 +196,20 @@ async function validateCanonicalTag(url, log) {
check: CANONICAL_CHECKS.CANONICAL_TAG_NONEMPTY.check,
success: true,
});
log.info(`Valid canonical URL resolved: ${canonicalUrl}`);
// Check if canonical URL points to itself
if (canonicalUrl === url) {
checks.push({
check: CANONICAL_CHECKS.CANONICAL_SELF_REFERENCED.check,
success: true,
});
log.info(`Canonical URL correctly references itself: ${canonicalUrl}`);
log.info(`Canonical URL ${canonicalUrl} references itself`);
} else {
checks.push({
check: CANONICAL_CHECKS.CANONICAL_SELF_REFERENCED.check,
success: false,
explanation: CANONICAL_CHECKS.CANONICAL_SELF_REFERENCED.explanation,
});
log.info(`Canonical URL does not reference itself: ${canonicalUrl}`);
log.info(`Canonical URL ${canonicalUrl} does not reference itself`);
}
} catch (error) {
checks.push({
Expand All @@ -239,11 +234,10 @@ async function validateCanonicalTag(url, log) {
check: CANONICAL_CHECKS.CANONICAL_TAG_IN_HEAD.check,
success: true,
});
log.info(`Canonical tag is correctly placed in the head section for URL: ${url}`);
}
}

log.info(`Validation checks for URL: ${url}, Checks: ${JSON.stringify(checks)}`);
log.info(`Checks: ${JSON.stringify(checks)}`);
return { canonicalUrl, checks };
} catch (error) {
const errorMessage = `Error validating canonical tag for ${url}: ${error.message}`;
Expand All @@ -265,10 +259,9 @@ async function validateCanonicalTag(url, log) {
* @param {string} canonicalUrl - The canonical URL to validate.
* @param {string} baseUrl - The base URL to compare against.
* @param log
* @returns {Array<Object>} Array of check results, each with a check and error if the check failed.
* @returns {Array<Object>} Array of check results.
*/

function validateCanonicalUrlFormat(canonicalUrl, baseUrl, log) {
export function validateCanonicalFormat(canonicalUrl, baseUrl, log) {
const checks = [];
let url;
let base;
Expand Down Expand Up @@ -313,7 +306,6 @@ function validateCanonicalUrlFormat(canonicalUrl, baseUrl, log) {
check: CANONICAL_CHECKS.CANONICAL_URL_ABSOLUTE.check,
success: true,
});
log.info(`Canonical URL is absolute: ${canonicalUrl}`);
}

// Check if the canonical URL has the same protocol as the base URL
Expand All @@ -329,7 +321,6 @@ function validateCanonicalUrlFormat(canonicalUrl, baseUrl, log) {
check: CANONICAL_CHECKS.CANONICAL_URL_SAME_PROTOCOL.check,
success: true,
});
log.info('Canonical URL uses the same protocol');
}

// Check if the canonical URL has the same domain as the base URL
Expand All @@ -345,7 +336,6 @@ function validateCanonicalUrlFormat(canonicalUrl, baseUrl, log) {
check: CANONICAL_CHECKS.CANONICAL_URL_SAME_DOMAIN.check,
success: true,
});
log.info(`Canonical URL ${canonicalUrl} has the same domain as base URL: ${canonicalUrl}`);
}

// Check if the canonical URL is in lowercase
Expand All @@ -361,7 +351,6 @@ function validateCanonicalUrlFormat(canonicalUrl, baseUrl, log) {
check: CANONICAL_CHECKS.CANONICAL_URL_LOWERCASED.check,
success: true,
});
log.info(`Canonical URL is lowercased: ${canonicalUrl}`);
}

return checks;
Expand All @@ -375,7 +364,7 @@ function validateCanonicalUrlFormat(canonicalUrl, baseUrl, log) {
* @param {Set<string>} [visitedUrls=new Set()] - A set of visited URLs to detect redirect loops.
* @returns {Promise<Object>} An object with the check result and any error if the check failed.
*/
async function validateCanonicalUrlContentsRecursive(canonicalUrl, log, visitedUrls = new Set()) {
export async function validateCanonicalRecursively(canonicalUrl, log, visitedUrls = new Set()) {
const checks = [];

// Check for redirect loops
Expand Down Expand Up @@ -407,7 +396,7 @@ async function validateCanonicalUrlContentsRecursive(canonicalUrl, log, visitedU
// Check for redirection to another URL
if (canonicalUrl !== finalUrl) {
log.info(`Canonical URL redirects to: ${finalUrl}`);
const result = await validateCanonicalUrlContentsRecursive(finalUrl, log, visitedUrls);
const result = await validateCanonicalRecursively(finalUrl, log, visitedUrls);
checks.push(...result.checks);
} else {
checks.push({
Expand Down Expand Up @@ -496,11 +485,11 @@ export async function canonicalAuditRunner(baseURL, context, site) {
if (canonicalUrl) {
log.info(`Found Canonical URL: ${canonicalUrl}`);

const urlFormatChecks = validateCanonicalUrlFormat(canonicalUrl, baseURL, log);
const urlFormatChecks = validateCanonicalFormat(canonicalUrl, baseURL, log);
log.info(`Canonical URL format results for ${canonicalUrl}: ${JSON.stringify(urlFormatChecks)}`);
checks.push(...urlFormatChecks);

const urlContentCheck = await validateCanonicalUrlContentsRecursive(canonicalUrl, log);
const urlContentCheck = await validateCanonicalRecursively(canonicalUrl, log);
log.info(`Canonical URL recursive result for ${canonicalUrl}: ${JSON.stringify(urlContentCheck)}`);
checks.push(...urlContentCheck);
}
Expand All @@ -523,7 +512,7 @@ export async function canonicalAuditRunner(baseURL, context, site) {
return acc;
}, {});

log.info(`Successfully completed canonical audit for site: ${baseURL}`);
log.info(`Successfully completed Canonical Audit for site: ${baseURL}`);
log.info(`Audit results: ${JSON.stringify(aggregatedResults)}`);

return {
Expand Down
138 changes: 138 additions & 0 deletions test/audits/canonical.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,141 @@
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* eslint-env mocha */

import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import nock from 'nock';
import {
getTopPagesForSiteId, validateCanonicalTag, validateCanonicalFormat,
validateCanonicalRecursively, canonicalAuditRunner,
} from '../../src/canonical/handler.js'; // Adjust the import path as necessary

chai.use(sinonChai);
chai.use(chaiAsPromised);
const { expect } = chai;

describe('Canonical URL Tests', () => {
let log;

beforeEach(() => {
log = {
info: sinon.stub(),
error: sinon.stub(),
};
});

afterEach(() => {
sinon.restore();
nock.cleanAll();
});

describe('getTopPagesForSiteId', () => {
it('should return top pages for a given site ID', async () => {
const dataAccess = {
getTopPagesForSite: sinon.stub().resolves([{ getURL: () => 'http://example.com/page1' }]),
};
const siteId = 'testSiteId';
const context = { log };

const result = await getTopPagesForSiteId(dataAccess, siteId, context, log);

expect(result).to.deep.equal([{ url: 'http://example.com/page1' }]);
expect(log.info).to.have.been.called;
});

it('should handle error and return an empty array', async () => {
const dataAccess = {
getTopPagesForSite: sinon.stub().rejects(new Error('Test error')),
};
const siteId = 'testSiteId';
const context = { log };

const result = await getTopPagesForSiteId(dataAccess, siteId, context, log);

expect(result).to.deep.equal([]);
expect(log.error).to.have.been.calledWith('Error retrieving top pages for site testSiteId: Test error');
});
});

describe('validateCanonicalTag', () => {
it('should handle missing canonical tag', async () => {
const url = 'http://example.com';
const html = '<!DOCTYPE html><html><head></head><body></body></html>';
nock('http://example.com').get('/').reply(200, html);

const result = await validateCanonicalTag(url, log);

expect(result.canonicalUrl).to.be.null;
expect(result.checks).to.deep.include({
check: 'canonical-tag-exists',
success: false,
explanation: 'The canonical tag is missing, which can lead to duplicate content issues and negatively affect SEO rankings.',
});
expect(log.info).to.have.been.called;
});

it('should handle fetch error', async () => {
const url = 'http://example.com';
nock('http://example.com').get('/').replyWithError('Test error');

const result = await validateCanonicalTag(url, log);

expect(result.canonicalUrl).to.be.null;
expect(result.checks).to.deep.include({ check: 'canonical-url-fetch-error', success: false, explanation: 'There was an error fetching the canonical URL, which prevents validation of the canonical tag.' });
// expect(log.error).to.have.been.calledWith('Error validating canonical tag for http://example.com: request to http://example.com/ failed, reason: Test error');
});
});

describe('validateCanonicalUrlFormat', () => {
it('should validate canonical URL format successfully', () => {
const canonicalUrl = 'http://example.com/page';
const baseUrl = 'http://example.com';

const result = validateCanonicalFormat(canonicalUrl, baseUrl, log);

expect(result).to.deep.include({ check: 'canonical-url-absolute', success: true });
expect(result).to.deep.include({ check: 'canonical-url-same-protocol', success: true });
expect(result).to.deep.include({ check: 'canonical-url-same-domain', success: true });
expect(result).to.deep.include({ check: 'canonical-url-lowercased', success: true });
});

it('should handle invalid canonical URL', () => {
const canonicalUrl = 'invalid-url';
const baseUrl = 'http://example.com';

const result = validateCanonicalFormat(canonicalUrl, baseUrl, log);

expect(result).to.deep.include({ check: 'url-defined', success: false, explanation: 'The URL is undefined or null, which prevents the canonical tag validation process.' });
expect(log.error).to.have.been.calledWith('Invalid URL: invalid-url');
});
});

describe('validateCanonicalUrlContentsRecursive', () => {
it('should validate canonical URL contents successfully', async () => {
const canonicalUrl = 'http://example.com/page';
nock('http://example.com').get('/page').reply(200);

const result = await validateCanonicalRecursively(canonicalUrl, log);

expect(result).to.deep.include({ check: 'canonical-url-status-ok', success: true });
expect(result).to.deep.include({ check: 'canonical-url-no-redirect', success: true });
});
});

describe('canonicalAuditRunner', () => {
it('should run canonical audit successfully', async () => {
const baseURL = 'http://example.com';
const context = { log, dataAccess: { getTopPagesForSite: sinon.stub().resolves([{ getURL: () => 'http://example.com/page1' }]) } };
const site = { getId: () => 'testSiteId' };

const result = await canonicalAuditRunner(baseURL, context, site);

expect(result).to.be.an('object');
expect(log.info).to.have.been.called;
});
});
});

0 comments on commit e1e4f6f

Please sign in to comment.