All files artifacts.js

84.48% Statements 49/58
57.69% Branches 15/26
83.33% Functions 5/6
89.09% Lines 49/55

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98    1x 1x     12x                 1x 1x 1x 1x       13x                       1x 1x 1x 1x 1x 1x 1x             1x 1x   1x 1x 1x   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x 1x 1x 1x   1x   1x     1x         1x 1x 1x     1x   1x 1x 1x 1x     1x  
'use strict';
 
const fs = require('fs');
const path = require('path');
 
function nullToEmpty(s) {
  return s == null ? '' : String(s);
}
 
function truncate(s, max) {
  if (s.length <= max) return s;
  return s.slice(0, max) + '...';
}
 
function toSeconds(duration) {
  const d = Number(duration) || 0;
  Iif (d <= 0) return 0.0;
  Iif (d > 1000) return d / 1000.0;
  return d;
}
 
function xmlEscape(s) {
  return String(s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}
 
/**
 * @param {object} summary — same shape as ShiftLeftRunSummary (plain object for JSON)
 */
function writeJsonSummary(outDir, relativePath, summary) {
  const full = path.join(outDir, relativePath);
  fs.mkdirSync(path.dirname(full), { recursive: true });
  const forJson = { ...summary };
  delete forJson.jsonSummaryRelativePath;
  delete forJson.testResultsXmlRelativePath;
  fs.writeFileSync(full, JSON.stringify(forJson, null, 2), 'utf8');
  return full;
}
 
/**
 * JUnit-style testsuite XML for Azure Pipelines Publish Test Results.
 */
function writeXmlTestResults(outDir, relativePath, summary, results) {
  const suiteName = `ShiftLeft Test Pack ${nullToEmpty(summary.packId)}`;
  const timestamp = new Date().toISOString();
 
  const tests = Math.max(summary.total || 0, results.resultsCount || 0);
  const failures = Math.max(0, summary.failed || 0);
  const errors = Math.max(0, summary.error || 0);
 
  let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
  xml += `<testsuite name="${xmlEscape(suiteName)}" tests="${tests}" failures="${failures}" errors="${errors}" timestamp="${xmlEscape(timestamp)}">\n`;
  xml += '  <properties>\n';
  xml += `    <property name="serverUrl" value="${xmlEscape(nullToEmpty(summary.serverUrl))}"/>\n`;
  xml += `    <property name="tenantId" value="${xmlEscape(nullToEmpty(summary.tenantId))}"/>\n`;
  xml += `    <property name="packId" value="${xmlEscape(nullToEmpty(summary.packId))}"/>\n`;
  xml += `    <property name="executionId" value="${xmlEscape(nullToEmpty(summary.executionId))}"/>\n`;
  xml += `    <property name="resultsPackName" value="${xmlEscape(nullToEmpty(results.packName))}"/>\n`;
  xml += `    <property name="resultsPackId" value="${xmlEscape(nullToEmpty(results.packId))}"/>\n`;
  xml += `    <property name="resultsExecutionId" value="${xmlEscape(nullToEmpty(results.executionId))}"/>\n`;
  xml += `    <property name="resultsStartTime" value="${xmlEscape(nullToEmpty(results.startTimeIso))}"/>\n`;
  xml += `    <property name="resultsEndTime" value="${xmlEscape(nullToEmpty(results.endTimeIso))}"/>\n`;
  xml += '  </properties>\n';
 
  for (const item of results.results) {
    const testName = item.testName != null ? item.testName : nullToEmpty(item.testId);
    const status = (item.status || '').trim().toUpperCase();
    const seconds = toSeconds(item.duration);
 
    xml += `  <testcase classname="ShiftLeft" name="${xmlEscape(testName)}" time="${seconds.toFixed(3)}">\n`;
 
    Iif (status === 'FAILED') {
      const err = nullToEmpty(item.error);
      xml += `    <failure message="${xmlEscape(truncate(err, 500))}">${xmlEscape(truncate(err, 2000))}</failure>\n`;
    } else Iif (status === 'ERROR') {
      const err = nullToEmpty(item.error);
      xml += `    <error message="${xmlEscape(truncate(err, 500))}">${xmlEscape(truncate(err, 2000))}</error>\n`;
    }
 
    const shiftleftMeta = `shiftleft.responseTimeMs=${item.responseTime} shiftleft.startTime=${nullToEmpty(item.startTimeIso)} shiftleft.endTime=${nullToEmpty(item.endTimeIso)}`;
    xml += `    <system-out>${xmlEscape(shiftleftMeta)}</system-out>\n`;
    xml += '  </testcase>\n';
  }
 
  xml += '</testsuite>\n';
 
  const full = path.join(outDir, relativePath);
  fs.mkdirSync(path.dirname(full), { recursive: true });
  fs.writeFileSync(full, xml, 'utf8');
  return full;
}
 
module.exports = { writeJsonSummary, writeXmlTestResults };