Skip to content

Merge pull request #14 from litlfred/copilot/fix-6cfd87ba-6e64-4107-9… #74

Merge pull request #14 from litlfred/copilot/fix-6cfd87ba-6e64-4107-9…

Merge pull request #14 from litlfred/copilot/fix-6cfd87ba-6e64-4107-9… #74

Workflow file for this run

name: QA Report
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main, develop ]
jobs:
qa-report:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
id: lint
continue-on-error: true
run: |
npm run lint 2>&1 | tee lint-output.txt
- name: Build project
id: build
continue-on-error: true
run: |
npm run build 2>&1 | tee build-output.txt
- name: Run tests with coverage
id: coverage
continue-on-error: true
run: |
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --coverage --coverageReporters=text-lcov 2>&1 | tee coverage-output.txt
- name: FML Compilation Tests
id: fml-compilation
continue-on-error: true
run: |
echo "=== FML Compilation Test Results ==="
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --testPathPattern="fml-compiler|fhir-mapping-language" --verbose 2>&1 | tee fml-compilation-output.txt
- name: FML Execution Tests
id: fml-execution
continue-on-error: true
run: |
echo "=== FML Execution Test Results ==="
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --testPathPattern="structure-map-executor|fhirpath-integration" --verbose 2>&1 | tee fml-execution-output.txt
- name: FHIR API Tests
id: fhir-api
continue-on-error: true
run: |
echo "=== FHIR API Test Results ==="
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --testPathPattern="api|enhanced-api" --verbose 2>&1 | tee fhir-api-output.txt
- name: Validation & Core Tests
id: validation-core
continue-on-error: true
run: |
echo "=== Validation & Core Test Results ==="
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --testPathPattern="validation-service|fml-runner|structure-map-retriever|enhanced-tokenizer" --verbose 2>&1 | tee validation-core-output.txt
- name: Generate QA Summary Table and Post PR Comment
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
// Helper function to read file safely
function readOutputFile(filename) {
try {
if (fs.existsSync(filename)) {
return fs.readFileSync(filename, 'utf8');
}
return '';
} catch (error) {
return `Error reading ${filename}: ${error.message}`;
}
}
// Helper function to extract error context from output
function extractErrorContext(output, maxLines = 10) {
if (!output) return 'No output captured';
const lines = output.split('\n');
const errorLines = [];
let capturing = false;
for (let i = 0; i < lines.length && errorLines.length < maxLines; i++) {
const line = lines[i];
// Look for error indicators
if (line.includes('FAIL') || line.includes('Error:') || line.includes('Failed:') ||
line.includes('● ') || line.includes('✕') || line.includes('ERRORS:')) {
capturing = true;
}
if (capturing) {
errorLines.push(line);
// Stop capturing after finding summary or next test
if (line.includes('Test Suites:') || line.includes('Tests:')) {
break;
}
}
}
return errorLines.length > 0 ? errorLines.slice(0, maxLines).join('\n') : 'No specific error details captured';
}
// Get step outcomes
const outcomes = {
lint: '${{ steps.lint.outcome }}',
build: '${{ steps.build.outcome }}',
coverage: '${{ steps.coverage.outcome }}',
fmlCompilation: '${{ steps.fml-compilation.outcome }}',
fmlExecution: '${{ steps.fml-execution.outcome }}',
fhirApi: '${{ steps.fhir-api.outcome }}',
validationCore: '${{ steps.validation-core.outcome }}'
};
// Read output files
const outputs = {
lint: readOutputFile('lint-output.txt'),
build: readOutputFile('build-output.txt'),
coverage: readOutputFile('coverage-output.txt'),
fmlCompilation: readOutputFile('fml-compilation-output.txt'),
fmlExecution: readOutputFile('fml-execution-output.txt'),
fhirApi: readOutputFile('fhir-api-output.txt'),
validationCore: readOutputFile('validation-core-output.txt')
};
// Extract test summary from coverage output
const coverageOutput = outputs.coverage;
let totalTests = 'unknown';
let totalSuites = 'unknown';
const testMatch = coverageOutput.match(/Tests:\s*(\d+\s+\w+)/);
const suiteMatch = coverageOutput.match(/Test Suites:\s*(\d+\s+\w+)/);
if (testMatch) totalTests = testMatch[1];
if (suiteMatch) totalSuites = suiteMatch[1];
// Build QA table
let qaTable = `## QA Report Summary - Node.js ${{ matrix.node-version }}
| Test Category | Status | Details | Error Context |

Check failure on line 160 in .github/workflows/qa.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/qa.yml

Invalid workflow file

You have an error in your yaml syntax on line 160
|---------------|--------|---------|---------------|`;
// Build Status
if (outcomes.build === 'success') {
qaTable += `\n| Build | ✅ Passed | TypeScript compilation successful | - |`;
} else {
const errorContext = extractErrorContext(outputs.build, 5);
qaTable += `\n| Build | ❌ Failed | TypeScript compilation failed | \`\`\`\n${errorContext}\n\`\`\` |`;
}
// Linting Status
if (outcomes.lint === 'success') {
qaTable += `\n| Linting | ✅ Passed | ESLint validation successful | - |`;
} else {
const errorContext = extractErrorContext(outputs.lint, 5);
qaTable += `\n| Linting | ❌ Failed | ESLint validation failed | \`\`\`\n${errorContext}\n\`\`\` |`;
}
// FML Compilation Tests
if (outcomes.fmlCompilation === 'success') {
qaTable += `\n| FML Compilation | ✅ Passed | FML parsing and compilation tests | - |`;
} else {
const errorContext = extractErrorContext(outputs.fmlCompilation, 8);
qaTable += `\n| FML Compilation | ❌ Failed | FML parsing and compilation tests | \`\`\`\n${errorContext}\n\`\`\` |`;
}
// FML Execution Tests
if (outcomes.fmlExecution === 'success') {
qaTable += `\n| FML Execution | ✅ Passed | StructureMap execution and FHIRPath tests | - |`;
} else {
const errorContext = extractErrorContext(outputs.fmlExecution, 8);
qaTable += `\n| FML Execution | ❌ Failed | StructureMap execution and FHIRPath tests | \`\`\`\n${errorContext}\n\`\`\` |`;
}
// FHIR API Tests
if (outcomes.fhirApi === 'success') {
qaTable += `\n| FHIR API | ✅ Passed | REST API endpoints and CRUD operations | - |`;
} else {
const errorContext = extractErrorContext(outputs.fhirApi, 8);
qaTable += `\n| FHIR API | ❌ Failed | REST API endpoints and CRUD operations | \`\`\`\n${errorContext}\n\`\`\` |`;
}
// Validation & Core Tests
if (outcomes.validationCore === 'success') {
qaTable += `\n| Validation & Core | ✅ Passed | Input validation and core library functions | - |`;
} else {
const errorContext = extractErrorContext(outputs.validationCore, 8);
qaTable += `\n| Validation & Core | ❌ Failed | Input validation and core library functions | \`\`\`\n${errorContext}\n\`\`\` |`;
}
// Overall Summary
const overallStatus = Object.values(outcomes).every(outcome => outcome === 'success') ? '✅ All QA checks passed' : '❌ Some QA checks failed';
qaTable += `
### Summary
- **Node.js Version:** ${{ matrix.node-version }}
- **Total Tests:** ${totalTests}
- **Test Suites:** ${totalSuites}
- **Overall Status:** ${overallStatus}
<details>
<summary>🔍 View Full Test Output</summary>
**Coverage Output:**
\`\`\`
${outputs.coverage.split('\n').slice(-20).join('\n')}
\`\`\`
</details>`;
// Post comment to PR if this is a pull request
if (context.eventName === 'pull_request') {
try {
// Check if a comment already exists from this workflow
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existingComment = comments.find(comment =>
comment.user.login === 'github-actions[bot]' &&
comment.body.includes(`QA Report Summary - Node.js ${{ matrix.node-version }}`)
);
if (existingComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: qaTable
});
console.log('Updated existing QA report comment');
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: qaTable
});
console.log('Created new QA report comment');
}
} catch (error) {
console.error('Error posting PR comment:', error);
// Fall back to step summary
core.summary.addRaw(qaTable).write();
}
} else {
// For push events, just write to step summary
core.summary.addRaw(qaTable).write();
}
- name: Upload test artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-node-${{ matrix.node-version }}
path: |
coverage/
dist/
*-output.txt