diff --git a/.gitignore b/.gitignore index 78752641..5e14b9a2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,8 @@ coverage-e2e/ # Test results and reports test-results/ -e2e-test-report.html -e2e-junit.xml +jest-results.json +jest-stare/ # Test temporary files and directories tests/temp/ diff --git a/jest.e2e.config.js b/jest.e2e.config.js index a7d6a4da..ba62d2ea 100644 --- a/jest.e2e.config.js +++ b/jest.e2e.config.js @@ -33,18 +33,21 @@ export default { reporters: [ 'default', [ - 'jest-html-reporter', + 'jest-stare', { - pageTitle: 'Task Master E2E Test Report', - outputPath: '/test-results/e2e-test-report.html', - includeFailureMsg: true, - includeConsoleLog: true, - dateFormat: 'yyyy-mm-dd HH:MM:ss', - theme: 'darkTheme', - sort: 'status', - executionTimeWarningThreshold: 5, - customCss: '.test-result { padding: 10px; }', - collapseSuitesByDefault: false + resultDir: 'test-results', + reportTitle: 'Task Master E2E Test Report', + additionalResultsProcessors: ['jest-junit'], + coverageLink: '../coverage-e2e/lcov-report/index.html', + jestStareConfigJson: 'jest-stare.config.json', + jestGlobalConfigJson: 'jest.e2e.config.json', + report: true, + reportSummary: true, + reportHeadline: 'Task Master E2E Test Results', + reportDescriptionHeadline: 'Comprehensive E2E test suite for all CLI commands', + disableCharts: false, + resultJson: 'jest-results.json', + resultHtml: 'index.html' } ], [ diff --git a/package-lock.json b/package-lock.json index 4855986b..9d0cd266 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "ink": "^5.0.1", "jest": "^29.7.0", "jest-environment-node": "^29.7.0", - "jest-html-reporter": "^4.3.0", + "jest-html-reporters": "^3.1.7", "jest-junit": "^16.0.0", "mcp-jest": "^1.0.10", "mock-fs": "^5.5.0", @@ -2701,8 +2701,8 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -2719,8 +2719,8 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">=12" }, @@ -2732,15 +2732,15 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -2757,8 +2757,8 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -2773,8 +2773,8 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -3033,30 +3033,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern/node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/@jest/reporters": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", @@ -5354,13 +5330,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -6704,16 +6673,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/dateformat": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.2.tgz", - "integrity": "sha512-EelsCzH0gMC2YmXuMeaZ3c6md1sUJQxyb1XXc4xaisi/K6qKukqZhKPrEQyRkdNIncgYyLoDTReq0nNyuKerTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6993,8 +6952,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", @@ -7304,16 +7263,6 @@ "node": ">= 0.8.0" } }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -7828,8 +7777,8 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -9078,8 +9027,8 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "devOptional": true, "license": "BlueOak-1.0.0", + "optional": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -9751,485 +9700,110 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-html-reporter": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/jest-html-reporter/-/jest-html-reporter-4.3.0.tgz", - "integrity": "sha512-lq4Zx35yc6Ehw513CXJ1ok3wUmkSiOImWcyLAmylfzrz7DAqtrhDF9V73F4qfstmGxlr8X0QrEjWsl/oqhf4sQ==", + "node_modules/jest-html-reporters": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/jest-html-reporters/-/jest-html-reporters-3.1.7.tgz", + "integrity": "sha512-GTmjqK6muQ0S0Mnksf9QkL9X9z2FGIpNSxC52E0PHDzjPQ1XDu2+XTI3B3FS43ZiUzD1f354/5FfwbNIBzT7ew==", "dev": true, "license": "MIT", "dependencies": { - "@jest/reporters": "^30.0.2", - "@jest/test-result": "^30.0.2", - "@jest/types": "^30.0.1", - "dateformat": "3.0.2", - "mkdirp": "^1.0.3", - "strip-ansi": "6.0.1", - "xmlbuilder": "15.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "jest": "19.x - 30.x", - "typescript": "^3.7.x || ^4.3.x || ^5.x" + "fs-extra": "^10.0.0", + "open": "^8.0.3" } }, - "node_modules/jest-html-reporter/node_modules/@jest/console": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.4.tgz", - "integrity": "sha512-tMLCDvBJBwPqMm4OAiuKm2uF5y5Qe26KgcMn+nrDSWpEW+eeFmqA0iO4zJfL16GP7gE3bUUQ3hIuUJ22AqVRnw==", + "node_modules/jest-html-reporters/node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.0.1", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/@jest/reporters": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.4.tgz", - "integrity": "sha512-6ycNmP0JSJEEys1FbIzHtjl9BP0tOZ/KN6iMeAKrdvGmUsa1qfRdlQRUDKJ4P84hJ3xHw1yTqJt4fvPNHhyE+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-html-reporter/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/@jest/test-result": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.4.tgz", - "integrity": "sha512-Mfpv8kjyKTHqsuu9YugB6z1gcdB3TSSOaKlehtVaiNlClMkEHY+5ZqCY2CrEE3ntpBMlstX/ShDAf84HKWsyIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.0.4", - "@jest/types": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/@jest/transform": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.4.tgz", - "integrity": "sha512-atvy4hRph/UxdCIBp+UB2jhEA/jJiUeGZ7QPgBi9jUUKNgi3WEoMXGNG7zbbELG2+88PMabUNCDchmqgJy3ELg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.0.1", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.0", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", - "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-html-reporter/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-html-reporter/node_modules/babel-plugin-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", - "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", + "node_modules/jest-html-reporters/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { "node": ">=12" } }, - "node_modules/jest-html-reporter/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/jest-html-reporters/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-html-reporter/node_modules/ci-info": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "node_modules/jest-html-reporters/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/jest-html-reporter/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/jest-html-reporters/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/jest-html-reporter/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-html-reporter/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/jest-html-reporter/node_modules/jest-haste-map": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.2.tgz", - "integrity": "sha512-telJBKpNLeCb4MaX+I5k496556Y2FiKR/QLZc0+MGBYl4k3OO0472drlV2LUe7c1Glng5HuAu+5GLYp//GpdOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.0.1", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", - "micromatch": "^4.0.8", - "walker": "^1.0.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "universalify": "^2.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.3" + "graceful-fs": "^4.1.6" } }, - "node_modules/jest-html-reporter/node_modules/jest-message-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz", - "integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==", + "node_modules/jest-html-reporters/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.1", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.0.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/jest-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz", - "integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.0.1", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/jest-worker": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.2.tgz", - "integrity": "sha512-RN1eQmx7qSLFA+o9pfJKlqViwL5wt+OL3Vff/A+/cPsmuw7NPwfgl33AP+/agRmHzPOFgXviRycR9kYwlcRQXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.2", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jest-html-reporter/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-html-reporter/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-html-reporter/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-html-reporter/node_modules/pretty-format": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz", - "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.1", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-html-reporter/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-html-reporters/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-html-reporter/node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 10.0.0" } }, "node_modules/jest-junit": { @@ -11551,8 +11125,8 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "devOptional": true, "license": "ISC", + "optional": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -12066,8 +11640,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "devOptional": true, - "license": "BlueOak-1.0.0" + "license": "BlueOak-1.0.0", + "optional": true }, "node_modules/package-manager-detector": { "version": "0.2.11", @@ -12210,8 +11784,8 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "devOptional": true, "license": "BlueOak-1.0.0", + "optional": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -13331,8 +12905,8 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -13346,15 +12920,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8" } @@ -13403,8 +12977,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -14062,8 +13636,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -14080,8 +13654,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14096,15 +13670,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8" } @@ -14113,8 +13687,8 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14224,16 +13798,6 @@ "dev": true, "license": "MIT" }, - "node_modules/xmlbuilder": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.0.0.tgz", - "integrity": "sha512-KLu/G0DoWhkncQ9eHSI6s0/w+T4TM7rQaLhtCaL6tORv8jFlJPlnGumsgTcGfYeS1qZ/IHqrvDG7zJZ4d7e+nw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, "node_modules/xsschema": { "version": "0.3.0-beta.8", "resolved": "https://registry.npmjs.org/xsschema/-/xsschema-0.3.0-beta.8.tgz", diff --git a/package.json b/package.json index 23b96972..6bf1697f 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,10 @@ "test:e2e:core": "node tests/e2e/run-e2e-tests.js --groups core", "test:e2e:providers": "node tests/e2e/run-e2e-tests.js --groups providers", "test:e2e:advanced": "node tests/e2e/run-e2e-tests.js --groups advanced", - "test:e2e:jest": "jest --config jest.e2e.config.js", - "test:e2e:jest:watch": "jest --config jest.e2e.config.js --watch", - "test:e2e:jest:command": "jest --config jest.e2e.config.js --testNamePattern", - "test:e2e:jest:report": "open test-results/e2e-test-report.html", + "test:e2e:jest": "node --experimental-vm-modules node_modules/.bin/jest --config jest.e2e.config.js", + "test:e2e:jest:watch": "node --experimental-vm-modules node_modules/.bin/jest --config jest.e2e.config.js --watch", + "test:e2e:jest:command": "node --experimental-vm-modules node_modules/.bin/jest --config jest.e2e.config.js --testNamePattern", + "test:e2e:jest:report": "open test-results/index.html", "prepare": "chmod +x bin/task-master.js mcp-server/server.js", "changeset": "changeset", "release": "changeset publish", @@ -133,8 +133,8 @@ "ink": "^5.0.1", "jest": "^29.7.0", "jest-environment-node": "^29.7.0", - "jest-html-reporter": "^4.3.0", "jest-junit": "^16.0.0", + "jest-html-reporters": "^3.1.7", "mcp-jest": "^1.0.10", "mock-fs": "^5.5.0", "prettier": "^3.5.3", diff --git a/tests/e2e/TEST-REPORTS.md b/tests/e2e/TEST-REPORTS.md index f55513e6..c95d4600 100644 --- a/tests/e2e/TEST-REPORTS.md +++ b/tests/e2e/TEST-REPORTS.md @@ -1,30 +1,40 @@ # E2E Test Reports -Task Master's E2E tests now generate comprehensive test reports similar to Playwright's reporting capabilities. +Task Master's E2E tests now generate comprehensive test reports using Jest Stare, providing an interactive and visually appealing test report similar to Playwright's reporting capabilities. ## Test Report Formats When you run `npm run test:e2e:jest`, the following reports are generated: -### 1. HTML Report -- **Location**: `test-results/e2e-test-report.html` +### 1. Jest Stare HTML Report +- **Location**: `test-results/index.html` - **Features**: - - Beautiful dark theme UI - - Test execution timeline - - Detailed failure messages + - Interactive dashboard with charts and graphs + - Test execution timeline and performance metrics + - Detailed failure messages with stack traces - Console output for each test + - Search and filter capabilities + - Pass/Fail/Skip statistics with visual charts + - Test duration analysis - Collapsible test suites - - Execution time warnings - - Sort by status (failed tests first) + - Coverage link integration + - Summary statistics -### 2. JUnit XML Report +### 2. JSON Results +- **Location**: `test-results/jest-results.json` +- **Use Cases**: + - Programmatic access to test results + - Custom reporting tools + - Test result analysis + +### 3. JUnit XML Report - **Location**: `test-results/e2e-junit.xml` - **Use Cases**: - CI/CD integration - Test result parsing - Historical tracking -### 3. Console Output +### 4. Console Output - Standard Jest terminal output with verbose mode enabled ## Running Tests with Reports diff --git a/tests/e2e/setup/jest-setup.js b/tests/e2e/setup/jest-setup.js index 4b691571..89823bcb 100644 --- a/tests/e2e/setup/jest-setup.js +++ b/tests/e2e/setup/jest-setup.js @@ -3,8 +3,9 @@ * Runs before each test file */ -const { TestHelpers } = require('../utils/test-helpers.cjs'); -const { TestLogger } = require('../utils/logger.cjs'); +import { jest, expect, afterAll } from '@jest/globals'; +import { TestHelpers } from '../utils/test-helpers.js'; +import { TestLogger } from '../utils/logger.js'; // Increase timeout for all E2E tests (can be overridden per test) jest.setTimeout(180000); @@ -79,4 +80,4 @@ global.createTestContext = (testName) => { afterAll(async () => { // Give time for any async operations to complete await new Promise((resolve) => setTimeout(resolve, 100)); -}); +}); \ No newline at end of file diff --git a/tests/e2e/tests/advanced.test.js b/tests/e2e/tests/advanced.test.js deleted file mode 100644 index ce44936b..00000000 --- a/tests/e2e/tests/advanced.test.js +++ /dev/null @@ -1,443 +0,0 @@ -export default async function testAdvanced(logger, helpers, context) { - const { testDir } = context; - logger.info('Starting advanced features tests...'); - const results = []; - - try { - // Test Tag Context Management - logger.info('Testing tag context management...'); - - // Create new tag contexts - const tag1Result = await helpers.taskMaster( - 'add-tag', - ['feature-auth', '--description', 'Authentication feature'], - { cwd: testDir } - ); - results.push({ - test: 'Create tag context - feature-auth', - passed: tag1Result.exitCode === 0, - output: tag1Result.stdout - }); - - const tag2Result = await helpers.taskMaster( - 'add-tag', - ['feature-api', '--description', 'API development'], - { cwd: testDir } - ); - results.push({ - test: 'Create tag context - feature-api', - passed: tag2Result.exitCode === 0, - output: tag2Result.stdout - }); - - // Add task to feature-auth tag - const task1Result = await helpers.taskMaster( - 'add-task', - ['--tag=feature-auth', '--prompt', 'Implement user authentication'], - { cwd: testDir } - ); - results.push({ - test: 'Add task to feature-auth tag', - passed: task1Result.exitCode === 0, - output: task1Result.stdout - }); - - // Add task to feature-api tag - const task2Result = await helpers.taskMaster( - 'add-task', - ['--tag=feature-api', '--prompt', 'Create REST API endpoints'], - { cwd: testDir } - ); - results.push({ - test: 'Add task to feature-api tag', - passed: task2Result.exitCode === 0, - output: task2Result.stdout - }); - - // List all tag contexts - const listTagsResult = await helpers.taskMaster('tags', [], { - cwd: testDir - }); - results.push({ - test: 'List all tag contexts', - passed: - listTagsResult.exitCode === 0 && - listTagsResult.stdout.includes('feature-auth') && - listTagsResult.stdout.includes('feature-api'), - output: listTagsResult.stdout - }); - - // List tasks in feature-auth tag - const taggedTasksResult = await helpers.taskMaster( - 'list', - ['--tag=feature-auth'], - { cwd: testDir } - ); - results.push({ - test: 'List tasks in feature-auth tag', - passed: - taggedTasksResult.exitCode === 0 && - taggedTasksResult.stdout.includes('Implement user authentication'), - output: taggedTasksResult.stdout - }); - - // Test Model Configuration - logger.info('Testing model configuration...'); - - // Set main model - const setMainModelResult = await helpers.taskMaster( - 'models', - ['--set-main', 'gpt-4'], - { cwd: testDir } - ); - results.push({ - test: 'Set main model', - passed: setMainModelResult.exitCode === 0, - output: setMainModelResult.stdout - }); - - // Set research model - const setResearchModelResult = await helpers.taskMaster( - 'models', - ['--set-research', 'claude-3-sonnet'], - { cwd: testDir } - ); - results.push({ - test: 'Set research model', - passed: setResearchModelResult.exitCode === 0, - output: setResearchModelResult.stdout - }); - - // Set fallback model - const setFallbackModelResult = await helpers.taskMaster( - 'models', - ['--set-fallback', 'gpt-3.5-turbo'], - { cwd: testDir } - ); - results.push({ - test: 'Set fallback model', - passed: setFallbackModelResult.exitCode === 0, - output: setFallbackModelResult.stdout - }); - - // Verify model configuration - const showModelsResult = await helpers.taskMaster('models', [], { - cwd: testDir - }); - results.push({ - test: 'Show model configuration', - passed: - showModelsResult.exitCode === 0 && - showModelsResult.stdout.includes('gpt-4') && - showModelsResult.stdout.includes('claude-3-sonnet') && - showModelsResult.stdout.includes('gpt-3.5-turbo'), - output: showModelsResult.stdout - }); - - // Test Task Expansion - logger.info('Testing task expansion...'); - - // Add task for expansion - const expandTaskResult = await helpers.taskMaster( - 'add-task', - ['--prompt', 'Build REST API'], - { cwd: testDir } - ); - const expandTaskMatch = expandTaskResult.stdout.match(/#(\d+)/); - const expandTaskId = expandTaskMatch ? expandTaskMatch[1] : null; - - results.push({ - test: 'Add task for expansion', - passed: expandTaskResult.exitCode === 0 && expandTaskId !== null, - output: expandTaskResult.stdout - }); - - if (expandTaskId) { - // Single task expansion - const expandResult = await helpers.taskMaster('expand', [expandTaskId], { - cwd: testDir - }); - results.push({ - test: 'Expand single task', - passed: - expandResult.exitCode === 0 && - expandResult.stdout.includes('subtasks'), - output: expandResult.stdout - }); - - // Verify expand worked - const afterExpandResult = await helpers.taskMaster( - 'show', - [expandTaskId], - { cwd: testDir } - ); - results.push({ - test: 'Verify task expansion', - passed: - afterExpandResult.exitCode === 0 && - afterExpandResult.stdout.includes('subtasks'), - output: afterExpandResult.stdout - }); - - // Force expand (re-expand) - const forceExpandResult = await helpers.taskMaster( - 'expand', - [expandTaskId, '--force'], - { cwd: testDir } - ); - results.push({ - test: 'Force expand task', - passed: forceExpandResult.exitCode === 0, - output: forceExpandResult.stdout - }); - } - - // Test Subtask Management - logger.info('Testing subtask management...'); - - // Add task for subtask testing - const subtaskParentResult = await helpers.taskMaster( - 'add-task', - ['--prompt', 'Create user interface'], - { cwd: testDir } - ); - const parentMatch = subtaskParentResult.stdout.match(/#(\d+)/); - const parentTaskId = parentMatch ? parentMatch[1] : null; - - if (parentTaskId) { - // Add subtasks manually - const addSubtask1Result = await helpers.taskMaster( - 'add-subtask', - ['--parent', parentTaskId, '--title', 'Design mockups'], - { cwd: testDir } - ); - results.push({ - test: 'Add subtask - Design mockups', - passed: addSubtask1Result.exitCode === 0, - output: addSubtask1Result.stdout - }); - - const addSubtask2Result = await helpers.taskMaster( - 'add-subtask', - ['--parent', parentTaskId, '--title', 'Implement components'], - { cwd: testDir } - ); - results.push({ - test: 'Add subtask - Implement components', - passed: addSubtask2Result.exitCode === 0, - output: addSubtask2Result.stdout - }); - - // List subtasks (use show command to see subtasks) - const listSubtasksResult = await helpers.taskMaster( - 'show', - [parentTaskId], - { cwd: testDir } - ); - results.push({ - test: 'List subtasks', - passed: - listSubtasksResult.exitCode === 0 && - listSubtasksResult.stdout.includes('Design mockups') && - listSubtasksResult.stdout.includes('Implement components'), - output: listSubtasksResult.stdout - }); - - // Update subtask - const subtaskId = `${parentTaskId}.1`; - const updateSubtaskResult = await helpers.taskMaster( - 'update-subtask', - ['--id', subtaskId, '--prompt', 'Create detailed mockups'], - { cwd: testDir } - ); - results.push({ - test: 'Update subtask', - passed: updateSubtaskResult.exitCode === 0, - output: updateSubtaskResult.stdout - }); - - // Remove subtask - const removeSubtaskId = `${parentTaskId}.2`; - const removeSubtaskResult = await helpers.taskMaster( - 'remove-subtask', - ['--id', removeSubtaskId], - { cwd: testDir } - ); - results.push({ - test: 'Remove subtask', - passed: removeSubtaskResult.exitCode === 0, - output: removeSubtaskResult.stdout - }); - - // Verify subtask changes - const verifySubtasksResult = await helpers.taskMaster( - 'show', - [parentTaskId], - { cwd: testDir } - ); - results.push({ - test: 'Verify subtask changes', - passed: - verifySubtasksResult.exitCode === 0 && - verifySubtasksResult.stdout.includes('Create detailed mockups') && - !verifySubtasksResult.stdout.includes('Implement components'), - output: verifySubtasksResult.stdout - }); - - // Clear all subtasks - const clearSubtasksResult = await helpers.taskMaster( - 'clear-subtasks', - ['--id', parentTaskId], - { cwd: testDir } - ); - results.push({ - test: 'Clear all subtasks', - passed: clearSubtasksResult.exitCode === 0, - output: clearSubtasksResult.stdout - }); - - // Verify subtasks cleared - const verifyClearResult = await helpers.taskMaster( - 'show', - [parentTaskId], - { cwd: testDir } - ); - results.push({ - test: 'Verify subtasks cleared', - passed: - verifyClearResult.exitCode === 0 && - !verifyClearResult.stdout.includes('Design mockups') && - !verifyClearResult.stdout.includes('Create detailed mockups'), - output: verifyClearResult.stdout - }); - } - - // Test Expand All - logger.info('Testing expand all...'); - - // Add multiple tasks - await helpers.taskMaster( - 'add-task', - ['--prompt', 'Task A for expand all'], - { cwd: testDir } - ); - await helpers.taskMaster( - 'add-task', - ['--prompt', 'Task B for expand all'], - { cwd: testDir } - ); - - const expandAllResult = await helpers.taskMaster('expand', ['--all'], { - cwd: testDir - }); - results.push({ - test: 'Expand all tasks', - passed: expandAllResult.exitCode === 0, - output: expandAllResult.stdout - }); - - // Test Generate Task Files - logger.info('Testing generate task files...'); - - // Generate files for a specific task - if (expandTaskId) { - const generateResult = await helpers.taskMaster( - 'generate', - [expandTaskId], - { cwd: testDir } - ); - results.push({ - test: 'Generate task files', - passed: generateResult.exitCode === 0, - output: generateResult.stdout - }); - - // Check if files were created - const taskFilePath = `${testDir}/tasks/task_${expandTaskId}.md`; - const fileExists = helpers.fileExists(taskFilePath); - - results.push({ - test: 'Verify generated task file exists', - passed: fileExists, - output: fileExists - ? `Task file created at ${taskFilePath}` - : 'Task file not found' - }); - } - - // Test Tag Context Integrity After Operations - logger.info('Testing tag context integrity after operations...'); - - // Verify tag contexts still exist - const finalTagListResult = await helpers.taskMaster('tags', [], { - cwd: testDir - }); - results.push({ - test: 'Final tag context list verification', - passed: - finalTagListResult.exitCode === 0 && - finalTagListResult.stdout.includes('feature-auth') && - finalTagListResult.stdout.includes('feature-api'), - output: finalTagListResult.stdout - }); - - // Verify tasks are still in their respective tag contexts - const finalTaggedTasksResult = await helpers.taskMaster( - 'list', - ['--tag=feature-api'], - { cwd: testDir } - ); - results.push({ - test: 'Final tasks in tag context verification', - passed: - finalTaggedTasksResult.exitCode === 0 && - finalTaggedTasksResult.stdout.includes('Create REST API endpoints'), - output: finalTaggedTasksResult.stdout - }); - - // Test Additional Advanced Features - logger.info('Testing additional advanced features...'); - - // Test priority task - const priorityTagResult = await helpers.taskMaster( - 'add-task', - ['--prompt', 'High priority task', '--priority', 'high'], - { cwd: testDir } - ); - results.push({ - test: 'Add task with high priority', - passed: priorityTagResult.exitCode === 0, - output: priorityTagResult.stdout - }); - - // Test filtering by status - const statusFilterResult = await helpers.taskMaster( - 'list', - ['--status', 'pending'], - { cwd: testDir } - ); - results.push({ - test: 'Filter by status', - passed: statusFilterResult.exitCode === 0, - output: statusFilterResult.stdout - }); - } catch (error) { - logger.error('Error in advanced features tests:', error); - results.push({ - test: 'Advanced features test suite', - passed: false, - error: error.message - }); - } - - const passed = results.filter((r) => r.passed).length; - const total = results.length; - - return { - name: 'Advanced Features', - passed, - total, - results, - summary: `Advanced features tests: ${passed}/${total} passed` - }; -} diff --git a/tests/e2e/tests/commands/add-dependency.test.js b/tests/e2e/tests/commands/add-dependency.test.js index eed2aad9..33811a1c 100644 --- a/tests/e2e/tests/commands/add-dependency.test.js +++ b/tests/e2e/tests/commands/add-dependency.test.js @@ -1,90 +1,99 @@ +/** + * E2E tests for add-dependency command + * Tests dependency management functionality + */ + import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; -import { mkdtempSync } from 'fs'; -import { tmpdir } from 'os'; +import { mkdtempSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'fs'; import { join } from 'path'; -import { rmSync, existsSync, readFileSync } from 'fs'; +import { tmpdir } from 'os'; describe('task-master add-dependency', () => { let testDir; let helpers; - beforeEach(() => { + beforeEach(async () => { // Create test directory - testDir = mkdtempSync(join(tmpdir(), 'tm-test-add-dep-')); - process.chdir(testDir); + testDir = mkdtempSync(join(tmpdir(), 'task-master-add-dep-')); - // Get helpers from global context - helpers = global.testHelpers; + // Initialize test helpers + const context = global.createTestContext('add-dependency'); + helpers = context.helpers; - // Copy .env if exists - const envPath = join(process.cwd(), '../../.env'); - if (existsSync(envPath)) { - const envContent = readFileSync(envPath, 'utf-8'); - helpers.writeFile('.env', envContent); + // Copy .env file if it exists + const mainEnvPath = join(process.cwd(), '.env'); + const testEnvPath = join(testDir, '.env'); + if (existsSync(mainEnvPath)) { + const envContent = readFileSync(mainEnvPath, 'utf8'); + writeFileSync(testEnvPath, envContent); } // Initialize task-master project - const initResult = helpers.taskMaster('init', ['-y']); + const initResult = await helpers.taskMaster('init', ['-y'], { + cwd: testDir + }); expect(initResult).toHaveExitCode(0); - // Ensure tasks.json exists - const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); - if (!helpers.fileExists(tasksPath)) { - helpers.writeFile(tasksPath, JSON.stringify({ tasks: [] }, null, 2)); + // Ensure tasks.json exists (bug workaround) + const tasksJsonPath = join(testDir, '.taskmaster/tasks/tasks.json'); + if (!existsSync(tasksJsonPath)) { + mkdirSync(join(testDir, '.taskmaster/tasks'), { recursive: true }); + writeFileSync(tasksJsonPath, JSON.stringify({ master: { tasks: [] } })); } }); afterEach(() => { // Clean up test directory - process.chdir('..'); - rmSync(testDir, { recursive: true, force: true }); + if (testDir && existsSync(testDir)) { + rmSync(testDir, { recursive: true, force: true }); + } }); describe('Basic dependency creation', () => { - it('should add a single dependency to a task', () => { + it('should add a single dependency to a task', async () => { // Create tasks - const dep = helpers.taskMaster('add-task', ['Dependency task', '-m']); + const dep = await helpers.taskMaster('add-task', ['--title', 'Dependency task', '--description', 'A dependency'], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); - const task = helpers.taskMaster('add-task', ['Main task', '-m']); + const task = await helpers.taskMaster('add-task', ['--title', 'Main task', '--description', 'Main task description'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); // Add dependency - const result = helpers.taskMaster('add-dependency', [taskId, depId]); + const result = await helpers.taskMaster('add-dependency', ['--id', taskId, '--depends-on', depId], { cwd: testDir }); expect(result).toHaveExitCode(0); - expect(result.stdout).toContain('Dependency added successfully'); - expect(result.stdout).toContain(`${taskId} now depends on ${depId}`); + expect(result.stdout).toContain('Successfully added dependency'); + expect(result.stdout).toContain(`Task ${taskId} now depends on ${depId}`); // Verify dependency was added - const showResult = helpers.taskMaster('show', [taskId]); + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout).toContain('Dependencies:'); - expect(showResult.stdout).toContain(`${depId} - Dependency task`); + expect(showResult.stdout).toContain(depId); }); - it('should add multiple dependencies at once', () => { + it('should add multiple dependencies at once', async () => { // Create dependency tasks - const dep1 = helpers.taskMaster('add-task', ['First dependency', '-m']); + const dep1 = await helpers.taskMaster('add-task', ['--title', 'First dependency', '--description', 'First dep'], { cwd: testDir }); const depId1 = helpers.extractTaskId(dep1.stdout); - const dep2 = helpers.taskMaster('add-task', ['Second dependency', '-m']); + const dep2 = await helpers.taskMaster('add-task', ['--title', 'Second dependency', '--description', 'Second dep'], { cwd: testDir }); const depId2 = helpers.extractTaskId(dep2.stdout); - const dep3 = helpers.taskMaster('add-task', ['Third dependency', '-m']); + const dep3 = await helpers.taskMaster('add-task', ['--title', 'Third dependency', '--description', 'Third dep'], { cwd: testDir }); const depId3 = helpers.extractTaskId(dep3.stdout); - const task = helpers.taskMaster('add-task', ['Main task', '-m']); + const task = await helpers.taskMaster('add-task', ['--title', 'Main task', '--description', 'Main task'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); // Add multiple dependencies - const result = helpers.taskMaster('add-dependency', [ - taskId, - `${depId1},${depId2},${depId3}` - ]); + const result = await helpers.taskMaster('add-dependency', [ + '--id', taskId, + '--depends-on', `${depId1},${depId2},${depId3}` + ], { cwd: testDir }); expect(result).toHaveExitCode(0); - expect(result.stdout).toContain('3 dependencies added'); + expect(result.stdout).toContain('Dependencies added'); // Verify all dependencies were added - const showResult = helpers.taskMaster('show', [taskId]); + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout).toContain(depId1); expect(showResult.stdout).toContain(depId2); expect(showResult.stdout).toContain(depId3); @@ -92,71 +101,74 @@ describe('task-master add-dependency', () => { }); describe('Dependency validation', () => { - it('should prevent circular dependencies', () => { + it('should prevent circular dependencies', async () => { // Create circular dependency chain - const task1 = helpers.taskMaster('add-task', ['Task 1', '-m']); + const task1 = await helpers.taskMaster('add-task', ['--title', 'Task 1', '--description', 'First task'], { cwd: testDir }); const id1 = helpers.extractTaskId(task1.stdout); - const task2 = helpers.taskMaster('add-task', ['Task 2', '-m']); + const task2 = await helpers.taskMaster('add-task', ['--title', 'Task 2', '--description', 'Second task'], { cwd: testDir }); const id2 = helpers.extractTaskId(task2.stdout); // Add first dependency - helpers.taskMaster('add-dependency', [id2, id1]); + await helpers.taskMaster('add-dependency', ['--id', id2, '--depends-on', id1], { cwd: testDir }); // Try to create circular dependency - const result = helpers.taskMaster('add-dependency', [id1, id2], { + const result = await helpers.taskMaster('add-dependency', ['--id', id1, '--depends-on', id2], { + cwd: testDir, allowFailure: true }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toContain('circular dependency'); }); - it('should prevent self-dependencies', () => { - const task = helpers.taskMaster('add-task', ['Task', '-m']); + it('should prevent self-dependencies', async () => { + const task = await helpers.taskMaster('add-task', ['--title', 'Task', '--description', 'A task'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); - const result = helpers.taskMaster('add-dependency', [taskId, taskId], { + const result = await helpers.taskMaster('add-dependency', ['--id', taskId, '--depends-on', taskId], { + cwd: testDir, allowFailure: true }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toContain('cannot depend on itself'); }); - it('should detect transitive circular dependencies', () => { + it('should detect transitive circular dependencies', async () => { // Create chain: A -> B -> C, then try C -> A - const taskA = helpers.taskMaster('add-task', ['Task A', '-m']); + const taskA = await helpers.taskMaster('add-task', ['--title', 'Task A', '--description', 'Task A'], { cwd: testDir }); const idA = helpers.extractTaskId(taskA.stdout); - const taskB = helpers.taskMaster('add-task', ['Task B', '-m']); + const taskB = await helpers.taskMaster('add-task', ['--title', 'Task B', '--description', 'Task B'], { cwd: testDir }); const idB = helpers.extractTaskId(taskB.stdout); - const taskC = helpers.taskMaster('add-task', ['Task C', '-m']); + const taskC = await helpers.taskMaster('add-task', ['--title', 'Task C', '--description', 'Task C'], { cwd: testDir }); const idC = helpers.extractTaskId(taskC.stdout); // Create chain - helpers.taskMaster('add-dependency', [idB, idA]); - helpers.taskMaster('add-dependency', [idC, idB]); + await helpers.taskMaster('add-dependency', ['--id', idB, '--depends-on', idA], { cwd: testDir }); + await helpers.taskMaster('add-dependency', ['--id', idC, '--depends-on', idB], { cwd: testDir }); // Try to create circular dependency - const result = helpers.taskMaster('add-dependency', [idA, idC], { + const result = await helpers.taskMaster('add-dependency', ['--id', idA, '--depends-on', idC], { + cwd: testDir, allowFailure: true }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toContain('circular dependency'); }); - it('should prevent duplicate dependencies', () => { - const dep = helpers.taskMaster('add-task', ['Dependency', '-m']); + it('should prevent duplicate dependencies', async () => { + const dep = await helpers.taskMaster('add-task', ['--title', 'Dependency', '--description', 'A dependency'], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); - const task = helpers.taskMaster('add-task', ['Task', '-m']); + const task = await helpers.taskMaster('add-task', ['--title', 'Task', '--description', 'A task'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); // Add dependency first time - helpers.taskMaster('add-dependency', [taskId, depId]); + await helpers.taskMaster('add-dependency', ['--id', taskId, '--depends-on', depId], { cwd: testDir }); // Try to add same dependency again - const result = helpers.taskMaster('add-dependency', [taskId, depId]); + const result = await helpers.taskMaster('add-dependency', ['--id', taskId, '--depends-on', depId], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain('already depends on'); expect(result.stdout).toContain('No changes made'); @@ -164,101 +176,106 @@ describe('task-master add-dependency', () => { }); describe('Status updates', () => { - it('should update task status to blocked when adding dependencies', () => { - const dep = helpers.taskMaster('add-task', [ + it('should update task status to blocked when adding dependencies', async () => { + const dep = await helpers.taskMaster('add-task', [ + '--title', 'Incomplete dependency', - '-m' - ]); + '--description', + 'Not done yet' + ], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); - const task = helpers.taskMaster('add-task', ['Task', '-m']); + const task = await helpers.taskMaster('add-task', ['--title', 'Task', '--description', 'A task'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); // Start the task - helpers.taskMaster('set-status', [taskId, 'in-progress']); + await helpers.taskMaster('set-status', [taskId, 'in-progress'], { cwd: testDir }); // Add dependency (should change status to blocked) - const result = helpers.taskMaster('add-dependency', [taskId, depId]); + const result = await helpers.taskMaster('add-dependency', ['--id', taskId, '--depends-on', depId], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain('Status changed to: blocked'); // Verify status - const showResult = helpers.taskMaster('show', [taskId]); + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout).toContain('Status: blocked'); }); - it('should not change status if all dependencies are complete', () => { - const dep = helpers.taskMaster('add-task', ['Complete dependency', '-m']); + it('should not change status if all dependencies are complete', async () => { + const dep = await helpers.taskMaster('add-task', ['--title', 'Complete dependency', '--description', 'Done'], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); - helpers.taskMaster('set-status', [depId, 'done']); + await helpers.taskMaster('set-status', [depId, 'done'], { cwd: testDir }); - const task = helpers.taskMaster('add-task', ['Task', '-m']); + const task = await helpers.taskMaster('add-task', ['--title', 'Task', '--description', 'A task'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); - helpers.taskMaster('set-status', [taskId, 'in-progress']); + await helpers.taskMaster('set-status', [taskId, 'in-progress'], { cwd: testDir }); // Add completed dependency - const result = helpers.taskMaster('add-dependency', [taskId, depId]); + const result = await helpers.taskMaster('add-dependency', ['--id', taskId, '--depends-on', depId], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).not.toContain('Status changed'); // Status should remain in-progress - const showResult = helpers.taskMaster('show', [taskId]); + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout).toContain('Status: in-progress'); }); }); describe('Subtask dependencies', () => { - it('should add dependency to a subtask', () => { + it('should add dependency to a subtask', async () => { // Create parent and dependency - const parent = helpers.taskMaster('add-task', ['Parent task', '-m']); + const parent = await helpers.taskMaster('add-task', ['--title', 'Parent task', '--description', 'Parent'], { cwd: testDir }); const parentId = helpers.extractTaskId(parent.stdout); - const dep = helpers.taskMaster('add-task', ['Dependency', '-m']); + const dep = await helpers.taskMaster('add-task', ['--title', 'Dependency', '--description', 'A dependency'], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); // Expand parent - helpers.taskMaster('expand', ['-i', parentId, '-n', '2'], { + await helpers.taskMaster('expand', ['-i', parentId, '-n', '2'], { + cwd: testDir, timeout: 60000 }); // Add dependency to subtask const subtaskId = `${parentId}.1`; - const result = helpers.taskMaster('add-dependency', [subtaskId, depId]); + const result = await helpers.taskMaster('add-dependency', ['--id', subtaskId, '--depends-on', depId], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain(`${subtaskId} now depends on ${depId}`); }); - it('should allow subtask to depend on another subtask', () => { + it('should allow subtask to depend on another subtask', async () => { // Create parent task - const parent = helpers.taskMaster('add-task', ['Parent', '-m']); + const parent = await helpers.taskMaster('add-task', ['--title', 'Parent', '--description', 'Parent task'], { cwd: testDir }); const parentId = helpers.extractTaskId(parent.stdout); // Expand to create subtasks - helpers.taskMaster('expand', ['-i', parentId, '-n', '3'], { + await helpers.taskMaster('expand', ['-i', parentId, '-n', '3'], { + cwd: testDir, timeout: 60000 }); // Make subtask 2 depend on subtask 1 - const result = helpers.taskMaster('add-dependency', [ - `${parentId}.2`, - `${parentId}.1` - ]); + const result = await helpers.taskMaster('add-dependency', [ + '--id', `${parentId}.2`, + '--depends-on', `${parentId}.1` + ], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain('Dependency added successfully'); }); - it('should prevent parent depending on its own subtask', () => { - const parent = helpers.taskMaster('add-task', ['Parent', '-m']); + it('should prevent parent depending on its own subtask', async () => { + const parent = await helpers.taskMaster('add-task', ['--title', 'Parent', '--description', 'Parent task'], { cwd: testDir }); const parentId = helpers.extractTaskId(parent.stdout); - helpers.taskMaster('expand', ['-i', parentId, '-n', '2'], { + await helpers.taskMaster('expand', ['-i', parentId, '-n', '2'], { + cwd: testDir, timeout: 60000 }); - const result = helpers.taskMaster( + const result = await helpers.taskMaster( 'add-dependency', - [parentId, `${parentId}.1`], - { allowFailure: true } + ['--id', parentId, '--depends-on', `${parentId}.1`], + { cwd: testDir, allowFailure: true } ); expect(result.exitCode).not.toBe(0); expect(result.stderr).toContain('cannot depend on its own subtask'); @@ -266,121 +283,122 @@ describe('task-master add-dependency', () => { }); describe('Bulk operations', () => { - it('should add dependencies to multiple tasks', () => { + it('should add dependencies to multiple tasks', async () => { // Create dependency - const dep = helpers.taskMaster('add-task', ['Shared dependency', '-m']); + const dep = await helpers.taskMaster('add-task', ['--title', 'Shared dependency', '--description', 'Shared dep'], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); // Create multiple tasks - const task1 = helpers.taskMaster('add-task', ['Task 1', '-m']); + const task1 = await helpers.taskMaster('add-task', ['--title', 'Task 1', '--description', 'First'], { cwd: testDir }); const id1 = helpers.extractTaskId(task1.stdout); - const task2 = helpers.taskMaster('add-task', ['Task 2', '-m']); + const task2 = await helpers.taskMaster('add-task', ['--title', 'Task 2', '--description', 'Second'], { cwd: testDir }); const id2 = helpers.extractTaskId(task2.stdout); - const task3 = helpers.taskMaster('add-task', ['Task 3', '-m']); + const task3 = await helpers.taskMaster('add-task', ['--title', 'Task 3', '--description', 'Third'], { cwd: testDir }); const id3 = helpers.extractTaskId(task3.stdout); // Add dependency to all tasks - const result = helpers.taskMaster('add-dependency', [ - `${id1},${id2},${id3}`, - depId - ]); + const result = await helpers.taskMaster('add-dependency', [ + '--id', `${id1},${id2},${id3}`, + '--depends-on', depId + ], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain('3 tasks updated'); // Verify all have the dependency for (const id of [id1, id2, id3]) { - const showResult = helpers.taskMaster('show', [id]); + const showResult = await helpers.taskMaster('show', [id], { cwd: testDir }); expect(showResult.stdout).toContain(depId); } }); - it('should add dependencies by range', () => { + it('should add dependencies by range', async () => { // Create dependency - const dep = helpers.taskMaster('add-task', ['Dependency', '-m']); + const dep = await helpers.taskMaster('add-task', ['--title', 'Dependency', '--description', 'A dep'], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); // Create sequential tasks const ids = []; for (let i = 0; i < 5; i++) { - const result = helpers.taskMaster('add-task', [`Task ${i + 1}`, '-m']); + const result = await helpers.taskMaster('add-task', ['--title', `Task ${i + 1}`, '--description', `Task number ${i + 1}`], { cwd: testDir }); ids.push(helpers.extractTaskId(result.stdout)); } // Add dependency to range - const result = helpers.taskMaster('add-dependency', [ + const result = await helpers.taskMaster('add-dependency', [ '--from', ids[1], '--to', ids[3], + '--depends-on', depId - ]); + ], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain('3 tasks updated'); // Verify middle tasks have dependency for (let i = 1; i <= 3; i++) { - const showResult = helpers.taskMaster('show', [ids[i]]); + const showResult = await helpers.taskMaster('show', [ids[i]], { cwd: testDir }); expect(showResult.stdout).toContain(depId); } // Verify edge tasks don't have dependency - const show0 = helpers.taskMaster('show', [ids[0]]); + const show0 = await helpers.taskMaster('show', [ids[0]], { cwd: testDir }); expect(show0.stdout).not.toContain(`Dependencies:.*${depId}`); }); }); describe('Complex dependency graphs', () => { - it('should handle diamond dependency pattern', () => { + it('should handle diamond dependency pattern', async () => { // Create diamond: A depends on B and C, both B and C depend on D - const taskD = helpers.taskMaster('add-task', ['Task D (base)', '-m']); + const taskD = await helpers.taskMaster('add-task', ['--title', 'Task D (base)', '--description', 'Base task'], { cwd: testDir }); const idD = helpers.extractTaskId(taskD.stdout); - const taskB = helpers.taskMaster('add-task', ['Task B', '-m']); + const taskB = await helpers.taskMaster('add-task', ['--title', 'Task B', '--description', 'Middle task B'], { cwd: testDir }); const idB = helpers.extractTaskId(taskB.stdout); - helpers.taskMaster('add-dependency', [idB, idD]); + await helpers.taskMaster('add-dependency', ['--id', idB, '--depends-on', idD], { cwd: testDir }); - const taskC = helpers.taskMaster('add-task', ['Task C', '-m']); + const taskC = await helpers.taskMaster('add-task', ['--title', 'Task C', '--description', 'Middle task C'], { cwd: testDir }); const idC = helpers.extractTaskId(taskC.stdout); - helpers.taskMaster('add-dependency', [idC, idD]); + await helpers.taskMaster('add-dependency', ['--id', idC, '--depends-on', idD], { cwd: testDir }); - const taskA = helpers.taskMaster('add-task', ['Task A (top)', '-m']); + const taskA = await helpers.taskMaster('add-task', ['--title', 'Task A (top)', '--description', 'Top task'], { cwd: testDir }); const idA = helpers.extractTaskId(taskA.stdout); // Add both dependencies to create diamond - const result = helpers.taskMaster('add-dependency', [ - idA, - `${idB},${idC}` - ]); + const result = await helpers.taskMaster('add-dependency', [ + '--id', idA, + '--depends-on', `${idB},${idC}` + ], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain('2 dependencies added'); // Verify the structure - const showResult = helpers.taskMaster('show', [idA]); + const showResult = await helpers.taskMaster('show', [idA], { cwd: testDir }); expect(showResult.stdout).toContain(idB); expect(showResult.stdout).toContain(idC); }); - it('should show transitive dependencies', () => { + it('should show transitive dependencies', async () => { // Create chain A -> B -> C -> D - const taskD = helpers.taskMaster('add-task', ['Task D', '-m']); + const taskD = await helpers.taskMaster('add-task', ['--title', 'Task D', '--description', 'End task'], { cwd: testDir }); const idD = helpers.extractTaskId(taskD.stdout); - const taskC = helpers.taskMaster('add-task', ['Task C', '-m']); + const taskC = await helpers.taskMaster('add-task', ['--title', 'Task C', '--description', 'Middle task'], { cwd: testDir }); const idC = helpers.extractTaskId(taskC.stdout); - helpers.taskMaster('add-dependency', [idC, idD]); + await helpers.taskMaster('add-dependency', ['--id', idC, '--depends-on', idD], { cwd: testDir }); - const taskB = helpers.taskMaster('add-task', ['Task B', '-m']); + const taskB = await helpers.taskMaster('add-task', ['--title', 'Task B', '--description', 'Middle task'], { cwd: testDir }); const idB = helpers.extractTaskId(taskB.stdout); - helpers.taskMaster('add-dependency', [idB, idC]); + await helpers.taskMaster('add-dependency', ['--id', idB, '--depends-on', idC], { cwd: testDir }); - const taskA = helpers.taskMaster('add-task', ['Task A', '-m']); + const taskA = await helpers.taskMaster('add-task', ['--title', 'Task A', '--description', 'Start task'], { cwd: testDir }); const idA = helpers.extractTaskId(taskA.stdout); - helpers.taskMaster('add-dependency', [idA, idB]); + await helpers.taskMaster('add-dependency', ['--id', idA, '--depends-on', idB], { cwd: testDir }); // Show should indicate full dependency chain - const result = helpers.taskMaster('show', [idA]); + const result = await helpers.taskMaster('show', [idA], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain('Dependencies:'); expect(result.stdout).toContain(idB); @@ -389,74 +407,79 @@ describe('task-master add-dependency', () => { }); describe('Tag context', () => { - it('should add dependencies within a tag', () => { + it('should add dependencies within a tag', async () => { // Create tag - helpers.taskMaster('add-tag', ['feature']); - helpers.taskMaster('use-tag', ['feature']); + await helpers.taskMaster('add-tag', ['feature'], { cwd: testDir }); + await helpers.taskMaster('use-tag', ['feature'], { cwd: testDir }); // Create tasks in feature tag - const dep = helpers.taskMaster('add-task', ['Feature dependency', '-m']); + const dep = await helpers.taskMaster('add-task', ['--title', 'Feature dependency', '--description', 'Dep in feature'], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); - const task = helpers.taskMaster('add-task', ['Feature task', '-m']); + const task = await helpers.taskMaster('add-task', ['--title', 'Feature task', '--description', 'Task in feature'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); // Add dependency with tag context - const result = helpers.taskMaster('add-dependency', [ - taskId, - depId, + const result = await helpers.taskMaster('add-dependency', [ + '--id', taskId, + '--depends-on', depId, '--tag', 'feature' - ]); + ], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain('[feature]'); }); - it('should prevent cross-tag dependencies by default', () => { + it('should prevent cross-tag dependencies by default', async () => { // Create tasks in different tags - const masterTask = helpers.taskMaster('add-task', ['Master task', '-m']); + const masterTask = await helpers.taskMaster('add-task', ['--title', 'Master task', '--description', 'In master tag'], { cwd: testDir }); const masterId = helpers.extractTaskId(masterTask.stdout); - helpers.taskMaster('add-tag', ['feature']); - helpers.taskMaster('use-tag', ['feature']); - const featureTask = helpers.taskMaster('add-task', [ + await helpers.taskMaster('add-tag', ['feature'], { cwd: testDir }); + await helpers.taskMaster('use-tag', ['feature'], { cwd: testDir }); + const featureTask = await helpers.taskMaster('add-task', [ + '--title', 'Feature task', - '-m' - ]); + '--description', + 'In feature tag' + ], { cwd: testDir }); const featureId = helpers.extractTaskId(featureTask.stdout); // Try to add cross-tag dependency - const result = helpers.taskMaster( + const result = await helpers.taskMaster( 'add-dependency', - [featureId, masterId, '--tag', 'feature'], - { allowFailure: true } + ['--id', featureId, '--depends-on', masterId, '--tag', 'feature'], + { cwd: testDir, allowFailure: true } ); // Depending on implementation, this might warn or fail }); }); describe('Error handling', () => { - it('should handle non-existent task IDs', () => { - const task = helpers.taskMaster('add-task', ['Task', '-m']); + it('should handle non-existent task IDs', async () => { + const task = await helpers.taskMaster('add-task', ['--title', 'Task', '--description', 'A task'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); - const result = helpers.taskMaster('add-dependency', [taskId, '999'], { + const result = await helpers.taskMaster('add-dependency', ['--id', taskId, '--depends-on', '999'], { + cwd: testDir, allowFailure: true }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toMatch(/Task.*999.*not found/i); }); - it('should handle invalid task ID format', () => { - const result = helpers.taskMaster('add-dependency', ['invalid-id', '1'], { + it('should handle invalid task ID format', async () => { + const result = await helpers.taskMaster('add-dependency', ['--id', 'invalid-id', '--depends-on', '1'], { + cwd: testDir, allowFailure: true }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toContain('Invalid task ID'); }); - it('should require both task and dependency IDs', () => { - const result = helpers.taskMaster('add-dependency', ['1'], { + it('should require both task and dependency IDs', async () => { + const result = await helpers.taskMaster('add-dependency', ['--id', '1'], { + cwd: testDir, allowFailure: true }); expect(result.exitCode).not.toBe(0); @@ -465,34 +488,34 @@ describe('task-master add-dependency', () => { }); describe('Output options', () => { - it('should support quiet mode', () => { - const dep = helpers.taskMaster('add-task', ['Dep', '-m']); + it('should support quiet mode', async () => { + const dep = await helpers.taskMaster('add-task', ['--title', 'Dep', '--description', 'A dep'], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); - const task = helpers.taskMaster('add-task', ['Task', '-m']); + const task = await helpers.taskMaster('add-task', ['--title', 'Task', '--description', 'A task'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); - const result = helpers.taskMaster('add-dependency', [ - taskId, - depId, + const result = await helpers.taskMaster('add-dependency', [ + '--id', taskId, + '--depends-on', depId, '-q' - ]); + ], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout.split('\n').length).toBeLessThan(3); }); - it('should support JSON output', () => { - const dep = helpers.taskMaster('add-task', ['Dep', '-m']); + it('should support JSON output', async () => { + const dep = await helpers.taskMaster('add-task', ['--title', 'Dep', '--description', 'A dep'], { cwd: testDir }); const depId = helpers.extractTaskId(dep.stdout); - const task = helpers.taskMaster('add-task', ['Task', '-m']); + const task = await helpers.taskMaster('add-task', ['--title', 'Task', '--description', 'A task'], { cwd: testDir }); const taskId = helpers.extractTaskId(task.stdout); - const result = helpers.taskMaster('add-dependency', [ - taskId, - depId, + const result = await helpers.taskMaster('add-dependency', [ + '--id', taskId, + '--depends-on', depId, '--json' - ]); + ], { cwd: testDir }); expect(result).toHaveExitCode(0); const json = JSON.parse(result.stdout); @@ -503,24 +526,24 @@ describe('task-master add-dependency', () => { }); describe('Visualization', () => { - it('should show dependency graph after adding', () => { + it('should show dependency graph after adding', async () => { // Create simple dependency chain - const task1 = helpers.taskMaster('add-task', ['Base task', '-m']); + const task1 = await helpers.taskMaster('add-task', ['--title', 'Base task', '--description', 'Base'], { cwd: testDir }); const id1 = helpers.extractTaskId(task1.stdout); - const task2 = helpers.taskMaster('add-task', ['Middle task', '-m']); + const task2 = await helpers.taskMaster('add-task', ['--title', 'Middle task', '--description', 'Middle'], { cwd: testDir }); const id2 = helpers.extractTaskId(task2.stdout); - const task3 = helpers.taskMaster('add-task', ['Top task', '-m']); + const task3 = await helpers.taskMaster('add-task', ['--title', 'Top task', '--description', 'Top'], { cwd: testDir }); const id3 = helpers.extractTaskId(task3.stdout); // Build chain - helpers.taskMaster('add-dependency', [id2, id1]); - const result = helpers.taskMaster('add-dependency', [id3, id2]); + await helpers.taskMaster('add-dependency', ['--id', id2, '--depends-on', id1], { cwd: testDir }); + const result = await helpers.taskMaster('add-dependency', ['--id', id3, '--depends-on', id2], { cwd: testDir }); expect(result).toHaveExitCode(0); expect(result.stdout).toContain('Dependency chain:'); expect(result.stdout).toMatch(/→|depends on/); }); }); -}); +}); \ No newline at end of file diff --git a/tests/e2e/tests/core.test.js b/tests/e2e/tests/core.test.js deleted file mode 100644 index dc0798a1..00000000 --- a/tests/e2e/tests/core.test.js +++ /dev/null @@ -1,379 +0,0 @@ -/** - * Core task operations test module - * Tests all fundamental task management functionality - */ - -export default async function testCoreOperations(logger, helpers, context) { - const { testDir } = context; - const results = { - status: 'passed', - errors: [] - }; - - try { - logger.info('Starting core task operations tests...'); - - // Test 1: List tasks (may have tasks from PRD parsing) - logger.info('\nTest 1: List tasks'); - const listResult1 = await helpers.taskMaster('list', [], { cwd: testDir }); - if (listResult1.exitCode !== 0) { - throw new Error(`List command failed: ${listResult1.stderr}`); - } - // Check for expected output patterns - either empty or with tasks - const hasValidOutput = - listResult1.stdout.includes('No tasks found') || - listResult1.stdout.includes('Task List') || - listResult1.stdout.includes('Project Dashboard') || - listResult1.stdout.includes('Listing tasks from'); - if (!hasValidOutput) { - throw new Error('Unexpected list output format'); - } - logger.success('✓ List tasks successful'); - - // Test 2: Add manual task - logger.info('\nTest 2: Add manual task'); - const addResult1 = await helpers.taskMaster( - 'add-task', - [ - '--title', - 'Write unit tests', - '--description', - 'Create comprehensive unit tests for the application' - ], - { cwd: testDir } - ); - if (addResult1.exitCode !== 0) { - throw new Error(`Failed to add manual task: ${addResult1.stderr}`); - } - const manualTaskId = helpers.extractTaskId(addResult1.stdout); - if (!manualTaskId) { - throw new Error('Failed to extract task ID from add output'); - } - logger.success(`✓ Added manual task with ID: ${manualTaskId}`); - - // Test 3: Add AI task - logger.info('\nTest 3: Add AI task'); - const addResult2 = await helpers.taskMaster( - 'add-task', - ['--prompt', 'Implement authentication system'], - { cwd: testDir } - ); - if (addResult2.exitCode !== 0) { - throw new Error(`Failed to add AI task: ${addResult2.stderr}`); - } - const aiTaskId = helpers.extractTaskId(addResult2.stdout); - if (!aiTaskId) { - throw new Error('Failed to extract AI task ID from add output'); - } - logger.success(`✓ Added AI task with ID: ${aiTaskId}`); - - // Test 4: Add another task for dependency testing - logger.info('\nTest 4: Add task for dependency testing'); - const addResult3 = await helpers.taskMaster( - 'add-task', - [ - '--title', - 'Create database schema', - '--description', - 'Design and implement the database schema' - ], - { cwd: testDir } - ); - if (addResult3.exitCode !== 0) { - throw new Error(`Failed to add database task: ${addResult3.stderr}`); - } - const dbTaskId = helpers.extractTaskId(addResult3.stdout); - if (!dbTaskId) { - throw new Error('Failed to extract database task ID'); - } - logger.success(`✓ Added database task with ID: ${dbTaskId}`); - - // Test 5: List tasks (should show our newly added tasks) - logger.info('\nTest 5: List all tasks'); - const listResult2 = await helpers.taskMaster('list', [], { cwd: testDir }); - if (listResult2.exitCode !== 0) { - throw new Error(`List command failed: ${listResult2.stderr}`); - } - // Check that we can find our task IDs in the output - const hasTask11 = listResult2.stdout.includes('11'); - const hasTask12 = listResult2.stdout.includes('12'); - const hasTask13 = listResult2.stdout.includes('13'); - - if (!hasTask11 || !hasTask12 || !hasTask13) { - throw new Error('Not all task IDs found in list output'); - } - - // Also check for partial matches (list may truncate titles) - const hasOurTasks = - listResult2.stdout.includes('Write') || - listResult2.stdout.includes('Create'); - if (hasOurTasks) { - logger.success('✓ List tasks shows our added tasks'); - } else { - logger.warning('Task titles may be truncated in list view'); - } - - // Test 6: Get next task - logger.info('\nTest 6: Get next task'); - const nextResult = await helpers.taskMaster('next', [], { cwd: testDir }); - if (nextResult.exitCode !== 0) { - throw new Error(`Next task command failed: ${nextResult.stderr}`); - } - logger.success('✓ Get next task successful'); - - // Test 7: Show task details - logger.info('\nTest 7: Show task details'); - const showResult = await helpers.taskMaster('show', [aiTaskId], { - cwd: testDir - }); - if (showResult.exitCode !== 0) { - throw new Error(`Show task details failed: ${showResult.stderr}`); - } - // Check that the task ID is shown and basic structure is present - if ( - !showResult.stdout.includes(`Task: #${aiTaskId}`) && - !showResult.stdout.includes(`ID: │ ${aiTaskId}`) - ) { - throw new Error('Task ID not found in show output'); - } - if ( - !showResult.stdout.includes('Status:') || - !showResult.stdout.includes('Priority:') - ) { - throw new Error('Task details missing expected fields'); - } - logger.success('✓ Show task details successful'); - - // Test 8: Add dependencies - logger.info('\nTest 8: Add dependencies'); - const addDepResult = await helpers.taskMaster( - 'add-dependency', - ['--id', aiTaskId, '--depends-on', dbTaskId], - { cwd: testDir } - ); - if (addDepResult.exitCode !== 0) { - throw new Error(`Failed to add dependency: ${addDepResult.stderr}`); - } - logger.success('✓ Added dependency successfully'); - - // Test 9: Verify dependency was added - logger.info('\nTest 9: Verify dependency'); - const showResult2 = await helpers.taskMaster('show', [aiTaskId], { - cwd: testDir - }); - if (showResult2.exitCode !== 0) { - throw new Error(`Show task failed: ${showResult2.stderr}`); - } - if ( - !showResult2.stdout.includes('Dependencies:') || - !showResult2.stdout.includes(dbTaskId) - ) { - throw new Error('Dependency not shown in task details'); - } - logger.success('✓ Dependency verified in task details'); - - // Test 10: Test circular dependency (should fail) - logger.info('\nTest 10: Test circular dependency prevention'); - const circularResult = await helpers.taskMaster( - 'add-dependency', - ['--id', dbTaskId, '--depends-on', aiTaskId], - { - cwd: testDir, - allowFailure: true - } - ); - if (circularResult.exitCode === 0) { - throw new Error('Circular dependency was not prevented'); - } - if (!circularResult.stderr.toLowerCase().includes('circular')) { - throw new Error('Expected circular dependency error message'); - } - logger.success('✓ Circular dependency prevented successfully'); - - // Test 11: Test non-existent dependency - logger.info('\nTest 11: Test non-existent dependency'); - const nonExistResult = await helpers.taskMaster( - 'add-dependency', - ['--id', '99999', '--depends-on', '88888'], - { - cwd: testDir, - allowFailure: true - } - ); - if (nonExistResult.exitCode === 0) { - throw new Error('Non-existent dependency was incorrectly allowed'); - } - logger.success('✓ Non-existent dependency handled correctly'); - - // Test 12: Remove dependency - logger.info('\nTest 12: Remove dependency'); - const removeDepResult = await helpers.taskMaster( - 'remove-dependency', - ['--id', aiTaskId, '--depends-on', dbTaskId], - { cwd: testDir } - ); - if (removeDepResult.exitCode !== 0) { - throw new Error(`Failed to remove dependency: ${removeDepResult.stderr}`); - } - logger.success('✓ Removed dependency successfully'); - - // Test 13: Validate dependencies - logger.info('\nTest 13: Validate dependencies'); - const validateResult = await helpers.taskMaster( - 'validate-dependencies', - [], - { cwd: testDir } - ); - if (validateResult.exitCode !== 0) { - throw new Error(`Dependency validation failed: ${validateResult.stderr}`); - } - logger.success('✓ Dependency validation successful'); - - // Test 14: Update task description - logger.info('\nTest 14: Update task description'); - const updateResult = await helpers.taskMaster( - 'update-task', - [manualTaskId, '--description', 'Write comprehensive unit tests'], - { cwd: testDir } - ); - if (updateResult.exitCode !== 0) { - throw new Error(`Failed to update task: ${updateResult.stderr}`); - } - logger.success('✓ Updated task description successfully'); - - // Test 15: Add subtask - logger.info('\nTest 15: Add subtask'); - const subtaskResult = await helpers.taskMaster( - 'add-subtask', - [manualTaskId, 'Write test for login'], - { cwd: testDir } - ); - if (subtaskResult.exitCode !== 0) { - throw new Error(`Failed to add subtask: ${subtaskResult.stderr}`); - } - const subtaskId = helpers.extractTaskId(subtaskResult.stdout) || '1.1'; - logger.success(`✓ Added subtask with ID: ${subtaskId}`); - - // Test 16: Verify subtask relationship - logger.info('\nTest 16: Verify subtask relationship'); - const showResult3 = await helpers.taskMaster('show', [manualTaskId], { - cwd: testDir - }); - if (showResult3.exitCode !== 0) { - throw new Error(`Show task failed: ${showResult3.stderr}`); - } - if (!showResult3.stdout.includes('Subtasks:')) { - throw new Error('Subtasks section not shown in parent task'); - } - logger.success('✓ Subtask relationship verified'); - - // Test 17: Set task status to in_progress - logger.info('\nTest 17: Set task status to in_progress'); - const statusResult1 = await helpers.taskMaster( - 'set-status', - [manualTaskId, 'in_progress'], - { cwd: testDir } - ); - if (statusResult1.exitCode !== 0) { - throw new Error(`Failed to update task status: ${statusResult1.stderr}`); - } - logger.success('✓ Set task status to in_progress'); - - // Test 18: Set task status to completed - logger.info('\nTest 18: Set task status to completed'); - const statusResult2 = await helpers.taskMaster( - 'set-status', - [dbTaskId, 'completed'], - { cwd: testDir } - ); - if (statusResult2.exitCode !== 0) { - throw new Error(`Failed to complete task: ${statusResult2.stderr}`); - } - logger.success('✓ Set task status to completed'); - - // Test 19: List tasks with status filter - logger.info('\nTest 19: List tasks by status'); - const listStatusResult = await helpers.taskMaster( - 'list', - ['--status', 'completed'], - { cwd: testDir } - ); - if (listStatusResult.exitCode !== 0) { - throw new Error(`List by status failed: ${listStatusResult.stderr}`); - } - if (!listStatusResult.stdout.includes('Create database schema')) { - throw new Error('Completed task not shown in filtered list'); - } - logger.success('✓ List tasks by status successful'); - - // Test 20: Remove single task - logger.info('\nTest 20: Remove single task'); - const removeResult1 = await helpers.taskMaster('remove-task', [dbTaskId], { - cwd: testDir - }); - if (removeResult1.exitCode !== 0) { - throw new Error(`Failed to remove task: ${removeResult1.stderr}`); - } - logger.success('✓ Removed single task successfully'); - - // Test 21: Remove multiple tasks - logger.info('\nTest 21: Remove multiple tasks'); - const removeResult2 = await helpers.taskMaster( - 'remove-task', - [manualTaskId, aiTaskId], - { cwd: testDir } - ); - if (removeResult2.exitCode !== 0) { - throw new Error( - `Failed to remove multiple tasks: ${removeResult2.stderr}` - ); - } - logger.success('✓ Removed multiple tasks successfully'); - - // Test 22: Verify tasks were removed - logger.info('\nTest 22: Verify tasks were removed'); - const listResult3 = await helpers.taskMaster('list', [], { cwd: testDir }); - if (listResult3.exitCode !== 0) { - throw new Error(`List command failed: ${listResult3.stderr}`); - } - // Check that our specific task IDs are no longer in the list - const stillHasTask11 = new RegExp(`\\b${manualTaskId}\\b`).test( - listResult3.stdout - ); - const stillHasTask12 = new RegExp(`\\b${aiTaskId}\\b`).test( - listResult3.stdout - ); - const stillHasTask13 = new RegExp(`\\b${dbTaskId}\\b`).test( - listResult3.stdout - ); - - if (stillHasTask11 || stillHasTask12 || stillHasTask13) { - throw new Error('Removed task IDs still appear in list'); - } - logger.success('✓ Verified tasks were removed'); - - // Test 23: Fix dependencies (cleanup) - logger.info('\nTest 23: Fix dependencies'); - const fixDepsResult = await helpers.taskMaster('fix-dependencies', [], { - cwd: testDir - }); - if (fixDepsResult.exitCode !== 0) { - // Non-critical, just log - logger.warning(`Fix dependencies had issues: ${fixDepsResult.stderr}`); - } else { - logger.success('✓ Fix dependencies command executed'); - } - - logger.info('\n✅ All core task operations tests passed!'); - } catch (error) { - results.status = 'failed'; - results.errors.push({ - test: 'core operations', - error: error.message, - stack: error.stack - }); - logger.error(`Core operations test failed: ${error.message}`); - } - - return results; -} diff --git a/tests/e2e/tests/mcp/expand-task-cli.test.js b/tests/e2e/tests/mcp/expand-task-cli.test.js new file mode 100644 index 00000000..2c1fd988 --- /dev/null +++ b/tests/e2e/tests/mcp/expand-task-cli.test.js @@ -0,0 +1,284 @@ +const { exec } = require('child_process'); +const { promisify } = require('util'); +const fs = require('fs').promises; +const path = require('path'); + +const execAsync = promisify(exec); + +// Helper function to run MCP inspector CLI commands +async function runMCPCommand(method, args = {}) { + const serverPath = path.join(__dirname, '../../../../mcp-server/server.js'); + let command = `npx @modelcontextprotocol/inspector --cli node ${serverPath} --method ${method}`; + + // Add tool-specific arguments + if (args.toolName) { + command += ` --tool-name ${args.toolName}`; + } + + // Add tool arguments + if (args.toolArgs) { + for (const [key, value] of Object.entries(args.toolArgs)) { + command += ` --tool-arg ${key}=${value}`; + } + } + + try { + const { stdout, stderr } = await execAsync(command, { + timeout: 60000, // 60 second timeout for AI operations + env: { ...process.env, NODE_ENV: 'test' } + }); + + if (stderr && !stderr.includes('DeprecationWarning')) { + console.error('MCP Command stderr:', stderr); + } + + return { stdout, stderr }; + } catch (error) { + console.error('MCP Command failed:', error); + throw error; + } +} + +describe('MCP Inspector CLI - expand_task Tool Tests', () => { + const testProjectPath = path.join(__dirname, '../../../../test-fixtures/mcp-expand-test-project'); + const tasksDir = path.join(testProjectPath, '.taskmaster/tasks'); + const tasksFile = path.join(tasksDir, 'tasks.json'); + + beforeAll(async () => { + // Create test project directory structure + await fs.mkdir(tasksDir, { recursive: true }); + + // Create sample tasks data + const sampleTasks = { + tasks: [ + { + id: 1, + description: 'Implement user authentication system', + status: 'pending', + tags: ['master'], + subtasks: [] + }, + { + id: 2, + description: 'Create API endpoints', + status: 'pending', + tags: ['master'], + subtasks: [ + { + id: '2.1', + description: 'Setup Express server', + status: 'pending' + } + ] + }, + { + id: 3, + description: 'Design database schema', + status: 'completed', + tags: ['master'] + } + ], + tags: { + master: { + name: 'master', + description: 'Main development branch' + } + }, + activeTag: 'master', + metadata: { + nextId: 4, + version: '1.0.0' + } + }; + + await fs.writeFile(tasksFile, JSON.stringify(sampleTasks, null, 2)); + }); + + afterAll(async () => { + // Clean up test project + await fs.rm(testProjectPath, { recursive: true, force: true }); + }); + + it('should list available tools including expand_task', async () => { + const { stdout } = await runMCPCommand('tools/list'); + const response = JSON.parse(stdout); + + expect(response).toHaveProperty('tools'); + expect(Array.isArray(response.tools)).toBe(true); + + const expandTaskTool = response.tools.find(tool => tool.name === 'expand_task'); + expect(expandTaskTool).toBeDefined(); + expect(expandTaskTool.description).toContain('Expand a task into subtasks'); + }); + + it('should expand a task without existing subtasks', async () => { + // Skip if no API key is set + if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) { + console.log('Skipping test: No AI API key found in environment'); + return; + } + + const { stdout } = await runMCPCommand('tools/call', { + toolName: 'expand_task', + toolArgs: { + id: '1', + projectRoot: testProjectPath, + num: '3', + prompt: 'Focus on security and authentication best practices' + } + }); + + const response = JSON.parse(stdout); + expect(response).toHaveProperty('content'); + expect(Array.isArray(response.content)).toBe(true); + + // Parse the text content to get result + const textContent = response.content.find(c => c.type === 'text'); + expect(textContent).toBeDefined(); + + const result = JSON.parse(textContent.text); + expect(result.task).toBeDefined(); + expect(result.task.id).toBe(1); + expect(result.subtasksAdded).toBeGreaterThan(0); + + // Verify the task was actually updated + const updatedTasks = JSON.parse(await fs.readFile(tasksFile, 'utf8')); + const expandedTask = updatedTasks.tasks.find(t => t.id === 1); + expect(expandedTask.subtasks.length).toBeGreaterThan(0); + }); + + it('should handle expansion with force flag for task with existing subtasks', async () => { + // Skip if no API key is set + if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) { + console.log('Skipping test: No AI API key found in environment'); + return; + } + + const { stdout } = await runMCPCommand('tools/call', { + toolName: 'expand_task', + toolArgs: { + id: '2', + projectRoot: testProjectPath, + force: 'true', + num: '2' + } + }); + + const response = JSON.parse(stdout); + const textContent = response.content.find(c => c.type === 'text'); + const result = JSON.parse(textContent.text); + + expect(result.task).toBeDefined(); + expect(result.task.id).toBe(2); + expect(result.subtasksAdded).toBe(2); + }); + + it('should reject expansion of completed task', async () => { + const { stdout } = await runMCPCommand('tools/call', { + toolName: 'expand_task', + toolArgs: { + id: '3', + projectRoot: testProjectPath + } + }); + + const response = JSON.parse(stdout); + expect(response).toHaveProperty('content'); + + const textContent = response.content.find(c => c.type === 'text'); + expect(textContent.text).toContain('Error'); + expect(textContent.text).toContain('completed'); + }); + + it('should handle invalid task ID', async () => { + const { stdout } = await runMCPCommand('tools/call', { + toolName: 'expand_task', + toolArgs: { + id: '999', + projectRoot: testProjectPath + } + }); + + const response = JSON.parse(stdout); + const textContent = response.content.find(c => c.type === 'text'); + expect(textContent.text).toContain('Error'); + expect(textContent.text).toContain('not found'); + }); + + it('should handle missing required parameters', async () => { + try { + await runMCPCommand('tools/call', { + toolName: 'expand_task', + toolArgs: { + // Missing id and projectRoot + num: '3' + } + }); + fail('Should have thrown an error'); + } catch (error) { + expect(error.message).toContain('validation'); + } + }); + + it('should work with custom tasks file path', async () => { + // Skip if no API key is set + if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) { + console.log('Skipping test: No AI API key found in environment'); + return; + } + + // Create custom tasks file + const customDir = path.join(testProjectPath, 'custom'); + await fs.mkdir(customDir, { recursive: true }); + const customTasksPath = path.join(customDir, 'my-tasks.json'); + await fs.copyFile(tasksFile, customTasksPath); + + const { stdout } = await runMCPCommand('tools/call', { + toolName: 'expand_task', + toolArgs: { + id: '1', + projectRoot: testProjectPath, + file: 'custom/my-tasks.json', + num: '2' + } + }); + + const response = JSON.parse(stdout); + const textContent = response.content.find(c => c.type === 'text'); + const result = JSON.parse(textContent.text); + + expect(result.task).toBeDefined(); + expect(result.subtasksAdded).toBe(2); + + // Verify the custom file was updated + const updatedData = JSON.parse(await fs.readFile(customTasksPath, 'utf8')); + const task = updatedData.tasks.find(t => t.id === 1); + expect(task.subtasks.length).toBe(2); + }); + + it('should handle expansion with research flag', async () => { + // Skip if no API key is set + if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY && !process.env.PERPLEXITY_API_KEY) { + console.log('Skipping test: No AI API key found in environment'); + return; + } + + const { stdout } = await runMCPCommand('tools/call', { + toolName: 'expand_task', + toolArgs: { + id: '1', + projectRoot: testProjectPath, + research: 'true', + num: '2' + } + }); + + const response = JSON.parse(stdout); + const textContent = response.content.find(c => c.type === 'text'); + + // Even if research fails, expansion should still work + const result = JSON.parse(textContent.text); + expect(result.task).toBeDefined(); + expect(result.subtasksAdded).toBeGreaterThanOrEqual(0); + }); +}); \ No newline at end of file diff --git a/tests/e2e/tests/mcp/mcp-jest-test.js b/tests/e2e/tests/mcp/mcp-jest-test.js deleted file mode 100644 index 056c44b1..00000000 --- a/tests/e2e/tests/mcp/mcp-jest-test.js +++ /dev/null @@ -1,146 +0,0 @@ -import { mcpTest } from 'mcp-jest'; -import { join, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import fs from 'fs'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const projectRoot = join(__dirname, '../../../..'); - -// Create test tasks file for testing -const testTasksPath = join(projectRoot, '.taskmaster/test-mcp-tasks.json'); -const testTasks = { - tasks: [ - { - id: 'mcp-test-001', - description: 'MCP Test task 1', - status: 'pending', - priority: 'high', - estimatedMinutes: 30, - actualMinutes: 0, - dependencies: [], - tags: ['test'], - subtasks: [ - { - id: 'mcp-test-001-1', - description: 'MCP Test subtask 1.1', - status: 'pending', - priority: 'medium', - estimatedMinutes: 15, - actualMinutes: 0 - } - ] - }, - { - id: 'mcp-test-002', - description: 'MCP Test task 2', - status: 'done', - priority: 'medium', - estimatedMinutes: 60, - actualMinutes: 60, - dependencies: ['mcp-test-001'], - tags: ['test', 'demo'], - subtasks: [] - } - ] -}; - -// Setup test data -fs.mkdirSync(join(projectRoot, '.taskmaster'), { recursive: true }); -fs.writeFileSync(testTasksPath, JSON.stringify(testTasks, null, 2)); - -// Run MCP Jest tests -async function runTests() { - try { - const results = await mcpTest( - { - command: 'node', - args: [join(projectRoot, 'mcp-server/server.js')], - env: process.env - }, - { - tools: { - initialize_project: { - args: { projectRoot: projectRoot }, - expect: (result) => - result.content[0].text.includes( - 'Project initialized successfully' - ) - }, - get_tasks: [ - { - name: 'get all tasks with subtasks', - args: { - projectRoot: projectRoot, - file: '.taskmaster/test-mcp-tasks.json', - withSubtasks: true - }, - expect: (result) => { - const text = result.content[0].text; - return ( - !result.isError && - text.includes('2 tasks found') && - text.includes('MCP Test task 1') && - text.includes('MCP Test task 2') && - text.includes('MCP Test subtask 1.1') - ); - } - }, - { - name: 'filter by done status', - args: { - projectRoot: projectRoot, - file: '.taskmaster/test-mcp-tasks.json', - status: 'done' - }, - expect: (result) => { - const text = result.content[0].text; - return ( - !result.isError && - text.includes('1 task found') && - text.includes('MCP Test task 2') && - !text.includes('MCP Test task 1') - ); - } - }, - { - name: 'handle non-existent file', - args: { - projectRoot: projectRoot, - file: '.taskmaster/non-existent.json' - }, - expect: (result) => - result.isError && result.content[0].text.includes('Error') - } - ] - } - } - ); - - console.log('\nTest Results:'); - console.log('============='); - console.log(`✅ Passed: ${results.passed}/${results.total}`); - - if (results.failed > 0) { - console.error(`❌ Failed: ${results.failed}`); - console.error('\nDetailed Results:'); - console.log(JSON.stringify(results, null, 2)); - } - - // Cleanup - if (fs.existsSync(testTasksPath)) { - fs.unlinkSync(testTasksPath); - } - - // Exit with appropriate code - process.exit(results.failed > 0 ? 1 : 0); - } catch (error) { - console.error('Test execution failed:', error); - // Cleanup on error - if (fs.existsSync(testTasksPath)) { - fs.unlinkSync(testTasksPath); - } - process.exit(1); - } -} - -runTests(); diff --git a/tests/e2e/tests/mcp/simple-mcp-test.js b/tests/e2e/tests/mcp/simple-mcp-test.js deleted file mode 100644 index 6c93c9af..00000000 --- a/tests/e2e/tests/mcp/simple-mcp-test.js +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env node - -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; -import { join, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import fs from 'fs'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const projectRoot = join(__dirname, '../../../..'); - -// Create test tasks file for testing -const testTasksPath = join(projectRoot, '.taskmaster/test-tasks.json'); -const testTasks = { - tasks: [ - { - id: 'test-001', - description: 'Test task 1', - status: 'pending', - priority: 'high', - estimatedMinutes: 30, - actualMinutes: 0, - dependencies: [], - tags: ['test'], - subtasks: [ - { - id: 'test-001-1', - description: 'Test subtask 1.1', - status: 'pending', - priority: 'medium', - estimatedMinutes: 15, - actualMinutes: 0 - } - ] - }, - { - id: 'test-002', - description: 'Test task 2', - status: 'done', - priority: 'medium', - estimatedMinutes: 60, - actualMinutes: 60, - dependencies: ['test-001'], - tags: ['test', 'demo'], - subtasks: [] - } - ] -}; - -async function runTests() { - console.log('Starting MCP server tests...\n'); - - // Setup test data - fs.mkdirSync(join(projectRoot, '.taskmaster'), { recursive: true }); - fs.writeFileSync(testTasksPath, JSON.stringify(testTasks, null, 2)); - - // Create transport by spawning the server - const transport = new StdioClientTransport({ - command: 'node', - args: ['mcp-server/server.js'], - env: process.env, - cwd: projectRoot - }); - - // Create client - const client = new Client( - { - name: 'test-client', - version: '1.0.0' - }, - { - capabilities: { - sampling: {} - } - } - ); - - let testResults = { - total: 0, - passed: 0, - failed: 0, - tests: [] - }; - - async function runTest(name, testFn) { - testResults.total++; - try { - await testFn(); - testResults.passed++; - testResults.tests.push({ name, status: 'passed' }); - console.log(`✅ ${name}`); - } catch (error) { - testResults.failed++; - testResults.tests.push({ name, status: 'failed', error: error.message }); - console.error(`❌ ${name}`); - console.error(` Error: ${error.message}`); - } - } - - try { - // Connect to server - await client.connect(transport); - console.log('Connected to MCP server\n'); - - // Test 1: List available tools - await runTest('List available tools', async () => { - const tools = await client.listTools(); - if (!tools.tools || tools.tools.length === 0) { - throw new Error('No tools found'); - } - const toolNames = tools.tools.map((t) => t.name); - if (!toolNames.includes('get_tasks')) { - throw new Error('get_tasks tool not found'); - } - console.log(` Found ${tools.tools.length} tools`); - }); - - // Test 2: Initialize project - await runTest('Initialize project', async () => { - const result = await client.callTool({ - name: 'initialize_project', - arguments: { - projectRoot: projectRoot - } - }); - if ( - !result.content[0].text.includes('Project initialized successfully') - ) { - throw new Error('Project initialization failed'); - } - }); - - // Test 3: Get all tasks - await runTest('Get all tasks with subtasks', async () => { - const result = await client.callTool({ - name: 'get_tasks', - arguments: { - projectRoot: projectRoot, - file: '.taskmaster/test-tasks.json', - withSubtasks: true - } - }); - - if (result.isError) { - throw new Error(`Tool returned error: ${result.content[0].text}`); - } - - const text = result.content[0].text; - const data = JSON.parse(text); - - if (!data.data || !data.data.tasks) { - throw new Error('Invalid response format'); - } - - if (data.data.tasks.length !== 2) { - throw new Error(`Expected 2 tasks, got ${data.data.tasks.length}`); - } - - const taskDescriptions = data.data.tasks.map((t) => t.description); - if ( - !taskDescriptions.includes('Test task 1') || - !taskDescriptions.includes('Test task 2') - ) { - throw new Error('Expected tasks not found'); - } - - // Check for subtask - const task1 = data.data.tasks.find((t) => t.id === 'test-001'); - if (!task1.subtasks || task1.subtasks.length === 0) { - throw new Error('Subtasks not found'); - } - if (task1.subtasks[0].description !== 'Test subtask 1.1') { - throw new Error('Expected subtask not found'); - } - }); - - // Test 4: Filter by status - await runTest('Filter tasks by done status', async () => { - const result = await client.callTool({ - name: 'get_tasks', - arguments: { - projectRoot: projectRoot, - file: '.taskmaster/test-tasks.json', - status: 'done' - } - }); - - if (result.isError) { - throw new Error(`Tool returned error: ${result.content[0].text}`); - } - - const text = result.content[0].text; - const data = JSON.parse(text); - - if (!data.data || !data.data.tasks) { - throw new Error('Invalid response format'); - } - - if (data.data.tasks.length !== 1) { - throw new Error( - `Expected 1 task with done status, got ${data.data.tasks.length}` - ); - } - - const task = data.data.tasks[0]; - if (task.description !== 'Test task 2') { - throw new Error(`Expected 'Test task 2', got '${task.description}'`); - } - if (task.status !== 'done') { - throw new Error(`Expected status 'done', got '${task.status}'`); - } - }); - - // Test 5: Handle non-existent file - await runTest('Handle non-existent file gracefully', async () => { - const result = await client.callTool({ - name: 'get_tasks', - arguments: { - projectRoot: projectRoot, - file: '.taskmaster/non-existent.json' - } - }); - - if (!result.isError) { - throw new Error('Expected error for non-existent file'); - } - if (!result.content[0].text.includes('Error')) { - throw new Error('Expected error message'); - } - }); - } catch (error) { - console.error('\nConnection error:', error.message); - testResults.failed = testResults.total; - } finally { - // Clean up - await client.close(); - if (fs.existsSync(testTasksPath)) { - fs.unlinkSync(testTasksPath); - } - - // Print summary - console.log('\n' + '='.repeat(50)); - console.log('Test Summary:'); - console.log(`Total: ${testResults.total}`); - console.log(`Passed: ${testResults.passed}`); - console.log(`Failed: ${testResults.failed}`); - console.log('='.repeat(50)); - - // Exit with appropriate code - process.exit(testResults.failed > 0 ? 1 : 0); - } -} - -runTests().catch(console.error); diff --git a/tests/e2e/tests/providers.test.js b/tests/e2e/tests/providers.test.js index 5c6c0a21..31abe3d2 100644 --- a/tests/e2e/tests/providers.test.js +++ b/tests/e2e/tests/providers.test.js @@ -1,186 +1,325 @@ -/** - * Multi-provider functionality test module - * Tests add-task operation across all configured providers - */ +const { exec } = require('child_process'); +const { promisify } = require('util'); +const fs = require('fs').promises; +const path = require('path'); +const os = require('os'); -export default async function testProviders(logger, helpers, context) { - const { testDir, config } = context; - const results = { - status: 'passed', - errors: [], - providerComparison: {}, - summary: { - totalProviders: 0, - successfulProviders: 0, - failedProviders: 0, - averageExecutionTime: 0, - successRate: '0%' - } - }; +const execAsync = promisify(exec); - try { - logger.info('Starting multi-provider tests...'); - - const providers = config.providers; - const standardPrompt = config.prompts.addTask; - - results.summary.totalProviders = providers.length; - let totalExecutionTime = 0; - - // Process providers in batches to avoid rate limits - const batchSize = 3; - for (let i = 0; i < providers.length; i += batchSize) { - const batch = providers.slice(i, i + batchSize); - - const batchPromises = batch.map(async (provider) => { - const providerResult = { - status: 'failed', - taskId: null, - executionTime: 0, - subtaskCount: 0, - features: { - hasTitle: false, - hasDescription: false, - hasSubtasks: false, - hasDependencies: false - }, - error: null, - taskDetails: null - }; - - const startTime = Date.now(); - - try { - logger.info( - `\nTesting provider: ${provider.name} with model: ${provider.model}` - ); - - // Step 1: Set the main model for this provider - logger.info(`Setting model to ${provider.model}...`); - const setModelResult = await helpers.taskMaster( - 'models', - ['--set-main', provider.model], - { cwd: testDir } - ); - if (setModelResult.exitCode !== 0) { - throw new Error( - `Failed to set model for ${provider.name}: ${setModelResult.stderr}` - ); - } - - // Step 2: Execute add-task with standard prompt - logger.info(`Adding task with ${provider.name}...`); - const addTaskArgs = ['--prompt', standardPrompt]; - if (provider.flags && provider.flags.length > 0) { - addTaskArgs.push(...provider.flags); - } - - const addTaskResult = await helpers.taskMaster( - 'add-task', - addTaskArgs, - { - cwd: testDir, - timeout: 120000 // 2 minutes timeout for AI tasks - } - ); - - if (addTaskResult.exitCode !== 0) { - throw new Error(`Add-task failed: ${addTaskResult.stderr}`); - } - - // Step 3: Extract task ID from output - const taskId = helpers.extractTaskId(addTaskResult.stdout); - if (!taskId) { - throw new Error(`Failed to extract task ID from output`); - } - providerResult.taskId = taskId; - logger.success(`✓ Created task ${taskId} with ${provider.name}`); - - // Step 4: Get task details - const showResult = await helpers.taskMaster('show', [taskId], { - cwd: testDir - }); - if (showResult.exitCode === 0) { - providerResult.taskDetails = showResult.stdout; - - // Analyze task features - providerResult.features.hasTitle = - showResult.stdout.includes('Title:') || - showResult.stdout.includes('Task:'); - providerResult.features.hasDescription = - showResult.stdout.includes('Description:'); - providerResult.features.hasSubtasks = - showResult.stdout.includes('Subtasks:'); - providerResult.features.hasDependencies = - showResult.stdout.includes('Dependencies:'); - - // Count subtasks - const subtaskMatches = showResult.stdout.match(/\d+\.\d+/g); - providerResult.subtaskCount = subtaskMatches - ? subtaskMatches.length - : 0; - } - - providerResult.status = 'success'; - results.summary.successfulProviders++; - } catch (error) { - providerResult.status = 'failed'; - providerResult.error = error.message; - results.summary.failedProviders++; - logger.error(`${provider.name} test failed: ${error.message}`); - } - - providerResult.executionTime = Date.now() - startTime; - totalExecutionTime += providerResult.executionTime; - - results.providerComparison[provider.name] = providerResult; - }); - - // Wait for batch to complete - await Promise.all(batchPromises); - - // Small delay between batches to avoid rate limits - if (i + batchSize < providers.length) { - logger.info('Waiting 2 seconds before next batch...'); - await helpers.wait(2000); - } - } - - // Calculate summary statistics - results.summary.averageExecutionTime = Math.round( - totalExecutionTime / providers.length - ); - results.summary.successRate = `${Math.round((results.summary.successfulProviders / results.summary.totalProviders) * 100)}%`; - - // Log summary - logger.info('\n=== Provider Test Summary ==='); - logger.info(`Total providers tested: ${results.summary.totalProviders}`); - logger.info(`Successful: ${results.summary.successfulProviders}`); - logger.info(`Failed: ${results.summary.failedProviders}`); - logger.info(`Success rate: ${results.summary.successRate}`); - logger.info( - `Average execution time: ${results.summary.averageExecutionTime}ms` - ); - - // Determine overall status - if (results.summary.failedProviders === 0) { - logger.success('✅ All provider tests passed!'); - } else if (results.summary.successfulProviders > 0) { - results.status = 'partial'; - logger.warning(`⚠️ ${results.summary.failedProviders} provider(s) failed`); - } else { - results.status = 'failed'; - logger.error('❌ All provider tests failed'); - } - } catch (error) { - results.status = 'failed'; - results.errors.push({ - test: 'provider tests', - error: error.message, - stack: error.stack - }); - logger.error(`Provider tests failed: ${error.message}`); - } - - return results; +// Helper function to run task-master commands +async function runTaskMaster(args, options = {}) { + const taskMasterPath = path.join(__dirname, '../../../scripts/task-master.js'); + const command = `node ${taskMasterPath} ${args.join(' ')}`; + + try { + const { stdout, stderr } = await execAsync(command, { + cwd: options.cwd || process.cwd(), + timeout: options.timeout || 30000, + env: { ...process.env, NODE_ENV: 'test' } + }); + + return { + exitCode: 0, + stdout: stdout.trim(), + stderr: stderr.trim() + }; + } catch (error) { + return { + exitCode: error.code || 1, + stdout: (error.stdout || '').trim(), + stderr: (error.stderr || error.message || '').trim() + }; + } } + +// Helper to extract task ID from output +function extractTaskId(output) { + const idMatch = output.match(/Task #?(\d+(?:\.\d+)?)/i); + return idMatch ? idMatch[1] : null; +} + +// Helper function to wait +function wait(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// Test configuration +const testConfig = { + providers: [ + { name: 'OpenAI GPT-4', model: 'openai:gpt-4', flags: [] }, + { name: 'OpenAI GPT-3.5', model: 'openai:gpt-3.5-turbo', flags: [] }, + { name: 'Anthropic Claude 3 Opus', model: 'anthropic:claude-3-opus-20240229', flags: [] }, + { name: 'Anthropic Claude 3 Sonnet', model: 'anthropic:claude-3-sonnet-20240229', flags: [] }, + { name: 'Anthropic Claude 3 Haiku', model: 'anthropic:claude-3-haiku-20240307', flags: [] }, + { name: 'Google Gemini Pro', model: 'google:gemini-pro', flags: [] }, + { name: 'Groq Llama 3 70B', model: 'groq:llama3-70b-8192', flags: [] }, + { name: 'Groq Mixtral', model: 'groq:mixtral-8x7b-32768', flags: [] } + ], + prompts: { + addTask: 'Create a comprehensive plan to build a task management CLI application with file-based storage and AI integration' + } +}; + +describe('Multi-Provider Functionality Tests', () => { + let testDir; + + beforeAll(async () => { + // Create temporary test directory + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'task-master-provider-test-')); + + // Initialize task-master in test directory + const initResult = await runTaskMaster(['init'], { cwd: testDir }); + if (initResult.exitCode !== 0) { + throw new Error(`Failed to initialize task-master: ${initResult.stderr}`); + } + }); + + afterAll(async () => { + // Clean up test directory + if (testDir) { + await fs.rm(testDir, { recursive: true, force: true }); + } + }); + + // Check if any AI API keys are available + const hasAIKeys = !!( + process.env.OPENAI_API_KEY || + process.env.ANTHROPIC_API_KEY || + process.env.GOOGLE_API_KEY || + process.env.GROQ_API_KEY + ); + + const testCondition = hasAIKeys ? it : it.skip; + + testCondition('should test add-task across multiple AI providers', async () => { + const results = { + providerComparison: {}, + summary: { + totalProviders: 0, + successfulProviders: 0, + failedProviders: 0, + averageExecutionTime: 0, + successRate: '0%' + } + }; + + // Filter providers based on available API keys + const availableProviders = testConfig.providers.filter(provider => { + if (provider.model.startsWith('openai:') && !process.env.OPENAI_API_KEY) return false; + if (provider.model.startsWith('anthropic:') && !process.env.ANTHROPIC_API_KEY) return false; + if (provider.model.startsWith('google:') && !process.env.GOOGLE_API_KEY) return false; + if (provider.model.startsWith('groq:') && !process.env.GROQ_API_KEY) return false; + return true; + }); + + results.summary.totalProviders = availableProviders.length; + let totalExecutionTime = 0; + + // Process providers in batches to avoid rate limits + const batchSize = 3; + for (let i = 0; i < availableProviders.length; i += batchSize) { + const batch = availableProviders.slice(i, i + batchSize); + + const batchPromises = batch.map(async (provider) => { + const providerResult = { + status: 'failed', + taskId: null, + executionTime: 0, + subtaskCount: 0, + features: { + hasTitle: false, + hasDescription: false, + hasSubtasks: false, + hasDependencies: false + }, + error: null, + taskDetails: null + }; + + const startTime = Date.now(); + + try { + console.log(`\nTesting provider: ${provider.name} with model: ${provider.model}`); + + // Step 1: Set the main model for this provider + console.log(`Setting model to ${provider.model}...`); + const setModelResult = await runTaskMaster( + ['models', '--set-main', provider.model], + { cwd: testDir } + ); + expect(setModelResult.exitCode).toBe(0); + + // Step 2: Execute add-task with standard prompt + console.log(`Adding task with ${provider.name}...`); + const addTaskArgs = ['add-task', '--prompt', testConfig.prompts.addTask]; + if (provider.flags && provider.flags.length > 0) { + addTaskArgs.push(...provider.flags); + } + + const addTaskResult = await runTaskMaster(addTaskArgs, { + cwd: testDir, + timeout: 120000 // 2 minutes timeout for AI tasks + }); + + expect(addTaskResult.exitCode).toBe(0); + + // Step 3: Extract task ID from output + const taskId = extractTaskId(addTaskResult.stdout); + expect(taskId).toBeTruthy(); + providerResult.taskId = taskId; + console.log(`✓ Created task ${taskId} with ${provider.name}`); + + // Step 4: Get task details + const showResult = await runTaskMaster(['show', taskId], { cwd: testDir }); + expect(showResult.exitCode).toBe(0); + + providerResult.taskDetails = showResult.stdout; + + // Analyze task features + providerResult.features.hasTitle = + showResult.stdout.includes('Title:') || + showResult.stdout.includes('Task:'); + providerResult.features.hasDescription = + showResult.stdout.includes('Description:'); + providerResult.features.hasSubtasks = + showResult.stdout.includes('Subtasks:'); + providerResult.features.hasDependencies = + showResult.stdout.includes('Dependencies:'); + + // Count subtasks + const subtaskMatches = showResult.stdout.match(/\d+\.\d+/g); + providerResult.subtaskCount = subtaskMatches ? subtaskMatches.length : 0; + + providerResult.status = 'success'; + results.summary.successfulProviders++; + } catch (error) { + providerResult.status = 'failed'; + providerResult.error = error.message; + results.summary.failedProviders++; + console.error(`${provider.name} test failed: ${error.message}`); + } + + providerResult.executionTime = Date.now() - startTime; + totalExecutionTime += providerResult.executionTime; + + results.providerComparison[provider.name] = providerResult; + }); + + // Wait for batch to complete + await Promise.all(batchPromises); + + // Small delay between batches to avoid rate limits + if (i + batchSize < availableProviders.length) { + console.log('Waiting 2 seconds before next batch...'); + await wait(2000); + } + } + + // Calculate summary statistics + results.summary.averageExecutionTime = Math.round( + totalExecutionTime / availableProviders.length + ); + results.summary.successRate = `${Math.round( + (results.summary.successfulProviders / results.summary.totalProviders) * 100 + )}%`; + + // Log summary + console.log('\n=== Provider Test Summary ==='); + console.log(`Total providers tested: ${results.summary.totalProviders}`); + console.log(`Successful: ${results.summary.successfulProviders}`); + console.log(`Failed: ${results.summary.failedProviders}`); + console.log(`Success rate: ${results.summary.successRate}`); + console.log(`Average execution time: ${results.summary.averageExecutionTime}ms`); + + // Log provider comparison details + console.log('\n=== Provider Feature Comparison ==='); + Object.entries(results.providerComparison).forEach(([providerName, result]) => { + console.log(`\n${providerName}:`); + console.log(` Status: ${result.status}`); + console.log(` Task ID: ${result.taskId || 'N/A'}`); + console.log(` Execution Time: ${result.executionTime}ms`); + console.log(` Subtask Count: ${result.subtaskCount}`); + console.log(` Features:`); + console.log(` - Has Title: ${result.features.hasTitle}`); + console.log(` - Has Description: ${result.features.hasDescription}`); + console.log(` - Has Subtasks: ${result.features.hasSubtasks}`); + console.log(` - Has Dependencies: ${result.features.hasDependencies}`); + if (result.error) { + console.log(` Error: ${result.error}`); + } + }); + + // Assertions + expect(results.summary.successfulProviders).toBeGreaterThan(0); + expect(results.summary.successRate).not.toBe('0%'); + }, 300000); // 5 minute timeout for entire test + + testCondition('should maintain task quality across different providers', async () => { + const standardPrompt = 'Create a simple todo list feature with add, remove, and list functionality'; + const providerResults = []; + + // Test a subset of providers to check quality consistency + const testProviders = [ + { name: 'OpenAI GPT-4', model: 'openai:gpt-4' }, + { name: 'Anthropic Claude 3 Sonnet', model: 'anthropic:claude-3-sonnet-20240229' } + ].filter(provider => { + if (provider.model.startsWith('openai:') && !process.env.OPENAI_API_KEY) return false; + if (provider.model.startsWith('anthropic:') && !process.env.ANTHROPIC_API_KEY) return false; + return true; + }); + + for (const provider of testProviders) { + console.log(`\nTesting quality with ${provider.name}...`); + + // Set model + const setModelResult = await runTaskMaster( + ['models', '--set-main', provider.model], + { cwd: testDir } + ); + expect(setModelResult.exitCode).toBe(0); + + // Add task + const addTaskResult = await runTaskMaster( + ['add-task', '--prompt', standardPrompt], + { cwd: testDir, timeout: 60000 } + ); + expect(addTaskResult.exitCode).toBe(0); + + const taskId = extractTaskId(addTaskResult.stdout); + expect(taskId).toBeTruthy(); + + // Get task details + const showResult = await runTaskMaster(['show', taskId], { cwd: testDir }); + expect(showResult.exitCode).toBe(0); + + // Analyze quality metrics + const subtaskCount = (showResult.stdout.match(/\d+\.\d+/g) || []).length; + const hasDescription = showResult.stdout.includes('Description:'); + const wordCount = showResult.stdout.split(/\s+/).length; + + providerResults.push({ + provider: provider.name, + taskId, + subtaskCount, + hasDescription, + wordCount + }); + } + + // Compare quality metrics + console.log('\n=== Quality Comparison ==='); + providerResults.forEach(result => { + console.log(`\n${result.provider}:`); + console.log(` Subtasks: ${result.subtaskCount}`); + console.log(` Has Description: ${result.hasDescription}`); + console.log(` Word Count: ${result.wordCount}`); + }); + + // Basic quality assertions + providerResults.forEach(result => { + expect(result.subtaskCount).toBeGreaterThan(0); + expect(result.hasDescription).toBe(true); + expect(result.wordCount).toBeGreaterThan(50); // Reasonable task detail + }); + }, 180000); // 3 minute timeout +}); \ No newline at end of file diff --git a/tests/e2e/tests/setup.test.js b/tests/e2e/tests/setup.test.js deleted file mode 100644 index 1c25d5d0..00000000 --- a/tests/e2e/tests/setup.test.js +++ /dev/null @@ -1,295 +0,0 @@ -import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'fs'; -import { join } from 'path'; -import { testConfig } from '../config/test-config.js'; - -/** - * Setup test module that handles initialization, PRD parsing, and complexity analysis - * @param {Object} logger - TestLogger instance - * @param {Object} helpers - TestHelpers instance - * @returns {Promise} Test results object with status and directory path - */ -export async function runSetupTest(logger, helpers) { - const testResults = { - status: 'pending', - testDir: null, - steps: { - createDirectory: false, - linkGlobally: false, - copyEnv: false, - initialize: false, - parsePrd: false, - analyzeComplexity: false, - generateReport: false - }, - errors: [], - prdPath: null, - complexityReport: null - }; - - try { - // Step 1: Create test directory with timestamp - logger.step('Creating test directory'); - const timestamp = new Date() - .toISOString() - .replace(/[:.]/g, '') - .replace('T', '_') - .slice(0, -1); - const testDir = join(testConfig.paths.baseTestDir, `run_${timestamp}`); - - if (!existsSync(testDir)) { - mkdirSync(testDir, { recursive: true }); - } - - testResults.testDir = testDir; - testResults.steps.createDirectory = true; - logger.success(`Test directory created: ${testDir}`); - - // Step 2: Link task-master globally - logger.step('Linking task-master globally'); - const linkResult = await helpers.executeCommand('npm', ['link'], { - cwd: testConfig.paths.projectRoot, - timeout: 60000 - }); - - if (linkResult.exitCode === 0) { - testResults.steps.linkGlobally = true; - logger.success('Task-master linked globally'); - } else { - throw new Error(`Failed to link task-master: ${linkResult.stderr}`); - } - - // Step 3: Copy .env file - logger.step('Copying .env file to test directory'); - const envSourcePath = testConfig.paths.mainEnvFile; - const envDestPath = join(testDir, '.env'); - - if (helpers.fileExists(envSourcePath)) { - if (helpers.copyFile(envSourcePath, envDestPath)) { - testResults.steps.copyEnv = true; - logger.success('.env file copied successfully'); - } else { - throw new Error('Failed to copy .env file'); - } - } else { - logger.warning('.env file not found at source, proceeding without it'); - } - - // Step 4: Initialize project with task-master init - logger.step('Initializing project with task-master'); - const initResult = await helpers.taskMaster( - 'init', - [ - '-y', - '--name="E2E Test ' + testDir.split('/').pop() + '"', - '--description="Automated E2E test run"' - ], - { - cwd: testDir, - timeout: 120000 - } - ); - - if (initResult.exitCode === 0) { - testResults.steps.initialize = true; - logger.success('Project initialized successfully'); - - // Save init debug log if available - const initDebugPath = join(testDir, 'init-debug.log'); - if (existsSync(initDebugPath)) { - logger.info('Init debug log saved'); - } - } else { - throw new Error(`Initialization failed: ${initResult.stderr}`); - } - - // Step 5: Parse PRD from sample file - logger.step('Parsing PRD from sample file'); - - // First, copy the sample PRD to the test directory - const prdSourcePath = testConfig.paths.samplePrdSource; - const prdDestPath = join(testDir, 'prd.txt'); - testResults.prdPath = prdDestPath; - - if (!helpers.fileExists(prdSourcePath)) { - // If sample PRD doesn't exist in fixtures, use the example PRD - const examplePrdPath = join( - testConfig.paths.projectRoot, - 'assets/example_prd.txt' - ); - if (helpers.fileExists(examplePrdPath)) { - helpers.copyFile(examplePrdPath, prdDestPath); - logger.info('Using example PRD file'); - } else { - // Create a minimal PRD for testing - const minimalPrd = ` -# Overview -A simple task management system for developers. - -# Core Features -- Task creation and management -- Task dependencies -- Status tracking -- Task prioritization - -# Technical Architecture -- Node.js backend -- REST API -- JSON data storage -- CLI interface - -# Development Roadmap -Phase 1: Core functionality -- Initialize project structure -- Implement task CRUD operations -- Add dependency management - -Phase 2: Enhanced features -- Add task prioritization -- Implement search functionality -- Add export capabilities - -# Logical Dependency Chain -1. Project setup and initialization -2. Core data models -3. Basic CRUD operations -4. Dependency system -5. CLI interface -6. Advanced features -`; - - writeFileSync(prdDestPath, minimalPrd); - logger.info('Created minimal PRD for testing'); - } - } else { - helpers.copyFile(prdSourcePath, prdDestPath); - } - - // Parse the PRD - const parsePrdResult = await helpers.taskMaster('parse-prd', ['prd.txt'], { - cwd: testDir, - timeout: 180000 - }); - - if (parsePrdResult.exitCode === 0) { - testResults.steps.parsePrd = true; - logger.success('PRD parsed successfully'); - - // Extract task count from output - const taskCountMatch = - parsePrdResult.stdout.match(/(\d+) tasks? created/i); - if (taskCountMatch) { - logger.info(`Created ${taskCountMatch[1]} tasks from PRD`); - } - } else { - throw new Error(`PRD parsing failed: ${parsePrdResult.stderr}`); - } - - // Step 6: Run complexity analysis - logger.step('Running complexity analysis on parsed tasks'); - // Ensure reports directory exists - const reportsDir = join(testDir, '.taskmaster/reports'); - if (!existsSync(reportsDir)) { - mkdirSync(reportsDir, { recursive: true }); - } - const analyzeResult = await helpers.taskMaster( - 'analyze-complexity', - [ - '--research', - '--output', - '.taskmaster/reports/task-complexity-report.json' - ], - { - cwd: testDir, - timeout: 240000 - } - ); - - if (analyzeResult.exitCode === 0) { - testResults.steps.analyzeComplexity = true; - logger.success('Complexity analysis completed'); - - // Extract complexity information from output - const complexityMatch = analyzeResult.stdout.match( - /Total Complexity Score: ([\d.]+)/ - ); - if (complexityMatch) { - logger.info(`Total complexity score: ${complexityMatch[1]}`); - } - } else { - throw new Error(`Complexity analysis failed: ${analyzeResult.stderr}`); - } - - // Step 7: Generate complexity report - logger.step('Generating complexity report'); - const reportResult = await helpers.taskMaster('complexity-report', [], { - cwd: testDir, - timeout: 60000 - }); - - if (reportResult.exitCode === 0) { - testResults.steps.generateReport = true; - logger.success('Complexity report generated'); - - // Check if complexity report file was created (not needed since complexity-report reads from the standard location) - const reportPath = join( - testDir, - '.taskmaster/reports/task-complexity-report.json' - ); - if (helpers.fileExists(reportPath)) { - testResults.complexityReport = helpers.readJson(reportPath); - logger.info('Complexity report saved to task-complexity-report.json'); - - // Log summary if available - if ( - testResults.complexityReport && - testResults.complexityReport.summary - ) { - const summary = testResults.complexityReport.summary; - logger.info(`Tasks analyzed: ${summary.totalTasks || 0}`); - logger.info(`Average complexity: ${summary.averageComplexity || 0}`); - } - } - } else { - logger.warning( - `Complexity report generation had issues: ${reportResult.stderr}` - ); - // Don't fail the test for report generation issues - testResults.steps.generateReport = true; - } - - // Verify tasks.json was created - const tasksJsonPath = join(testDir, '.taskmaster/tasks/tasks.json'); - if (helpers.fileExists(tasksJsonPath)) { - const taskCount = helpers.getTaskCount(tasksJsonPath); - logger.info(`Verified tasks.json exists with ${taskCount} tasks`); - } else { - throw new Error('tasks.json was not created'); - } - - // All steps completed successfully - testResults.status = 'success'; - logger.success('Setup test completed successfully'); - } catch (error) { - testResults.status = 'failed'; - testResults.errors.push(error.message); - logger.error(`Setup test failed: ${error.message}`); - - // Log which steps completed - logger.info('Completed steps:'); - Object.entries(testResults.steps).forEach(([step, completed]) => { - if (completed) { - logger.info(` ✓ ${step}`); - } else { - logger.info(` ✗ ${step}`); - } - }); - } - - // Flush logs before returning - logger.flush(); - - return testResults; -} - -// Export default for direct execution -export default runSetupTest;