-
Notifications
You must be signed in to change notification settings - Fork 39
Added tests to increase coverage % #300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1a2e7dd
3e71512
27ed08a
7a6b34a
fe0c3e8
06096a3
93f6ccd
6346d1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| name: SonarQube Analysis | ||
|
|
||
| on: | ||
| push: | ||
| branches: [master, main] | ||
| pull_request: | ||
| branches: [master, main] | ||
|
|
||
| jobs: | ||
| sonarqube: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20 | ||
|
|
||
| - name: Create NYC folder | ||
| run: mkdir -p .nyc_output | ||
|
|
||
| - name: Install dependencies | ||
| run: npm install --legacy-peer-deps | ||
|
|
||
| - name: Run unit tests with coverage | ||
| run: npm test | ||
| continue-on-error: true | ||
|
|
||
| - name: Generate LCOV and HTML coverage reports | ||
| run: npx nyc report --reporter=lcov --reporter=html --report-dir=coverage | ||
| continue-on-error: true | ||
|
|
||
| - name: Generate metrics JSON | ||
| run: node scripts/generate-metrics.js | ||
|
|
||
| - name: Post test inventory to summary | ||
| if: always() | ||
| run: | | ||
| if [ -f metrics.json ]; then | ||
| node -e " | ||
| const m = require('./metrics.json'); | ||
| const cov = m.coverage?.lines?.pct != null ? m.coverage.lines.pct + '%' : 'N/A'; | ||
| const brCov = m.coverage?.branches?.pct != null ? m.coverage.branches.pct + '%' : 'N/A'; | ||
| const fnCov = m.coverage?.functions?.pct != null ? m.coverage.functions.pct + '%' : 'N/A'; | ||
| let md = '## 📊 Test Inventory & Coverage — ' + m.repository + '\n\n'; | ||
| md += '| Metric | Value |\n|---|---|\n'; | ||
| md += '| **Repository** | ' + m.repository + ' |\n'; | ||
| md += '| **Version** | ' + m.version + ' |\n'; | ||
| md += '| **Unit Test Files** | ' + m.tests.unitFiles + ' |\n'; | ||
| md += '| **Integration Test Files** | ' + m.tests.integrationFiles + ' |\n'; | ||
| md += '| **Total Test Files** | ' + m.tests.totalFiles + ' |\n'; | ||
| md += '| **Line Coverage** | ' + cov + ' |\n'; | ||
| md += '| **Branch Coverage** | ' + brCov + ' |\n'; | ||
| md += '| **Function Coverage** | ' + fnCov + ' |\n\n'; | ||
| if (m.tests.unitTestFiles.length > 0) { | ||
| md += '### Unit Tests\n'; | ||
| m.tests.unitTestFiles.forEach(f => md += '- \`' + f + '\`\n'); | ||
| md += '\n'; | ||
| } | ||
| if (m.tests.integrationTestFiles.length > 0) { | ||
| md += '### Integration Tests\n'; | ||
| m.tests.integrationTestFiles.forEach(f => md += '- \`' + f + '\`\n'); | ||
| } | ||
| require('fs').appendFileSync(process.env.GITHUB_STEP_SUMMARY, md); | ||
| " | ||
| fi | ||
|
|
||
| - name: SonarQube Cloud Scan | ||
| uses: SonarSource/sonarcloud-github-action@v3 | ||
| env: | ||
| SONAR_TOKEN: ${{ secrets.SONAR }} | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Upload metrics artifact | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: test-metrics | ||
| path: metrics.json | ||
| if-no-files-found: ignore | ||
|
|
||
| - name: Upload HTML coverage report | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: coverage-report-html | ||
| path: coverage/ | ||
| if-no-files-found: ignore |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| /** | ||
| * Generates a JSON metrics file for the current repository. | ||
| * Designed to run during CI after tests + coverage have completed. | ||
| * | ||
| * Output: metrics.json in the repo root | ||
| * | ||
| * Metrics captured: | ||
| * - repository name and timestamp | ||
| * - test file counts (unit vs integration) | ||
| * - coverage summary parsed from LCOV | ||
| */ | ||
|
|
||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
|
|
||
| const repoRoot = process.cwd(); | ||
|
|
||
| function getPackageInfo() { | ||
| try { | ||
| const pkg = JSON.parse( | ||
| fs.readFileSync(path.join(repoRoot, "package.json"), "utf8") | ||
| ); | ||
| return { | ||
| name: pkg.name, | ||
| version: pkg.version, | ||
| repository: pkg.repository?.url || pkg.repository || "", | ||
| }; | ||
| } catch { | ||
| return { name: path.basename(repoRoot), version: "unknown", repository: "" }; | ||
| } | ||
| } | ||
|
|
||
| function findTestFiles(dir, pattern) { | ||
| const results = []; | ||
| if (!fs.existsSync(dir)) return results; | ||
|
|
||
| const entries = fs.readdirSync(dir, { withFileTypes: true }); | ||
| for (const entry of entries) { | ||
| const fullPath = path.join(dir, entry.name); | ||
| if (entry.isDirectory()) { | ||
| results.push(...findTestFiles(fullPath, pattern)); | ||
| } else if (pattern.test(entry.name)) { | ||
| results.push(fullPath); | ||
| } | ||
| } | ||
| return results; | ||
| } | ||
|
|
||
| function classifyTests() { | ||
| const testDir = path.join(repoRoot, "test"); | ||
| const allTestFiles = findTestFiles(testDir, /\.(test|spec)\.js$|^(?!setup)[a-zA-Z]+\.js$/); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The regular expression to find test files seems a bit fragile. The part |
||
|
|
||
| const unit = []; | ||
| const integration = []; | ||
|
|
||
| for (const file of allTestFiles) { | ||
| const relative = path.relative(repoRoot, file); | ||
| if (relative.includes("setup") || relative.includes("mock") || relative.includes("fixture")) { | ||
| continue; | ||
| } | ||
| if (relative.includes("integration")) { | ||
| integration.push(relative); | ||
| } else { | ||
| unit.push(relative); | ||
| } | ||
| } | ||
|
|
||
| return { unit, integration }; | ||
| } | ||
|
|
||
| function parseLcov() { | ||
| const lcovPaths = [ | ||
| path.join(repoRoot, "coverage", "lcov.info"), | ||
| path.join(repoRoot, "coverage", "lcov-report", "lcov.info"), | ||
| ]; | ||
|
|
||
| let lcovContent = null; | ||
| for (const p of lcovPaths) { | ||
| if (fs.existsSync(p)) { | ||
| lcovContent = fs.readFileSync(p, "utf8"); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (!lcovContent) { | ||
| return { lines: null, branches: null, functions: null, statements: null }; | ||
| } | ||
|
|
||
| let linesHit = 0, linesTotal = 0; | ||
| let branchesHit = 0, branchesTotal = 0; | ||
| let functionsHit = 0, functionsTotal = 0; | ||
|
|
||
| for (const line of lcovContent.split("\n")) { | ||
| if (line.startsWith("LH:")) linesHit += parseInt(line.slice(3), 10); | ||
| if (line.startsWith("LF:")) linesTotal += parseInt(line.slice(3), 10); | ||
| if (line.startsWith("BRH:")) branchesHit += parseInt(line.slice(4), 10); | ||
| if (line.startsWith("BRF:")) branchesTotal += parseInt(line.slice(4), 10); | ||
| if (line.startsWith("FNH:")) functionsHit += parseInt(line.slice(4), 10); | ||
| if (line.startsWith("FNF:")) functionsTotal += parseInt(line.slice(4), 10); | ||
| } | ||
|
|
||
| const pct = (hit, total) => | ||
| total > 0 ? Math.round((hit / total) * 10000) / 100 : null; | ||
|
|
||
| return { | ||
| lines: { hit: linesHit, total: linesTotal, pct: pct(linesHit, linesTotal) }, | ||
| branches: { hit: branchesHit, total: branchesTotal, pct: pct(branchesHit, branchesTotal) }, | ||
| functions: { hit: functionsHit, total: functionsTotal, pct: pct(functionsHit, functionsTotal) }, | ||
| }; | ||
| } | ||
|
|
||
| function main() { | ||
| const pkg = getPackageInfo(); | ||
| const tests = classifyTests(); | ||
| const coverage = parseLcov(); | ||
|
|
||
| const metrics = { | ||
| repository: pkg.name, | ||
| version: pkg.version, | ||
| repositoryUrl: pkg.repository, | ||
| generatedAt: new Date().toISOString(), | ||
| tests: { | ||
| unitFiles: tests.unit.length, | ||
| integrationFiles: tests.integration.length, | ||
| totalFiles: tests.unit.length + tests.integration.length, | ||
| unitTestFiles: tests.unit, | ||
| integrationTestFiles: tests.integration, | ||
| }, | ||
| coverage, | ||
| }; | ||
|
|
||
| const outPath = path.join(repoRoot, "metrics.json"); | ||
| fs.writeFileSync(outPath, JSON.stringify(metrics, null, 2)); | ||
| console.log(`Metrics written to ${outPath}`); | ||
| console.log(JSON.stringify(metrics, null, 2)); | ||
| } | ||
|
|
||
| main(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| sonar.projectKey=vikita91_lob-node | ||
| sonar.organization=lob-qa | ||
|
|
||
| sonar.projectName=lob-node | ||
| sonar.projectVersion=7.1.0 | ||
|
|
||
| sonar.sources=lib | ||
| sonar.tests=test | ||
| sonar.test.inclusions=test/**/*.js,test/integration/**/*.test.js | ||
| sonar.exclusions=test/** | ||
|
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a couple of potential issues with the SonarQube configuration:
|
||
|
|
||
| sonar.javascript.lcov.reportPaths=coverage/lcov.info | ||
|
|
||
| sonar.sourceEncoding=UTF-8 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
catchblock is empty. While there's a fallback mechanism, swallowing the error silently is not ideal. Ifpackage.jsonis unreadable or malformed, the script will proceed with default values without any indication of what went wrong. It's a good practice to log the error tostderrto aid in debugging.