@devicecloud.dev/dcd
Version:
Better cloud maestro testing
140 lines (139 loc) • 6.35 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestSubmissionService = void 0;
const node_crypto_1 = require("node:crypto");
const path = require("node:path");
const methods_1 = require("../methods");
const mimeTypeLookupByExtension = {
zip: 'application/zip',
};
/**
* Service for building test submission form data
*/
class TestSubmissionService {
/**
* Build FormData for test submission
* @param config Test submission configuration
* @returns FormData ready to be submitted to the API
*/
async buildTestFormData(config) {
const { appBinaryId, flowFile, executionPlan, commonRoot, cliVersion, env = [], metadata = [], googlePlay = false, androidApiLevel, androidDevice, androidNoSnapshot, iOSVersion, iOSDevice, name, runnerType, maestroVersion, deviceLocale, orientation, mitmHost, mitmPath, retry, continueOnFailure = true, report, showCrosshairs, maestroChromeOnboarding, raw, debug = false, logger, } = config;
const { allExcludeTags, allIncludeTags, flowMetadata, flowOverrides, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
const { flows: sequentialFlows = [] } = sequence ?? {};
const testFormData = new FormData();
const envObject = this.parseKeyValuePairs(env);
const metadataObject = this.parseKeyValuePairs(metadata);
if (Object.keys(envObject).length > 0) {
this.logDebug(debug, logger, `[DEBUG] Environment variables: ${JSON.stringify(envObject)}`);
}
if (Object.keys(metadataObject).length > 0) {
this.logDebug(debug, logger, `[DEBUG] User metadata: ${JSON.stringify(metadataObject)}`);
}
// Log non-YAML file assets being uploaded
if (referencedFiles.length > 0) {
const nonYamlFiles = referencedFiles.filter((file) => !file.endsWith('.yaml') && !file.endsWith('.yml'));
if (nonYamlFiles.length > 0) {
this.logDebug(debug, logger, `[DEBUG] Uploading ${nonYamlFiles.length} non-YAML file asset(s):`);
for (const file of nonYamlFiles) {
const normalizedPath = this.normalizeFilePath(file, commonRoot);
this.logDebug(debug, logger, `[DEBUG] - ${normalizedPath}`);
}
}
}
this.logDebug(debug, logger, `[DEBUG] Compressing files from path: ${flowFile}`);
const buffer = await (0, methods_1.compressFilesFromRelativePath)(flowFile?.endsWith('.yaml') || flowFile?.endsWith('.yml')
? path.dirname(flowFile)
: flowFile, [
...new Set([
...referencedFiles,
...testFileNames,
...sequentialFlows,
]),
], commonRoot);
this.logDebug(debug, logger, `[DEBUG] Compressed file size: ${buffer.length} bytes`);
// Calculate SHA-256 hash of the flow ZIP
const sha = (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
this.logDebug(debug, logger, `[DEBUG] Flow ZIP SHA-256: ${sha}`);
const blob = new Blob([buffer], {
type: mimeTypeLookupByExtension.zip,
});
testFormData.set('file', blob, 'flowFile.zip');
testFormData.set('sha', sha);
testFormData.set('appBinaryId', appBinaryId);
testFormData.set('testFileNames', JSON.stringify(this.normalizePaths(testFileNames, commonRoot)));
testFormData.set('flowMetadata', JSON.stringify(this.normalizePathMap(flowMetadata, commonRoot)));
testFormData.set('testFileOverrides', JSON.stringify(this.normalizePathMap(flowOverrides, commonRoot)));
testFormData.set('sequentialFlows', JSON.stringify(this.normalizePaths(sequentialFlows, commonRoot)));
testFormData.set('env', JSON.stringify(envObject));
testFormData.set('googlePlay', googlePlay ? 'true' : 'false');
const configPayload = {
allExcludeTags,
allIncludeTags,
androidNoSnapshot,
autoRetriesRemaining: retry,
continueOnFailure,
deviceLocale,
maestroVersion,
mitmHost,
mitmPath,
orientation,
raw: JSON.stringify(raw),
report,
showCrosshairs,
maestroChromeOnboarding,
version: cliVersion,
};
testFormData.set('config', JSON.stringify(configPayload));
if (Object.keys(metadataObject).length > 0) {
const metadataPayload = { userMetadata: metadataObject };
testFormData.set('metadata', JSON.stringify(metadataPayload));
this.logDebug(debug, logger, `[DEBUG] Sending metadata to API: ${JSON.stringify(metadataPayload)}`);
}
this.setOptionalFields(testFormData, {
androidApiLevel,
androidDevice,
iOSDevice,
iOSVersion,
name,
runnerType,
});
if (workspaceConfig) {
testFormData.set('workspaceConfig', JSON.stringify(workspaceConfig));
}
return testFormData;
}
logDebug(debug, logger, message) {
if (debug && logger) {
logger(message);
}
}
normalizeFilePath(filePath, commonRoot) {
return filePath.replaceAll(commonRoot, '.').split(path.sep).join('/');
}
normalizePathMap(map, commonRoot) {
return Object.fromEntries(Object.entries(map).map(([key, value]) => [
this.normalizeFilePath(key, commonRoot),
value,
]));
}
normalizePaths(paths, commonRoot) {
return paths.map((p) => this.normalizeFilePath(p, commonRoot));
}
parseKeyValuePairs(pairs) {
// eslint-disable-next-line unicorn/no-array-reduce
return pairs.reduce((acc, cur) => {
const [key, ...value] = cur.split('=');
// handle case where value includes an equals sign
acc[key] = value.join('=');
return acc;
}, {});
}
setOptionalFields(formData, fields) {
for (const [key, value] of Object.entries(fields)) {
if (value) {
formData.set(key, value.toString());
}
}
}
}
exports.TestSubmissionService = TestSubmissionService;