@testlio/cli
Version:
Official Testlio platform command-line interface
179 lines (150 loc) • 6.45 kB
JavaScript
const axios = require('axios');
const Joi = require('joi');
const fs = require('fs');
const FormData = require('form-data');
const pathModule = require('path');
const { findDeviceResult } = require('./utils');
const FAILURE = 1;
const SUCCESS = 0;
const ONE_MB_IN_BYTES = 1024 ** 2;
const ASSET_FILE_MAX_FILE_SIZE_MB = 50 * ONE_MB_IN_BYTES; // 50 MBs
const printHelp = () => {
console.log(`Upload asset
Usage:
testlio upload-asset [OPTIONS]
Options:
--deviceResultId (Mutually exclusive) Device result entity ID to which the result package belongs.
--automatedBrowserId (Mutually exclusive) Automated browser entity ID to which the result package belongs.
--automatedDeviceId (Mutually exclusive) Automated device entity ID to which the result package belongs.
--path (Required) Local path to the asset that you want to upload.
--type (Required) Type of the asset. Possible values: "result_package", "video", "screenshot", "log", "other".
--source (Required) Source of the asset. Possible values: "aws_device_farm", "aws_cloud_watch", "allure", "ecs2", "unknown".
--projectConfig [path] (Optional) Path to project config. Defaults to 'project-config.json'.
Note: One of the following options must be specified: --deviceResultId, --automatedBrowserId, --automatedDeviceId.
`);
};
const setAuthorizationToken = (token) => {
axios.defaults.headers.common.Authorization = `Bearer ${token}`;
};
const setBaseUri = (baseURI) => {
axios.defaults.baseURL = baseURI;
};
const acceptedTypes = ['video', 'screenshot', 'log', 'other'];
const acceptedSources = ['allure', 'unknown'];
const projectConfigSchema = Joi.object({
baseURI: Joi.string().required(),
resultCollectionGuid: Joi.string().required()
});
const uploadAssetSchema = Joi.object({
deviceResultId: Joi.string(),
automatedBrowserId: Joi.string(),
automatedDeviceId: Joi.string(),
path: Joi.string().required().label('Path to the asset file you want to upload'),
source: Joi.string()
.valid(...acceptedSources)
.required(),
type: Joi.string()
.valid(...acceptedTypes)
.required(),
projectConfig: Joi.string().default('project-config.json')
})
.xor('deviceResultId', 'automatedBrowserId', 'automatedDeviceId')
.label(
'One of the following options must be specified: --deviceResultId, --automatedBrowserId, --automatedDeviceId'
);
const uploadAsset = async (collectionGuid, deviceResultGuid, pathToAssetFile, type, source) => {
if (!deviceResultGuid) throw new Error(`Device result guid is required!`);
if (!type) throw new Error(`Type is required!`);
if (!source) throw new Error(`Source is required!`);
const { size } = fs.statSync(pathToAssetFile);
if (size > ASSET_FILE_MAX_FILE_SIZE_MB) {
throw new Error(
`Uploading ${pathToAssetFile} failed: max. file size ${ASSET_FILE_MAX_FILE_SIZE_MB / ONE_MB_IN_BYTES}MB`
);
}
console.log(`Storing asset for device result with ID '${deviceResultGuid}'`);
const form = new FormData();
form.append('source', source);
form.append('type', type);
const options = {};
const fileExtension = pathModule.extname(pathToAssetFile); // e.g., '.mp4'
const fileBaseName = pathModule.basename(pathToAssetFile, fileExtension); // Removes the extension from the basename
options.filename = `${fileBaseName}${fileExtension}`;
options.contentType = 'application/octet-stream';
const payloadData = fs.createReadStream(pathToAssetFile);
options.knownLength = size;
form.append('file', payloadData, options);
const { status } = await axios.post(
`/result/v1/collections/${collectionGuid}/device-results/${deviceResultGuid}/assets`,
form,
{
headers: {
...form.getHeaders(),
'Content-Length': form.getLengthSync()
},
maxContentLength: Infinity,
maxBodyLength: Infinity
}
);
if (status !== 201) {
throw new Error(`Failed to upload the asset`);
}
};
const findDeviceResultOrUseDirectId = async (
deviceResultId,
automatedBrowserId,
automatedDeviceId,
resultCollectionGuid
) => {
if (deviceResultId) return deviceResultId;
const resultGuid = automatedDeviceId || automatedBrowserId;
const resultType = automatedDeviceId ? 'automatedDevice' : 'automatedBrowser';
return findDeviceResult(resultCollectionGuid, resultGuid, resultType, process.env.RESULT_ID);
};
module.exports = async (params) => {
if (params.h || params.help) {
printHelp();
return;
}
const {
deviceResultId,
automatedBrowserId,
automatedDeviceId,
path,
projectConfig: projectConfigFilePath,
source,
type
} = Joi.attempt(params, uploadAssetSchema);
if (!fs.existsSync(projectConfigFilePath)) throw new Error(`File "${projectConfigFilePath}" not found!`);
if (!fs.existsSync(path))
throw new Error(`File "${path}" not found! Please provide a valid path to the asset file you want to upload!`);
try {
const projectConfig = JSON.parse(fs.readFileSync(projectConfigFilePath).toString());
const { baseURI, resultCollectionGuid } = Joi.attempt(projectConfig, projectConfigSchema.unknown());
if (!process.env.RESULT_ID) {
console.log('For results parsing, RESULTS_ID must be provided');
return FAILURE;
}
if (!process.env.RUN_API_TOKEN) {
console.log('Please provide RUN API TOKEN');
return FAILURE;
}
setBaseUri(baseURI);
setAuthorizationToken(process.env.RUN_API_TOKEN);
const deviceResultGuid = await findDeviceResultOrUseDirectId(
deviceResultId,
automatedBrowserId,
automatedDeviceId,
resultCollectionGuid
);
await uploadAsset(resultCollectionGuid, deviceResultGuid, path, type, source);
console.log(
`Asset uploaded successfully with type "${type}" and source "${source}" to device result ${deviceResultGuid} with path "${path}"`
);
return SUCCESS;
} catch (e) {
console.log(`Something went wrong while trying to upload the asset`, e);
return FAILURE;
}
};