"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeSnippetIntoErrorStack = exports.trimArray = exports.isImageInfoWithState = exports.isImageBufferData = exports.getTestHash = exports.getDetailsFileName = exports.getTitleDelimiter = exports.getToggledCheckboxState = exports.isCheckboxUnchecked = exports.isCheckboxIndeterminate = exports.isCheckboxChecked = exports.normalizeUrls = exports.fetchFile = exports.isUrl = exports.isBase64Image = exports.determineStatus = exports.hasDiff = exports.getError = exports.hasUnrelatedToScreenshotsErrors = exports.hasFailedImages = exports.hasNoRefImageErrors = exports.isNoRefImageError = exports.isImageDiffError = exports.isAssertViewError = exports.mkTestId = exports.wrapLinkByTag = exports.getRelativeUrl = exports.getUrlWithBase = exports.determineFinalStatus = exports.isUpdatedStatus = exports.isSkippedStatus = exports.isErrorStatus = exports.isRunningStatus = exports.isIdleStatus = exports.isFailStatus = exports.isSuccessStatus = exports.logger = exports.getShortMD5 = void 0;
const crypto_1 = __importDefault(require("crypto"));
const lodash_1 = require("lodash");
const url_1 = __importDefault(require("url"));
const axios_1 = __importDefault(require("axios"));
const constants_1 = require("./constants");
const checked_statuses_1 = require("./constants/checked-statuses");
const errors_1 = require("./errors");
const getShortMD5 = (str) => {
    return crypto_1.default.createHash('md5').update(str, 'ascii').digest('hex').substr(0, 7);
};
exports.getShortMD5 = getShortMD5;
const statusPriority = [
    // non-final
    constants_1.RUNNING, constants_1.QUEUED,
    // final
    constants_1.ERROR, constants_1.FAIL, constants_1.UPDATED, constants_1.SUCCESS, constants_1.IDLE, constants_1.SKIPPED
];
exports.logger = (0, lodash_1.pick)(console, ['log', 'warn', 'error']);
const isSuccessStatus = (status) => status === constants_1.SUCCESS;
exports.isSuccessStatus = isSuccessStatus;
const isFailStatus = (status) => status === constants_1.FAIL;
exports.isFailStatus = isFailStatus;
const isIdleStatus = (status) => status === constants_1.IDLE;
exports.isIdleStatus = isIdleStatus;
const isRunningStatus = (status) => status === constants_1.RUNNING;
exports.isRunningStatus = isRunningStatus;
const isErrorStatus = (status) => status === constants_1.ERROR;
exports.isErrorStatus = isErrorStatus;
const isSkippedStatus = (status) => status === constants_1.SKIPPED;
exports.isSkippedStatus = isSkippedStatus;
const isUpdatedStatus = (status) => status === constants_1.UPDATED;
exports.isUpdatedStatus = isUpdatedStatus;
const determineFinalStatus = (statuses) => {
    if (!statuses.length) {
        return constants_1.SUCCESS;
    }
    const set = new Set(statuses);
    for (const status of statusPriority) {
        if (set.has(status)) {
            return status;
        }
    }
    console.error('Unknown statuses: ' + JSON.stringify(statuses));
    return null;
};
exports.determineFinalStatus = determineFinalStatus;
const getUrlWithBase = (url, base) => {
    try {
        const userUrl = new URL(url ?? '', base);
        // Manually overriding properties, because if url is absolute, the above won't work
        if (!(0, lodash_1.isEmpty)(base)) {
            const baseUrl = new URL(base);
            userUrl.host = baseUrl.host;
            userUrl.protocol = baseUrl.protocol;
            userUrl.port = baseUrl.port;
            userUrl.username = baseUrl.username;
            userUrl.password = baseUrl.password;
        }
        return userUrl.href;
    }
    catch {
        return url || base || '';
    }
};
exports.getUrlWithBase = getUrlWithBase;
const getRelativeUrl = (absoluteUrl) => {
    try {
        const urlObj = new URL(absoluteUrl);
        return urlObj.pathname + urlObj.search;
    }
    catch {
        return absoluteUrl;
    }
};
exports.getRelativeUrl = getRelativeUrl;
const wrapLinkByTag = (text) => {
    return text.replace(/https?:\/\/[^\s]*/g, (url) => {
        return `<a target="_blank" href="${url}">${url}</a>`;
    });
};
exports.wrapLinkByTag = wrapLinkByTag;
const mkTestId = (fullTitle, browserId) => {
    return fullTitle + '.' + browserId;
};
exports.mkTestId = mkTestId;
const isAssertViewError = (error) => {
    return error?.name === errors_1.ErrorName.ASSERT_VIEW;
};
exports.isAssertViewError = isAssertViewError;
const isImageDiffError = (error) => {
    return error?.name === errors_1.ErrorName.IMAGE_DIFF;
};
exports.isImageDiffError = isImageDiffError;
const isNoRefImageError = (error) => {
    return error?.name === errors_1.ErrorName.NO_REF_IMAGE;
};
exports.isNoRefImageError = isNoRefImageError;
const hasNoRefImageErrors = ({ assertViewResults = [] }) => {
    return assertViewResults.some((assertViewResult) => (0, exports.isNoRefImageError)(assertViewResult));
};
exports.hasNoRefImageErrors = hasNoRefImageErrors;
const hasFailedImages = (imagesInfo = []) => {
    return imagesInfo.some((imageInfo) => {
        return imageInfo.stateName &&
            ((0, exports.isErrorStatus)(imageInfo.status) || (0, exports.isFailStatus)(imageInfo.status) || (0, exports.isNoRefImageError)(imageInfo) || (0, exports.isImageDiffError)(imageInfo));
    });
};
exports.hasFailedImages = hasFailedImages;
const hasUnrelatedToScreenshotsErrors = (error) => {
    return !(0, exports.isNoRefImageError)(error) &&
        !(0, exports.isImageDiffError)(error) &&
        !(0, exports.isAssertViewError)(error);
};
exports.hasUnrelatedToScreenshotsErrors = hasUnrelatedToScreenshotsErrors;
const getError = (error) => {
    if (!error) {
        return undefined;
    }
    return (0, lodash_1.pick)(error, ['name', 'message', 'stack', 'stateName', 'snippet']);
};
exports.getError = getError;
const hasDiff = (assertViewResults) => {
    return assertViewResults.some((result) => (0, exports.isImageDiffError)(result));
};
exports.hasDiff = hasDiff;
/* This method tries to determine true status of testResult by using fields like error, imagesInfo */
const determineStatus = (testResult) => {
    if (!(0, exports.hasFailedImages)(testResult.imagesInfo) &&
        !(0, exports.isSkippedStatus)(testResult.status) &&
        (!testResult.error || !(0, exports.hasUnrelatedToScreenshotsErrors)(testResult.error))) {
        return constants_1.SUCCESS;
    }
    const imageErrors = (testResult.imagesInfo ?? []).map(imagesInfo => imagesInfo.error ?? {});
    if ((0, exports.hasDiff)(imageErrors) || (0, exports.hasNoRefImageErrors)({ assertViewResults: imageErrors })) {
        return constants_1.FAIL;
    }
    if (!(0, lodash_1.isEmpty)(testResult.error)) {
        return constants_1.ERROR;
    }
    return testResult.status;
};
exports.determineStatus = determineStatus;
const isBase64Image = (image) => {
    return Boolean(image?.base64);
};
exports.isBase64Image = isBase64Image;
const isUrl = (str) => {
    if (typeof str !== 'string') {
        return false;
    }
    const parsedUrl = url_1.default.parse(str);
    return !!parsedUrl.host && !!parsedUrl.protocol;
};
exports.isUrl = isUrl;
const fetchFile = async (url, options) => {
    try {
        const { data, status } = await axios_1.default.get(url, options);
        return { data, status };
    }
    catch (e) { // eslint-disable-line @typescript-eslint/no-explicit-any
        exports.logger.warn(`Error while fetching ${url}`, e);
        // 'unknown' for request blocked by CORS policy
        const status = e.response ? e.response.status : 'unknown';
        return { data: null, status };
    }
};
exports.fetchFile = fetchFile;
const isRelativeUrl = (url) => {
    try {
        // eslint-disable-next-line no-new
        new URL(url);
        return false;
    }
    catch (e) {
        return true;
    }
};
const normalizeUrls = (urls = [], baseUrl) => {
    const baseUrlsSearch = new URL(baseUrl).search;
    return urls.map(url => {
        try {
            const newUrl = new URL(url, baseUrl);
            // URL's parameters can specify directory at file server
            if (isRelativeUrl(url) && !newUrl.search) {
                newUrl.search = baseUrlsSearch;
            }
            return newUrl.href;
        }
        catch (e) {
            exports.logger.warn(`Can not normalize url '${url} for base url '${baseUrl}'`, e);
            return url;
        }
    });
};
exports.normalizeUrls = normalizeUrls;
// TODO: use enum types instead of numbers below
const isCheckboxChecked = (status) => Number(status) === checked_statuses_1.CHECKED;
exports.isCheckboxChecked = isCheckboxChecked;
const isCheckboxIndeterminate = (status) => Number(status) === checked_statuses_1.INDETERMINATE;
exports.isCheckboxIndeterminate = isCheckboxIndeterminate;
const isCheckboxUnchecked = (status) => Number(status) === checked_statuses_1.UNCHECKED;
exports.isCheckboxUnchecked = isCheckboxUnchecked;
const getToggledCheckboxState = (status) => (0, exports.isCheckboxChecked)(status) ? checked_statuses_1.UNCHECKED : checked_statuses_1.CHECKED;
exports.getToggledCheckboxState = getToggledCheckboxState;
const getTitleDelimiter = (toolName) => {
    return toolName === constants_1.ToolName.Playwright ? constants_1.PWT_TITLE_DELIMITER : constants_1.TESTPLANE_TITLE_DELIMITER;
};
exports.getTitleDelimiter = getTitleDelimiter;
function getDetailsFileName(testId, browserId, attempt) {
    return `${testId}-${browserId}_${Number(attempt) + 1}_${Date.now()}.json`;
}
exports.getDetailsFileName = getDetailsFileName;
const getTestHash = (testResult) => {
    return testResult.testPath.concat(testResult.browserId, testResult.attempt.toString()).join(' ');
};
exports.getTestHash = getTestHash;
const isImageBufferData = (imageData) => {
    return Boolean(imageData.buffer);
};
exports.isImageBufferData = isImageBufferData;
const isImageInfoWithState = (imageInfo) => {
    return Boolean(imageInfo.stateName);
};
exports.isImageInfoWithState = isImageInfoWithState;
const trimArray = (array) => {
    let indexBegin = 0;
    let indexEnd = array.length;
    while (indexBegin < array.length && !array[indexBegin]) {
        indexBegin++;
    }
    while (indexEnd > 0 && !array[indexEnd - 1]) {
        indexEnd--;
    }
    return array.slice(indexBegin, indexEnd);
};
exports.trimArray = trimArray;
const getErrorTitle = (e) => {
    let errorName = e.name;
    if (!errorName && e.stack) {
        const columnIndex = e.stack.indexOf(':');
        if (columnIndex !== -1) {
            errorName = e.stack.slice(0, columnIndex);
        }
        else {
            errorName = e.stack.slice(0, e.stack.indexOf('\n'));
        }
    }
    if (!errorName) {
        errorName = 'Error';
    }
    return e.message ? `${errorName}: ${e.message}` : errorName;
};
const getErrorRawStackFrames = (e) => {
    const errorTitle = getErrorTitle(e) + '\n';
    const errorTitleStackIndex = e.stack.indexOf(errorTitle);
    if (errorTitleStackIndex !== -1) {
        return e.stack.slice(errorTitleStackIndex + errorTitle.length);
    }
    const errorString = e.toString ? e.toString() + '\n' : '';
    const errorStringIndex = e.stack.indexOf(errorString);
    if (errorString && errorStringIndex !== -1) {
        return e.stack.slice(errorStringIndex + errorString.length);
    }
    const errorMessageStackIndex = e.stack.indexOf(e.message);
    const errorMessageEndsStackIndex = e.stack.indexOf('\n', errorMessageStackIndex + e.message.length);
    return e.stack.slice(errorMessageEndsStackIndex + 1);
};
const cloneError = (error) => {
    const originalProperties = ['name', 'message', 'stack'];
    const clonedError = new Error(error.message);
    originalProperties.forEach(property => {
        delete clonedError[property];
    });
    const customProperties = Object.getOwnPropertyNames(error);
    originalProperties.concat(customProperties).forEach((property) => {
        clonedError[property] = error[property]; // eslint-disable-line @typescript-eslint/no-explicit-any
    });
    return clonedError;
};
const mergeSnippetIntoErrorStack = (error) => {
    if (!error.snippet) {
        return error;
    }
    const clonedError = cloneError(error);
    delete clonedError.snippet;
    if (!error.stack) {
        clonedError.stack = [
            getErrorTitle(error),
            error.snippet
        ].join('\n');
        return clonedError;
    }
    const grayBegin = '\x1B[90m';
    const grayEnd = '\x1B[39m';
    clonedError.stack = [
        getErrorTitle(error),
        error.snippet,
        grayBegin + getErrorRawStackFrames(error) + grayEnd
    ].join('\n');
    return clonedError;
};
exports.mergeSnippetIntoErrorStack = mergeSnippetIntoErrorStack;
//# sourceMappingURL=common-utils.js.map