UNPKG

@testlio/cli

Version:

Official Testlio platform command-line interface

179 lines (150 loc) 6.45 kB
#! /usr/bin/env node 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; } };