diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9240e9fc..f1fb2ff2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,13 +22,62 @@ jobs: with: node-version: ${{ matrix.node-version }} - - name: Backend Tests + - name: Backend Tests (with coverage) run: | rm package-lock.json npm install -g npm@latest npm install npm run build - npm run test:backend + npm run test:backend -- --coverage + + - name: Upload backend coverage report + if: ${{ always() && hashFiles('coverage/**/coverage-summary.json') != '' }} + uses: actions/upload-artifact@v4 + with: + name: backend-coverage-${{ matrix.node-version }} + path: coverage + retention-days: 5 + + - name: Publish backend coverage summary + if: ${{ always() && matrix.node-version == '22.x' }} + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + const globModule = require('glob'); + + const matches = globModule.sync('coverage/**/coverage-summary.json', { + cwd: process.cwd(), + ignore: ['**/node_modules/**'], + }); + + if (!matches.length) { + core.warning('Coverage summary not found (expected coverage/**/coverage-summary.json). Did Vitest run with --coverage and include the json-summary reporter?'); + return; + } + + const summaryPath = path.resolve(matches[0]); + const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8')); + const metrics = ['lines', 'statements', 'branches', 'functions'] + .map((key) => ({ + label: key.charAt(0).toUpperCase() + key.slice(1), + ...summary.total[key], + })); + + core.summary + .addHeading('Backend coverage') + .addTable([ + ['Metric', 'Covered', 'Total', 'Pct'], + ...metrics.map(({ label, covered, total, pct }) => [ + label, + `${covered}`, + `${total}`, + `${pct}%`, + ]), + ]) + .addRaw('Full HTML report is available in the uploaded `backend-coverage-${{ matrix.node-version }}` artifact.') + .write(); api-test: name: API tests (node env, api-test) diff --git a/.gitignore b/.gitignore index c5df3843c..c2b1df4ad 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,7 @@ AGENTS.md .roo # source maps -*.map \ No newline at end of file +*.map + + +coverage/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1b7d01def..3e184dcad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,8 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.46.1", "@typescript-eslint/parser": "^8.46.1", + "@vitest/coverage-v8": "^4.0.14", + "@vitest/ui": "^4.0.14", "chalk": "^4.1.0", "clean-css": "^5.3.2", "dotenv": "^16.4.5", @@ -1272,6 +1274,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@bufbuild/protobuf": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.1.tgz", @@ -6425,6 +6437,13 @@ "node": ">=18" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -8145,6 +8164,53 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.14.tgz", + "integrity": "sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.14", + "ast-v8-to-istanbul": "^0.3.8", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.14", + "vitest": "4.0.14" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@vitest/expect": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.14.tgz", @@ -8252,6 +8318,36 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/ui": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.14.tgz", + "integrity": "sha512-fvDz8o7SQpFLoSBo6Cudv+fE85/fPCkwTnLAN85M+Jv7k59w2mSIjT9Q5px7XwGrmYqqKBEYxh/09IBGd1E7AQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/utils": "4.0.14", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.0.14" + } + }, + "node_modules/@vitest/ui/node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/utils": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.14.tgz", @@ -8928,6 +9024,25 @@ "node": "*" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", + "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -15052,6 +15167,18 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -15465,6 +15592,16 @@ "node": ">=4" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -18297,6 +18434,21 @@ "node": ">=8" } }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -19380,6 +19532,16 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/touch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", @@ -19899,6 +20061,7 @@ "integrity": "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.14", "@vitest/mocker": "4.0.14", diff --git a/package.json b/package.json index dd5c2394b..7ffd60d78 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.46.1", "@typescript-eslint/parser": "^8.46.1", + "@vitest/coverage-v8": "^4.0.14", + "@vitest/ui": "^4.0.14", "chalk": "^4.1.0", "clean-css": "^5.3.2", "dotenv": "^16.4.5", @@ -40,9 +42,10 @@ "yaml": "^2.8.1" }, "scripts": { - "test": "npx mocha src/phoenix/test && npx vitest run src/backend && node src/backend/tools/test.mjs", + "test": "npx mocha src/phoenix/test && npx vitest run --config=src/backend/vitest.config.ts && node src/backend/tools/test.mjs", "test:puterjs-api": "vitest run tests/puterJsApiTests", - "test:backend": "npm run build:ts; vitest run --config=src/backend/vitest.config.ts src/backend", + "test:backend": "npm run build:ts; vitest run --config=src/backend/vitest.config.ts", + "test:backend-coverage": "npm run build:ts; vitest run --config=src/backend/vitest.config.ts", "start=gui": "nodemon --exec \"node dev-server.js\" ", "start": "node ./tools/run-selfhosted.js", "prestart": "npm run build:ts", @@ -92,4 +95,4 @@ "engines": { "node": ">=20.19.5" } -} \ No newline at end of file +} diff --git a/src/backend/vitest.config.ts b/src/backend/vitest.config.ts index c842952a6..5a14fc364 100644 --- a/src/backend/vitest.config.ts +++ b/src/backend/vitest.config.ts @@ -8,9 +8,18 @@ export default defineConfig(({ mode }) => ({ environment: 'jsdom', setupFiles: [], coverage: { - reporter: ['text', 'json', 'html'], - exclude: [], + provider: 'v8', + reporter: ['text', 'json', 'json-summary', 'html', 'lcov'], + include: ['src/backend/**/*.js', 'src/backend/**/*.mjs', 'src/backend/**/*.ts', 'src/backend/**/*.ts'], + exclude: [ + '**/types/**', + '**/constants/**', + '**/*.d.ts', + '**/dist/**', + '**/*.min.*', + ], }, env: loadEnv(mode, '', 'PUTER_'), + include: ['src/backend/**/*.test.ts', 'src/backend/**/*.test.js'] }, }));