All files / src quality-overview.ts

100% Statements 23/23
100% Branches 24/24
100% Functions 10/10
100% Lines 23/23

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 99 100 101 102 103 104 105                                                        122x   137x     2x 120x 76x                 116x 134x             27x     27x             116x     116x   115x     115x 115x       115x     116x 116x   116x 116x           116x                   232x 232x   137x      
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
 
import {
  evaluateProjectDirectory,
  type ProjectCheckEvaluation,
} from "./check-command.js";
import { summarizeCoverage, type CoverageCount } from "./coverage.js";
import type { VerificationProjectCheckResult } from "./check.js";
 
import { readAllureCounts, type AllureCounts } from "./ci-summary.js";
import { parseCoverageSummary } from "./coverage-summary.js";
 
export type OverallStatus = "PASS" | "INCOMPLETE" | "FAIL";
 
export interface QualityOverview {
  readonly status: OverallStatus;
  readonly requirementCoverage?: CoverageCount;
  readonly missingEvidence?: number;
  readonly testResults?: AllureCounts;
  readonly codeCoverageLines?: number;
}
 
/** Use Moura's authoritative pair states: failed Evidence outranks incomplete coverage. */
export function overallStatus(
  check: VerificationProjectCheckResult,
  hasEvaluationProblems = false,
): OverallStatus {
  if (
    check.entries.some(
      (entry) => entry.status === "FAIL" || entry.status === "BROKEN",
    )
  )
    return "FAIL";
  return hasEvaluationProblems ||
    check.entries.some((entry) => entry.status !== "PASS")
    ? "INCOMPLETE"
    : "PASS";
}
 
/** Missing runtime Evidence includes explicit not-yet-implemented required pairs. */
export function countMissingEvidence(
  check: VerificationProjectCheckResult,
): number {
  return check.entries.filter(
    (entry) => entry.status === "MISSING" || entry.status === "UNIMPLEMENTED",
  ).length;
}
 
export async function collectQualityOverview(
  root: string,
): Promise<QualityOverview> {
  const evaluation = await evaluateProjectDirectory(root, {
    strictTraceability: true,
  });
  return qualityOverviewFromEvaluation(root, evaluation);
}
 
export async function qualityOverviewFromEvaluation(
  root: string,
  evaluation: ProjectCheckEvaluation,
): Promise<QualityOverview> {
  let status: OverallStatus = "INCOMPLETE";
  let requirementCoverage: CoverageCount | undefined;
  let missingEvidence: number | undefined;
  if (evaluation.kind === "checked") {
    const hasEvaluationProblems =
      evaluation.adapterIssues.length > 0 ||
      evaluation.check.evidenceIssues.length > 0 ||
      evaluation.traceabilityDiagnostics.length > 0;
    status = overallStatus(evaluation.check, hasEvaluationProblems);
    requirementCoverage = summarizeCoverage(
      evaluation.manifest,
      evaluation.check,
    ).requirements;
    missingEvidence = countMissingEvidence(evaluation.check);
  }
 
  const testResults = await optional(async () =>
    readAllureCounts(resolve(root, "allure-results")),
  );
  const coverage = await optional(async () =>
    parseCoverageSummary(
      JSON.parse(
        await readFile(resolve(root, "coverage/coverage-summary.json"), "utf8"),
      ) as unknown,
    ),
  );
  return {
    status,
    ...(requirementCoverage ? { requirementCoverage } : {}),
    ...(missingEvidence === undefined ? {} : { missingEvidence }),
    ...(testResults ? { testResults } : {}),
    ...(coverage ? { codeCoverageLines: coverage.lines.pct } : {}),
  };
}
 
async function optional<T>(read: () => Promise<T>): Promise<T | undefined> {
  try {
    return await read();
  } catch {
    return undefined;
  }
}