Key changes: - Run URL checks more frequently on every change. - Introduce environment variable to randomly select and limit URLs tested, this way the tests will provide quicker feedback on code changes. Other supporting changes: - Log more information about test before running the test to enable easier troubleshooting. - Move shuffle function for arrays for reusability and missing tests.
102 lines
3.9 KiB
TypeScript
102 lines
3.9 KiB
TypeScript
import { test, expect } from 'vitest';
|
|
import { parseApplication } from '@/application/Parser/ApplicationParser';
|
|
import { indentText } from '@tests/shared/Text';
|
|
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
|
import { shuffle } from '@/application/Common/Shuffle';
|
|
import { type UrlStatus, formatUrlStatus } from './StatusChecker/UrlStatus';
|
|
import { getUrlStatusesInParallel, type BatchRequestOptions } from './StatusChecker/BatchStatusChecker';
|
|
import { TestExecutionDetailsLogger } from './TestExecutionDetailsLogger';
|
|
import { extractDocumentationUrls } from './DocumentationUrlExtractor';
|
|
|
|
// arrange
|
|
const logger = new TestExecutionDetailsLogger();
|
|
logger.logTestSectionStartDelimiter();
|
|
const app = parseApplication();
|
|
let urls = extractDocumentationUrls({
|
|
logger,
|
|
urlExclusionPatterns: [
|
|
/^https:\/\/archive\.ph/, // Drops HEAD/GET requests via fetch/curl, responding to Postman/Chromium.
|
|
],
|
|
application: app,
|
|
});
|
|
urls = filterUrlsToEnvironmentCheckLimit(urls);
|
|
logger.logLabeledInformation('URLs submitted for testing', urls.length.toString());
|
|
const requestOptions: BatchRequestOptions = {
|
|
domainOptions: {
|
|
sameDomainParallelize: false, // be nice to our third-party servers
|
|
sameDomainDelayInMs: 5 /* sec */ * 1000,
|
|
},
|
|
requestOptions: {
|
|
retryExponentialBaseInMs: 3 /* sec */ * 1000,
|
|
requestTimeoutInMs: 60 /* sec */ * 1000,
|
|
additionalHeaders: { referer: app.projectDetails.homepage },
|
|
randomizeTlsFingerprint: true,
|
|
},
|
|
followOptions: {
|
|
followRedirects: true,
|
|
enableCookies: true,
|
|
},
|
|
};
|
|
logger.logLabeledInformation('HTTP request options', JSON.stringify(requestOptions, null, 2));
|
|
const testTimeoutInMs = urls.length * 60 /* seconds */ * 1000;
|
|
logger.logLabeledInformation('Scheduled test duration', convertMillisecondsToHumanReadableFormat(testTimeoutInMs));
|
|
logger.logTestSectionEndDelimiter();
|
|
test(`all URLs (${urls.length}) should be alive`, async () => {
|
|
// act
|
|
console.log('URLS', urls); // TODO: Delete
|
|
const results = await getUrlStatusesInParallel(urls, requestOptions);
|
|
// assert
|
|
const deadUrls = results.filter((r) => r.code === undefined || !isOkStatusCode(r.code));
|
|
expect(deadUrls).to.have.lengthOf(
|
|
0,
|
|
formatAssertionMessage([createReportForDeadUrlStatuses(deadUrls)]),
|
|
);
|
|
}, testTimeoutInMs);
|
|
|
|
function isOkStatusCode(statusCode: number): boolean {
|
|
return statusCode >= 200 && statusCode < 300;
|
|
}
|
|
|
|
function createReportForDeadUrlStatuses(deadUrlStatuses: readonly UrlStatus[]): string {
|
|
return `\n${deadUrlStatuses.map((status) => indentText(formatUrlStatus(status))).join('\n---\n')}\n`;
|
|
}
|
|
|
|
function filterUrlsToEnvironmentCheckLimit(originalUrls: string[]): string[] {
|
|
const { RANDOMIZED_URL_CHECK_LIMIT } = process.env;
|
|
logger.logLabeledInformation('URL check limit', RANDOMIZED_URL_CHECK_LIMIT || 'Unlimited');
|
|
if (RANDOMIZED_URL_CHECK_LIMIT !== undefined && RANDOMIZED_URL_CHECK_LIMIT !== '') {
|
|
const maxUrlsInTest = parseInt(RANDOMIZED_URL_CHECK_LIMIT, 10);
|
|
if (Number.isNaN(maxUrlsInTest)) {
|
|
throw new Error(`Invalid URL limit: ${RANDOMIZED_URL_CHECK_LIMIT}`);
|
|
}
|
|
if (maxUrlsInTest < originalUrls.length) {
|
|
return shuffle(originalUrls).slice(0, maxUrlsInTest);
|
|
}
|
|
}
|
|
return originalUrls;
|
|
}
|
|
|
|
function convertMillisecondsToHumanReadableFormat(milliseconds: number): string {
|
|
const timeParts: string[] = [];
|
|
const addTimePart = (amount: number, label: string) => {
|
|
if (amount === 0) {
|
|
return;
|
|
}
|
|
timeParts.push(`${amount} ${label}`);
|
|
};
|
|
|
|
const hours = milliseconds / (1000 * 60 * 60);
|
|
const absoluteHours = Math.floor(hours);
|
|
addTimePart(absoluteHours, 'hours');
|
|
|
|
const minutes = (hours - absoluteHours) * 60;
|
|
const absoluteMinutes = Math.floor(minutes);
|
|
addTimePart(absoluteMinutes, 'minutes');
|
|
|
|
const seconds = (minutes - absoluteMinutes) * 60;
|
|
const absoluteSeconds = Math.floor(seconds);
|
|
addTimePart(absoluteSeconds, 'seconds');
|
|
|
|
return timeParts.join(', ');
|
|
}
|