Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
124 changes: 122 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ module.exports = {
},
env: { es6: true, node: true, commonjs: true },
rules: {
// ===============================
// KEEP EXISTING RELAXED RULES
// ===============================
'global-require': 'off',
'func-names': 'off',
'no-underscore-dangle': 'off',
Expand All @@ -21,8 +24,94 @@ module.exports = {
'template-curly-spacing': 'off',
indent: 'off',
'linebreak-style': 0,
'no-console': 'off',
'no-console': 'warn',
'consistent-return': 'off',

// ===============================
// LIGHT ENTERPRISE ADDITIONS (MOSTLY WARNINGS)
// ===============================

// Critical Error Prevention (errors only for breaking stuff)
'no-undef': 'error',
'no-unused-vars': ['warn', {
vars: 'local',
args: 'none',
varsIgnorePattern: '^_',
argsIgnorePattern: '^_'
}],
'no-unreachable': 'error',
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',

// Security (light - just warnings to start awareness)
'no-eval': 'warn',
'no-implied-eval': 'warn',
'no-new-func': 'warn',
'no-script-url': 'warn',

// Code Quality (warnings only - gradual improvement)
'no-var': 'warn', // Encourage let/const
'prefer-const': 'warn', // Encourage immutability
'no-magic-numbers': ['warn', {
ignore: [-1, 0, 1, 2, 100, 200, 201, 400, 401, 403, 404, 500],
ignoreArrayIndexes: true
}],
'prefer-template': 'warn',
'no-duplicate-imports': 'warn',
'object-shorthand': 'warn',

// Async/Promise Best Practices (warnings)
'no-return-await': 'warn',
'prefer-promise-reject-errors': 'warn',
'no-async-promise-executor': 'warn',

// Node.js Specific (warnings)
'no-path-concat': 'warn',
'no-process-exit': 'warn',
'handle-callback-err': 'warn',
'new-cap': 'warn',
'no-lonely-if': 'warn',
'no-nested-ternary': 'warn',
'camelcase': 'warn',
'radix': 'warn',
'no-restricted-syntax': 'warn',


// Light Complexity Control (warnings with high thresholds)
'complexity': ['warn', { max: 15 }], // High threshold for lazy devs
'max-depth': ['warn', { max: 5 }],
'max-params': ['warn', { max: 4 }],
'max-lines-per-function': ['warn', {
max: 100,
skipBlankLines: true,
skipComments: true
}],

// Import Organization (warnings only)
'import/order': ['warn', {
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'never' // Keep it simple
}],
'import/newline-after-import': 'warn',
'import/no-duplicates': 'warn',

// Performance Hints (warnings)
'no-loop-func': 'warn',

// API Design (warnings for better practices)
'no-throw-literal': 'warn',
'prefer-rest-params': 'warn',
'prefer-spread': 'warn',

// Database/Backend Specific (warnings)
'no-eq-null': 'warn', // Encourage strict equality
'eqeqeq': ['warn', 'smart'], // Allow == null for lazy devs

// Documentation Encouragement (warnings)
'spaced-comment': ['warn', 'always', {
markers: ['/', '!', '*'],
exceptions: ['-', '+', '*']
}],
},
settings: {
'import/resolver': {
Expand All @@ -32,9 +121,40 @@ module.exports = {
},
},
overrides: [
// Test Files - More Relaxed
{
files: ['**/*.test.js', '**/*.spec.js', 'src/test/**/*.js', 'src/__tests__/**/*.js'],
env: { jest: true },
rules: {
// Relax rules for test files
'no-magic-numbers': 'off',
'max-lines-per-function': 'off',
'complexity': 'off',
'max-params': 'off',
'prefer-promise-reject-errors': 'off',
'no-console': 'off',
'import/no-extraneous-dependencies': 'off',
}
},

// Config Files - Super Relaxed
{
files: ['*.config.js', '.eslintrc.js', 'babel.config.js', 'webpack.config.js'],
rules: {
'no-console': 'off',
'import/no-extraneous-dependencies': 'off',
'global-require': 'off',
}
},

// Migration Files - Relaxed (if using DB migrations)
{
files: ['**/migrations/*.js', '**/seeders/*.js'],
rules: {
'no-console': 'off',
'max-lines-per-function': 'off',
'no-magic-numbers': 'off',
}
}
],
};
};
76 changes: 66 additions & 10 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,75 @@ on:
branches: [ development ]

jobs:
build:
lint:
name: Lint Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20.x
- name: Checkout code
uses: actions/checkout@v4

- name: Use Node.js 20.17.0
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 20.17.0
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run lint
run: npm run lint

- run: npm ci
- name: Run email sender check
run: node src/utilities/debugEmailSender.js
- run: npm run test


test:
name: Tests with 10% Coverage Enforcement
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Use Node.js 20.17.0
uses: actions/setup-node@v4
with:
node-version: 20.17.0
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run tests with coverage enforcement
run: npm run test:ci

- name: Print detailed coverage report
if: always()
run: |
echo "📊 COVERAGE ENFORCEMENT REPORT 📊"
echo "=================================="
echo "Minimum Required: Lines: 34%, Statements: 34%, Functions: 27%, Branches: 12%"
echo "=================================="
if [ -f coverage/coverage-summary.json ]; then
node -e "
const coverage = require('./coverage/coverage-summary.json');
const { lines, statements, functions, branches } = coverage.total;

console.log('📈 CURRENT COVERAGE:');
console.log(\`📄 Lines: \${lines.pct}% \${lines.pct >= 34 ? '✅ PASS' : '❌ FAIL'}\`);
console.log(\`📝 Statements: \${statements.pct}% \${statements.pct >= 34 ? '✅ PASS' : '❌ FAIL'}\`);
console.log(\`🔧 Functions: \${functions.pct}% \${functions.pct >= 27 ? '✅ PASS' : '❌ FAIL'}\`);
console.log(\`🌿 Branches: \${branches.pct}% \${branches.pct >= 12 ? '✅ PASS' : '❌ FAIL'}\`);

const allPass = [lines, statements, functions, branches].every(metric => metric.pct >= 60);
console.log('================================');
console.log(\`🎯 OVERALL: \${allPass ? '✅ COVERAGE REQUIREMENTS MET' : '❌ COVERAGE REQUIREMENTS FAILED'}\`);
console.log('================================');
"
else
echo "❌ No coverage report generated"
fi

- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: backend-coverage-report
path: coverage/
40 changes: 34 additions & 6 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,40 @@
module.exports = {
roots: ['<rootDir>/src'],
collectCoverageFrom: [
'!<rootDir>/src/**/*.js', // need to collect coverage from all files that are involved in serving an API request
'!<rootDir>/src/server.js',
'!<rootDir>/src/models/**/*.js',
'!<rootDir>/src/startup/**/*.js',
'!<rootDir>/src/test/**/*.js',
'!<rootDir>/src/utilities/**/*.js', // need to collect coverage from utilities after all unit tests have been created
'src/**/*.js',
'src/controllers/**/*.js',
'src/routes/**/*.js',
'src/utilities/**/*.js',
'src/helpers/**/*.js',
'src/models/**/*.js',
'src/services/**/*.js',
'src/middleware/**/*.js',
// Exclude test files and mock data
'!src/**/*.test.js',
'!src/**/*.spec.js',
'!src/**/*MockData.js',
'!src/**/*MockData.jsx',
'!src/test/**',
'!src/__tests__/**'
],
// Coverage thresholds - Start light and increase gradually
coverageThreshold: {
global: {
branches: 9,
functions: 24,
lines: 30,
statements: 30
},

},

// Coverage reporters - shows in terminal and generates reports
coverageReporters: [
'text', // Terminal output
'text-summary', // Brief summary
'lcov', // For CI/CD tools
'html', // HTML report in coverage/ folder
'json' // JSON report for parsing
],
testTimeout: 60000, // 1 minute for CI environments
coverageDirectory: 'coverage',
Expand Down
6 changes: 5 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
},
"scripts": {
"test": "jest --passWithNoTests --silent --noStackTrace --runInBand --forceExit --detectOpenHandles",
"test:watch": "jest --watch --passWithNoTests",
"test:coverage": "jest --coverage --passWithNoTests",
"test:coverage:watch": "jest --coverage --watch --passWithNoTests",
"test:coverage:ci": "jest --coverage --watchAll=false --passWithNoTests --verbose",
"test:coverage:enforce": "jest --coverage --passWithNoTests --bail",
"test:threshold": "jest --coverage --passWithNoTests --verbose --bail",
"test:summary": "jest --coverage --passWithNoTests --verbose --coverageReporters=text-summary",
"test:verbose": "jest --passWithNoTests --runInBand",
"test:unit": "npm test -- --watch -c jest-unit.config.js",
"test:integration": "npm test -- --watch -c jest-integration.config.js",
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/automation/githubController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const githubService = require('../../services/automation/githubService');
const { checkAppAccess } = require('./utils');
const appAccessService = require('../../services/automation/appAccessService');
const { checkAppAccess } = require('./utils');

async function inviteUser(req, res) {
const { username, targetUser, teamIds = [] } = req.body;
Expand Down
1 change: 0 additions & 1 deletion src/controllers/automation/sentryController.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const sentryService = require('../../services/automation/sentryService');

const appAccessService = require('../../services/automation/appAccessService');
const { checkAppAccess } = require('./utils');

Expand Down
2 changes: 1 addition & 1 deletion src/controllers/automation/slackController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const slackService = require('../../services/automation/slackService'); // Import the Slack service
const { checkAppAccess } = require('./utils');
const appAccessService = require('../../services/automation/appAccessService');
const { checkAppAccess } = require('./utils');

// Email validation function
function validateEmail(email) {
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/badgeController.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ const cache = require('../utilities/nodeCache');
const Badge = require('../models/badge');
const helper = require('../utilities/permissions');
const escapeRegex = require('../utilities/escapeRegex');
const badgeController = require('./badgeController');
const { mockReq, mockRes, assertResMock } = require('../test');
const UserProfile = require('../models/userProfile');
const badgeController = require('./badgeController');

const makeSut = () => {
const { postBadge, getAllBadges, assignBadges, deleteBadge } = badgeController(Badge);
Expand Down
2 changes: 0 additions & 2 deletions src/controllers/bmdashboard/bmLoginController.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

const config = require('../../config');

const userprofile = require('../../models/userProfile');

const bmLoginController = function () {
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/dashBoardController.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ jest.mock('../utilities/emailSender', () => jest.fn());
const uuidv4 = require('uuid/v4');
const emailSender = require('../utilities/emailSender');
const { mockReq, mockRes, assertResMock } = require('../test');
const forgotPwdController = require('./forgotPwdcontroller');
const UserProfile = require('../models/userProfile');
const escapeRegex = require('../utilities/escapeRegex');
const forgotPwdController = require('./forgotPwdcontroller');

uuidv4.mockReturnValue('');
emailSender.mockImplementation(() => Promise.resolve());
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/emailController.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const jwt = require('jsonwebtoken');
// eslint-disable-next-line no-unused-vars
const { mockReq, mockRes, assertResMock } = require('../test');
const emailController = require('./emailController');
const userProfile = require('../models/userProfile');
const emailController = require('./emailController');

jest.mock('jsonwebtoken');
jest.mock('../models/userProfile');
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/forcePwdController.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const bcrypt = require('bcryptjs');
const forcePwdcontroller = require('./forcePwdController');
const userProfile = require('../models/userProfile');
const { mockReq, mockRes, assertResMock } = require('../test');
const forcePwdcontroller = require('./forcePwdController');

const makeSut = () => {
const { forcePwd } = forcePwdcontroller(userProfile);
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/hgnFormResponseController.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ jest.mock('../utilities/permissions', () => ({
}));

const FormResponse = require('../models/hgnFormResponse');
const hgnFormController = require('./hgnFormResponseController');
const { hasPermission } = require('../utilities/permissions');
const hgnFormController = require('./hgnFormResponseController');

describe('HgnFormResponseController', () => {
let mockReq;
Expand Down
Loading
Loading