diff --git a/actions/parse-ci-reports/src/parsers/CheckStyleParser.test.js b/actions/parse-ci-reports/src/parsers/CheckStyleParser.test.js
new file mode 100644
index 0000000..60898ba
--- /dev/null
+++ b/actions/parse-ci-reports/src/parsers/CheckStyleParser.test.js
@@ -0,0 +1,60 @@
+import { describe, it } from "node:test";
+import assert from "node:assert/strict";
+import { CheckStyleParser } from "./CheckStyleParser.js";
+
+const EMPTY_CHECKSTYLE_XML = `
+`;
+
+const CHECKSTYLE_WITH_ISSUES_XML = `
+
+
+
+
+`;
+
+describe("CheckStyleParser", () => {
+ it("keeps auto-pattern path detection synchronized", () => {
+ const parser = new CheckStyleParser();
+ const filePath = "application/humanize-checkstyle-result.xml";
+
+ assert.ok(parser.matchesAutoPatterns(filePath));
+ assert.ok(parser.canParse(filePath, EMPTY_CHECKSTYLE_XML));
+ });
+
+ it("identifies valid checkstyle XML reports without file entries", () => {
+ const parser = new CheckStyleParser();
+
+ assert.ok(
+ parser.canParse(
+ "application/humanize-report-checkstyle.xml",
+ EMPTY_CHECKSTYLE_XML,
+ ),
+ );
+ });
+
+ it("parses empty checkstyle reports as zero lint issues", () => {
+ const parser = new CheckStyleParser();
+
+ const reportData = parser.parse(
+ EMPTY_CHECKSTYLE_XML,
+ "application/humanize-report-checkstyle.xml",
+ );
+ assert.strictEqual(reportData.lintIssues.length, 0);
+ });
+
+ it("parses checkstyle issues from file entries", () => {
+ const parser = new CheckStyleParser();
+
+ const reportData = parser.parse(
+ CHECKSTYLE_WITH_ISSUES_XML,
+ "checkstyle-result.xml",
+ );
+
+ assert.strictEqual(reportData.lintIssues.length, 1);
+ assert.strictEqual(reportData.lintIssues[0].file, "src/index.js");
+ assert.strictEqual(reportData.lintIssues[0].line, 5);
+ assert.strictEqual(reportData.lintIssues[0].column, 1);
+ assert.strictEqual(reportData.lintIssues[0].severity, "warning");
+ assert.strictEqual(reportData.lintIssues[0].rule, "no-console");
+ });
+});
diff --git a/actions/parse-ci-reports/src/parsers/CoberturaParser.test.js b/actions/parse-ci-reports/src/parsers/CoberturaParser.test.js
new file mode 100644
index 0000000..fcc2e0a
--- /dev/null
+++ b/actions/parse-ci-reports/src/parsers/CoberturaParser.test.js
@@ -0,0 +1,46 @@
+import { describe, it } from "node:test";
+import assert from "node:assert/strict";
+import { CoberturaParser } from "./CoberturaParser.js";
+
+const SAMPLE_COBERTURA_XML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+describe("CoberturaParser", () => {
+ it("keeps auto-pattern path detection synchronized", () => {
+ const parser = new CoberturaParser();
+ const filePath = "coverage/humanize-coverage.xml";
+
+ assert.ok(parser.matchesAutoPatterns(filePath));
+ assert.ok(parser.canParse(filePath, SAMPLE_COBERTURA_XML));
+ });
+
+ it("identifies Cobertura XML reports", () => {
+ const parser = new CoberturaParser();
+
+ assert.ok(parser.canParse("coverage/coverage.xml", SAMPLE_COBERTURA_XML));
+ assert.ok(!parser.canParse("reports/report.txt", SAMPLE_COBERTURA_XML));
+ });
+
+ it("parses coverage totals", () => {
+ const parser = new CoberturaParser();
+ const reportData = parser.parse(SAMPLE_COBERTURA_XML, "coverage.xml");
+
+ assert.ok(reportData.coverage);
+ assert.strictEqual(reportData.coverage.lines.total, 2);
+ assert.strictEqual(reportData.coverage.lines.covered, 1);
+ assert.strictEqual(reportData.coverage.lines.percentage, 50);
+ });
+});
diff --git a/actions/parse-ci-reports/src/parsers/ESLintParser.test.js b/actions/parse-ci-reports/src/parsers/ESLintParser.test.js
new file mode 100644
index 0000000..35f9551
--- /dev/null
+++ b/actions/parse-ci-reports/src/parsers/ESLintParser.test.js
@@ -0,0 +1,51 @@
+import { describe, it } from "node:test";
+import assert from "node:assert/strict";
+import { ESLintParser } from "./ESLintParser.js";
+
+const SAMPLE_ESLINT_JSON = JSON.stringify([
+ {
+ filePath: "src/app.js",
+ messages: [
+ {
+ ruleId: "no-console",
+ severity: 2,
+ message: "Unexpected console statement.",
+ line: 3,
+ column: 1,
+ },
+ ],
+ },
+]);
+
+describe("ESLintParser", () => {
+ it("keeps auto-pattern path detection synchronized", () => {
+ const parser = new ESLintParser();
+ const filePath = "reports/humanize-eslint-report.json";
+
+ assert.ok(parser.matchesAutoPatterns(filePath));
+ assert.ok(parser.canParse(filePath, SAMPLE_ESLINT_JSON));
+ });
+
+ it("identifies ESLint report format", () => {
+ const parser = new ESLintParser();
+
+ assert.ok(parser.canParse("eslint-report.json", SAMPLE_ESLINT_JSON));
+ assert.ok(!parser.canParse("eslint-report.txt", SAMPLE_ESLINT_JSON));
+ assert.ok(!parser.canParse("eslint-report.json", "{}"));
+ });
+
+ it("parses lint issues from ESLint JSON", () => {
+ const parser = new ESLintParser();
+ const reportData = parser.parse(SAMPLE_ESLINT_JSON, "eslint-report.json");
+
+ assert.strictEqual(reportData.lintIssues.length, 1);
+ const [issue] = reportData.lintIssues;
+
+ assert.strictEqual(issue.file, "src/app.js");
+ assert.strictEqual(issue.line, 3);
+ assert.strictEqual(issue.column, 1);
+ assert.strictEqual(issue.severity, "error");
+ assert.strictEqual(issue.rule, "no-console");
+ assert.strictEqual(issue.message, "Unexpected console statement.");
+ });
+});
diff --git a/actions/parse-ci-reports/src/parsers/LCOVParser.test.js b/actions/parse-ci-reports/src/parsers/LCOVParser.test.js
new file mode 100644
index 0000000..6fbaa19
--- /dev/null
+++ b/actions/parse-ci-reports/src/parsers/LCOVParser.test.js
@@ -0,0 +1,43 @@
+import { describe, it } from "node:test";
+import assert from "node:assert/strict";
+import { LCOVParser } from "./LCOVParser.js";
+
+const SAMPLE_LCOV = `TN:
+SF:src/app.js
+DA:1,1
+DA:2,0
+FNDA:1,handler
+BRDA:2,0,0,1
+BRDA:2,0,1,0
+end_of_record
+`;
+
+describe("LCOVParser", () => {
+ it("keeps auto-pattern path detection synchronized", () => {
+ const parser = new LCOVParser();
+ const filePath = "coverage/humanize-lcov.info";
+
+ assert.ok(parser.matchesAutoPatterns(filePath));
+ assert.ok(parser.canParse(filePath, SAMPLE_LCOV));
+ });
+
+ it("identifies LCOV reports", () => {
+ const parser = new LCOVParser();
+
+ assert.ok(parser.canParse("coverage/lcov.info", SAMPLE_LCOV));
+ assert.ok(!parser.canParse("coverage/report.txt", SAMPLE_LCOV));
+ });
+
+ it("parses line, function, and branch coverage", () => {
+ const parser = new LCOVParser();
+ const reportData = parser.parse(SAMPLE_LCOV, "lcov.info");
+
+ assert.ok(reportData.coverage);
+ assert.strictEqual(reportData.coverage.lines.total, 2);
+ assert.strictEqual(reportData.coverage.lines.covered, 1);
+ assert.strictEqual(reportData.coverage.functions.total, 1);
+ assert.strictEqual(reportData.coverage.functions.covered, 1);
+ assert.strictEqual(reportData.coverage.branches.total, 2);
+ assert.strictEqual(reportData.coverage.branches.covered, 1);
+ });
+});