Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6459966
fix: fixed an issue where a NullPointerException was thrown on unset …
nil-malh Oct 1, 2026
a8aa257
fix: close URLClassLoader in PluginRegistry to prevent file handle leak
nil-malh Oct 1, 2026
a0c8c11
fix: add missing schemas directory default to reference.conf
nil-malh Oct 1, 2026
1e5fcf7
fix: harden XML parsers against XXE attacks in XMLUtils
nil-malh Oct 1, 2026
ff1bcfd
fix: enforce error taxonomy and add missing exception constructors
nil-malh Oct 1, 2026
d855404
fix: correct over-suppression of CHILD_NODELIST_LENGTH in XMLUtils
nil-malh Oct 1, 2026
3539b50
fix: fixed an issue where a SchemaRegistryClient was instantiated at …
nil-malh Oct 1, 2026
651a78e
chore: fixed some tests
nil-malh Oct 1, 2026
a0b5246
(feat/bug-fixes) style: spotless
nil-malh Oct 1, 2026
ffcdffb
fix: improve byte buffer handling in AvroDeserializer
nil-malh Oct 2, 2026
c2d2c7a
fix: include cause in ConsumerException for better error handling
nil-malh Oct 2, 2026
27e059f
fix: handle null values in record matchers and improve logging
nil-malh Oct 2, 2026
b909825
fix: enhance security in SAXParserFactory and improve documentation
nil-malh Oct 2, 2026
57c9377
style: update comments for consistency in punctuation
nil-malh Oct 2, 2026
b55c287
test: add ClasspathTestPlugin for lifecycle call assertions
nil-malh Oct 2, 2026
9ba36a1
refactor: enhance plugin loading and shutdown logic
nil-malh Oct 2, 2026
aa8b70f
fix: improve handling of empty records in AttributeRecordMatcher
nil-malh Oct 2, 2026
3b45d30
feat: add AvroJson and AvroLogicalTypesSerializationTest for JSON con…
nil-malh Oct 2, 2026
3768184
feat: enhance DynamicVariableFactory for thread-safety and case-insen…
nil-malh Oct 2, 2026
a0655e9
fix: improve getLine method to handle null content and out-of-bounds …
nil-malh Oct 2, 2026
1f4ae44
refactor: enhance timeout handling and improve documentation in Kafka…
nil-malh Oct 2, 2026
d6145f2
feat: added two new MatchResult noRecords and nullValue
nil-malh Oct 2, 2026
c52efc6
style: improve comments
nil-malh Oct 2, 2026
2f57c48
test: add TombstoneMatcherTest to verify tombstone handling across ma…
nil-malh Oct 2, 2026
21b00fb
refactor: extract null value message to constant in MatchResult
nil-malh Oct 2, 2026
e7156af
style: spotless
nil-malh Oct 2, 2026
aba34d9
fix: null pointer exception on empty TimestampVariable when format is…
nil-malh Oct 4, 2026
b5c6c7a
ci: update CI workflow to use reusable Maven CI and add manual integr…
nil-malh Oct 4, 2026
d308e7c
(feat/bug-fixes) style: spotless
nil-malh Oct 4, 2026
372cc84
ci: fix workflow paths in CI and dependency review configurations
nil-malh Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 14 additions & 290 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
# ============================================================
# CI β€” Continuous Integration
#
# Triggered on:
# β€’ push β†’ main (every merge β€” post-merge validation)
# β€’ pull_request β†’ main (every PR β€” pre-merge validation)
#
# Restricting push to main avoids duplicate job runs: when a
# feature branch has an open PR, GitHub fires both push and
# pull_request events. Limiting push to main means each commit
# is evaluated exactly once.
#
# Two independent jobs run in parallel:
#
# lint β†’ Spotless code-style check
# build β†’ Compile + tests (unit + Testcontainers) + JaCoCo
#
# A synthetic `ci-success` job aggregates the two results so
# branch-protection rules only need to track one required check.
# ============================================================
name: CI

on:
push:
branches: [ main ] # feature branches are covered by pull_request β€” avoids duplicate runs
branches: [ main ]
paths-ignore:
- "**.md"
- "LICENSE"
Expand All @@ -37,284 +17,28 @@ on:
- ".github/ISSUE_TEMPLATE/**"
- ".github/pull_request_template.md"
- ".github/SECURITY.md"
workflow_dispatch:
inputs:
run-integration-tests:
description: "Run integration tests (manual opt-in, useful for PR branches)"
type: boolean
default: false

# Cancel in-progress runs for the same branch/PR (keep only the latest)
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

env:
JAVA_VERSION: "25"
MAVEN_OPTS: >-
-Xmx2048m
-XX:+EnableDynamicAgentLoading
-Dmaven.repo.local=${{ github.workspace }}/.m2/repository

# Minimal default permissions β€” each job declares only what it needs
permissions:
contents: read

# ──────────────────────────────────────────────────────────────
jobs:

# ── 1. Code Style ─────────────────────────────────────────
lint:
name: 🎨 Code Style (Spotless)
runs-on: ubuntu-latest
permissions:
contents: read # checkout only

steps:
- name: Checkout
uses: actions/checkout@v7 # v6: credentials stored in $RUNNER_TEMP, not .git/config

- name: Set up Java ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v6
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
cache: maven

- name: Spotless β€” check formatting
run: mvn spotless:check --no-transfer-progress

# ── 2. Build, Test & Coverage ────────────────────────────
build:
name: πŸ”¨ Build, Test & Coverage
runs-on: ubuntu-latest
permissions:
contents: read # checkout only
pull-requests: write # post / update the sticky CI report comment

steps:
- name: Checkout
uses: actions/checkout@v7 # v6: credentials stored in $RUNNER_TEMP, not .git/config

- name: Set up Java ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v6
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
cache: maven

# Testcontainers: disable Ryuk to avoid permission issues on GH runners
- name: Build, run tests & enforce coverage (β‰₯ 70 %)
run: mvn verify --no-transfer-progress -Dspotless.check.skip=true
env:
TESTCONTAINERS_RYUK_DISABLED: "true"

# Always upload the HTML + XML JaCoCo report so it is available
# for SonarCloud and for manual inspection
- name: Upload JaCoCo HTML report
if: always()
uses: actions/upload-artifact@v7
with:
name: jacoco-html-${{ github.run_id }}
path: target/site/jacoco/
retention-days: 14

# Upload the binary .exec β€” kept for future tooling integration
- name: Upload JaCoCo exec
if: >
github.ref == 'refs/heads/main' ||
github.event_name == 'pull_request'
uses: actions/upload-artifact@v7
with:
name: jacoco-exec-${{ github.run_id }}
path: target/jacoco.exec
retention-days: 7

# Upload Surefire XML reports so GitHub can parse test results
- name: Upload Surefire reports
if: always()
uses: actions/upload-artifact@v7
with:
name: surefire-reports-${{ github.run_id }}
path: target/surefire-reports/
retention-days: 7

# ── Generate CI report ────────────────────────────────
# Parses Surefire XML + JaCoCo XML and produces a single
# Markdown report written to:
# β€’ $GITHUB_STEP_SUMMARY β†’ visible in the Actions run page
# β€’ /tmp/ci-report.md β†’ picked up by the PR comment step
- name: πŸ“Š Generate CI report (tests + coverage)
if: always()
run: |
python3 << 'PYEOF'
import xml.etree.ElementTree as ET
import glob, os

# ── Surefire results ─────────────────────────────
# WHY we count <testcase> elements instead of reading the
# root <testsuite tests="N"> attribute:
#
# JUnit 5 with @Nested classes causes surefire to write
# *nested* <testsuite> elements inside the root one.
# The root's `tests` attribute then reflects only direct
# (non-nested) test methods, producing a count far below
# what Maven itself reports (e.g. 68 vs 613).
#
# Counting every <testcase> descendant recursively gives
# the true total regardless of nesting depth.
total = failures = errors = skipped = 0
failed_list = []

for path in sorted(glob.glob('target/surefire-reports/TEST-*.xml')):
try:
root = ET.parse(path).getroot()
suite_name = root.get('name', path)

for tc in root.findall('.//testcase'):
total += 1
if tc.find('failure') is not None:
failures += 1
failed_list.append(f"{tc.get('classname', suite_name)}#{tc.get('name', '?')}")
elif tc.find('error') is not None:
errors += 1
failed_list.append(f"{tc.get('classname', suite_name)}#{tc.get('name', '?')}")
elif tc.find('skipped') is not None:
skipped += 1
except Exception as e:
print(f'Warning: could not parse {path}: {e}')

passed = total - failures - errors - skipped
t_status = 'βœ…' if failures + errors == 0 else '❌'

# ── JaCoCo coverage ──────────────────────────────
line_pct = branch_pct = method_pct = 0.0
line_cov = line_tot = branch_cov = branch_tot = method_cov = method_tot = 0
cov_ok = False

try:
root = ET.parse('target/site/jacoco/jacoco.xml').getroot()
for counter in root.findall('counter'):
ctype = counter.get('type')
covered = int(counter.get('covered', 0))
missed = int(counter.get('missed', 0))
tot = covered + missed
pct = (covered / tot * 100) if tot > 0 else 0.0
if ctype == 'LINE': line_pct, line_cov, line_tot = pct, covered, tot
elif ctype == 'BRANCH': branch_pct, branch_cov, branch_tot = pct, covered, tot
elif ctype == 'METHOD': method_pct, method_cov, method_tot = pct, covered, tot
cov_ok = True
except Exception as e:
print(f'Warning: could not parse JaCoCo XML: {e}')

threshold = 70.0
c_status = 'βœ…' if line_pct >= threshold else '⚠️'

# ── Build the Markdown report ─────────────────────
lines = []
lines += [f'## {t_status} Test Results\n']
lines += ['| | Metric | Count |', '|:---:|:---|---:|']
lines += [f'| βœ… | Passed | {passed} |']
lines += [f'| ❌ | Failed | {failures + errors} |']
lines += [f'| ⏭️ | Skipped | {skipped} |']
lines += [f'| πŸ“Š | **Total** | **{total}** |']

if failed_list:
lines += ['', '### ❌ Failing Tests']
for t in failed_list[:15]:
lines += [f'- `{t}`']
if len(failed_list) > 15:
lines += [f'- *… and {len(failed_list) - 15} more*']

lines += ['']

if cov_ok:
lines += [f'## {c_status} Coverage\n']
lines += ['| | Type | Coverage | Covered / Total |', '|:---:|:---|---:|---:|']
lines += [f'| πŸ“ | Lines | **{line_pct:.1f}%** | {line_cov} / {line_tot} |']
lines += [f'| 🌿 | Branches | {branch_pct:.1f}% | {branch_cov} / {branch_tot} |']
lines += [f'| πŸ”§ | Methods | {method_pct:.1f}% | {method_cov} / {method_tot} |']
if line_pct < threshold:
lines += ['', f'> ⚠️ Line coverage **{line_pct:.1f}%** is below the required **{threshold:.0f}%** threshold']
else:
lines += ['## ⚠️ Coverage report not available']

report = '\n'.join(lines) + '\n'

# Write to Actions job summary
with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as f:
f.write(report)

# Write to file for the PR comment step below
with open('/tmp/ci-report.md', 'w') as f:
f.write(report)

print(report)
PYEOF

# ── Sticky PR comment ─────────────────────────────────
# Posts a single comment that is updated (not duplicated) on
# every push to the same PR using a hidden HTML marker.
# Only runs on pull_request events β€” skipped on direct pushes.
- name: πŸ’¬ Post CI report to PR
if: ${{ always() && github.event_name == 'pull_request' }}
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
if (!fs.existsSync('/tmp/ci-report.md')) {
console.log('CI report file not found β€” skipping comment.');
return;
}

const marker = '<!-- ktestify-ci-report -->';
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
const content = fs.readFileSync('/tmp/ci-report.md', 'utf8');
const body = `${marker}\n${content}\n---\n*πŸ”„ [CI run #${process.env.GITHUB_RUN_NUMBER}](${runUrl}) Β· ${new Date().toUTCString()}*`;

// Find an existing comment with our marker
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100,
});

const existing = comments.find(c => c.body?.includes(marker));

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
console.log(`βœ… Updated CI report comment #${existing.id}`);
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
console.log('βœ… Created new CI report comment');
}

# ── 3. Aggregate status (single required check) ───────────
ci-success:
name: βœ… CI passed
runs-on: ubuntu-latest
needs: [ lint, build ]
if: always()
permissions:
contents: read
steps:
- name: Evaluate results
run: |
lint="${{ needs.lint.result }}"
build="${{ needs.build.result }}"
echo "lint β†’ $lint"
echo "build β†’ $build"
if [[ "$lint" != "success" || "$build" != "success" ]]; then
echo "❌ One or more jobs failed."
exit 1
fi
echo "βœ… All CI jobs passed."
ci:
name: Maven CI
uses: ktestify/.github/.github/workflows/reusable-maven-ci.yml@main
with:
java-version: "25"
run-integration-tests: ${{ github.event_name == 'workflow_dispatch' && inputs.run-integration-tests || false }}
maven-opts: ""



Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ permissions:

jobs:
review:
uses: ktestify/.github/.github/workflows/reusable-dependency-review.yml@main
uses: ktestify/.github/workflows/reusable-dependency-review.yml@main
Loading
Loading