@contract-case/case-core
Version:
Core functionality for the ContractCase contract testing suite
163 lines • 9.68 kB
JavaScript
;
// We need to allow underscores because they're part of the HAL response
/* eslint-disable no-underscore-dangle */
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeBrokerApi = void 0;
const case_plugin_base_1 = require("@contract-case/case-plugin-base");
const axios_1 = require("./axios");
const versionString_1 = require("../../entities/versionString");
const logNotices_1 = require("./logNotices");
const trimSlash = (str) => {
if (typeof str !== 'string')
return '';
if (str.endsWith('/')) {
return trimSlash(str.substring(0, str.length - 1));
}
return str;
};
const validatePrecondition = (baseUrl, authToken, basicAuth) => {
if (baseUrl === undefined || baseUrl === '') {
return new case_plugin_base_1.CaseConfigurationError("Can't access a broker without specifying the base URL. Set the environment variable CASE_BROKER_BASEURL or the config property brokerBaseUrl", 'DONT_ADD_LOCATION', 'INVALID_CONFIG');
}
if (typeof baseUrl !== 'string') {
return new case_plugin_base_1.CaseConfigurationError(`Expected the baseurl to be a string, but it was '${typeof authToken}'`, 'DONT_ADD_LOCATION', 'INVALID_CONFIG');
}
if (authToken === undefined && basicAuth === undefined) {
return new case_plugin_base_1.CaseConfigurationError("Can't access a broker without an authorization token or basic auth set. Set the environment variable CASE_BROKER_CI_TOKEN", 'DONT_ADD_LOCATION', 'INVALID_CONFIG');
}
if (authToken !== undefined) {
if (authToken === '') {
return new case_plugin_base_1.CaseConfigurationError("Can't access a broker without an authorization token. Set the environment variable CASE_BROKER_CI_TOKEN", 'DONT_ADD_LOCATION', 'INVALID_CONFIG');
}
if (typeof authToken !== 'string') {
return new case_plugin_base_1.CaseConfigurationError(`Expected the authToken to be a string, but it was '${typeof authToken}'`, 'DONT_ADD_LOCATION', 'INVALID_CONFIG');
}
}
if (authToken === undefined && basicAuth === undefined) {
return new case_plugin_base_1.CaseConfigurationError("Can't access a broker without an authorization token or basic auth set. Set the environment variable CASE_BROKER_CI_TOKEN", 'DONT_ADD_LOCATION', 'INVALID_CONFIG');
}
return undefined;
};
const makeBrokerApi = (configContext) => {
const authToken = configContext['_case:currentRun:context:brokerCiAccessToken'];
const baseUrl = configContext['_case:currentRun:context:brokerBaseUrl'];
const basicAuth = configContext['_case:currentRun:context:brokerBasicAuth'];
const auth = (authToken ?? basicAuth);
const server = (0, axios_1.makeAxiosConnector)(trimSlash(baseUrl), auth);
const err = validatePrecondition(baseUrl, authToken, basicAuth);
return {
publishContract: (contract, version, branch, logContext) => {
if (err !== undefined)
throw err;
logContext.logger.debug(`Publishing contract for ${contract.description.consumerName}@${version} -> ${contract.description.providerName} to broker at ${baseUrl}`);
return server
.authedPost('/contracts/publish', {
pacticipantName: contract.description.consumerName,
pacticipantVersionNumber: version,
...(branch !== false ? { branch } : {}),
tags: [],
contracts: [
{
consumerName: contract.description.consumerName,
providerName: contract.description.providerName,
// We claim that this is a pact spec, because otherwise the pact
// broker won't accept it. However, this value has no effect on the
// broker's behaviour.
// TODO: Replace this with '_case::contract' when the broker supports it
specification: 'pact',
contentType: 'application/json',
content: Buffer.from(JSON.stringify(contract)).toString('base64'),
},
],
}, logContext)
.then((data) => {
(0, logNotices_1.logNotices)(data.notices, logContext);
});
},
publishVerificationResults: (contract, success, providerVersion, branch, logContext) => Promise.resolve()
.then(() => {
if (err !== undefined)
throw err;
if (contract._links === undefined) {
logContext.logger.maintainerDebug('No links section in the following contract:', contract);
throw new case_plugin_base_1.CaseConfigurationError(`Trying to publish verification status for the contract between '${contract.description.consumerName}' and '${contract.description.providerName}', but it doesn't have a links section.\n\nThis usually means it wasn't downloaded from a broker. We can't publish verification status for non-brokered contracts.`, 'DONT_ADD_LOCATION', 'NON_BROKERED_CONTRACT');
}
if (contract._links['pb:publish-verification-results'] === undefined) {
logContext.logger.maintainerDebug('No pb:publish-verification-result section in the following contract:', contract);
throw new case_plugin_base_1.CaseConfigurationError(`The contract between '${contract.description.consumerName}' and '${contract.description.providerName}' doesn't have a publish verification results URL, so we can't publish it.`, 'DONT_ADD_LOCATION', 'NON_BROKERED_CONTRACT');
}
})
.then(() => (0, axios_1.makeAxiosConnector)(contract._links['pb:publish-verification-results'].href, auth)
.authedPost('', {
providerApplicationVersion: providerVersion,
success,
...(branch !== false ? { branch } : {}),
verifiedByImplementation: 'ContractCase',
verifiedByVersion: versionString_1.caseVersion,
executionDate: new Date(Date.now()).toISOString(),
tags: [],
}, logContext)
.then((t) => t)),
downloadContract: (url, logContext) => {
if (err !== undefined)
throw err;
return (0, axios_1.makeAxiosConnector)(url, auth).authedGet('', {}, logContext);
},
urlsForVerification: (serviceName, logContext) => {
if (err !== undefined)
throw err;
logContext.logger.debug(`Finding contracts to verify for service '${serviceName}' on broker at ${baseUrl}`);
const path = `/pacts/provider/${encodeURIComponent(serviceName)}/for-verification`;
logContext.logger.maintainerDebug(`forVerification path is: ${path}`);
return server
.authedPost(path, {
consumerVersionSelectors: [
{
mainBranch: true,
},
{
deployedOrReleased: true,
},
{ latest: true },
],
providerVersionTags: ['main'],
}, logContext)
.then((d) => {
logContext.logger.deepMaintainerDebug(`Pacts for verification responded with`, JSON.stringify(d, undefined, 2));
const numPacts = d._embedded.pacts.length;
logContext.logger.debug(`Broker returned ${numPacts} URLs to possible contracts for verification`);
return d._embedded.pacts.map((contract) => contract._links.self);
});
},
canDeploy: (serviceName, serviceVersion, environment, logContext) => {
if (err !== undefined)
throw err;
logContext.logger.debug(`Asking if it's safe to deploy '${serviceName}' at version '${serviceVersion}' to '${environment}', using broker at ${baseUrl}`);
return server
.authedGet('/can-i-deploy', {
pacticipant: serviceName,
version: serviceVersion,
environment,
}, logContext)
.then((data) => {
logContext.logger.maintainerDebug(`Can-I-Deploy returned with`, JSON.stringify(data, undefined, 2));
if (data.summary.deployable === true &&
(data.summary.failed !== 0 || data.summary.unknown !== 0)) {
logContext.logger.warn(`!!!! The broker is not behaving as it is documented !!!!`);
logContext.logger.warn(`The broker said it was safe to deploy but had a non zero count of services in state failed (${data.summary.failed}) or unknown (${data.summary.unknown})`);
logContext.logger.warn(`Please raise this with the maintainers of the broker you are using`);
}
if (data.summary.deployable !== true) {
logContext.logger.debug("The broker said it's not safe to deploy. Here's the matrix from the broker, which may be useful:", data.matrix);
}
(0, logNotices_1.logNotices)(data.notices, logContext);
return {
deployable: data.summary.deployable === true,
reason: `${data.summary.reason}\nsuccess (${data.summary.success}), failed (${data.summary.failed}), unknown / never-verified (${data.summary.unknown}) services`,
};
});
},
};
};
exports.makeBrokerApi = makeBrokerApi;
//# sourceMappingURL=broker.js.map