Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#35: create webapisync option in cert utils #36

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ add package `@reso/reso-certification-utils` and yarn install --check-files. Aft
## Batch Test Runner
Runs a batch of tests using a provided configuration file.

[**MORE INFO**](./utils/batch-test-runner/README.md)
[**MORE INFO**](./lib/batch-test-runner/README.md)

## Restore Certification Server
Restores a RESO Certification API Server using a set of existing results in a given directory structure.

[**MORE INFO**](./utils/restore-utils/README.md)
[**MORE INFO**](./lib/restore-utils/README.md)

## Tests

Expand Down
59 changes: 21 additions & 38 deletions common.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,10 @@ const availableVersions = {
};

const getCurrentVersion = endorsementName =>
endorsementName &&
availableVersions[endorsementName] &&
availableVersions[endorsementName].currentVersion;
endorsementName && availableVersions[endorsementName] && availableVersions[endorsementName].currentVersion;

const getPreviousVersion = endorsementName =>
endorsementName &&
availableVersions[endorsementName] &&
availableVersions[endorsementName].previousVersion;
endorsementName && availableVersions[endorsementName] && availableVersions[endorsementName].previousVersion;

/**
* Determines whether the given endorsementName is valid.
Expand All @@ -60,8 +56,7 @@ const getPreviousVersion = endorsementName =>
* @returns true if the endorsementName is valid, false otherwise.
* @throws error if parameters aren't valid
*/
const isValidEndorsement = endorsementName =>
endorsementName && !!availableVersions[endorsementName];
const isValidEndorsement = endorsementName => endorsementName && !!availableVersions[endorsementName];

/**
* Determines whether the version is valid for the given endorsement.
Expand Down Expand Up @@ -90,9 +85,7 @@ const getEndorsementMetadata = (endorsementName, version) => {
}

if (!isValidVersion(endorsementName, version)) {
throw new Error(
`Invalid endorsement version! endorsementKey: ${endorsementName}, version: ${version}`
);
throw new Error(`Invalid endorsement version! endorsementKey: ${endorsementName}, version: ${version}`);
}

const ddVersion = version || CURRENT_DATA_DICTIONARY_VERSION,
Expand All @@ -104,10 +97,7 @@ const getEndorsementMetadata = (endorsementName, version) => {
version: `${ddVersion}`,
/* TODO: add versions to JSON results file names in the Commander */
jsonResultsFiles: [METADATA_REPORT_JSON, DATA_AVAILABILITY_REPORT_JSON],
htmlReportFiles: [
`data-dictionary-${ddVersion}.html`,
`data-availability.dd-${ddVersion}.html`
],
htmlReportFiles: [`data-dictionary-${ddVersion}.html`, `data-availability.dd-${ddVersion}.html`],
logFileName: COMMANDER_LOG_FILE_NAME
};
}
Expand All @@ -117,11 +107,7 @@ const getEndorsementMetadata = (endorsementName, version) => {
directoryName: `${endorsements.DATA_DICTIONARY_WITH_IDX}`,
version: `${version}`,
/* TODO: add versions to JSON results file names in the Commander */
jsonResultsFiles: [
METADATA_REPORT_JSON,
DATA_AVAILABILITY_REPORT_JSON,
IDX_DIFFERENCE_REPORT_JSON
],
jsonResultsFiles: [METADATA_REPORT_JSON, DATA_AVAILABILITY_REPORT_JSON, IDX_DIFFERENCE_REPORT_JSON],
htmlReportFiles: [
`data-dictionary-${ddVersion}.html`,
`data-availability.dd-${ddVersion}.html`,
Expand Down Expand Up @@ -191,15 +177,10 @@ const buildRecipientEndorsementPath = ({
if (!endorsementName) throw Error('endorsementName is required!');
if (!version) throw Error('version is required!');

if (!isValidEndorsement(endorsementName))
throw new Error(`Invalid endorsementName: ${endorsementName}`);
if (!isValidEndorsement(endorsementName)) throw new Error(`Invalid endorsementName: ${endorsementName}`);
if (!isValidVersion(endorsementName, version)) throw new Error(`Invalid version: ${version}`);

return path.join(
`${providerUoi}-${providerUsi}`,
recipientUoi,
currentOrArchived
);
return path.join(`${providerUoi}-${providerUsi}`, recipientUoi, currentOrArchived);
};

/**
Expand All @@ -212,13 +193,7 @@ const buildRecipientEndorsementPath = ({
* @param {String} providerUsi
* @param {String} recipientUoi
*/
const archiveEndorsement = ({
providerUoi,
providerUsi,
recipientUoi,
endorsementName,
version
} = {}) => {
const archiveEndorsement = ({ providerUoi, providerUsi, recipientUoi, endorsementName, version } = {}) => {
const currentRecipientPath = buildRecipientEndorsementPath({
providerUoi,
providerUsi,
Expand Down Expand Up @@ -270,13 +245,20 @@ const createResoScriptClientCredentialsConfig = ({ serviceRootUri, clientCredent
` <ClientSecret>${clientCredentials.clientSecret}</ClientSecret>` +
` <TokenURI>${clientCredentials.tokenUri}</TokenURI>` +
` ${
clientCredentials.scope
? '<ClientScope>' + clientCredentials.scope + '</ClientScope>'
: EMPTY_STRING
clientCredentials.scope ? '<ClientScope>' + clientCredentials.scope + '</ClientScope>' : EMPTY_STRING
}` +
' </ClientSettings>' +
'</OutputScript>';

const checkFileExists = async filePath => {
try {
await fs.promises.access(filePath);
return true;
} catch (err) {
return false;
}
};

module.exports = {
endorsements,
availableVersions,
Expand All @@ -291,5 +273,6 @@ module.exports = {
getCurrentVersion,
getPreviousVersion,
CURRENT_DATA_DICTIONARY_VERSION,
CURRENT_WEB_API_CORE_VERSION
CURRENT_WEB_API_CORE_VERSION,
checkFileExists
};
21 changes: 18 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#! /usr/bin/env node
const { program } = require('commander');
const { restore } = require('./utils/restore-utils');
const { runTests } = require('./utils/batch-test-runner');
const { restore } = require('./lib/restore-utils/data-dictionary');
const { runTests } = require('./lib/batch-test-runner');
const { syncWebApi } = require('./lib/restore-utils/web-api-core');

program
.name('reso-certification-utils')
Expand All @@ -12,13 +13,27 @@ program
.command('restore')
.option('-p, --pathToResults <string>', 'Path to test results')
.option('-u, --url <string>', 'URL of Certification API')
.option('--deleteExistingReport', 'Flag to delete existing report')
.description('Restores local or S3 results to a RESO Certification API instance')
.action(restore);

program
.command('syncWebApiResults')
.option('-p, --pathToResults <string>', 'Path to test results')
.option('-u, --url <string>', 'URL of Certification API')
.option('-r, --recipients <string>', 'Comma-separated list of recipient orgs')
.option('-i, --system <string>', 'Unique system identifier')
.option('--deleteExistingReport', 'Flag to delete existing report')
.description('Restores local or S3 Web API results to a RESO Certification API instance')
.action(syncWebApi);

program
.command('runDDTests')
.requiredOption('-p, --pathToConfigFile <string>', 'Path to config file')
.option('-a, --runAvailability', 'Flag to run data availability tests, otherwise only metadata tests are run')
.option(
'-a, --runAvailability',
'Flag to run data availability tests, otherwise only metadata tests are run'
)
.description('Runs Data Dictionary tests')
.action(runTests);

Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,66 @@ const postDataDictionaryResultsToApi = async ({
}
};

const postWebAPIResultsToApi = async ({
url,
providerUoi,
providerUsi,
recipientUoi,
webAPIReport = {}
} = {}) => {
if (!url) throw new Error('url is required!');
if (!providerUoi) throw new Error('providerUoi is required!');
if (!providerUsi) throw new Error('providerUsi is required!');
if (!recipientUoi) throw new Error('recipientUoi is required!');
if (!Object.keys(webAPIReport)?.length) throw new Error('web API report is empty!');

try {
const { id: reportId = null } =
(
await axios.post(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: We should be using the Fetch API for everything at this point.

Copy link
Member

@darnjo darnjo Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can write the Auth part as a one-liner

const getBearerTokenAuthHeader = (token = '') => (token?.length ? { Authorization: `Bearer ${token}` } : {});

headers = {
  ...headers,
  ...getOAuth2BearerTokenHeader(authInfo)
};

Then call Fetch API like so:

const response = await fetch(requestUri, { headers });

`${url}/api/v1/certification_reports/web_api_server_core/${providerUoi}`,
webAPIReport,
{
maxContentLength: Infinity,
maxBodyLength: Infinity,
headers: {
Authorization: `ApiKey ${CERTIFICATION_API_KEY}`,
recipientUoi,
'Content-Type': 'application/json',
providerUsi
}
}
)
).data || {};

if (!reportId) throw new Error('Did not receive the required id parameter from the response!');

return reportId;
} catch (err) {
throw new Error(`Could not post web API results to API! ${err}`);
}
};

const deleteExistingDDOrWebAPIReport = async ({ url, reportId } = {}) => {
if (!url) throw new Error('url is required!');
if (!reportId) throw new Error('reportId is required!');

try {
const { deletedReport } =
(
await axios.delete(`${url}/api/v1/certification_reports/${reportId}`, {
headers: {
Authorization: `ApiKey ${CERTIFICATION_API_KEY}`
}
})
).data || {};

return deletedReport;
} catch (err) {
throw new Error(err);
}
};

const postDataAvailabilityResultsToApi = async ({ url, reportId, dataAvailabilityReport = {} } = {}) => {
if (!url) throw new Error('url is required!');
if (!reportId) throw new Error('reportId is required!');
Expand Down Expand Up @@ -106,8 +166,27 @@ const processDataDictionaryResults = async ({
}
};

const processWebAPIResults = async ({ url, providerUoi, providerUsi, recipientUoi, webAPIReport = {} }) => {
try {
//wait for the dust to settle to avoid thrashing the server
await sleep(API_DEBOUNCE_SECONDS * 1000);

const reportId = await postWebAPIResultsToApi({
url,
providerUoi,
providerUsi,
recipientUoi,
webAPIReport
});

return reportId;
} catch (err) {
throw new Error(`Could not process webAPI results! ${err}`);
}
};

const getOrgsMap = async () => {
const { Data: orgs = [] } = (await axios.get(ORGS_DATA_URL)).data;
const { Organizations: orgs = [] } = (await axios.get(ORGS_DATA_URL)).data;

if (!orgs?.length) throw new Error('ERROR: could not fetch Org data!');

Expand Down Expand Up @@ -149,6 +228,29 @@ const findDataDictionaryReport = async ({ serverUrl, providerUoi, providerUsi, r
}
};

const findWebAPIReport = async ({ serverUrl, providerUoi, providerUsi, recipientUoi } = {}) => {
const url = `${serverUrl}/api/v1/certification_reports/summary/${recipientUoi}`,
config = {
headers: {
Authorization: `ApiKey ${CERTIFICATION_API_KEY}`
}
};

try {
const { data = [] } = await axios.get(url, config);

return data.find(
item =>
item?.type === 'web_api_server_core' &&
item?.providerUoi === providerUoi &&
//provider USI isn't in the data set at the moment, only filter if it's present
(item?.providerUsi ? item.providerUsi === providerUsi : true)
);
} catch (err) {
throw new Error(`Could not connect to ${url}`);
}
};

const getOrgSystemsMap = async () => {
return (await axios.get(SYSTEMS_DATA_URL))?.data?.values.slice(1).reduce((acc, [providerUoi, , usi]) => {
if (!acc[providerUoi]) acc[providerUoi] = [];
Expand All @@ -157,10 +259,22 @@ const getOrgSystemsMap = async () => {
}, {});
};

const getSystemsMap = async () => {
return (await axios.get(SYSTEMS_DATA_URL))?.data?.values.slice(1).reduce((acc, [providerUoi, , usi]) => {
acc[usi] = providerUoi;
return acc;
}, {});
};

module.exports = {
processDataDictionaryResults,
getOrgsMap,
getOrgSystemsMap,
findDataDictionaryReport,
sleep
sleep,
findWebAPIReport,
postWebAPIResultsToApi,
processWebAPIResults,
getSystemsMap,
deleteExistingDDOrWebAPIReport
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading