├── .eslintrc.js ├── .github └── workflows │ ├── node.js.yml │ └── npm-publish.yml ├── .gitignore ├── .npmignore ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── sample.PNG ├── src ├── cli │ ├── generation.js │ ├── index.js │ ├── instrumentation.js │ └── utils.js ├── generator │ ├── components │ │ ├── AssignmentOperation │ │ │ ├── AssignmentOperation.js │ │ │ └── AssignmentOperation.test.js │ │ ├── DependencyInjectionStubBlock │ │ │ ├── DependencyInjectionStubBlock.js │ │ │ └── DependencyInjectionStubBlock.test.js │ │ ├── DescribeFunctionBlock │ │ │ ├── DescribeFunctionBlock.js │ │ │ └── DescribeFunctionBlock.test.js │ │ ├── ExpectStatement │ │ │ ├── ExpectStatement.js │ │ │ └── ExpectStatement.test.js │ │ ├── FunctionStubBlock │ │ │ ├── FunctionStubBlock.js │ │ │ └── FunctionStubBlock.test.js │ │ ├── ImportStatements │ │ │ ├── ImportStatements.js │ │ │ └── ImportStatements.test.js │ │ ├── InputAssignment │ │ │ ├── InputAssignment.js │ │ │ └── InputAssignment.test.js │ │ ├── InvokeStatement │ │ │ ├── InvokeStatement.js │ │ │ └── InvokeStatement.test.js │ │ ├── ItBlock │ │ │ ├── ItBlock.js │ │ │ └── ItBlock.test.js │ │ ├── JestMockImplementationStatement │ │ │ ├── JestMockImplementationStatement.js │ │ │ └── JestMockImplementationStatement.test.js │ │ ├── MockFunctionStubBlock │ │ │ ├── MockFunctionStubBlock.js │ │ │ └── MockFunctionStubBlock.test.js │ │ ├── MockImportBlock │ │ │ ├── MockImportBlock.js │ │ │ └── MockImportBlock.test.js │ │ ├── PackagedExternalFile │ │ │ ├── PackagedExternalFile.js │ │ │ └── PackagedExternalFile.test.js │ │ └── TestFileBlock │ │ │ ├── TestFileBlock.js │ │ │ └── TestFileBlock.test.js │ ├── external-data-aggregator.js │ ├── external-data-aggregator.test.js │ ├── index.js │ ├── utils.js │ └── utils.test.js ├── index.js ├── index.ts ├── plugin │ ├── blacklist-generator.js │ ├── blacklist-generator.test.js │ ├── capture-logic.js │ ├── capture-logic.test.js │ ├── dependency-injection.js │ ├── file-meta.js │ ├── index.js │ ├── meta.js │ ├── mocks-logic.js │ ├── object-capture-logic.js │ ├── object-capture-logic.test.js │ ├── used-plugins.js │ └── wrapper-logic.js ├── recorder │ ├── file-meta.js │ ├── index.js │ ├── injection │ │ ├── di-recorder.js │ │ ├── di-recorder.test.js │ │ ├── index.js │ │ ├── injector.js │ │ ├── injector.test.js │ │ ├── param-crawler.js │ │ └── param-crawler.test.js │ ├── manager.js │ ├── manager.test.js │ ├── mock │ │ ├── capture.js │ │ ├── capture.test.js │ │ ├── index.js │ │ └── wrapper.js │ ├── robustness-tests │ │ ├── dynamic-type-inference.test.js │ │ ├── hash-helper.test.js │ │ ├── manager.test.js │ │ ├── misc.test.js │ │ └── object-traverser.test.js │ ├── user-functions │ │ ├── capture-logic.js │ │ ├── capture-logic.test.js │ │ ├── index.js │ │ ├── pre.js │ │ ├── pre.test.js │ │ ├── wrapper.js │ │ └── wrapper.test.js │ └── utils │ │ ├── cls-recordings.js │ │ ├── cls-recordings.test.js │ │ ├── dynamic-type-inference.js │ │ ├── dynamic-type-inference.test.js │ │ ├── hash-helper.js │ │ ├── hash-helper.test.js │ │ ├── manager-helpers.js │ │ ├── manager-helpers.test.js │ │ ├── misc.js │ │ ├── misc.test.js │ │ ├── object-traverser.js │ │ └── object-traverser.test.js └── util │ ├── cls-provider │ ├── async-hook-provider.js │ ├── async-hook-provider.test.js │ └── index.js │ ├── constants.js │ ├── misc.js │ ├── misc.test.js │ ├── walker.js │ └── walker.test.js └── test_integration ├── flows ├── 01_module_exports │ ├── 01_module_exports.js │ ├── 01_module_exports_activity.json │ ├── 01_module_exports_generated.test.js │ └── 01_module_exports_instrumented.js ├── 02_async_functions │ ├── 02_async_functions.js │ ├── 02_async_functions_activity.json │ ├── 02_async_functions_generated.test.js │ └── 02_async_functions_instrumented.js ├── 03_ecma_export │ ├── 03_ecma_export.js │ ├── 03_ecma_export_activity.json │ ├── 03_ecma_export_generated.test.js │ └── 03_ecma_export_instrumented.js ├── 04_unserializeable │ ├── 04_unserializeable.js │ ├── 04_unserializeable_activity.json │ ├── 04_unserializeable_generated.test.js │ └── 04_unserializeable_instrumented.js ├── 05_dependency_injection │ ├── 05_dependency_injection.js │ ├── 05_dependency_injection_activity.json │ ├── 05_dependency_injection_generated.test.js │ └── 05_dependency_injection_instrumented.js ├── 06_mocks │ ├── 06_mocks.js │ ├── 06_mocks │ │ ├── expContinuationFn_0_fsReadFileSync0.mock.js │ │ ├── expContinuationFn_0_result.mock.js │ │ ├── getTodo_0_auxilary1Foo40.mock.js │ │ ├── getTodo_0_fsReadFileSync0.mock.js │ │ ├── getTodo_0_fsReadFileSync1.mock.js │ │ └── getTodo_0_result.mock.js │ ├── 06_mocks_activity.json │ ├── 06_mocks_generated.test.js │ ├── 06_mocks_instrumented.js │ ├── auxilary1.js │ ├── auxilary2.js │ └── response.json ├── 07_large_payload │ ├── 07_large_payload.js │ ├── 07_large_payload │ │ ├── getClickCountsHelper_0_requestDataCb0.mock.js │ │ ├── getClickCountsHelper_0_result.mock.js │ │ ├── getClickCounts_0_result.mock.js │ │ └── getClickCounts_0_undefined0.mock.js │ ├── 07_large_payload_activity.json │ ├── 07_large_payload_generated.test.js │ └── 07_large_payload_instrumented.js ├── 08_this │ ├── 08_this.js │ ├── 08_this_activity.json │ ├── 08_this_generated.test.js │ └── 08_this_instrumented.js ├── 09_typescript_exports │ ├── 09_typescript_exports.js │ ├── 09_typescript_exports_activity.json │ ├── 09_typescript_exports_generated.test.js │ └── 09_typescript_exports_instrumented.js ├── 10_anon_export_default │ ├── 10_anon_export_default.js │ ├── 10_anon_export_default_activity.json │ ├── 10_anon_export_default_generated.test.js │ └── 10_anon_export_default_instrumented.js ├── 11_higher_order │ ├── 11_higher_order.js │ ├── 11_higher_order_activity.json │ ├── 11_higher_order_generated.test.js │ └── 11_higher_order_instrumented.js ├── 12_unwanted_injections │ ├── 12_unwanted_injections.js │ ├── 12_unwanted_injections_activity.json │ ├── 12_unwanted_injections_generated.test.js │ └── 12_unwanted_injections_instrumented.js ├── 13_anon_ts_export_default │ ├── 13_anon_ts_export_default.js │ ├── 13_anon_ts_export_default_activity.json │ ├── 13_anon_ts_export_default_generated.test.js │ └── 13_anon_ts_export_default_instrumented.js ├── 14_anon_module_exports_default │ ├── 14_anon_module_exports_default.js │ ├── 14_anon_module_exports_default_activity.json │ ├── 14_anon_module_exports_default_generated.test.js │ └── 14_anon_module_exports_default_instrumented.js ├── 15_named_module_exports_default │ ├── 15_named_module_exports_default.js │ ├── 15_named_module_exports_default_activity.json │ ├── 15_named_module_exports_default_generated.test.js │ └── 15_named_module_exports_default_instrumented.js ├── 16_exported_objects │ ├── 16_exported_objects.js │ ├── 16_exported_objects │ │ └── largeObjLargeFun_0_result.mock.js │ ├── 16_exported_objects_activity.json │ ├── 16_exported_objects_generated.test.js │ └── 16_exported_objects_instrumented.js ├── 17_param_mutation │ ├── 17_param_mutation.js │ ├── 17_param_mutation_activity.json │ ├── 17_param_mutation_generated.test.js │ └── 17_param_mutation_instrumented.js ├── 18_record_stub_params │ ├── 18_record_stub_params.js │ ├── 18_record_stub_params_activity.json │ ├── 18_record_stub_params_generated.test.js │ ├── 18_record_stub_params_instrumented.js │ └── auxilary.js ├── 19_demo │ ├── 19_demo.js │ ├── 19_demo_activity.json │ ├── 19_demo_generated.test.js │ ├── 19_demo_instrumented.js │ └── sample.json ├── driver.test.js ├── generator.test.js └── plugin.test.js └── util ├── __snapshots__ └── walker.test.js.snap ├── walker.test.js └── walking_test ├── a.js ├── d.exe ├── directory ├── b.jsx └── e.tsx └── f.ts /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | jest: true 6 | }, 7 | extends: 'airbnb', 8 | globals: { 9 | Atomics: 'readonly', 10 | SharedArrayBuffer: 'readonly', 11 | }, 12 | parserOptions: { 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | plugins: [ 20 | 'react', 21 | 'babel' 22 | ], 23 | rules: { 24 | 'react/jsx-filename-extension': 0, 25 | // Babel AST transform is not pure 26 | 'no-param-reassign': 0, 27 | // Code is less "indenty" with continue 28 | 'no-continue': 0 29 | }, 30 | }; -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [14.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 14 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-npm: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v1 27 | with: 28 | node-version: 14 29 | registry-url: https://registry.npmjs.org/ 30 | - run: npm ci 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and not Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Stores VSCode versions used for testing VSCode extensions 107 | .vscode-test 108 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | test_integration/* 3 | node_modules/* 4 | .vscode/* 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest All", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest", 9 | "args": ["--runInBand"], 10 | "console": "integratedTerminal", 11 | "internalConsoleOptions": "neverOpen", 12 | "disableOptimisticBPs": true, 13 | "windows": { 14 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 15 | } 16 | }, 17 | { 18 | "type": "node", 19 | "request": "launch", 20 | "name": "Jest Current File", 21 | "program": "${workspaceFolder}/node_modules/.bin/jest", 22 | "args": [ 23 | "${fileBasenameNoExtension}", 24 | "--config", 25 | "jest.config.js" 26 | ], 27 | "console": "integratedTerminal", 28 | "internalConsoleOptions": "neverOpen", 29 | "disableOptimisticBPs": true, 30 | "windows": { 31 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 32 | } 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol": "\n" 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | TODO -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Souradeep Nanda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | '@babel/preset-typescript', 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unit-test-recorder", 3 | "bin": { 4 | "unit-test-recorder": "src/index.js" 5 | }, 6 | "version": "0.2.0", 7 | "description": "A cli tool records usage and generates unit tests", 8 | "main": "index.js", 9 | "scripts": { 10 | "start": "node ./src/index.js", 11 | "lint": "eslint ./src/*", 12 | "lint:fix": "eslint ./src/* --fix", 13 | "test": "jest", 14 | "test:als": "UTR_EXPERIMENTAL_ALS=true jest", 15 | "test:watch": "jest --watch", 16 | "test:coverage": "jest --coverage" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/Ghost---Shadow/unit-test-recorder" 21 | }, 22 | "keywords": [ 23 | "babel", 24 | "testing", 25 | "cli", 26 | "jest", 27 | "instrumentation" 28 | ], 29 | "author": "Souradeep Nanda", 30 | "license": "MIT", 31 | "devDependencies": { 32 | "@babel/preset-env": "^7.4.4", 33 | "eslint": "^5.16.0", 34 | "eslint-config-airbnb": "^17.1.0", 35 | "eslint-plugin-babel": "^5.3.0", 36 | "eslint-plugin-import": "^2.17.2", 37 | "eslint-plugin-jsx-a11y": "^6.2.1", 38 | "eslint-plugin-react": "^7.12.4", 39 | "husky": "^2.1.0", 40 | "jest": "^25.3.0", 41 | "jest-file-snapshot": "^0.3.6" 42 | }, 43 | "dependencies": { 44 | "@babel/core": "^7.8.7", 45 | "@babel/generator": "^7.4.4", 46 | "@babel/parser": "^7.4.4", 47 | "@babel/plugin-proposal-class-properties": "^7.4.4", 48 | "@babel/preset-typescript": "^7.8.3", 49 | "@babel/template": "^7.8.3", 50 | "@babel/traverse": "^7.4.4", 51 | "@babel/types": "^7.8.3", 52 | "cls-hooked": "^4.2.2", 53 | "is-promise": "^4.0.0", 54 | "json-stable-stringify": "^1.0.1", 55 | "lodash": "^4.17.15", 56 | "mkdirp": "^1.0.4", 57 | "prettier": "^1.19.1", 58 | "rimraf": "^3.0.2", 59 | "typescript": "^3.9.5", 60 | "uuid": "^8.0.0", 61 | "yargs": "^15.3.1" 62 | }, 63 | "jest": { 64 | "testPathIgnorePatterns": [ 65 | "/node_modules/" 66 | ], 67 | "watchPathIgnorePatterns": [ 68 | "fixtures/*", 69 | "__file_snapshots__" 70 | ] 71 | }, 72 | "husky": { 73 | "hooks": { 74 | "pre-commit": "npm run lint", 75 | "pre-push": "npm test" 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /sample.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ghost---Shadow/unit-test-recorder/053135e947b9e6287c33063ee6c8f4b11b786328/sample.PNG -------------------------------------------------------------------------------- /src/cli/generation.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const mkdirp = require('mkdirp'); 4 | const { promisify } = require('util'); 5 | 6 | const { RecorderManager } = require('../recorder'); 7 | const { extractTestsFromState } = require('../generator'); 8 | const { getTestFileNameForFile } = require('../util/misc'); 9 | 10 | const writeFileAsync = promisify(fs.writeFile); 11 | 12 | const writeTestAndExternalData = async ({ testObj, packagedArguments }) => { 13 | console.log('Writing test for: ', testObj.filePath); 14 | mkdirp.sync(path.dirname(testObj.filePath)); 15 | const testFileName = getTestFileNameForFile(testObj.filePath, packagedArguments); 16 | const testFilePromise = writeFileAsync(testFileName, testObj.fileString); 17 | const externalDataPromises = testObj.externalData.map((ed) => { 18 | console.log('Creating dir:', path.dirname(ed.filePath)); 19 | console.log('Dumping: ', ed.filePath); 20 | mkdirp.sync(path.dirname(ed.filePath)); // TODO: Make async 21 | return writeFileAsync(ed.filePath, ed.fileString); 22 | }); 23 | return Promise.all([testFilePromise, ...externalDataPromises]); 24 | }; 25 | 26 | const generateAllTests = async (packagedArguments) => { 27 | try { 28 | console.log('Serializing activities. This may take a while...'); 29 | // Make sure activity.json is the source of truth for generaton 30 | RecorderManager.dumpToDisk(); 31 | RecorderManager.loadFromDisk(); 32 | 33 | console.log('Generating test cases'); 34 | const testData = await extractTestsFromState(RecorderManager.recorderState, packagedArguments); 35 | 36 | console.log('Dumping test cases to disk'); 37 | const writePromises = testData 38 | .map(testObj => ({ testObj, packagedArguments })) 39 | .map(writeTestAndExternalData); 40 | await Promise.all(writePromises); 41 | process.exit(); 42 | } catch (e) { 43 | console.error(e); 44 | process.exit(1); 45 | } 46 | }; 47 | 48 | module.exports = { generateAllTests }; 49 | -------------------------------------------------------------------------------- /src/cli/instrumentation.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const mkdirp = require('mkdirp'); 4 | const babel = require('@babel/core'); 5 | const { default: generate } = require('@babel/generator'); 6 | const parser = require('@babel/parser'); 7 | const { default: traverse } = require('@babel/traverse'); 8 | const prettier = require('prettier'); 9 | 10 | const myPlugin = require('../plugin'); 11 | const { walk, filterFiles } = require('../util/walker'); 12 | const { parserPlugins, generatorOptions } = require('../plugin/used-plugins'); 13 | const { RecorderManager } = require('../recorder'); 14 | 15 | // TODO: Make async 16 | const transformFile = (fileName, whiteListedModules) => { 17 | const inputDir = './'; // TODO 18 | const outputDir = './'; // TODO 19 | try { 20 | console.log('Transforming:', fileName); 21 | const inputCode = fs.readFileSync(fileName, 'utf8'); 22 | const ast = parser.parse(inputCode, { 23 | sourceType: 'module', 24 | plugins: parserPlugins, 25 | }); 26 | 27 | // Run the plugin 28 | const importPath = path.resolve(path.join(__dirname, '../recorder')); 29 | traverse(ast, myPlugin(babel).visitor, null, { fileName, importPath, whiteListedModules }); 30 | const { code } = generate(ast, generatorOptions); 31 | const formattedCode = prettier.format(code, { 32 | singleQuote: true, 33 | parser: 'babel', 34 | }); 35 | 36 | const relativePath = path.relative(inputDir, fileName); 37 | const outputFilePath = path.join(outputDir, relativePath); 38 | 39 | mkdirp.sync(path.dirname(outputFilePath)); 40 | fs.writeFileSync(outputFilePath, formattedCode); 41 | } catch (err) { 42 | console.error('Error for file:', fileName); 43 | console.error(err); 44 | } 45 | }; 46 | 47 | const instrumentAllFiles = (packagedArguments) => { 48 | const { entryPoint } = packagedArguments; 49 | const sourceDir = path.dirname(entryPoint); 50 | const allFiles = walk(sourceDir); 51 | const filteredFiles = filterFiles(packagedArguments, allFiles); 52 | 53 | let whiteListedModules = { fs: true, axios: true }; 54 | if (fs.existsSync('whitelist.json')) { 55 | try { 56 | whiteListedModules = JSON.parse(fs.readFileSync('whitelist.json').toString()); 57 | console.log('Found whitelist', whiteListedModules); 58 | } catch (e) { 59 | console.error('Error loading whitelist. Using default instead', whiteListedModules); 60 | console.error(e); 61 | } 62 | } else { 63 | console.log('Recording mocks for these modules'); 64 | console.log(whiteListedModules); 65 | console.log('Please create ./whitelist.json if you wish to modify the whitelist'); 66 | } 67 | 68 | filteredFiles.forEach(fileName => transformFile(fileName, whiteListedModules)); 69 | 70 | // Load existing state if present 71 | RecorderManager.loadFromDisk(); 72 | 73 | console.log('Injection complete. Starting server...'); 74 | console.log('Press Ctrl + C to stop recording and dump the tests'); 75 | }; 76 | 77 | module.exports = { instrumentAllFiles }; 78 | -------------------------------------------------------------------------------- /src/cli/utils.js: -------------------------------------------------------------------------------- 1 | const cp = require('child_process'); 2 | const fs = require('fs'); 3 | const ts = require('typescript'); 4 | 5 | const rimraf = require('rimraf'); 6 | 7 | const compileAndGetOutputDir = (typescriptConfig) => { 8 | if (fs.existsSync(typescriptConfig)) { 9 | console.log(`Typescript config found at ${typescriptConfig}`); 10 | const contents = fs.readFileSync(typescriptConfig).toString(); 11 | const { config, error } = ts.parseConfigFileTextToJson(typescriptConfig, contents); 12 | 13 | if (error) throw new Error(error); 14 | 15 | const tsBuildDir = config.compilerOptions.outDir; 16 | if (fs.existsSync(tsBuildDir)) { 17 | console.log('Purging build directory'); 18 | rimraf.sync(tsBuildDir); 19 | } 20 | console.log('Recompiling'); 21 | cp.execSync('tsc'); 22 | console.log('Compiled'); 23 | return tsBuildDir; 24 | } 25 | return null; 26 | }; 27 | 28 | module.exports = { 29 | compileAndGetOutputDir, 30 | }; 31 | -------------------------------------------------------------------------------- /src/generator/components/AssignmentOperation/AssignmentOperation.js: -------------------------------------------------------------------------------- 1 | const { 2 | wrapSafely, 3 | shouldMoveToExternal, 4 | generateNameForExternal, 5 | } = require('../../utils'); 6 | 7 | const { PackagedExternalFile } = require('../PackagedExternalFile/PackagedExternalFile'); 8 | const { AggregatorManager } = require('../../external-data-aggregator'); 9 | 10 | const AssignmentOperation = (props) => { 11 | const { 12 | meta, 13 | packagedArguments, 14 | maybeObject, 15 | lIdentifier, 16 | captureIndex, 17 | paramType, 18 | } = props; 19 | const { path } = meta; 20 | 21 | const { sizeLimit, isTypescript } = packagedArguments; 22 | const suffix = isTypescript ? ' as any' : ''; 23 | 24 | if (!shouldMoveToExternal(maybeObject, sizeLimit)) { 25 | const code = `let ${lIdentifier} = ${wrapSafely(maybeObject, paramType)}${suffix}`; 26 | return code; 27 | } 28 | const { identifier, filePath, importPath } = generateNameForExternal( 29 | meta, captureIndex, lIdentifier, 30 | ); 31 | const fileString = PackagedExternalFile({ obj: maybeObject, packagedArguments }); 32 | const code = `let ${lIdentifier} = ${identifier}`; 33 | const externalData = [{ 34 | fileString, 35 | identifier, 36 | filePath, 37 | importPath, 38 | }]; 39 | AggregatorManager.addExternalData(path, externalData); 40 | 41 | return `${code}${suffix}`; 42 | }; 43 | 44 | module.exports = { AssignmentOperation }; 45 | -------------------------------------------------------------------------------- /src/generator/components/DependencyInjectionStubBlock/DependencyInjectionStubBlock.js: -------------------------------------------------------------------------------- 1 | const { 2 | JestMockImplementationStatement, 3 | } = require('../JestMockImplementationStatement/JestMockImplementationStatement'); 4 | 5 | const JestMockDeclaration = ({ lIdentifier }) => `${lIdentifier} = jest.fn()`; 6 | 7 | // Drop the __proto__ because we dont want to 8 | // deal with immutable stuff 9 | // TODO: Make sure there would be no side effects 10 | // with polymorphism 11 | const dropProtoFromInjections = (injections) => { 12 | const dropProto = str => str 13 | .split('.') 14 | .filter(part => part !== '__proto__') 15 | .join('.'); 16 | return Object.keys(injections).reduce((acc, key) => { 17 | const newKey = dropProto(key); 18 | return { 19 | ...acc, 20 | [newKey]: injections[key], 21 | }; 22 | }, {}); 23 | }; 24 | 25 | const DependencyInjectionStubBlock = (props) => { 26 | const { 27 | capture, meta, captureIndex, packagedArguments, 28 | } = props; 29 | if (!capture.injections) return ''; 30 | 31 | // Drop the __proto__ from keys 32 | capture.injections = dropProtoFromInjections(capture.injections); 33 | 34 | // Generate mock functions 35 | const injectedFunctionMocks = []; 36 | Object.keys(capture.injections) 37 | .forEach((injPath) => { 38 | const lIdentifier = injPath; 39 | const { captures } = capture.injections[injPath]; 40 | injectedFunctionMocks.push(JestMockDeclaration({ lIdentifier })); 41 | captures.forEach((innerCapture, innerCaptureIndex) => { 42 | const payload = innerCapture.result; 43 | const paramType = innerCapture.types.result; 44 | const innerProps = { 45 | meta, 46 | captureIndex, 47 | innerCaptureIndex, 48 | lIdentifier, 49 | payload, 50 | paramType, 51 | packagedArguments, 52 | }; 53 | const code = JestMockImplementationStatement(innerProps); 54 | injectedFunctionMocks.push(code); 55 | }); 56 | }); 57 | 58 | return injectedFunctionMocks.join('\n'); 59 | }; 60 | 61 | module.exports = { DependencyInjectionStubBlock, JestMockDeclaration }; 62 | -------------------------------------------------------------------------------- /src/generator/components/DescribeFunctionBlock/DescribeFunctionBlock.js: -------------------------------------------------------------------------------- 1 | const { ItBlock } = require('../ItBlock/ItBlock'); 2 | 3 | const generateComments = (meta) => { 4 | const { requiresContructorInjection } = meta; 5 | if (requiresContructorInjection) { 6 | return { 7 | failure: true, 8 | startComments: '/* This function requires injection of Constructor (WIP)', 9 | endComments: '*/', 10 | }; 11 | } 12 | return { failure: false, startComments: '', endComments: '' }; 13 | }; 14 | 15 | const DescribeFunctionBlock = (props) => { 16 | const { functionActivity, packagedArguments } = props; 17 | const { meta, captures } = functionActivity; 18 | const { maxTestsPerFunction } = packagedArguments; 19 | const slicedCaptures = maxTestsPerFunction === -1 20 | ? captures : captures.slice(0, maxTestsPerFunction); 21 | const itBlocks = slicedCaptures 22 | .map((capture, index) => ItBlock({ 23 | meta, 24 | capture, 25 | captureIndex: index, 26 | packagedArguments, 27 | })); 28 | const { startComments, endComments } = generateComments(meta); 29 | const functionName = meta.name; 30 | const describeBlock = ` 31 | ${startComments} 32 | describe('${functionName}',()=>{ 33 | ${itBlocks.join('\n')} 34 | }) 35 | ${endComments} 36 | `; 37 | return describeBlock; 38 | }; 39 | 40 | module.exports = { DescribeFunctionBlock }; 41 | -------------------------------------------------------------------------------- /src/generator/components/DescribeFunctionBlock/DescribeFunctionBlock.test.js: -------------------------------------------------------------------------------- 1 | const prettier = require('prettier'); 2 | 3 | const { DescribeFunctionBlock } = require('./DescribeFunctionBlock'); 4 | 5 | describe('DescribeFunctionBlock', () => { 6 | const meta = { 7 | path: 'dir/file.js', 8 | name: 'functionName', 9 | relativePath: './', 10 | paramIds: ['a', 'b'], 11 | }; 12 | const packagedArguments = {}; 13 | const capture = { 14 | params: [1, 2], 15 | result: 3, 16 | types: { 17 | params: ['Number', 'Number'], 18 | result: 'Number', 19 | }, 20 | }; 21 | const functionActivity = { meta, captures: [capture, capture] }; 22 | it('should generate code', () => { 23 | const props = { 24 | functionActivity, 25 | packagedArguments, 26 | }; 27 | 28 | const code = DescribeFunctionBlock(props); 29 | const formattedCode = prettier.format(code, { 30 | singleQuote: true, 31 | parser: 'babel', 32 | }); 33 | expect(formattedCode).toMatchInlineSnapshot(` 34 | "describe('functionName', () => { 35 | it('should work for case 1', () => { 36 | let a = 1; 37 | let b = 2; 38 | let result = 3; 39 | 40 | const actual = functionName(a, b); 41 | expect(actual).toEqual(result); 42 | }); 43 | 44 | it('should work for case 2', () => { 45 | let a = 1; 46 | let b = 2; 47 | let result = 3; 48 | 49 | const actual = functionName(a, b); 50 | expect(actual).toEqual(result); 51 | }); 52 | }); 53 | " 54 | `); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/generator/components/ExpectStatement/ExpectStatement.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const ExpectStatement = (props) => { 4 | const { capture } = props; 5 | const resultType = _.get(capture, 'types.result'); 6 | 7 | const defaultReturn = 'expect(actual).toEqual(result)'; 8 | return { 9 | Object: 'expect(actual).toMatchObject(result)', 10 | Function: 'expect(actual.toString()).toEqual(result)', 11 | }[resultType] || defaultReturn; 12 | }; 13 | 14 | module.exports = { ExpectStatement }; 15 | -------------------------------------------------------------------------------- /src/generator/components/ExpectStatement/ExpectStatement.test.js: -------------------------------------------------------------------------------- 1 | const { ExpectStatement } = require('./ExpectStatement'); 2 | 3 | describe('ExpectStatement', () => { 4 | it('should generate code when result is Number', () => { 5 | const capture = { 6 | types: { 7 | result: 'Number', 8 | }, 9 | }; 10 | const props = { capture }; 11 | const code = ExpectStatement(props); 12 | expect(code).toMatchInlineSnapshot('"expect(actual).toEqual(result)"'); 13 | }); 14 | it('should generate code when result is Object', () => { 15 | const capture = { 16 | types: { 17 | result: 'Object', 18 | }, 19 | }; 20 | const props = { capture }; 21 | const code = ExpectStatement(props); 22 | expect(code).toMatchInlineSnapshot( 23 | '"expect(actual).toMatchObject(result)"', 24 | ); 25 | }); 26 | it('should generate code when result is Function', () => { 27 | const capture = { 28 | types: { 29 | result: 'Function', 30 | }, 31 | }; 32 | const props = { capture }; 33 | const code = ExpectStatement(props); 34 | expect(code).toMatchInlineSnapshot( 35 | '"expect(actual.toString()).toEqual(result)"', 36 | ); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/generator/components/FunctionStubBlock/FunctionStubBlock.js: -------------------------------------------------------------------------------- 1 | const { 2 | MockFunctionStubBlock, 3 | } = require('../MockFunctionStubBlock/MockFunctionStubBlock'); 4 | 5 | const { 6 | DependencyInjectionStubBlock, 7 | } = require('../DependencyInjectionStubBlock/DependencyInjectionStubBlock'); 8 | 9 | const FunctionStubBlock = (props) => { 10 | const mockStubs = MockFunctionStubBlock(props); 11 | const diStubs = DependencyInjectionStubBlock(props); 12 | return `${mockStubs}\n${diStubs}`; 13 | }; 14 | 15 | module.exports = { 16 | FunctionStubBlock, 17 | }; 18 | -------------------------------------------------------------------------------- /src/generator/components/ImportStatements/ImportStatements.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const { 4 | AggregatorManager, 5 | } = require('../../external-data-aggregator'); 6 | 7 | const DefaultImportStatement = (props) => { 8 | const { importPath, identifier, packagedArguments } = props; 9 | const { isTypescript } = packagedArguments; 10 | if (isTypescript) { 11 | return `import * as ${identifier} from '${importPath}'`; 12 | } 13 | return `const ${identifier} = require('${importPath}');`; 14 | }; 15 | 16 | const EcmaDefaultImportStatement = (props) => { 17 | const { importPath, identifier, packagedArguments } = props; 18 | const { isTypescript } = packagedArguments; 19 | if (isTypescript) { 20 | return `import ${identifier} from '${importPath}'`; 21 | } 22 | return `const {default:${identifier}} = require('${importPath}');`; 23 | }; 24 | 25 | const DestructureImportStatement = (props) => { 26 | const { importPath, identifier, packagedArguments } = props; 27 | const { isTypescript } = packagedArguments; 28 | if (isTypescript) { 29 | return `import {${identifier}} from '${importPath}'`; 30 | } 31 | return `const {${identifier}} = require('${importPath}');`; 32 | }; 33 | 34 | const FunctionImportStatements = ({ exportedFunctions, packagedArguments }) => { 35 | const importedFunctions = Object.keys(exportedFunctions); 36 | // Functions in objects have names like obj.fun1, obj.fun2 37 | const cleanImportedFunctions = _.uniqBy( 38 | importedFunctions.map(name => ({ 39 | identifier: name.split('.')[0], 40 | meta: exportedFunctions[name].meta, 41 | })), 42 | 'identifier', 43 | ); 44 | const importStatements = cleanImportedFunctions.map(({ identifier, meta }) => { 45 | const { isDefault, isEcmaDefault, importPath } = meta; 46 | const props = { identifier, importPath, packagedArguments }; 47 | if (isEcmaDefault) return EcmaDefaultImportStatement(props); 48 | if (isDefault) return DefaultImportStatement(props); 49 | return DestructureImportStatement(props); 50 | }); 51 | 52 | return importStatements.join('\n'); 53 | }; 54 | 55 | const ExternalDataImportStatements = (props) => { 56 | const { path, packagedArguments } = props; 57 | const { isTypescript } = packagedArguments; 58 | const externalData = AggregatorManager.getExternalData(path); 59 | const externalsWithoutMocks = externalData.filter(ed => !ed.isMock); 60 | const ImportStatement = isTypescript ? EcmaDefaultImportStatement : DefaultImportStatement; 61 | const statements = externalsWithoutMocks 62 | .map(data => ({ ...data, packagedArguments })) 63 | .map(ImportStatement); 64 | return statements.join('\n'); 65 | }; 66 | 67 | const ImportStatements = (props) => { 68 | const { exportedFunctions, path, packagedArguments } = props; 69 | 70 | const functionImportStatements = FunctionImportStatements({ 71 | exportedFunctions, 72 | packagedArguments, 73 | }); 74 | const externalImportStatements = ExternalDataImportStatements({ 75 | path, 76 | packagedArguments, 77 | }); 78 | 79 | return `${functionImportStatements}\n\n${externalImportStatements}\n`; 80 | }; 81 | 82 | module.exports = { 83 | DefaultImportStatement, 84 | EcmaDefaultImportStatement, 85 | DestructureImportStatement, 86 | 87 | FunctionImportStatements, 88 | ExternalDataImportStatements, 89 | 90 | ImportStatements, 91 | }; 92 | -------------------------------------------------------------------------------- /src/generator/components/InputAssignment/InputAssignment.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const { inferTypeOfObject } = require('../../../recorder/utils/dynamic-type-inference'); 4 | const { AssignmentOperation } = require('../AssignmentOperation/AssignmentOperation'); 5 | 6 | const primeObjectForInjections = (maybeObject, paramId, injections) => { 7 | if (inferTypeOfObject(injections) !== 'Object') return maybeObject; 8 | const dropProto = injArr => injArr.filter(elem => elem !== '__proto__'); 9 | const allInjections = Object.keys(injections); 10 | const injArray = allInjections.map(inj => inj.split('.')); 11 | const injectionsOfCurrentParam = injArray.filter(injArr => injArr[0] === paramId); 12 | const droppedProto = injectionsOfCurrentParam.map(dropProto); 13 | const cleanedInjection = droppedProto.map(injArr => injArr.slice(1, injArr.length - 1)); 14 | cleanedInjection.forEach((inj) => { 15 | _.set(maybeObject, inj, {}); 16 | }); 17 | return maybeObject; 18 | }; 19 | 20 | const InputAssignment = (props) => { 21 | const { 22 | capture, 23 | meta, 24 | captureIndex, 25 | packagedArguments, 26 | } = props; 27 | const { paramIds } = meta; 28 | const { params, types } = capture; 29 | const paramTypes = _.get(types, 'params', []); 30 | 31 | const inputStatementData = paramIds 32 | .map((paramId, index) => { 33 | const maybeObject = params[index]; 34 | const lIdentifier = paramId; 35 | const paramType = paramTypes[index]; 36 | const maybePrimedObject = primeObjectForInjections(maybeObject, paramId, capture.injections); 37 | const code = AssignmentOperation( 38 | { 39 | meta, 40 | packagedArguments, 41 | maybeObject: maybePrimedObject, 42 | lIdentifier, 43 | captureIndex, 44 | paramType, 45 | }, 46 | ); 47 | return code; 48 | }); 49 | const resultCode = AssignmentOperation( 50 | { 51 | meta, 52 | packagedArguments, 53 | maybeObject: capture.result, 54 | lIdentifier: 'result', 55 | captureIndex, 56 | paramType: _.get(capture, 'types.result'), 57 | }, 58 | ); 59 | return inputStatementData.concat(resultCode).join('\n'); 60 | }; 61 | 62 | module.exports = { InputAssignment, primeObjectForInjections }; 63 | -------------------------------------------------------------------------------- /src/generator/components/InputAssignment/InputAssignment.test.js: -------------------------------------------------------------------------------- 1 | const prettier = require('prettier'); 2 | 3 | const { 4 | InputAssignment, 5 | primeObjectForInjections, 6 | } = require('./InputAssignment'); 7 | 8 | describe('InputAssignment', () => { 9 | describe('InputAssignment', () => { 10 | const meta = { 11 | path: 'dir/file.js', 12 | name: 'functionName', 13 | relativePath: './', 14 | paramIds: ['a', 'b'], 15 | }; 16 | const packagedArguments = {}; 17 | const captureIndex = 0; 18 | const capture = { 19 | params: [1, 2], 20 | result: 3, 21 | types: { 22 | params: ['Number', 'Number'], 23 | result: 'Number', 24 | }, 25 | }; 26 | it('should generate code when payload is small', () => { 27 | const props = { 28 | capture, 29 | meta, 30 | captureIndex, 31 | packagedArguments, 32 | }; 33 | 34 | const code = InputAssignment(props); 35 | const formattedCode = prettier.format(code, { 36 | singleQuote: true, 37 | parser: 'babel', 38 | }); 39 | expect(formattedCode).toMatchInlineSnapshot(` 40 | "let a = 1; 41 | let b = 2; 42 | let result = 3; 43 | " 44 | `); 45 | }); 46 | }); 47 | describe('primeObjectForInjections', () => { 48 | it('should add extra dependencies', () => { 49 | const obj = {}; 50 | const paramId = 'obj1'; 51 | const injections = { 52 | 'obj1.foo': {}, 53 | 'obj1.bar.__proto__.baz': {}, 54 | 'obj2.faz': {}, 55 | }; 56 | const expected = { bar: {} }; 57 | expect(primeObjectForInjections(obj, paramId, injections)).toEqual( 58 | expected, 59 | ); 60 | }); 61 | it('should not crash for non-object likes', () => { 62 | const obj = 2; 63 | const paramId = 'obj1'; 64 | const injections = { 65 | 'obj1.foo': {}, 66 | 'obj1.bar.__proto__.baz': {}, 67 | 'obj2.faz': {}, 68 | }; 69 | const expected = 2; 70 | expect(primeObjectForInjections(obj, paramId, injections)).toEqual( 71 | expected, 72 | ); 73 | }); 74 | it('should not if injections is undefined for null', () => { 75 | const obj = {}; 76 | const paramId = 'obj1'; 77 | const injections = null; 78 | const expected = {}; 79 | expect(primeObjectForInjections(obj, paramId, injections)).toEqual( 80 | expected, 81 | ); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /src/generator/components/InvokeStatement/InvokeStatement.js: -------------------------------------------------------------------------------- 1 | const InvokeStatement = (props) => { 2 | const { meta } = props; 3 | const functionIdentifier = meta.name; 4 | const { doesReturnPromise, paramIds } = meta; 5 | const invokeExpression = `${functionIdentifier}(${paramIds.join(',')})`; 6 | 7 | const awaitString = doesReturnPromise ? 'await ' : ''; 8 | return `const actual = ${awaitString}${invokeExpression}`; 9 | }; 10 | 11 | module.exports = { InvokeStatement }; 12 | -------------------------------------------------------------------------------- /src/generator/components/InvokeStatement/InvokeStatement.test.js: -------------------------------------------------------------------------------- 1 | const { InvokeStatement } = require('./InvokeStatement'); 2 | 3 | describe('InvokeStatement', () => { 4 | const meta = { 5 | doesReturnPromise: true, 6 | paramIds: ['a', 'b'], 7 | name: 'functionIdentifier', 8 | }; 9 | it('should generate code', () => { 10 | const props = { meta }; 11 | 12 | const code = InvokeStatement(props); 13 | expect(code).toMatchInlineSnapshot( 14 | '"const actual = await functionIdentifier(a,b)"', 15 | ); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/generator/components/ItBlock/ItBlock.js: -------------------------------------------------------------------------------- 1 | const { InputAssignment } = require('../InputAssignment/InputAssignment'); 2 | const { FunctionStubBlock } = require('../FunctionStubBlock/FunctionStubBlock'); 3 | const { InvokeStatement } = require('../InvokeStatement/InvokeStatement'); 4 | const { ExpectStatement } = require('../ExpectStatement/ExpectStatement'); 5 | 6 | const ItBlock = (props) => { 7 | const { 8 | meta, 9 | captureIndex, 10 | // packagedArguments, // Required by children 11 | // capture, // Required by children 12 | } = props; 13 | const { doesReturnPromise } = meta; 14 | const asyncString = doesReturnPromise ? 'async ' : ''; 15 | return ` 16 | it('should work for case ${captureIndex + 1}', ${asyncString}()=>{ 17 | ${InputAssignment(props)} 18 | ${FunctionStubBlock(props)} 19 | ${InvokeStatement(props)} 20 | ${ExpectStatement(props)} 21 | }) 22 | `; 23 | }; 24 | 25 | module.exports = { ItBlock }; 26 | -------------------------------------------------------------------------------- /src/generator/components/ItBlock/ItBlock.test.js: -------------------------------------------------------------------------------- 1 | const prettier = require('prettier'); 2 | 3 | const { ItBlock } = require('./ItBlock'); 4 | 5 | describe('ItBlock', () => { 6 | const meta = { 7 | path: 'dir/file.js', 8 | name: 'functionName', 9 | relativePath: './', 10 | paramIds: ['a', 'b'], 11 | }; 12 | const packagedArguments = {}; 13 | const captureIndex = 0; 14 | const capture = { 15 | params: [1, 2], 16 | result: 3, 17 | types: { 18 | params: ['Number', 'Number'], 19 | result: 'Number', 20 | }, 21 | }; 22 | it('should generate code', () => { 23 | const props = { 24 | meta, 25 | captureIndex, 26 | functionIdentifier: 'functionName', 27 | packagedArguments, 28 | capture, 29 | }; 30 | 31 | const code = ItBlock(props); 32 | const formattedCode = prettier.format(code, { 33 | singleQuote: true, 34 | parser: 'babel', 35 | }); 36 | expect(formattedCode).toMatchInlineSnapshot(` 37 | "it('should work for case 1', () => { 38 | let a = 1; 39 | let b = 2; 40 | let result = 3; 41 | 42 | const actual = functionName(a, b); 43 | expect(actual).toEqual(result); 44 | }); 45 | " 46 | `); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/generator/components/JestMockImplementationStatement/JestMockImplementationStatement.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const { 4 | wrapSafely, 5 | shouldMoveToExternal, 6 | generateNameForExternal, 7 | } = require('../../utils'); 8 | 9 | const { PackagedExternalFile } = require('../PackagedExternalFile/PackagedExternalFile'); 10 | const { AggregatorManager } = require('../../external-data-aggregator'); 11 | 12 | const JestMockImplementationStatement = ({ 13 | meta, 14 | captureIndex, 15 | innerCaptureIndex, 16 | lIdentifier, 17 | payload, 18 | paramType, 19 | packagedArguments, 20 | }) => { 21 | const { path } = meta; 22 | 23 | const inner = rhs => `${lIdentifier}.mockReturnValueOnce(${rhs})`; 24 | const { sizeLimit } = packagedArguments; 25 | 26 | if (!shouldMoveToExternal(payload, sizeLimit)) { 27 | const code = inner(wrapSafely(payload, paramType)); 28 | return code; 29 | } 30 | const { identifier, filePath, importPath } = generateNameForExternal( 31 | meta, captureIndex, _.camelCase(`${lIdentifier}${innerCaptureIndex}`), 32 | ); 33 | const fileString = PackagedExternalFile({ obj: payload, packagedArguments }); 34 | const code = inner(identifier); 35 | const externalData = [{ 36 | fileString, 37 | identifier, 38 | filePath, 39 | importPath, 40 | }]; 41 | AggregatorManager.addExternalData(path, externalData); 42 | 43 | return code; 44 | }; 45 | 46 | module.exports = { JestMockImplementationStatement }; 47 | -------------------------------------------------------------------------------- /src/generator/components/JestMockImplementationStatement/JestMockImplementationStatement.test.js: -------------------------------------------------------------------------------- 1 | jest.mock('../../utils', () => { 2 | const actualUtils = jest.requireActual('../../utils'); 3 | const originalImplementation = actualUtils.shouldMoveToExternal; 4 | 5 | return { 6 | ...actualUtils, 7 | shouldMoveToExternal: jest.fn().mockImplementation(originalImplementation), 8 | }; 9 | }); 10 | jest.mock('../../external-data-aggregator', () => ({ 11 | AggregatorManager: { addExternalData: jest.fn() }, 12 | })); 13 | 14 | const utils = require('../../utils'); 15 | const eda = require('../../external-data-aggregator'); 16 | 17 | const { 18 | JestMockImplementationStatement, 19 | } = require('./JestMockImplementationStatement'); 20 | 21 | describe('JestMockImplementationStatement', () => { 22 | beforeEach(() => { 23 | jest.clearAllMocks(); 24 | }); 25 | const meta = { 26 | path: 'dir/file.js', 27 | name: 'functionName', 28 | relativePath: './', 29 | }; 30 | const captureIndex = 0; 31 | const innerCaptureIndex = 0; 32 | const packagedArguments = {}; 33 | const payload = 42; 34 | const lIdentifier = 'fs.lIdentifier'; 35 | const paramType = 'Number'; 36 | it('should generate code when payload is small', () => { 37 | jest.spyOn(utils, 'shouldMoveToExternal').mockReturnValueOnce(false); 38 | const props = { 39 | meta, 40 | captureIndex, 41 | innerCaptureIndex, 42 | lIdentifier, 43 | payload, 44 | paramType, 45 | packagedArguments, 46 | }; 47 | 48 | const code = JestMockImplementationStatement(props); 49 | expect(code).toMatchInlineSnapshot( 50 | '"fs.lIdentifier.mockReturnValueOnce(42)"', 51 | ); 52 | expect(eda.AggregatorManager.addExternalData.mock.calls.length).toBe(0); 53 | }); 54 | it('should generate code when payload is large', () => { 55 | const props = { 56 | meta, 57 | captureIndex, 58 | innerCaptureIndex, 59 | lIdentifier, 60 | payload, 61 | paramType, 62 | packagedArguments, 63 | }; 64 | jest.spyOn(utils, 'shouldMoveToExternal').mockReturnValueOnce(true); 65 | const code = JestMockImplementationStatement(props); 66 | const path = eda.AggregatorManager.addExternalData.mock.calls[0][0]; 67 | const externalData = eda.AggregatorManager.addExternalData.mock.calls[0][1]; 68 | expect(code).toMatchInlineSnapshot( 69 | '"fs.lIdentifier.mockReturnValueOnce(functionName0fsLIdentifier0)"', 70 | ); 71 | expect(path).toEqual(meta.path); 72 | expect(externalData).toMatchInlineSnapshot(` 73 | Array [ 74 | Object { 75 | "filePath": "dir/file/functionName_0_fsLIdentifier0.mock.js", 76 | "fileString": "module.exports = 42; 77 | ", 78 | "identifier": "functionName0fsLIdentifier0", 79 | "importPath": "./file/functionName_0_fsLIdentifier0.mock", 80 | }, 81 | ] 82 | `); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /src/generator/components/MockFunctionStubBlock/MockFunctionStubBlock.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const { 4 | JestMockImplementationStatement, 5 | } = require('../JestMockImplementationStatement/JestMockImplementationStatement'); 6 | 7 | const MockFunctionStubBlock = (props) => { 8 | const { 9 | meta, 10 | captureIndex, 11 | packagedArguments, 12 | capture, 13 | } = props; 14 | if (!capture.mocks) return ''; 15 | 16 | const allStubs = []; 17 | 18 | Object.keys(capture.mocks).forEach((importPath) => { 19 | const moduleToMock = capture.mocks[importPath]; 20 | const importIdentifier = _.camelCase(importPath); 21 | Object.keys(moduleToMock).forEach((fnName) => { 22 | const { captures } = moduleToMock[fnName]; 23 | captures.forEach((innerCapture, innerCaptureIndex) => { 24 | const payload = innerCapture.result; 25 | const paramType = innerCapture.types.result; 26 | const lIdentifier = `${importIdentifier}.${fnName}`; 27 | const innerProps = { 28 | meta, 29 | captureIndex, 30 | innerCaptureIndex, 31 | lIdentifier, 32 | payload, 33 | paramType, 34 | packagedArguments, 35 | }; 36 | const code = JestMockImplementationStatement(innerProps); 37 | allStubs.push(code); 38 | }); 39 | }); 40 | }); 41 | return allStubs.join('\n'); 42 | }; 43 | 44 | module.exports = { 45 | MockFunctionStubBlock, 46 | }; 47 | -------------------------------------------------------------------------------- /src/generator/components/MockImportBlock/MockImportBlock.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const { DefaultImportStatement } = require('../ImportStatements/ImportStatements'); 4 | 5 | const JestMockStatement = ({ importPath }) => `jest.mock('${importPath}');`; 6 | 7 | const SpecialImportStatement = ({ importPath, originalImportPath, packagedArguments }) => { 8 | const identifier = _.camelCase(originalImportPath); 9 | const { isTypescript } = packagedArguments; 10 | if (!isTypescript) { 11 | return DefaultImportStatement({ importPath, identifier, packagedArguments }); 12 | } 13 | const newIdentifier = `${identifier}Original`; 14 | const importStatement = DefaultImportStatement({ 15 | importPath, 16 | identifier: newIdentifier, 17 | packagedArguments, 18 | }); 19 | return importStatement; 20 | }; 21 | 22 | const ReassignmentStatement = ({ originalImportPath, packagedArguments }) => { 23 | const { isTypescript } = packagedArguments; 24 | if (!isTypescript) return ''; 25 | 26 | const identifier = _.camelCase(originalImportPath); 27 | const newIdentifier = `${identifier}Original`; 28 | 29 | return `const ${identifier} = ${newIdentifier} as any`; 30 | }; 31 | 32 | const MockImportBlock = ({ meta, packagedArguments }) => { 33 | const mockStatements = meta.mocks 34 | .map(importPath => JestMockStatement({ importPath })); 35 | 36 | const importStatements = _.zip(meta.mocks, meta.originalMocks) 37 | .map(([importPath, originalImportPath]) => SpecialImportStatement({ 38 | importPath, 39 | originalImportPath, 40 | packagedArguments, 41 | })); 42 | 43 | const reassignmentStatements = meta.originalMocks 44 | .map(originalImportPath => ReassignmentStatement({ 45 | originalImportPath, 46 | packagedArguments, 47 | })); 48 | 49 | const importStatementStr = importStatements.join('\n'); 50 | const reassignmentStatementStr = reassignmentStatements.join('\n'); 51 | const mockStatementsStr = mockStatements.join('\n'); 52 | 53 | return `${importStatementStr}\n\n${reassignmentStatementStr}\n\n${mockStatementsStr}`; 54 | }; 55 | 56 | module.exports = { 57 | JestMockStatement, 58 | MockImportBlock, 59 | }; 60 | -------------------------------------------------------------------------------- /src/generator/components/MockImportBlock/MockImportBlock.test.js: -------------------------------------------------------------------------------- 1 | const prettier = require('prettier'); 2 | 3 | const { JestMockStatement, MockImportBlock } = require('./MockImportBlock'); 4 | 5 | describe('MockImportBlock', () => { 6 | describe('JestMockStatement', () => { 7 | it('should generate code', () => { 8 | const props = { importPath: 'fs' }; 9 | const code = JestMockStatement(props); 10 | expect(code).toMatchInlineSnapshot("\"jest.mock('fs');\""); 11 | }); 12 | }); 13 | describe('MockImportBlock', () => { 14 | describe('javascript', () => { 15 | it('should generate code', () => { 16 | const props = { 17 | meta: { 18 | mocks: ['m1', '../dir1/m2', 'm3', '../dir1/m4', 'm5'], 19 | originalMocks: ['m1', './m2', 'm3', './m4', 'm5'], 20 | }, 21 | packagedArguments: {}, 22 | }; 23 | const code = MockImportBlock(props); 24 | const formattedCode = prettier.format(code, { 25 | singleQuote: true, 26 | parser: 'babel', 27 | }); 28 | expect(formattedCode).toMatchInlineSnapshot(` 29 | "const m1 = require('m1'); 30 | const m2 = require('../dir1/m2'); 31 | const m3 = require('m3'); 32 | const m4 = require('../dir1/m4'); 33 | const m5 = require('m5'); 34 | 35 | jest.mock('m1'); 36 | jest.mock('../dir1/m2'); 37 | jest.mock('m3'); 38 | jest.mock('../dir1/m4'); 39 | jest.mock('m5'); 40 | " 41 | `); 42 | }); 43 | }); 44 | describe('typescript', () => { 45 | it('should generate code', () => { 46 | const props = { 47 | meta: { 48 | mocks: ['m1', '../dir1/m2', 'm3', '../dir1/m4', 'm5'], 49 | originalMocks: ['m1', './m2', 'm3', './m4', 'm5'], 50 | }, 51 | packagedArguments: { isTypescript: true }, 52 | }; 53 | const code = MockImportBlock(props); 54 | const formattedCode = prettier.format(code, { 55 | singleQuote: true, 56 | parser: 'typescript', 57 | }); 58 | expect(formattedCode).toMatchInlineSnapshot(` 59 | "import * as m1Original from 'm1'; 60 | import * as m2Original from '../dir1/m2'; 61 | import * as m3Original from 'm3'; 62 | import * as m4Original from '../dir1/m4'; 63 | import * as m5Original from 'm5'; 64 | 65 | const m1 = m1Original as any; 66 | const m2 = m2Original as any; 67 | const m3 = m3Original as any; 68 | const m4 = m4Original as any; 69 | const m5 = m5Original as any; 70 | 71 | jest.mock('m1'); 72 | jest.mock('../dir1/m2'); 73 | jest.mock('m3'); 74 | jest.mock('../dir1/m4'); 75 | jest.mock('m5'); 76 | " 77 | `); 78 | }); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /src/generator/components/PackagedExternalFile/PackagedExternalFile.js: -------------------------------------------------------------------------------- 1 | const prettier = require('prettier'); 2 | 3 | const { wrapSafely } = require('../../utils'); 4 | 5 | const PackagedExternalFile = ({ obj, packagedArguments }) => { 6 | const { isTypescript } = packagedArguments; 7 | const dialect = isTypescript ? 'typescript' : 'javascript'; 8 | const wrappedObj = wrapSafely(obj); 9 | const code = { 10 | typescript: `export default ${wrappedObj}`, 11 | javascript: `module.exports = ${wrappedObj}`, 12 | }[dialect]; 13 | 14 | return prettier.format(code, { 15 | singleQuote: true, 16 | parser: 'babel', 17 | }); 18 | }; 19 | 20 | module.exports = { 21 | PackagedExternalFile, 22 | }; 23 | -------------------------------------------------------------------------------- /src/generator/components/PackagedExternalFile/PackagedExternalFile.test.js: -------------------------------------------------------------------------------- 1 | const { PackagedExternalFile } = require('./PackagedExternalFile'); 2 | 3 | describe('PackagedExternalFile', () => { 4 | it('should generate code for javascript', () => { 5 | const obj = { a: 42 }; 6 | const packagedArguments = {}; 7 | const props = { obj, packagedArguments }; 8 | const code = PackagedExternalFile(props); 9 | expect(code).toMatchInlineSnapshot(` 10 | "module.exports = { 11 | a: 42 12 | }; 13 | " 14 | `); 15 | }); 16 | it('should generate code for typescript', () => { 17 | const obj = { a: 42 }; 18 | const packagedArguments = { isTypescript: true }; 19 | const props = { obj, packagedArguments }; 20 | const code = PackagedExternalFile(props); 21 | expect(code).toMatchInlineSnapshot(` 22 | "export default { 23 | a: 42 24 | }; 25 | " 26 | `); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/generator/components/TestFileBlock/TestFileBlock.js: -------------------------------------------------------------------------------- 1 | const { DescribeFunctionBlock } = require('../DescribeFunctionBlock/DescribeFunctionBlock'); 2 | const { MockImportBlock } = require('../MockImportBlock/MockImportBlock'); 3 | const { ImportStatements } = require('../ImportStatements/ImportStatements'); 4 | 5 | const TestFileBlock = (props) => { 6 | const { 7 | fileName, filePath, fileData, packagedArguments, 8 | } = props; 9 | const { exportedFunctions } = fileData; 10 | const describeBlocks = Object 11 | .keys(exportedFunctions) 12 | .map((functionName) => { 13 | const code = DescribeFunctionBlock({ 14 | functionActivity: exportedFunctions[functionName], 15 | packagedArguments, 16 | }); 17 | return code; 18 | }); 19 | 20 | const importStatements = ImportStatements({ 21 | packagedArguments, 22 | exportedFunctions, 23 | path: filePath, 24 | }); 25 | 26 | const mockImportStatements = MockImportBlock({ ...fileData, packagedArguments }); 27 | 28 | const result = ` 29 | ${mockImportStatements} 30 | 31 | ${importStatements} 32 | 33 | describe('${fileName}',()=>{ 34 | ${describeBlocks.join('\n')} 35 | }) 36 | `; 37 | 38 | return result; 39 | }; 40 | 41 | module.exports = { TestFileBlock }; 42 | -------------------------------------------------------------------------------- /src/generator/components/TestFileBlock/TestFileBlock.test.js: -------------------------------------------------------------------------------- 1 | const prettier = require('prettier'); 2 | 3 | const { TestFileBlock } = require('./TestFileBlock'); 4 | 5 | jest.mock('../../external-data-aggregator', () => ({ 6 | AggregatorManager: { getExternalData: jest.fn() }, 7 | })); 8 | 9 | const { AggregatorManager } = require('../../external-data-aggregator'); 10 | 11 | describe('TestFileBlock', () => { 12 | const filePath = 'dir/file.js'; 13 | const meta = { 14 | path: filePath, 15 | name: 'functionName', 16 | relativePath: './', 17 | paramIds: ['a', 'b'], 18 | importPath: './functionName', 19 | }; 20 | const mocks = { 21 | fs: { 22 | readFileSync: { 23 | captures: [ 24 | { 25 | params: ['a'], 26 | result: ['a'], 27 | types: { 28 | params: ['String'], 29 | result: 'String', 30 | }, 31 | }, 32 | ], 33 | }, 34 | }, 35 | '../../someScript': {}, 36 | }; 37 | const packagedArguments = {}; 38 | const capture = { 39 | mocks, 40 | params: [1, 2], 41 | result: 3, 42 | types: { 43 | params: ['Number', 'Number'], 44 | result: 'Number', 45 | }, 46 | }; 47 | const functionActivity = { meta, captures: [capture, capture] }; 48 | it('should generate code', () => { 49 | AggregatorManager.getExternalData.mockReturnValueOnce([ 50 | { importPath: 'dir1/foo.mock.js', identifier: 'foo' }, 51 | { importPath: 'dir1/bar.mock.js', identifier: 'bar' }, 52 | { importPath: 'dir1/baz.mock.js', identifier: 'baz', isMock: true }, 53 | ]); 54 | 55 | const props = { 56 | fileName: 'file', 57 | filePath, 58 | fileData: { 59 | meta: { 60 | mocks: ['../../someScript', 'fs'], 61 | originalMocks: ['../../someScript', 'fs'], 62 | }, 63 | exportedFunctions: { 64 | [meta.name]: functionActivity, 65 | }, 66 | relativePath: './', 67 | }, 68 | packagedArguments, 69 | }; 70 | 71 | const code = TestFileBlock(props); 72 | const formattedCode = prettier.format(code, { 73 | singleQuote: true, 74 | parser: 'babel', 75 | }); 76 | expect(formattedCode).toMatchInlineSnapshot(` 77 | "const someScript = require('../../someScript'); 78 | const fs = require('fs'); 79 | 80 | jest.mock('../../someScript'); 81 | jest.mock('fs'); 82 | 83 | const { functionName } = require('./functionName'); 84 | 85 | const foo = require('dir1/foo.mock.js'); 86 | const bar = require('dir1/bar.mock.js'); 87 | 88 | describe('file', () => { 89 | describe('functionName', () => { 90 | it('should work for case 1', () => { 91 | let a = 1; 92 | let b = 2; 93 | let result = 3; 94 | fs.readFileSync.mockReturnValueOnce(['a']); 95 | 96 | const actual = functionName(a, b); 97 | expect(actual).toEqual(result); 98 | }); 99 | 100 | it('should work for case 2', () => { 101 | let a = 1; 102 | let b = 2; 103 | let result = 3; 104 | fs.readFileSync.mockReturnValueOnce(['a']); 105 | 106 | const actual = functionName(a, b); 107 | expect(actual).toEqual(result); 108 | }); 109 | }); 110 | }); 111 | " 112 | `); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /src/generator/external-data-aggregator.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | // TODO: Use redux 4 | const AggregatorManager = { 5 | validatePath(path) { 6 | const addr = ['externalData', path]; 7 | if (!_.get(this, addr)) { 8 | _.set(this, addr, []); 9 | } 10 | }, 11 | clear() { 12 | this.externalData = {}; 13 | }, 14 | addExternalData(path, externalData) { 15 | this.validatePath(path); 16 | this.externalData[path].push(...externalData); 17 | }, 18 | getExternalData(path) { 19 | this.validatePath(path); 20 | return this.externalData[path]; 21 | }, 22 | }; 23 | 24 | module.exports = { AggregatorManager }; 25 | -------------------------------------------------------------------------------- /src/generator/external-data-aggregator.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | AggregatorManager, 3 | } = require('./external-data-aggregator'); 4 | 5 | describe('external-data-aggregator', () => { 6 | beforeEach(() => { 7 | AggregatorManager.clear(); 8 | }); 9 | describe('validatepath', () => { 10 | it('should create path if not exists', () => { 11 | const path = 'newPath'; 12 | AggregatorManager.validatePath(path); 13 | expect(AggregatorManager.externalData[path]).toEqual([]); 14 | }); 15 | }); 16 | describe('addExternalData', () => { 17 | it('should add external data to state', () => { 18 | const path = 'newPath'; 19 | const externalData = [ 20 | { 21 | fileString: 'fileString1', 22 | identifier: 'identifier1', 23 | filePath: 'filePath1', 24 | importPath: 'importPath1', 25 | }, 26 | { 27 | fileString: 'fileString2', 28 | identifier: 'identifier2', 29 | filePath: 'filePath2', 30 | importPath: 'importPath2', 31 | }, 32 | ]; 33 | AggregatorManager.addExternalData(path, [externalData[0]]); 34 | AggregatorManager.addExternalData(path, [externalData[1]]); 35 | expect(AggregatorManager.externalData[path]).toEqual(externalData); 36 | }); 37 | }); 38 | describe('getExternalData', () => { 39 | it('should retrieve external data from path', () => { 40 | const path = 'newPath'; 41 | const externalData = [ 42 | { 43 | fileString: 'fileString1', 44 | identifier: 'identifier1', 45 | filePath: 'filePath1', 46 | importPath: 'importPath1', 47 | }, 48 | ]; 49 | AggregatorManager.addExternalData(path, externalData); 50 | expect(AggregatorManager.getExternalData(path)).toEqual(externalData); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/generator/index.js: -------------------------------------------------------------------------------- 1 | // TODO: Use babel template 2 | const prettier = require('prettier'); 3 | const { 4 | filePathToFileName, 5 | getOutputFilePath, 6 | offsetMocks, 7 | } = require('./utils'); 8 | 9 | const { TestFileBlock } = require('./components/TestFileBlock/TestFileBlock'); 10 | const { AggregatorManager } = require('./external-data-aggregator'); 11 | 12 | // maxTestsPerFunction: -1 == inf 13 | // outputDir === null means use the same directory as inputDir 14 | const extractTestsFromState = (state, packagedArguments) => Object 15 | .keys(state) 16 | .map((filePath) => { 17 | try { 18 | // Generate output file path and store it in the state meta 19 | const { 20 | outputFilePath, 21 | importPath, 22 | relativePath, 23 | } = getOutputFilePath(filePath, packagedArguments); 24 | Object.keys(state[filePath].exportedFunctions).forEach((functionName) => { 25 | state[filePath].exportedFunctions[functionName].meta.importPath = importPath; 26 | state[filePath].exportedFunctions[functionName].meta.relativePath = relativePath; 27 | state[filePath].exportedFunctions[functionName].meta 28 | .tsBuildDir = packagedArguments.tsBuildDir; 29 | }); 30 | state[filePath].importPath = importPath; 31 | state[filePath].relativePath = relativePath; 32 | state[filePath].tsBuildDir = packagedArguments.tsBuildDir; 33 | offsetMocks(state, filePath, packagedArguments); 34 | 35 | // Generate file name from file path 36 | const fileName = filePathToFileName(filePath); 37 | 38 | // Generate tests 39 | console.log('Generating tests for ', fileName); 40 | const code = TestFileBlock({ 41 | fileName, 42 | filePath, 43 | fileData: state[filePath], 44 | packagedArguments, 45 | }); 46 | let fileString = ''; 47 | 48 | // Prettify the results 49 | try { 50 | fileString = prettier.format(code, { 51 | singleQuote: true, 52 | parser: packagedArguments.isTypescript ? 'typescript' : 'babel', 53 | }); 54 | } catch (e) { 55 | console.error(e); 56 | fileString = code; 57 | } 58 | 59 | const externalData = AggregatorManager.getExternalData(filePath); 60 | 61 | return { filePath: outputFilePath, fileString, externalData }; 62 | } catch (e) { 63 | console.error('Error tests for ', filePath); 64 | return { filePath, fileString: `'${e.stack.toString()}'`, externalData: [] }; 65 | } 66 | }); 67 | 68 | module.exports = { 69 | extractTestsFromState, 70 | }; 71 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('./cli'); 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ts-node 2 | 3 | require('./cli'); 4 | -------------------------------------------------------------------------------- /src/plugin/blacklist-generator.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/a/42658586/1217998 2 | const getBlackListForProperty = type => Object 3 | .getOwnPropertyNames(type.prototype || Object) 4 | .filter(prop => !{ caller: true, callee: true, arguments: true }[prop]) 5 | .filter((prop) => { 6 | try { 7 | return typeof type.prototype[prop] === 'function'; 8 | } catch (e) { 9 | return true; 10 | } 11 | }); 12 | 13 | const getBlackList = () => { 14 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures 15 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects 16 | const types = [ 17 | // Primitives 18 | Boolean, Number, String, /* BigInt, */ Symbol, 19 | // Object likes 20 | Object, Array, Map, Set, WeakMap, WeakSet, Date, 21 | // Arrays 22 | Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, 23 | Uint16Array, Int32Array, Uint32Array, Float32Array, 24 | Float64Array, /* BigInt64Array, BigUint64Array, */ 25 | // Function 26 | Function, 27 | // Global properties 28 | Error, RegExp, Math, 29 | // Global Structured data 30 | ArrayBuffer, SharedArrayBuffer, Atomics, DataView, JSON, 31 | // Control abstraction 32 | Promise, /* Generator, GeneratorFunction, AsyncFunction, Iterator, AsyncIterator, */ 33 | // Reflection 34 | Reflect, Proxy, 35 | // Intl 36 | Intl, 37 | // WebAssembly 38 | /* WebAssembly, */ 39 | ]; 40 | const allProperties = types.reduce((acc, type) => { 41 | const keys = getBlackListForProperty(type); 42 | const obj = keys.reduce((innerAcc, key) => ({ ...innerAcc, [key]: true }), {}); 43 | return { ...acc, ...obj }; 44 | }, {}); 45 | return allProperties; 46 | }; 47 | 48 | module.exports = { getBlackList }; 49 | -------------------------------------------------------------------------------- /src/plugin/capture-logic.test.js: -------------------------------------------------------------------------------- 1 | const { default: traverse } = require('@babel/traverse'); 2 | const parser = require('@babel/parser'); 3 | 4 | const { parserPlugins } = require('./used-plugins'); 5 | 6 | const { captureFunForDi } = require('./capture-logic'); 7 | 8 | describe('capture-logic', () => { 9 | describe('captureFunForDi', () => { 10 | it('should capture typescript exports', () => { 11 | const outerFun = 'outerFun'; 12 | const injFun = 'injFun'; 13 | const code = `exports.${outerFun} = obj => obj.${injFun}();`; 14 | const visitor = { 15 | Program: { 16 | enter() { 17 | this.injectionBlackList = {}; 18 | this.injectedFunctions = {}; 19 | this.captureFunForDi = captureFunForDi.bind(this); 20 | }, 21 | exit() { 22 | expect(this.injectedFunctions[injFun][outerFun].paths).toBeTruthy(); 23 | }, 24 | }, 25 | CallExpression(path) { 26 | this.captureFunForDi(path); 27 | }, 28 | }; 29 | const ast = parser.parse(code, { 30 | sourceType: 'module', 31 | plugins: parserPlugins, 32 | }); 33 | traverse(ast, visitor, null, {}); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/plugin/dependency-injection.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const { newFunctionNameGenerator } = require('../util/misc'); 3 | 4 | // TODO: Only reachable injections should be unclobbered 5 | function getValidInjections() { 6 | // const validFunctionLut = this.validFunctions 7 | // .reduce((acc, vf) => ({ ...acc, [vf.name]: true }), {}); 8 | const result = Object.keys(this.injectedFunctions).reduce((acc, name) => { 9 | const parentFunctionNames = Object.keys(this.injectedFunctions[name]); 10 | const innerObjs = parentFunctionNames.reduce((innerAcc, parentFunctionName) => { 11 | const { objName } = this.injectedFunctions[name][parentFunctionName]; 12 | // TODO: Why topLevelBindings? 13 | // const isParentFunctionTopLevel = this.topLevelBindings[parentFunctionName]; 14 | // const isParentObjTopLevel = this.topLevelBindings[objName]; 15 | const isParentFunctionTopLevel = this.functionsToReplace[parentFunctionName]; 16 | const isParentObjTopLevel = this.functionsToReplace[objName]; 17 | if (!isParentFunctionTopLevel && !isParentObjTopLevel) return innerAcc; 18 | const { paths } = this.injectedFunctions[name][parentFunctionName]; 19 | return innerAcc.concat([{ paths, name, parentFunctionName }]); 20 | }, []); 21 | return acc.concat(innerObjs); 22 | }, []); 23 | return result; 24 | } 25 | 26 | // Rename all the dependency injected functions 27 | // So that we do not overwrite existing function 28 | // to avoid side effects 29 | function unclobberInjections() { 30 | this.validDependencyInjections.forEach(({ name, paths }) => { 31 | const newFunctionName = newFunctionNameGenerator(name, this.fileName); 32 | // TODO: Make sure newFunctionName is not clobbering 33 | // another function 34 | paths.forEach((path) => { 35 | path.node.name = newFunctionName; 36 | }); 37 | }); 38 | } 39 | 40 | // Add these functions to meta so that recorder can pick it up 41 | function addInjectedFunctionsToMeta() { 42 | const injectionWhitelist = _.uniq(this.validDependencyInjections.map(({ name }) => name)); 43 | 44 | // Instrument injected functions 45 | this.validFunctions = this.validFunctions 46 | .map(funObj => _.merge(funObj, { injectionWhitelist })); 47 | 48 | // Instrument injected functions in exported objects 49 | Object.keys(this.capturedObjects).forEach((objName) => { 50 | const numFuns = this.capturedObjects[objName].funObjs.length; 51 | for (let i = 0; i < numFuns; i += 1) { 52 | const old = this.capturedObjects[objName].funObjs[i]; 53 | this.capturedObjects[objName].funObjs[i] = _.merge(old, { injectionWhitelist }); 54 | } 55 | }); 56 | } 57 | 58 | module.exports = { unclobberInjections, addInjectedFunctionsToMeta, getValidInjections }; 59 | -------------------------------------------------------------------------------- /src/plugin/file-meta.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const t = require('@babel/types'); 3 | const { default: template } = require('@babel/template'); 4 | const { isWhitelisted } = require('../util/misc'); 5 | 6 | const fileMetaTemplate = template(` 7 | recordFileMeta(META); 8 | `); 9 | 10 | const fileMetaGenerator = (meta) => { 11 | const { 12 | path, mocks, 13 | } = meta; 14 | return t.objectExpression([ 15 | t.objectProperty(t.identifier('path'), t.stringLiteral(path)), 16 | t.objectProperty(t.identifier('mocks'), t.arrayExpression(mocks.map(mock => t.stringLiteral(mock)))), 17 | ]); 18 | }; 19 | 20 | const getImportsToMock = (importedModules, whiteListedModules) => _.uniq( 21 | Object 22 | .keys(importedModules) 23 | .map(importedAs => importedModules[importedAs].moduleName) 24 | .filter(moduleName => isWhitelisted(moduleName, whiteListedModules)), 25 | ); 26 | 27 | function addRecordFileMeta(path) { 28 | const mocks = getImportsToMock(this.importedModules, this.whiteListedModules); 29 | const metaAst = fileMetaGenerator({ 30 | mocks, 31 | path: this.fileName, 32 | }); 33 | const ast = fileMetaTemplate({ META: metaAst }); 34 | 35 | path.pushContainer('body', ast); 36 | } 37 | 38 | module.exports = { addRecordFileMeta }; 39 | -------------------------------------------------------------------------------- /src/plugin/meta.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types'); 2 | 3 | const metaGenerator = (path, funObj) => { 4 | const { 5 | name, isAsync, paramIds, isDefault, isEcmaDefault, injectionWhitelist, isObject, 6 | } = funObj; 7 | return t.objectExpression([ 8 | t.objectProperty(t.identifier('path'), t.stringLiteral(path)), 9 | t.objectProperty(t.identifier('name'), t.stringLiteral(name)), 10 | // t.objectProperty(t.identifier('localName'), t.stringLiteral(localName)), 11 | t.objectProperty(t.identifier('paramIds'), t.arrayExpression(paramIds.map(pid => t.stringLiteral(pid)))), 12 | t.objectProperty(t.identifier('injectionWhitelist'), t.arrayExpression(injectionWhitelist.map(wl => t.stringLiteral(wl)))), 13 | t.objectProperty(t.identifier('isDefault'), t.booleanLiteral(isDefault)), 14 | t.objectProperty(t.identifier('isEcmaDefault'), t.booleanLiteral(isEcmaDefault)), 15 | t.objectProperty(t.identifier('isAsync'), t.booleanLiteral(isAsync)), 16 | t.objectProperty(t.identifier('isObject'), t.booleanLiteral(isObject)), 17 | ]); 18 | }; 19 | 20 | module.exports = { metaGenerator }; 21 | -------------------------------------------------------------------------------- /src/plugin/object-capture-logic.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const t = require('@babel/types'); 3 | const { default: template } = require('@babel/template'); 4 | const { metaGenerator } = require('./meta'); 5 | 6 | const objectInjectorTemplate = template(` 7 | const TEMP_FUN = LHS_ME; 8 | LHS_ME = (...p) => recorderWrapper(META, TEMP_FUN, ...p); 9 | `); 10 | 11 | const memberExpressionFromFqn = (fqn) => { 12 | const fqnArr = fqn.split('.').map(part => t.identifier(part)); 13 | return fqnArr.reduce((acc, next) => t.memberExpression(acc, next)); 14 | }; 15 | 16 | const generateMeAst = (path, fqn) => { 17 | const lhsAst = memberExpressionFromFqn(fqn); 18 | const rhsArr = fqn.split('.'); 19 | const tempFun = path.scope.generateUidIdentifier(rhsArr[rhsArr.length - 1]); 20 | return { lhsAst, tempFun }; 21 | }; 22 | 23 | function instrumentValidObjects() { 24 | const capturedObjectNames = Object.keys(this.capturedObjects); 25 | // this.functionsToReplace has all exported identifiers, not just functions 26 | const exportedObjects = capturedObjectNames 27 | .filter(name => this.functionsToReplace[name].isExported); 28 | exportedObjects.forEach((objName) => { 29 | const { path, funObjs } = this.capturedObjects[objName]; 30 | funObjs.forEach((funObj) => { 31 | const newFunObj = _.merge( 32 | funObj, 33 | this.functionsToReplace[objName], 34 | this.capturedObjects[objName], 35 | ); 36 | const metaAst = metaGenerator(this.fileName, newFunObj); 37 | const { lhsAst, tempFun } = generateMeAst(path, newFunObj.name); 38 | const objectInjectorAst = objectInjectorTemplate({ 39 | TEMP_FUN: tempFun, 40 | LHS_ME: lhsAst, 41 | META: metaAst, 42 | }); 43 | path.insertAfter(objectInjectorAst); 44 | 45 | // Mark that wrapper must be imported 46 | this.atLeastOneRecorderWrapperUsed = true; 47 | }); 48 | }); 49 | } 50 | 51 | // Captures the fully qualified name for all function likes 52 | // within objects 53 | const traverseProperties = (objName, objectProperties) => { 54 | const result = []; 55 | objectProperties.forEach((property) => { 56 | const functionName = property.key.name; 57 | const name = `${objName}.${functionName}`; 58 | if (t.isArrowFunctionExpression(property.value)) { 59 | const paramIds = property.value.params.map(p => p.name); 60 | const isAsync = !!property.value.async; 61 | result.push({ 62 | name, 63 | functionName, 64 | paramIds, 65 | isAsync, 66 | }); 67 | } 68 | if (t.isObjectMethod(property)) { 69 | const paramIds = property.params.map(p => p.name); 70 | const isAsync = !!property.async; 71 | result.push({ 72 | name, 73 | functionName, 74 | paramIds, 75 | isAsync, 76 | }); 77 | } 78 | if (t.isObjectExpression(property.value)) { 79 | const subObjName = `${objName}.${property.key.name}`; 80 | const { properties } = property.value; 81 | const subResult = traverseProperties(subObjName, properties); 82 | result.push(...subResult); 83 | } 84 | }); 85 | return result; 86 | }; 87 | 88 | // Capture object from object expression 89 | function captureObjFromOe(path) { 90 | const leftName = _.get(path, 'parent.left.name'); 91 | const idName = _.get(path, 'parent.id.name'); 92 | const properties = _.get(path, 'node.properties', []); 93 | const objectName = leftName || idName; 94 | if (!objectName) return; 95 | const funObjs = traverseProperties(objectName, properties); 96 | if (funObjs.length) { 97 | const grandParentPath = _.get(path, 'parentPath.parentPath'); 98 | this.capturedObjects[objectName] = { 99 | path: grandParentPath, 100 | funObjs, 101 | isObject: true, 102 | }; 103 | } 104 | } 105 | 106 | module.exports = { captureObjFromOe, instrumentValidObjects, memberExpressionFromFqn }; 107 | -------------------------------------------------------------------------------- /src/plugin/object-capture-logic.test.js: -------------------------------------------------------------------------------- 1 | const { default: generate } = require('@babel/generator'); 2 | 3 | const { memberExpressionFromFqn } = require('./object-capture-logic'); 4 | 5 | describe('object-capture-logic', () => { 6 | describe('memberExpressionFromFqn', () => { 7 | it('should generate appropriate code', () => { 8 | const ast = memberExpressionFromFqn('foo.bar.baz'); 9 | const { code } = generate(ast); 10 | expect(code).toMatchInlineSnapshot('"foo.bar.baz"'); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/plugin/used-plugins.js: -------------------------------------------------------------------------------- 1 | const parserPlugins = [ 2 | 'jsx', 3 | 'classProperties', // '@babel/plugin-proposal-class-properties', 4 | 'typescript', // '@babel/preset-typescript', 5 | ]; 6 | 7 | const generatorOptions = { 8 | retainLines: true, 9 | retainFunctionParens: true, 10 | }; 11 | 12 | module.exports = { 13 | parserPlugins, 14 | generatorOptions, 15 | }; 16 | -------------------------------------------------------------------------------- /src/plugin/wrapper-logic.js: -------------------------------------------------------------------------------- 1 | const { default: template } = require('@babel/template'); 2 | const t = require('@babel/types'); 3 | const { metaGenerator } = require('./meta'); 4 | 5 | const buildRequire = template(` 6 | const { IDENTIFIER } = require(SOURCE); 7 | `); 8 | 9 | function maybeAddImportStatement(path) { 10 | if (this.validFunctions.length || this.atLeastOneRecorderWrapperUsed) { 11 | const recorderImportStatement = buildRequire({ 12 | SOURCE: t.stringLiteral(this.importPath), 13 | IDENTIFIER: t.identifier('recorderWrapper'), 14 | }); 15 | path.unshiftContainer('body', recorderImportStatement); 16 | } 17 | 18 | if (this.atLeastOneMockUsed) { 19 | const recorderImportStatement = buildRequire({ 20 | SOURCE: t.stringLiteral(this.importPath), 21 | IDENTIFIER: t.identifier('mockRecorderWrapper'), 22 | }); 23 | path.unshiftContainer('body', recorderImportStatement); 24 | } 25 | 26 | const recorderImportStatement = buildRequire({ 27 | SOURCE: t.stringLiteral(this.importPath), 28 | IDENTIFIER: t.identifier('recordFileMeta'), 29 | }); 30 | path.unshiftContainer('body', recorderImportStatement); 31 | } 32 | 33 | function getValidFunctions() { 34 | const getExportedName = (localName) => { 35 | const { exportedAs } = this.functionsToReplace[localName]; 36 | if (exportedAs && exportedAs !== 'default') { 37 | return exportedAs; 38 | } 39 | return localName; 40 | }; 41 | 42 | // The identifier must be export and a function 43 | return Object.keys(this.functionsToReplace) 44 | .filter(localName => this.functionsToReplace[localName].isExported 45 | && this.functionsToReplace[localName].isFunction) 46 | .map(localName => ({ 47 | // localName, 48 | name: getExportedName(localName), 49 | ...this.functionsToReplace[localName], 50 | })); 51 | } 52 | 53 | const expgen = template.expression('(...p) => recorderWrapper(META, FUN_AST, ...p)'); 54 | 55 | const getAstWithWrapper = ( 56 | filePath, 57 | funObj, 58 | ) => { 59 | const functionAst = funObj.path.node; 60 | const { name: functionName, isAsync } = funObj; 61 | const meta = metaGenerator(filePath, funObj); 62 | if (functionAst.type === 'ArrowFunctionExpression') { 63 | return expgen({ 64 | META: meta, 65 | FUN_AST: t.arrowFunctionExpression(functionAst.params, functionAst.body, isAsync), 66 | }); 67 | } 68 | if (functionAst.type === 'FunctionDeclaration') { 69 | return t.variableDeclaration('const', [ 70 | t.variableDeclarator( 71 | t.identifier(functionName), 72 | expgen({ 73 | META: meta, 74 | FUN_AST: t.functionExpression( 75 | t.identifier(functionName), 76 | functionAst.params, 77 | functionAst.body, 78 | null, 79 | isAsync, 80 | ), 81 | }), 82 | ), 83 | ]); 84 | } 85 | console.error('Unknown type:', functionName, functionAst.type); 86 | return functionAst; 87 | }; 88 | 89 | function injectValidFunctions() { 90 | this.validFunctions.forEach((funObj) => { 91 | const newAst = getAstWithWrapper(this.fileName, funObj); 92 | newAst.async = funObj.isAsync; 93 | funObj.path.replaceWith(newAst); 94 | }); 95 | } 96 | 97 | module.exports = { 98 | getValidFunctions, 99 | maybeAddImportStatement, 100 | injectValidFunctions, 101 | }; 102 | -------------------------------------------------------------------------------- /src/recorder/file-meta.js: -------------------------------------------------------------------------------- 1 | const RecorderManager = require('./manager'); 2 | 3 | const recordFileMeta = (meta) => { 4 | try { 5 | // console.log(meta); 6 | const { path } = meta; 7 | const address = ['recorderState', path, 'meta']; 8 | RecorderManager.record(address, meta, { path }); 9 | } catch (e) { 10 | console.error(e); 11 | } 12 | }; 13 | 14 | module.exports = { 15 | recordFileMeta, 16 | }; 17 | -------------------------------------------------------------------------------- /src/recorder/index.js: -------------------------------------------------------------------------------- 1 | const RecorderManager = require('./manager'); 2 | const { mockRecorderWrapper } = require('./mock'); 3 | const { recorderWrapper } = require('./user-functions'); 4 | const { recordFileMeta } = require('./file-meta'); 5 | 6 | module.exports = { 7 | recorderWrapper, 8 | mockRecorderWrapper, 9 | RecorderManager, 10 | recordFileMeta, 11 | }; 12 | -------------------------------------------------------------------------------- /src/recorder/injection/di-recorder.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const RecorderManager = require('../manager'); 4 | // const { checkAndSetHash } = require('./utils/hash-helper'); 5 | const { generateTypesObj } = require('../utils/dynamic-type-inference'); 6 | 7 | const recordInjectedActivity = (captureIndex, meta, data) => { 8 | const { 9 | paramIndex, fppkey, params, result, 10 | } = data; 11 | const { 12 | path, name, paramIds, 13 | } = meta; 14 | // Fully qualified name 15 | const fqn = fppkey ? `${paramIds[paramIndex]}.${fppkey}` : paramIds[paramIndex]; 16 | const basePath = ['recorderState', path, 'exportedFunctions', name, 'captures', captureIndex, 'injections', fqn]; 17 | const destinationPath = [...basePath, 'captures']; 18 | try { 19 | // TODO: Put it back later 20 | // if (checkAndSetHash(RecorderManager, basePath, params)) { 21 | // return; 22 | // } 23 | // Record types from this capture 24 | const old = _.get(RecorderManager, destinationPath, []); 25 | const innerCaptureIndex = old.length; 26 | const addr = [...destinationPath, innerCaptureIndex]; 27 | const types = generateTypesObj({ params, result }); 28 | RecorderManager.recordTrio(addr, params, result, types); 29 | } catch (e) { 30 | console.error(e); 31 | } 32 | }; 33 | 34 | module.exports = { 35 | recordInjectedActivity, 36 | }; 37 | -------------------------------------------------------------------------------- /src/recorder/injection/di-recorder.test.js: -------------------------------------------------------------------------------- 1 | jest.mock('../manager', () => ({ 2 | recordTrio: jest.fn(), 3 | })); 4 | jest.mock('../utils/dynamic-type-inference', () => ({ 5 | generateTypesObj: () => ({ params: ['Number', 'Number'], result: 'Number' }), 6 | })); 7 | const RecorderManager = require('../manager'); 8 | const { 9 | recordInjectedActivity, 10 | } = require('./di-recorder'); 11 | 12 | describe('di-recorder', () => { 13 | describe('recordInjectedActivity', () => { 14 | beforeEach(() => { 15 | jest.clearAllMocks(); 16 | }); 17 | it('should call recordmanager correctly for objectlikes', () => { 18 | const meta = { 19 | path: 'path', name: 'name', paramIds: ['a', 'b'], 20 | }; 21 | const paramIndex = 0; 22 | const fppkey = 'foo.bar'; 23 | const params = [1, 2]; 24 | const result = 3; 25 | const captureIndex = 0; 26 | const data = { 27 | paramIndex, fppkey, params, result, 28 | }; 29 | recordInjectedActivity(captureIndex, meta, data); 30 | const addr = ['recorderState', 'path', 'exportedFunctions', 'name', 'captures', 0, 'injections', 'a.foo.bar', 'captures', 0]; 31 | const types = { params: ['Number', 'Number'], result: 'Number' }; 32 | expect(RecorderManager.recordTrio.mock.calls).toEqual([ 33 | [addr, params, result, types], 34 | ]); 35 | }); 36 | it('should call recordmanager correctly for functionlikes', () => { 37 | const meta = { 38 | path: 'path', name: 'name', paramIds: ['a', 'b'], 39 | }; 40 | const paramIndex = 0; 41 | const fppkey = null; 42 | const params = [1, 2]; 43 | const result = 3; 44 | const captureIndex = 0; 45 | const data = { 46 | paramIndex, fppkey, params, result, 47 | }; 48 | recordInjectedActivity(captureIndex, meta, data); 49 | const addr = ['recorderState', 'path', 'exportedFunctions', 'name', 'captures', 0, 'injections', 'a', 'captures', 0]; 50 | const types = { params: ['Number', 'Number'], result: 'Number' }; 51 | expect(RecorderManager.recordTrio.mock.calls).toEqual([ 52 | [addr, params, result, types], 53 | ]); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/recorder/injection/index.js: -------------------------------------------------------------------------------- 1 | const { injectDependencyInjections } = require('./param-crawler'); 2 | const { markForConstructorInjection, injectFunctionDynamically } = require('./injector'); 3 | const { recordInjectedActivity } = require('./di-recorder'); 4 | 5 | module.exports = { 6 | markForConstructorInjection, 7 | recordInjectedActivity, 8 | injectFunctionDynamically, 9 | injectDependencyInjections, 10 | }; 11 | -------------------------------------------------------------------------------- /src/recorder/injection/injector.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const isPromise = require('is-promise'); 3 | const { v4: uuidv4 } = require('uuid'); 4 | 5 | const { getNamespace } = require('../../util/cls-provider'); 6 | 7 | const RecorderManager = require('../manager'); 8 | const { shouldRecordStubParams } = require('../utils/misc'); 9 | const { recordToCls } = require('../utils/cls-recordings'); 10 | 11 | const { 12 | CLS_NAMESPACE, 13 | KEY_UUID, 14 | KEY_INJECTIONS, 15 | KEY_UUID_LUT, 16 | } = require('../../util/constants'); 17 | 18 | const markForConstructorInjection = () => { 19 | const session = getNamespace(CLS_NAMESPACE); 20 | const stack = session.get('stack'); 21 | const meta = _.last(stack); 22 | const { path, name } = meta; 23 | // No tests will be generated for this 24 | // For now 25 | const address = ['recorderState', path, 'exportedFunctions', name, 'meta', 'requiresContructorInjection']; 26 | try { 27 | RecorderManager.record(address, true); 28 | } catch (e) { 29 | console.error(e); 30 | // Do nothing? 31 | } 32 | }; 33 | 34 | 35 | const updateUuidLut = (uuid, paramIndex) => { 36 | const session = getNamespace(CLS_NAMESPACE); 37 | const stack = session.get('stack'); 38 | const top = stack.length - 1; 39 | _.set(stack, [top, KEY_UUID_LUT, uuid], paramIndex); 40 | session.set('stack', stack); 41 | }; 42 | 43 | const getTransformedParamIndex = (uuid, defaultParamIndex) => { 44 | const session = getNamespace(CLS_NAMESPACE); 45 | const stack = session.get('stack'); 46 | const meta = _.last(stack); 47 | return _.get(meta, [KEY_UUID_LUT, uuid], defaultParamIndex); 48 | }; 49 | 50 | const injectFunctionDynamically = (maybeFunction, paramIndex, fppkey) => { 51 | if (_.isFunction(maybeFunction)) { 52 | // Already injected 53 | if (maybeFunction[KEY_UUID]) { 54 | updateUuidLut(maybeFunction[KEY_UUID], paramIndex); 55 | return maybeFunction; 56 | } 57 | 58 | const OldFp = maybeFunction; 59 | // eslint-disable-next-line 60 | function injectedFunction(...paramsOfInjected) { 61 | // https://stackoverflow.com/a/31060154/1217998 62 | if (new.target) { 63 | markForConstructorInjection(); 64 | // https://stackoverflow.com/a/47469377/1217998 65 | return new OldFp(...paramsOfInjected); 66 | } 67 | const clonedParams = shouldRecordStubParams() ? _.cloneDeep(paramsOfInjected) : []; 68 | const result = OldFp.apply(this, paramsOfInjected); 69 | const funcUuid = injectedFunction[KEY_UUID]; 70 | const newParamIndex = getTransformedParamIndex(funcUuid, paramIndex); 71 | const data = { 72 | paramIndex: newParamIndex, 73 | fppkey, 74 | params: clonedParams, 75 | [KEY_UUID]: funcUuid, 76 | }; 77 | if (isPromise(result)) { 78 | result.then(res => recordToCls(KEY_INJECTIONS, { ...data, result: res })); 79 | } else { 80 | recordToCls(KEY_INJECTIONS, { ...data, result }); 81 | } 82 | return result; 83 | } 84 | injectedFunction[KEY_UUID] = uuidv4(); 85 | updateUuidLut(injectedFunction[KEY_UUID], paramIndex); 86 | return injectedFunction; 87 | } 88 | return maybeFunction; 89 | }; 90 | 91 | module.exports = { 92 | injectFunctionDynamically, 93 | markForConstructorInjection, 94 | }; 95 | -------------------------------------------------------------------------------- /src/recorder/injection/param-crawler.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const { getNamespace } = require('../../util/cls-provider'); 3 | 4 | const { traverseBfs } = require('../utils/object-traverser'); 5 | const { newFunctionNameGenerator } = require('../../util/misc'); 6 | const { injectFunctionDynamically } = require('./injector'); 7 | 8 | const { 9 | CLS_NAMESPACE, 10 | } = require('../../util/constants'); 11 | 12 | const injectDependencyInjections = (params) => { 13 | const session = getNamespace(CLS_NAMESPACE); 14 | const stack = session.get('stack'); 15 | const meta = _.last(stack); 16 | const { injectionWhitelist, path: fileName } = meta; 17 | params.forEach((param, paramIndex) => { 18 | try { 19 | // If param is an object/array with functions 20 | if (_.isObject(param) && !_.isFunction(param)) { 21 | const iterator = traverseBfs(param, injectionWhitelist); 22 | for ( 23 | let path = iterator.next().value; 24 | path !== undefined; 25 | path = iterator.next().value 26 | ) { 27 | const existingProperty = _.get(param, path); 28 | const lIndex = path.length - 1; 29 | const newFnName = newFunctionNameGenerator(path[lIndex], fileName); 30 | const newPath = _.clone(path); 31 | newPath[lIndex] = newFnName; 32 | const fppkey = path.join('.'); 33 | const propertyToInject = _.get(param, newPath, existingProperty); 34 | const injectedProperty = injectFunctionDynamically( 35 | propertyToInject, paramIndex, fppkey, 36 | ); 37 | _.set(param, newPath, injectedProperty); 38 | } 39 | } else { 40 | params[paramIndex] = injectFunctionDynamically( 41 | params[paramIndex], paramIndex, null, 42 | ); 43 | } 44 | } catch (e) { 45 | // Ignore this param 46 | console.error(e); 47 | } 48 | }); 49 | }; 50 | 51 | module.exports = { 52 | injectDependencyInjections, 53 | }; 54 | -------------------------------------------------------------------------------- /src/recorder/mock/capture.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const RecorderManager = require('../manager'); 3 | // const { checkAndSetHash } = require('./utils/hash-helper'); 4 | const { generateTypesObj } = require('../utils/dynamic-type-inference'); 5 | 6 | const captureMockActivity = (captureIndex, meta, data) => { 7 | const { name: functionName } = meta; 8 | const { mockMeta, params, result } = data; 9 | const { path, moduleName, name } = mockMeta; 10 | 11 | const pathToCapture = ['recorderState', path, 'exportedFunctions', functionName, 'captures', captureIndex]; 12 | const basePath = [...pathToCapture, 'mocks', moduleName, name]; 13 | const address = [...basePath, 'captures']; 14 | 15 | // TODO: Put it back later 16 | // if (checkAndSetHash(RecorderManager, basePath, params)) { 17 | // return; 18 | // } 19 | 20 | // Record types from this capture 21 | const types = generateTypesObj({ params, result }); 22 | const old = _.get(RecorderManager, address, []); 23 | const innerCaptureIndex = old.length; 24 | const addr = [...address, innerCaptureIndex]; 25 | RecorderManager.recordTrio(addr, params, result, types); 26 | }; 27 | 28 | module.exports = { captureMockActivity }; 29 | -------------------------------------------------------------------------------- /src/recorder/mock/capture.test.js: -------------------------------------------------------------------------------- 1 | // jest.mock('../utils/dynamic-type-inference'); 2 | jest.mock('../manager'); 3 | 4 | // const dti = require('../utils/dynamic-type-inference'); 5 | const RecorderManager = require('../manager'); 6 | 7 | const { captureMockActivity } = require('./capture'); 8 | 9 | describe('mock-capture', () => { 10 | describe('captureMockActivity', () => { 11 | beforeEach(() => { 12 | jest.resetAllMocks(); 13 | }); 14 | it('should set stack and inject functions', () => { 15 | const params = [1, 2]; 16 | const result = 3; 17 | const mockMeta = { 18 | path: 'dir1/file1.js', 19 | moduleName: 'fs', 20 | name: 'readFileSync', 21 | }; 22 | const meta = { 23 | name: 'fun1', 24 | }; 25 | const types = { 26 | params: ['Number', 'Number'], 27 | result: 'Number', 28 | }; 29 | const captureIndex = 0; 30 | const data = { mockMeta, params, result }; 31 | captureMockActivity(captureIndex, meta, data); 32 | const addr = ['recorderState', 'dir1/file1.js', 'exportedFunctions', 'fun1', 'captures', 0, 'mocks', 'fs', 'readFileSync', 'captures', 0]; 33 | expect(RecorderManager.recordTrio.mock.calls).toEqual([ 34 | [addr, params, result, types], 35 | ]); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/recorder/mock/index.js: -------------------------------------------------------------------------------- 1 | const { mockRecorderWrapper } = require('./wrapper'); 2 | 3 | module.exports = { 4 | mockRecorderWrapper, 5 | }; 6 | -------------------------------------------------------------------------------- /src/recorder/mock/wrapper.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | // const { checkAndSetHash } = require('./utils/hash-helper'); 3 | const isPromise = require('is-promise'); 4 | const { shouldRecordStubParams } = require('../utils/misc'); 5 | const { recordToCls } = require('../utils/cls-recordings'); 6 | const { KEY_MOCKS } = require('../../util/constants'); 7 | 8 | const mockRecorderWrapper = (meta, oldFp, ...p) => { 9 | const clonedParams = shouldRecordStubParams() ? _.cloneDeep(p) : []; 10 | const result = oldFp(...p); 11 | try { 12 | const data = { mockMeta: meta, params: clonedParams }; 13 | if (isPromise(result)) { 14 | result.then(res => recordToCls(KEY_MOCKS, { ...data, result: res })); 15 | } else { 16 | recordToCls(KEY_MOCKS, { ...data, result }); 17 | } 18 | } catch (e) { 19 | console.error(e); 20 | } 21 | return result; 22 | }; 23 | 24 | module.exports = { 25 | mockRecorderWrapper, 26 | }; 27 | -------------------------------------------------------------------------------- /src/recorder/user-functions/capture-logic.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const { getNamespace } = require('../../util/cls-provider'); 3 | 4 | const RecorderManager = require('../manager'); 5 | const { checkAndSetHash } = require('../utils/hash-helper'); 6 | const { generateTypesObj } = require('../utils/dynamic-type-inference'); 7 | const { 8 | recordInjectedActivity, 9 | } = require('../injection/di-recorder'); 10 | const { 11 | captureMockActivity, 12 | } = require('../mock/capture'); 13 | const { 14 | recordAllToRecorderState, 15 | promoteInjections, 16 | } = require('../utils/cls-recordings'); 17 | 18 | const { 19 | CLS_NAMESPACE, 20 | KEY_UUID, 21 | KEY_INJECTIONS, 22 | KEY_MOCKS, 23 | } = require('../../util/constants'); 24 | 25 | const processFunctionLikeParam = (param) => { 26 | // Ignore falsey types 27 | if (!param) return param; 28 | 29 | if (_.isFunction(param) && !param[KEY_UUID]) { 30 | return param.toString(); 31 | } 32 | // Will be mocked separately 33 | if (param[KEY_UUID]) return null; 34 | return param; 35 | }; 36 | 37 | const captureUserFunction = (params, result) => { 38 | const session = getNamespace(CLS_NAMESPACE); 39 | const stack = session.get('stack'); 40 | const meta = _.last(stack); 41 | 42 | const { path, name, doesReturnPromise } = meta; 43 | // Record types from this capture 44 | const types = generateTypesObj({ params, result }); 45 | 46 | // TODO: Handle higher order functions 47 | if (_.isFunction(result)) { 48 | result = result.toString(); 49 | } 50 | params = params.map(processFunctionLikeParam); 51 | const newCapture = { params, result, types }; 52 | 53 | const addrToCurrentFun = ['recorderState', path, 'exportedFunctions', name]; 54 | const addrToDoesReturnPromise = [...addrToCurrentFun, 'meta', 'doesReturnPromise']; 55 | const captures = _.get(RecorderManager, [...addrToCurrentFun, 'captures'], []); 56 | const captureIndex = captures.length; 57 | const addrToCaptureIndex = [...addrToCurrentFun, 'captures', captureIndex]; 58 | 59 | // Record if the function returned a promise 60 | RecorderManager.record(addrToDoesReturnPromise, doesReturnPromise, false); 61 | 62 | const basePath = ['recorderState', path, 'exportedFunctions', name]; 63 | if (checkAndSetHash(RecorderManager, basePath, newCapture)) { 64 | // Capture already exists 65 | RecorderManager.record(addrToCaptureIndex, null, null); 66 | // Copy all dependency injections to parent 67 | promoteInjections(); 68 | return; 69 | } 70 | 71 | RecorderManager.recordTrio(addrToCaptureIndex, params, result, types); 72 | 73 | // Record all dependency injections 74 | recordAllToRecorderState(KEY_INJECTIONS, recordInjectedActivity, captureIndex); 75 | recordAllToRecorderState(KEY_MOCKS, captureMockActivity, captureIndex); 76 | 77 | // Copy all dependency injections to parent 78 | promoteInjections(); 79 | }; 80 | 81 | module.exports = { captureUserFunction, processFunctionLikeParam }; 82 | -------------------------------------------------------------------------------- /src/recorder/user-functions/index.js: -------------------------------------------------------------------------------- 1 | const { recorderWrapper } = require('./wrapper'); 2 | 3 | module.exports = { recorderWrapper }; 4 | -------------------------------------------------------------------------------- /src/recorder/user-functions/pre.js: -------------------------------------------------------------------------------- 1 | const { getNamespace } = require('../../util/cls-provider'); 2 | const { injectDependencyInjections } = require('../injection'); 3 | const RecorderManager = require('../manager'); 4 | 5 | const { 6 | CLS_NAMESPACE, 7 | } = require('../../util/constants'); 8 | 9 | const pre = (meta, params) => { 10 | const { path, name } = meta; 11 | 12 | // Record meta 13 | const address = ['recorderState', path, 'exportedFunctions', name, 'meta']; 14 | RecorderManager.record(address, meta); 15 | 16 | // Set stack in continuation local storage 17 | const session = getNamespace(CLS_NAMESPACE); 18 | const originalStackRef = session.get('stack'); 19 | 20 | // Set stack as own meta 21 | session.set('stack', [meta]); 22 | 23 | // Set reference to parent's meta 24 | session.set('originalStackRef', originalStackRef || []); 25 | 26 | // Shim all dependency injections 27 | // Mutating call 28 | injectDependencyInjections(params); 29 | }; 30 | 31 | module.exports = { pre }; 32 | -------------------------------------------------------------------------------- /src/recorder/user-functions/pre.test.js: -------------------------------------------------------------------------------- 1 | 2 | jest.mock('../../util/cls-provider'); 3 | jest.mock('../injection'); 4 | jest.mock('../manager'); 5 | 6 | const cls = require('../../util/cls-provider'); 7 | const inj = require('../injection'); 8 | const RecorderManager = require('../manager'); 9 | 10 | const { pre } = require('./pre'); 11 | 12 | describe('user-function-pre', () => { 13 | describe('pre', () => { 14 | beforeEach(() => { 15 | jest.resetAllMocks(); 16 | }); 17 | it('should set stack and inject functions', () => { 18 | const params = [1, 2]; 19 | const meta = { 20 | path: 'dir1/file1.js', 21 | name: 'fun1', 22 | }; 23 | const set = jest.fn(); 24 | const get = jest.fn().mockReturnValue(undefined); 25 | cls.getNamespace.mockImplementation(() => ({ set, get })); 26 | pre(meta, params); 27 | const addrToDoesReturnPromise = ['recorderState', 'dir1/file1.js', 'exportedFunctions', 'fun1', 'meta']; 28 | expect(RecorderManager.record.mock.calls).toEqual([ 29 | [addrToDoesReturnPromise, meta], 30 | ]); 31 | expect(set.mock.calls).toEqual([ 32 | ['stack', [meta]], 33 | ['originalStackRef', []], 34 | ]); 35 | expect(inj.injectDependencyInjections.mock.calls).toEqual([ 36 | [params], 37 | ]); 38 | }); 39 | it('should stack on meta', () => { 40 | const params = [1, 2]; 41 | const meta = { 42 | path: 'dir1/file1.js', 43 | name: 'fun1', 44 | }; 45 | const oldMeta = { 46 | path: 'dir1/file1.js', 47 | name: 'fun2', 48 | }; 49 | const set = jest.fn(); 50 | const get = jest.fn().mockReturnValue([oldMeta]); 51 | cls.getNamespace.mockImplementation(() => ({ set, get })); 52 | pre(meta, params); 53 | const addrToDoesReturnPromise = ['recorderState', 'dir1/file1.js', 'exportedFunctions', 'fun1', 'meta']; 54 | expect(RecorderManager.record.mock.calls).toEqual([ 55 | [addrToDoesReturnPromise, meta], 56 | ]); 57 | expect(set.mock.calls).toEqual([ 58 | ['stack', [meta]], 59 | ['originalStackRef', [oldMeta]], 60 | ]); 61 | expect(inj.injectDependencyInjections.mock.calls).toEqual([ 62 | [params], 63 | ]); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/recorder/user-functions/wrapper.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const isPromise = require('is-promise'); 3 | const { createNamespace } = require('../../util/cls-provider'); 4 | 5 | const { captureUserFunction } = require('./capture-logic'); 6 | const { pre } = require('./pre'); 7 | 8 | const { 9 | CLS_NAMESPACE, 10 | } = require('../../util/constants'); 11 | 12 | 13 | const session = createNamespace(CLS_NAMESPACE); 14 | 15 | const recorderWrapper = (meta, innerFunction, ...params) => { 16 | const originalParams = _.cloneDeep(params); 17 | try { 18 | pre(meta, params); 19 | } catch (e) { 20 | console.error(e); 21 | // Dont try to record if pre fails 22 | return innerFunction(...originalParams); 23 | } 24 | // If the function itself throws exception then 25 | // the user should handle it 26 | const result = innerFunction(...params); 27 | try { 28 | const stack = session.get('stack'); 29 | const top = stack.length - 1; 30 | 31 | if (isPromise(result)) { 32 | stack[top].doesReturnPromise = true; 33 | session.set('stack', stack); 34 | result.then(res => captureUserFunction(originalParams, res)); 35 | } else { 36 | stack[top].doesReturnPromise = false; 37 | session.set('stack', stack); 38 | captureUserFunction(originalParams, result); 39 | } 40 | } catch (e) { 41 | console.error(e); 42 | } 43 | 44 | return result; 45 | }; 46 | 47 | const boundRecorderWrapper = (...p) => session.bind(recorderWrapper, session.createContext())(...p); 48 | 49 | module.exports = { 50 | recorderWrapper: boundRecorderWrapper, 51 | unBoundRecorderWrapper: recorderWrapper, // For testing 52 | }; 53 | -------------------------------------------------------------------------------- /src/recorder/utils/cls-recordings.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const { getNamespace } = require('../../util/cls-provider'); 3 | 4 | const { 5 | CLS_NAMESPACE, 6 | KEY_UUID, 7 | KEY_INJECTIONS, 8 | KEY_MOCKS, 9 | KEY_UUID_LUT, 10 | } = require('../../util/constants'); 11 | 12 | const recordToCls = (key, data) => { 13 | const session = getNamespace(CLS_NAMESPACE); 14 | if (!session.active) return; 15 | 16 | const stack = session.get('stack'); 17 | const top = stack.length - 1; 18 | const injections = stack[top][key] || []; 19 | injections.push(data); 20 | stack[top][key] = injections; 21 | session.set('stack', stack); 22 | }; 23 | 24 | const recordAllToRecorderState = (key, recorderFunction, captureIndex) => { 25 | const session = getNamespace(CLS_NAMESPACE); 26 | const stack = session.get('stack'); 27 | const top = _.last(stack); 28 | const injections = top[key] || []; 29 | const meta = _.omit(top, [KEY_INJECTIONS, KEY_MOCKS]); 30 | 31 | injections.forEach((data) => { 32 | recorderFunction(captureIndex, meta, data); 33 | }); 34 | }; 35 | 36 | const promoteInjections = () => { 37 | // Parent needs all the injections recorded by child 38 | const session = getNamespace(CLS_NAMESPACE); 39 | const stack = session.get('stack'); 40 | const originalStackRef = session.get('originalStackRef'); 41 | 42 | const selfRef = _.last(stack); 43 | const parentRef = _.last(originalStackRef); 44 | 45 | // If self is parent then do nothing 46 | if (selfRef === parentRef) return; 47 | 48 | // If there is no parent, then do nothing 49 | if (!parentRef) return; 50 | 51 | const promoteInner = (key, useLut) => { 52 | const childInjections = selfRef[key] || []; 53 | const parentInjections = parentRef[key] || []; 54 | const lut = parentRef[KEY_UUID_LUT] || {}; 55 | 56 | // Align the paramIndex from child to parent 57 | const crossCorrelatedChildren = useLut ? childInjections 58 | .map(cInj => ({ 59 | ...cInj, 60 | paramIndex: lut[cInj[KEY_UUID]], 61 | })) 62 | .filter(cInj => cInj.paramIndex !== undefined) : childInjections; 63 | 64 | const newInjections = parentInjections.concat(crossCorrelatedChildren); 65 | parentRef[key] = newInjections; 66 | }; 67 | 68 | promoteInner(KEY_INJECTIONS, true); 69 | promoteInner(KEY_MOCKS, false); 70 | 71 | 72 | // Set the updated reference as stack 73 | session.set('stack', originalStackRef); 74 | }; 75 | 76 | module.exports = { 77 | recordToCls, 78 | recordAllToRecorderState, 79 | promoteInjections, 80 | }; 81 | -------------------------------------------------------------------------------- /src/recorder/utils/dynamic-type-inference.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | // https://github.com/lodash/lodash/issues/1845#issuecomment-339773840 4 | const inferTypeOfObject = (obj) => { 5 | try { 6 | if (_.isArray(obj)) return 'Array'; 7 | 8 | const matches = Object.prototype.toString.call(obj).match(/\s([a-zA-Z]+)/); 9 | if (matches === null) return matches; 10 | const inferedType = matches[1]; 11 | const isUnknownObject = ['Date', 'Function'].indexOf(inferedType) === -1 && _.isObjectLike(obj); 12 | return isUnknownObject ? 'Object' : inferedType; 13 | } catch (e) { 14 | console.error(e); 15 | return null; 16 | } 17 | }; 18 | 19 | const generateTypesObj = (capture) => { 20 | const params = capture.params.map(p => inferTypeOfObject(p)); 21 | const result = inferTypeOfObject(capture.result); 22 | return { params, result }; 23 | }; 24 | 25 | module.exports = { 26 | inferTypeOfObject, 27 | generateTypesObj, 28 | }; 29 | -------------------------------------------------------------------------------- /src/recorder/utils/dynamic-type-inference.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | inferTypeOfObject, 3 | generateTypesObj, 4 | } = require('./dynamic-type-inference'); 5 | 6 | describe('dynamic-type-inference', () => { 7 | describe('inferTypeOfObject', () => { 8 | it('should infer type for date', () => { 9 | const actual = inferTypeOfObject(new Date()); 10 | expect(actual).toEqual('Date'); 11 | }); 12 | it('should read past Symbol.toStringTag', () => { 13 | // https://stackoverflow.com/a/59666904/1217998 14 | const obj = { 15 | get [Symbol.toStringTag]() { 16 | return 'Person'; 17 | }, 18 | }; 19 | const actual = inferTypeOfObject(obj); 20 | expect(actual).toEqual('Object'); 21 | }); 22 | }); 23 | describe('generateTypesObj', () => { 24 | it('should infer type for date', () => { 25 | const capture = { 26 | params: [ 27 | () => {}, 28 | 1, 29 | null, 30 | ], 31 | result: { a: 1 }, 32 | }; 33 | const expected = { 34 | params: ['Function', 'Number', 'Null'], 35 | result: 'Object', 36 | }; 37 | const actual = generateTypesObj(capture); 38 | expect(actual).toEqual(expected); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/recorder/utils/hash-helper.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const _ = require('lodash'); 3 | const { safeStringify } = require('./manager-helpers'); 4 | 5 | const generateHashForParam = (params) => { 6 | // TODO: safeStringify converts functions to null 7 | const str = safeStringify(params); 8 | const hash = crypto.createHash('md5').update(str).digest('base64'); 9 | return hash; 10 | }; 11 | 12 | const checkAndSetHash = (RecorderManager, basePath, params) => { 13 | const hashTablePath = [...basePath, 'hashTable']; 14 | 15 | if (!_.get(RecorderManager, hashTablePath)) { 16 | _.set(RecorderManager, hashTablePath, {}); 17 | } 18 | const hash = generateHashForParam(params); 19 | const pathWithHash = [...hashTablePath, hash]; 20 | if (_.get(RecorderManager, pathWithHash)) { 21 | return true; 22 | } 23 | _.set(RecorderManager, pathWithHash, true); 24 | return false; 25 | }; 26 | 27 | module.exports = { 28 | generateHashForParam, 29 | checkAndSetHash, 30 | }; 31 | -------------------------------------------------------------------------------- /src/recorder/utils/hash-helper.test.js: -------------------------------------------------------------------------------- 1 | const { generateHashForParam } = require('./hash-helper'); 2 | 3 | describe('hash-helper', () => { 4 | describe('generateHashForParam', () => { 5 | it('should generate hash for serializeable params', () => { 6 | const params = [1, 2, 'a', 'b']; 7 | expect(generateHashForParam(params)).toMatchInlineSnapshot( 8 | '"bcenZxMdEYCh1WrmLQe3aA=="', 9 | ); 10 | }); 11 | it('should generate hash for non-serializeable params', () => { 12 | const obj = { a: 1 }; 13 | obj.obj = obj; 14 | const params = [obj]; 15 | expect(generateHashForParam(params)).toMatchInlineSnapshot( 16 | '"doPDmaA/Mx07jt0IoImW9A=="', 17 | ); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/recorder/utils/misc.js: -------------------------------------------------------------------------------- 1 | const shouldRecordStubParams = () => { 2 | try { 3 | return !!JSON.parse(process.env.UTR_RECORD_STUB_PARAMS); 4 | } catch (e) { 5 | return false; 6 | } 7 | }; 8 | 9 | module.exports = { shouldRecordStubParams }; 10 | -------------------------------------------------------------------------------- /src/recorder/utils/misc.test.js: -------------------------------------------------------------------------------- 1 | const { shouldRecordStubParams } = require('./misc'); 2 | 3 | describe('recorder.misc', () => { 4 | describe('shouldRecordStubParams', () => { 5 | it('should work if boolean like', () => { 6 | process.env.UTR_RECORD_STUB_PARAMS = true; 7 | expect(shouldRecordStubParams()).toBe(true); 8 | process.env.UTR_RECORD_STUB_PARAMS = false; 9 | expect(shouldRecordStubParams()).toBe(false); 10 | }); 11 | it('should work if string like', () => { 12 | process.env.UTR_RECORD_STUB_PARAMS = 'true'; 13 | expect(shouldRecordStubParams()).toBe(true); 14 | process.env.UTR_RECORD_STUB_PARAMS = 'false'; 15 | expect(shouldRecordStubParams()).toBe(false); 16 | }); 17 | it('should work if undefined', () => { 18 | process.env.UTR_RECORD_STUB_PARAMS = undefined; 19 | expect(shouldRecordStubParams()).toBe(false); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/util/cls-provider/async-hook-provider.js: -------------------------------------------------------------------------------- 1 | const { AsyncLocalStorage } = require('async_hooks'); 2 | 3 | const namespaces = {}; 4 | process.namespaces = namespaces; 5 | 6 | function Namespace(name) { 7 | this.asyncLocalStorage = new AsyncLocalStorage(); 8 | this.name = name; 9 | Object.defineProperties(this, { 10 | active: { get() { return !!this.asyncLocalStorage.getStore(); } }, 11 | }); 12 | this.run = (fn) => { 13 | const oldStore = this.asyncLocalStorage.getStore(); 14 | const store = Object.create(oldStore || {}); 15 | this.asyncLocalStorage.run(store || {}, fn); 16 | }; 17 | this.bind = (fn) => { 18 | const newFn = (...p) => { 19 | const oldStore = this.asyncLocalStorage.getStore(); 20 | const store = Object.create(oldStore || {}); 21 | return this.asyncLocalStorage.run(store || {}, () => fn(...p)); 22 | }; 23 | return newFn; 24 | }; 25 | this.get = (key) => { 26 | const store = this.asyncLocalStorage.getStore(); 27 | return store ? store[key] : undefined; 28 | }; 29 | this.set = (key, value) => { 30 | const store = this.asyncLocalStorage.getStore(); 31 | if (!store) { throw new Error('No active context'); } 32 | store[key] = value; 33 | }; 34 | this.createContext = () => ({}); // No operation 35 | } 36 | 37 | const createNamespace = (name) => { 38 | const ns = new Namespace(name); 39 | namespaces[name] = ns; 40 | return ns; 41 | }; 42 | 43 | const getNamespace = name => namespaces[name]; 44 | 45 | module.exports = { 46 | getNamespace, 47 | createNamespace, 48 | }; 49 | -------------------------------------------------------------------------------- /src/util/cls-provider/async-hook-provider.test.js: -------------------------------------------------------------------------------- 1 | const { createNamespace } = require('./async-hook-provider'); 2 | 3 | describe('async-hook-provider', () => { 4 | it('should nest contexts correctly', async () => { 5 | const session = createNamespace('default'); 6 | 7 | const fun2 = async () => { 8 | session.set('fun2', true); 9 | expect(session.get('fun1')).toEqual(true); 10 | expect(session.get('fun2')).toEqual(true); 11 | }; 12 | 13 | const boundFun2 = (...p) => session.bind(fun2, session.createContext())(...p); 14 | 15 | const fun1 = async () => { 16 | session.set('fun1', true); 17 | await boundFun2(); 18 | expect(session.get('fun1')).toEqual(true); 19 | expect(session.get('fun2')).toEqual(undefined); 20 | }; 21 | 22 | const f1 = (...p) => session.bind(fun1, session.createContext())(...p); 23 | 24 | await f1(); 25 | }); 26 | it('should not double bind', async () => { 27 | const session = createNamespace('default'); 28 | 29 | const pushSelf = (name) => { 30 | const stack = session.get('stack') || []; 31 | stack.push(name); 32 | session.set('stack', stack); 33 | }; 34 | 35 | const fun1 = async () => { 36 | pushSelf('fun1'); 37 | expect(session.get('stack')).toEqual(['fun1']); 38 | }; 39 | 40 | const f1 = (...p) => session.bind(fun1, session.createContext())(...p); 41 | const ff1 = (...p) => session.bind(f1)(...p); 42 | 43 | await ff1(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/util/cls-provider/index.js: -------------------------------------------------------------------------------- 1 | const clsHooked = require('cls-hooked'); 2 | const als = require('./async-hook-provider'); 3 | 4 | module.exports = { 5 | true: als, 6 | false: clsHooked, 7 | }[process.env.UTR_EXPERIMENTAL_ALS || 'false']; 8 | -------------------------------------------------------------------------------- /src/util/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // CLS namespace name 3 | CLS_NAMESPACE: 'CLS_NAMESPACE', 4 | 5 | // Common keys 6 | KEY_UUID: 'UTR_UUID', 7 | KEY_INJECTIONS: 'injections', 8 | KEY_MOCKS: 'mocks', 9 | KEY_UUID_LUT: 'uuidLut', 10 | }; 11 | -------------------------------------------------------------------------------- /src/util/misc.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const getTestFileNameForFile = (filePath, packagedArguments) => { 4 | const { testExt, isTypescript } = packagedArguments; 5 | const ext = isTypescript ? 'ts' : 'js'; 6 | return filePath.replace(/\.[jt]s/, `.${testExt}.${ext}`); 7 | }; 8 | 9 | // TODO: Make sure it doesnt clober any existing functions of this object 10 | const newFunctionNameGenerator = (functionName, fileName) => _.camelCase(`${fileName}.${functionName}`); 11 | 12 | const isWhitelisted = (moduleName, whiteListedModules) => { 13 | if (`${moduleName}`.startsWith('.')) return true; 14 | if (whiteListedModules[moduleName]) return true; 15 | return false; 16 | }; 17 | 18 | module.exports = { 19 | getTestFileNameForFile, 20 | newFunctionNameGenerator, 21 | isWhitelisted, 22 | }; 23 | -------------------------------------------------------------------------------- /src/util/misc.test.js: -------------------------------------------------------------------------------- 1 | const { getTestFileNameForFile } = require('./misc'); 2 | 3 | describe('misc', () => { 4 | describe('getTestFileNameForFile', () => { 5 | it('should work for windows files', () => { 6 | const input = 'C:\\foo\\bar.js'; 7 | const expected = 'C:\\foo\\bar.test.js'; 8 | const testExt = 'test'; 9 | 10 | expect(getTestFileNameForFile(input, { testExt })).toEqual(expected); 11 | }); 12 | it('should work for unix files', () => { 13 | const input = '/usr/Desktop/foo/bar.js'; 14 | const expected = '/usr/Desktop/foo/bar.spec.js'; 15 | const testExt = 'spec'; 16 | 17 | expect(getTestFileNameForFile(input, { testExt })).toEqual(expected); 18 | }); 19 | it('should work for typescript', () => { 20 | const input = '/usr/Desktop/foo/bar.ts'; 21 | const expected = '/usr/Desktop/foo/bar.spec.ts'; 22 | const testExt = 'spec'; 23 | 24 | expect(getTestFileNameForFile(input, { testExt, isTypescript: true })).toEqual(expected); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/util/walker.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | // TODO: Ignore all directories listed in .gitignore 5 | const checkIfDirectoryShouldBeIgnored = fullPath => !!fullPath 6 | .match(/node_modules/); 7 | 8 | // TODO: Ignore all directories listed in .gitignore 9 | const checkIfFileShouldBeIgnored = (fullPath) => { 10 | const hasJsExtension = fullPath.trim().match(/\.jsx?$/); 11 | const isTestFile = fullPath.trim().match(/(test.jsx?|spec.jsx?)/); 12 | 13 | return !(hasJsExtension && !isTestFile); 14 | }; 15 | 16 | const walkHelper = (rootDir, allFiles = [], cifsbi) => { 17 | const files = fs.readdirSync(rootDir); 18 | files.forEach((file) => { 19 | const fullPath = path.join(rootDir, file); 20 | if (fs.statSync(fullPath).isDirectory()) { 21 | if (!checkIfDirectoryShouldBeIgnored(fullPath)) { 22 | walkHelper(fullPath, allFiles, cifsbi); 23 | } 24 | } else if (!cifsbi(fullPath)) { 25 | allFiles.push(fullPath); 26 | } 27 | }); 28 | return allFiles; 29 | }; 30 | 31 | const walk = (rootDir, cifsbi = checkIfFileShouldBeIgnored) => { 32 | const allFiles = walkHelper(rootDir, [], cifsbi); 33 | // Dont use windows style paths 34 | const rectifiedPaths = allFiles.map(p => p.replace(/\\/g, '/')); 35 | return rectifiedPaths; 36 | }; 37 | 38 | const filterFiles = (packagedArguments, allFiles) => { 39 | const { entryPoint, exceptFiles, onlyFiles } = packagedArguments; 40 | 41 | const matchesAny = (val, arr) => arr 42 | .reduce((acc, next) => acc || val.match(new RegExp(next)), false); 43 | 44 | return allFiles.filter((fileName) => { 45 | const isBlacklisted = matchesAny(fileName, exceptFiles.concat(entryPoint)); 46 | if (isBlacklisted) return false; 47 | const isWhitelisted = matchesAny(fileName, onlyFiles); 48 | // if whitelist is empty then allow all 49 | return isWhitelisted || !onlyFiles.length; 50 | }); 51 | }; 52 | 53 | module.exports = { 54 | walk, 55 | filterFiles, 56 | checkIfDirectoryShouldBeIgnored, 57 | checkIfFileShouldBeIgnored, 58 | }; 59 | -------------------------------------------------------------------------------- /src/util/walker.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | checkIfDirectoryShouldBeIgnored, 3 | checkIfFileShouldBeIgnored, 4 | filterFiles, 5 | } = require('./walker'); 6 | 7 | describe('walker', () => { 8 | describe('checkIfDirectoryShouldBeIgnored', () => { 9 | it('should ignore node_modules', () => { 10 | expect(checkIfDirectoryShouldBeIgnored('node_modules/blah')).toBeTruthy(); 11 | }); 12 | }); 13 | describe('checkIfFileShouldBeIgnored', () => { 14 | it('should accept js and jsx files', () => { 15 | expect(checkIfFileShouldBeIgnored('/asdasd/blah.js')).toBeFalsy(); 16 | expect(checkIfFileShouldBeIgnored('/asdasd/blah.jsx')).toBeFalsy(); 17 | expect(checkIfFileShouldBeIgnored('test\\walking_test\\a.js')).toBeFalsy(); 18 | expect(checkIfFileShouldBeIgnored('C:\\Users\\username\\Desktop\\i18ize\\captive-app\\src\\App.js')).toBeFalsy(); 19 | }); 20 | it('should reject spec and test files', () => { 21 | expect(checkIfFileShouldBeIgnored('a.test.js')).toBeTruthy(); 22 | expect(checkIfFileShouldBeIgnored('b.spec.js')).toBeTruthy(); 23 | }); 24 | it('should reject all other files', () => { 25 | expect(checkIfFileShouldBeIgnored('not_a_virus.exe')).toBeTruthy(); 26 | }); 27 | }); 28 | describe('filterFiles', () => { 29 | it('should filter out entrypoint', () => { 30 | const packagedArguments = { 31 | entryPoint: 'entrypoint.js', 32 | exceptFiles: [], 33 | onlyFiles: [], 34 | }; 35 | const allFiles = ['entrypoint.js', 'dir1/file1.js', 'dir2/file2.js']; 36 | const expected = ['dir1/file1.js', 'dir2/file2.js']; 37 | expect(filterFiles(packagedArguments, allFiles)).toEqual(expected); 38 | }); 39 | it('should filter out except files', () => { 40 | const packagedArguments = { 41 | entryPoint: 'entrypoint.js', 42 | exceptFiles: ['dir1/file2.js', 'dir2/file2.js'], 43 | onlyFiles: [], 44 | }; 45 | const allFiles = ['entrypoint.js', 'dir1/file1.js', 'dir1/file2.js', 'dir2/file2.js']; 46 | const expected = ['dir1/file1.js']; 47 | expect(filterFiles(packagedArguments, allFiles)).toEqual(expected); 48 | }); 49 | it('should filter in only files', () => { 50 | const packagedArguments = { 51 | entryPoint: 'entrypoint.js', 52 | exceptFiles: [], 53 | onlyFiles: ['dir1/file2.js', 'dir2/file2.js'], 54 | }; 55 | const allFiles = ['entrypoint.js', 'dir1/file1.js', 'dir1/file2.js', 'dir2/file2.js']; 56 | const expected = ['dir1/file2.js', 'dir2/file2.js']; 57 | expect(filterFiles(packagedArguments, allFiles)).toEqual(expected); 58 | }); 59 | it('should prioritize except over only', () => { 60 | const packagedArguments = { 61 | entryPoint: 'entrypoint.js', 62 | exceptFiles: ['dir2/file2.js'], 63 | onlyFiles: ['dir1/file2.js', 'dir2/file2.js'], 64 | }; 65 | const allFiles = ['entrypoint.js', 'dir1/file1.js', 'dir1/file2.js', 'dir2/file2.js']; 66 | const expected = ['dir1/file2.js']; 67 | expect(filterFiles(packagedArguments, allFiles)).toEqual(expected); 68 | }); 69 | it('should should support regex', () => { 70 | const packagedArguments = { 71 | entryPoint: 'entrypoint.js', 72 | exceptFiles: [], 73 | onlyFiles: ['dir1/*', '.*file4.js'], 74 | }; 75 | const allFiles = ['entrypoint.js', 'dir1/file1.js', 'dir1/file2.js', 'dir2/file2.js', 'dir3/file4.js']; 76 | const expected = ['dir1/file1.js', 'dir1/file2.js', 'dir3/file4.js']; 77 | expect(filterFiles(packagedArguments, allFiles)).toEqual(expected); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test_integration/flows/01_module_exports/01_module_exports.js: -------------------------------------------------------------------------------- 1 | const foo1 = (a, b) => a + b; 2 | 3 | function bar(a, b) { 4 | const c = a - b; 5 | return c; 6 | } 7 | 8 | const SOME_CONSTANT = 42; 9 | 10 | const specialParams = (a, { b, c }, d = 1) => a + b + c + d; 11 | 12 | function specialParams2(a, { b, c }, d = 1) { 13 | return a + b + c + d; 14 | } 15 | 16 | module.exports = { 17 | foo: foo1, 18 | SOME_CONSTANT, 19 | bar, 20 | specialParams, 21 | specialParams2, 22 | }; 23 | -------------------------------------------------------------------------------- /test_integration/flows/01_module_exports/01_module_exports_generated.test.js: -------------------------------------------------------------------------------- 1 | const { bar } = require('./01_module_exports'); 2 | const { foo } = require('./01_module_exports'); 3 | const { specialParams } = require('./01_module_exports'); 4 | const { specialParams2 } = require('./01_module_exports'); 5 | 6 | describe('01_module_exports', () => { 7 | describe('bar', () => { 8 | it('should work for case 1', () => { 9 | let a = 2; 10 | let b = 2; 11 | let result = 0; 12 | 13 | const actual = bar(a, b); 14 | expect(actual).toEqual(result); 15 | }); 16 | }); 17 | 18 | describe('foo', () => { 19 | it('should work for case 1', () => { 20 | let a = 1; 21 | let b = 2; 22 | let result = 3; 23 | 24 | const actual = foo(a, b); 25 | expect(actual).toEqual(result); 26 | }); 27 | 28 | it('should work for case 2', () => { 29 | let a = 'A'; 30 | let b = 'B'; 31 | let result = 'AB'; 32 | 33 | const actual = foo(a, b); 34 | expect(actual).toEqual(result); 35 | }); 36 | }); 37 | 38 | describe('specialParams', () => { 39 | it('should work for case 1', () => { 40 | let a = 1; 41 | let _obj = { 42 | b: 1, 43 | c: 1 44 | }; 45 | let d = undefined; 46 | let result = 4; 47 | 48 | const actual = specialParams(a, _obj, d); 49 | expect(actual).toEqual(result); 50 | }); 51 | 52 | it('should work for case 2', () => { 53 | let a = 1; 54 | let _obj = { 55 | b: 1, 56 | c: 1 57 | }; 58 | let d = 2; 59 | let result = 5; 60 | 61 | const actual = specialParams(a, _obj, d); 62 | expect(actual).toEqual(result); 63 | }); 64 | }); 65 | 66 | describe('specialParams2', () => { 67 | it('should work for case 1', () => { 68 | let a = 1; 69 | let _obj2 = { 70 | b: 1, 71 | c: 1 72 | }; 73 | let d = undefined; 74 | let result = 4; 75 | 76 | const actual = specialParams2(a, _obj2, d); 77 | expect(actual).toEqual(result); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test_integration/flows/01_module_exports/01_module_exports_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const foo1 = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 'test_integration/flows/01_module_exports/01_module_exports.js', 7 | name: 'foo', 8 | paramIds: ['a', 'b'], 9 | injectionWhitelist: [], 10 | isDefault: false, 11 | isEcmaDefault: false, 12 | isAsync: false, 13 | isObject: false 14 | }, 15 | (a, b) => a + b, 16 | ...p 17 | ); 18 | const bar = (...p) => 19 | recorderWrapper( 20 | { 21 | path: 'test_integration/flows/01_module_exports/01_module_exports.js', 22 | name: 'bar', 23 | paramIds: ['a', 'b'], 24 | injectionWhitelist: [], 25 | isDefault: false, 26 | isEcmaDefault: false, 27 | isAsync: false, 28 | isObject: false 29 | }, 30 | function bar(a, b) { 31 | const c = a - b; 32 | return c; 33 | }, 34 | ...p 35 | ); 36 | 37 | const SOME_CONSTANT = 42; 38 | 39 | const specialParams = (...p) => 40 | recorderWrapper( 41 | { 42 | path: 'test_integration/flows/01_module_exports/01_module_exports.js', 43 | name: 'specialParams', 44 | paramIds: ['a', '_obj', 'd'], 45 | injectionWhitelist: [], 46 | isDefault: false, 47 | isEcmaDefault: false, 48 | isAsync: false, 49 | isObject: false 50 | }, 51 | (a, { b, c }, d = 1) => a + b + c + d, 52 | ...p 53 | ); 54 | const specialParams2 = (...p) => 55 | recorderWrapper( 56 | { 57 | path: 'test_integration/flows/01_module_exports/01_module_exports.js', 58 | name: 'specialParams2', 59 | paramIds: ['a', '_obj2', 'd'], 60 | injectionWhitelist: [], 61 | isDefault: false, 62 | isEcmaDefault: false, 63 | isAsync: false, 64 | isObject: false 65 | }, 66 | function specialParams2(a, { b, c }, d = 1) { 67 | return a + b + c + d; 68 | }, 69 | ...p 70 | ); 71 | 72 | module.exports = { 73 | foo: foo1, 74 | SOME_CONSTANT, 75 | bar, 76 | specialParams, 77 | specialParams2 78 | }; 79 | recordFileMeta({ 80 | path: 'test_integration/flows/01_module_exports/01_module_exports.js', 81 | mocks: [] 82 | }); 83 | -------------------------------------------------------------------------------- /test_integration/flows/02_async_functions/02_async_functions.js: -------------------------------------------------------------------------------- 1 | const mocksios = (email, request) => new Promise((resolve) => { 2 | setTimeout(() => { 3 | resolve({ email, request }); 4 | }, 1); 5 | }); 6 | 7 | const getSocialInfo = async (email) => { 8 | const facebookInfo = await mocksios(email, 'facebook'); 9 | const twitterInfo = await mocksios(email, 'twitter'); 10 | 11 | return { facebookInfo, twitterInfo }; 12 | }; 13 | 14 | const getFacebookInfoHelper = email => mocksios(email, 'facebook'); 15 | 16 | module.exports.getFacebookInfo = getFacebookInfoHelper; 17 | module.exports.getSocialInfo = getSocialInfo; 18 | -------------------------------------------------------------------------------- /test_integration/flows/02_async_functions/02_async_functions_activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_integration/flows/02_async_functions/02_async_functions.js": { 3 | "exportedFunctions": { 4 | "getFacebookInfo": { 5 | "captures": [ 6 | { 7 | "params": [ 8 | "email" 9 | ], 10 | "result": { 11 | "email": "email", 12 | "request": "facebook" 13 | }, 14 | "types": { 15 | "params": [ 16 | "String" 17 | ], 18 | "result": "Object" 19 | } 20 | } 21 | ], 22 | "hashTable": { 23 | "QH3HU2B26SG4xehO+jKMUA==": true 24 | }, 25 | "meta": { 26 | "doesReturnPromise": true, 27 | "injectionWhitelist": [ 28 | ], 29 | "isAsync": false, 30 | "isDefault": false, 31 | "isEcmaDefault": false, 32 | "isObject": false, 33 | "name": "getFacebookInfo", 34 | "paramIds": [ 35 | "email" 36 | ], 37 | "path": "test_integration/flows/02_async_functions/02_async_functions.js" 38 | } 39 | }, 40 | "getSocialInfo": { 41 | "captures": [ 42 | { 43 | "params": [ 44 | "email" 45 | ], 46 | "result": { 47 | "facebookInfo": { 48 | "email": "email", 49 | "request": "facebook" 50 | }, 51 | "twitterInfo": { 52 | "email": "email", 53 | "request": "twitter" 54 | } 55 | }, 56 | "types": { 57 | "params": [ 58 | "String" 59 | ], 60 | "result": "Object" 61 | } 62 | } 63 | ], 64 | "hashTable": { 65 | "ivhA6obL4h2UYHkzRpB/Pw==": true 66 | }, 67 | "meta": { 68 | "doesReturnPromise": true, 69 | "injectionWhitelist": [ 70 | ], 71 | "isAsync": true, 72 | "isDefault": false, 73 | "isEcmaDefault": false, 74 | "isObject": false, 75 | "name": "getSocialInfo", 76 | "paramIds": [ 77 | "email" 78 | ], 79 | "path": "test_integration/flows/02_async_functions/02_async_functions.js" 80 | } 81 | } 82 | }, 83 | "meta": { 84 | "mocks": [ 85 | ], 86 | "path": "test_integration/flows/02_async_functions/02_async_functions.js" 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /test_integration/flows/02_async_functions/02_async_functions_generated.test.js: -------------------------------------------------------------------------------- 1 | const { getFacebookInfo } = require('./02_async_functions'); 2 | const { getSocialInfo } = require('./02_async_functions'); 3 | 4 | describe('02_async_functions', () => { 5 | describe('getFacebookInfo', () => { 6 | it('should work for case 1', async () => { 7 | let email = 'email'; 8 | let result = { 9 | email: 'email', 10 | request: 'facebook' 11 | }; 12 | 13 | const actual = await getFacebookInfo(email); 14 | expect(actual).toMatchObject(result); 15 | }); 16 | }); 17 | 18 | describe('getSocialInfo', () => { 19 | it('should work for case 1', async () => { 20 | let email = 'email'; 21 | let result = { 22 | facebookInfo: { 23 | email: 'email', 24 | request: 'facebook' 25 | }, 26 | twitterInfo: { 27 | email: 'email', 28 | request: 'twitter' 29 | } 30 | }; 31 | 32 | const actual = await getSocialInfo(email); 33 | expect(actual).toMatchObject(result); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test_integration/flows/02_async_functions/02_async_functions_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const mocksios = (email, request) => 4 | new Promise(resolve => { 5 | setTimeout(() => { 6 | resolve({ email, request }); 7 | }, 1); 8 | }); 9 | 10 | const getSocialInfo = async (...p) => 11 | recorderWrapper( 12 | { 13 | path: 'test_integration/flows/02_async_functions/02_async_functions.js', 14 | name: 'getSocialInfo', 15 | paramIds: ['email'], 16 | injectionWhitelist: [], 17 | isDefault: false, 18 | isEcmaDefault: false, 19 | isAsync: true, 20 | isObject: false 21 | }, 22 | async email => { 23 | const facebookInfo = await mocksios(email, 'facebook'); 24 | const twitterInfo = await mocksios(email, 'twitter'); 25 | 26 | return { facebookInfo, twitterInfo }; 27 | }, 28 | ...p 29 | ); 30 | 31 | const getFacebookInfoHelper = (...p) => 32 | recorderWrapper( 33 | { 34 | path: 'test_integration/flows/02_async_functions/02_async_functions.js', 35 | name: 'getFacebookInfo', 36 | paramIds: ['email'], 37 | injectionWhitelist: [], 38 | isDefault: false, 39 | isEcmaDefault: false, 40 | isAsync: false, 41 | isObject: false 42 | }, 43 | email => mocksios(email, 'facebook'), 44 | ...p 45 | ); 46 | 47 | module.exports.getFacebookInfo = getFacebookInfoHelper; 48 | module.exports.getSocialInfo = getSocialInfo; 49 | recordFileMeta({ 50 | path: 'test_integration/flows/02_async_functions/02_async_functions.js', 51 | mocks: [] 52 | }); 53 | -------------------------------------------------------------------------------- /test_integration/flows/03_ecma_export/03_ecma_export.js: -------------------------------------------------------------------------------- 1 | export const ecma1 = (a, b) => a + b; 2 | 3 | const ecma2 = b => b * 3; 4 | 5 | const ecma33 = a => a / 2; 6 | const ecma4 = a => a / 4; 7 | 8 | export { ecma33 as ecma3, ecma4 }; 9 | 10 | export default ecma2; 11 | -------------------------------------------------------------------------------- /test_integration/flows/03_ecma_export/03_ecma_export_generated.test.js: -------------------------------------------------------------------------------- 1 | const { ecma1 } = require('./03_ecma_export'); 2 | const { default: ecma2 } = require('./03_ecma_export'); 3 | const { ecma3 } = require('./03_ecma_export'); 4 | const { ecma4 } = require('./03_ecma_export'); 5 | 6 | describe('03_ecma_export', () => { 7 | describe('ecma1', () => { 8 | it('should work for case 1', () => { 9 | let a = 1; 10 | let b = 2; 11 | let result = 3; 12 | 13 | const actual = ecma1(a, b); 14 | expect(actual).toEqual(result); 15 | }); 16 | }); 17 | 18 | describe('ecma2', () => { 19 | it('should work for case 1', () => { 20 | let b = 1; 21 | let result = 3; 22 | 23 | const actual = ecma2(b); 24 | expect(actual).toEqual(result); 25 | }); 26 | }); 27 | 28 | describe('ecma3', () => { 29 | it('should work for case 1', () => { 30 | let a = 1; 31 | let result = 0.5; 32 | 33 | const actual = ecma3(a); 34 | expect(actual).toEqual(result); 35 | }); 36 | }); 37 | 38 | describe('ecma4', () => { 39 | it('should work for case 1', () => { 40 | let a = 1; 41 | let result = 0.25; 42 | 43 | const actual = ecma4(a); 44 | expect(actual).toEqual(result); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test_integration/flows/03_ecma_export/03_ecma_export_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | export const ecma1 = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 'test_integration/flows/03_ecma_export/03_ecma_export.js', 7 | name: 'ecma1', 8 | paramIds: ['a', 'b'], 9 | injectionWhitelist: [], 10 | isDefault: false, 11 | isEcmaDefault: false, 12 | isAsync: false, 13 | isObject: false 14 | }, 15 | (a, b) => a + b, 16 | ...p 17 | ); 18 | 19 | const ecma2 = (...p) => 20 | recorderWrapper( 21 | { 22 | path: 'test_integration/flows/03_ecma_export/03_ecma_export.js', 23 | name: 'ecma2', 24 | paramIds: ['b'], 25 | injectionWhitelist: [], 26 | isDefault: true, 27 | isEcmaDefault: true, 28 | isAsync: false, 29 | isObject: false 30 | }, 31 | b => b * 3, 32 | ...p 33 | ); 34 | 35 | const ecma33 = (...p) => 36 | recorderWrapper( 37 | { 38 | path: 'test_integration/flows/03_ecma_export/03_ecma_export.js', 39 | name: 'ecma3', 40 | paramIds: ['a'], 41 | injectionWhitelist: [], 42 | isDefault: false, 43 | isEcmaDefault: false, 44 | isAsync: false, 45 | isObject: false 46 | }, 47 | a => a / 2, 48 | ...p 49 | ); 50 | const ecma4 = (...p) => 51 | recorderWrapper( 52 | { 53 | path: 'test_integration/flows/03_ecma_export/03_ecma_export.js', 54 | name: 'ecma4', 55 | paramIds: ['a'], 56 | injectionWhitelist: [], 57 | isDefault: false, 58 | isEcmaDefault: false, 59 | isAsync: false, 60 | isObject: false 61 | }, 62 | a => a / 4, 63 | ...p 64 | ); 65 | 66 | export { ecma33 as ecma3, ecma4 }; 67 | 68 | export default ecma2; 69 | recordFileMeta({ 70 | path: 'test_integration/flows/03_ecma_export/03_ecma_export.js', 71 | mocks: [] 72 | }); 73 | -------------------------------------------------------------------------------- /test_integration/flows/04_unserializeable/04_unserializeable.js: -------------------------------------------------------------------------------- 1 | const circularReference = (a) => { 2 | const obj = { a }; 3 | obj.obj = obj; 4 | return obj; 5 | }; 6 | 7 | const returnAFunction = (a, f2) => b => a + f2(b); 8 | 9 | const getElapsedTime = (start, end) => { 10 | const y2k = new Date('2000-01-31T18:30:00.000Z'); 11 | const delta = end.getTime() - start.getTime(); 12 | const result = new Date(); 13 | result.setTime(y2k.getTime() + delta); 14 | return result; 15 | }; 16 | 17 | const returnsNaN = a => Number.parseFloat(a); 18 | 19 | module.exports = { 20 | circularReference, 21 | returnAFunction, 22 | getElapsedTime, 23 | returnsNaN, 24 | }; 25 | -------------------------------------------------------------------------------- /test_integration/flows/04_unserializeable/04_unserializeable_generated.test.js: -------------------------------------------------------------------------------- 1 | const { circularReference } = require('./04_unserializeable'); 2 | const { getElapsedTime } = require('./04_unserializeable'); 3 | const { returnAFunction } = require('./04_unserializeable'); 4 | const { returnsNaN } = require('./04_unserializeable'); 5 | 6 | describe('04_unserializeable', () => { 7 | describe('circularReference', () => { 8 | it('should work for case 1', () => { 9 | let a = 1; 10 | let result = { 11 | a: 1 12 | }; 13 | 14 | const actual = circularReference(a); 15 | expect(actual).toMatchObject(result); 16 | }); 17 | }); 18 | 19 | describe('getElapsedTime', () => { 20 | it('should work for case 1', () => { 21 | let start = new Date('2018-02-01T00:00:00.000Z'); 22 | let end = new Date('2019-02-01T00:00:00.000Z'); 23 | let result = new Date('2001-01-30T18:30:00.000Z'); 24 | 25 | const actual = getElapsedTime(start, end); 26 | expect(actual).toEqual(result); 27 | }); 28 | }); 29 | 30 | describe('returnAFunction', () => { 31 | it('should work for case 1', () => { 32 | let a = 1; 33 | let f2 = 'a => a * 2'; 34 | let result = 'b => a + f2(b)'; 35 | 36 | const actual = returnAFunction(a, f2); 37 | expect(actual.toString()).toEqual(result); 38 | }); 39 | }); 40 | 41 | describe('returnsNaN', () => { 42 | it('should work for case 1', () => { 43 | let a = 'a'; 44 | let result = Number.NaN; 45 | 46 | const actual = returnsNaN(a); 47 | expect(actual).toEqual(result); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test_integration/flows/04_unserializeable/04_unserializeable_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const circularReference = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 'test_integration/flows/04_unserializeable/04_unserializeable.js', 7 | name: 'circularReference', 8 | paramIds: ['a'], 9 | injectionWhitelist: [], 10 | isDefault: false, 11 | isEcmaDefault: false, 12 | isAsync: false, 13 | isObject: false 14 | }, 15 | a => { 16 | const obj = { a }; 17 | obj.obj = obj; 18 | return obj; 19 | }, 20 | ...p 21 | ); 22 | 23 | const returnAFunction = (...p) => 24 | recorderWrapper( 25 | { 26 | path: 'test_integration/flows/04_unserializeable/04_unserializeable.js', 27 | name: 'returnAFunction', 28 | paramIds: ['a', 'f2'], 29 | injectionWhitelist: [], 30 | isDefault: false, 31 | isEcmaDefault: false, 32 | isAsync: false, 33 | isObject: false 34 | }, 35 | (a, f2) => b => a + f2(b), 36 | ...p 37 | ); 38 | 39 | const getElapsedTime = (...p) => 40 | recorderWrapper( 41 | { 42 | path: 'test_integration/flows/04_unserializeable/04_unserializeable.js', 43 | name: 'getElapsedTime', 44 | paramIds: ['start', 'end'], 45 | injectionWhitelist: [], 46 | isDefault: false, 47 | isEcmaDefault: false, 48 | isAsync: false, 49 | isObject: false 50 | }, 51 | (start, end) => { 52 | const y2k = new Date('2000-01-31T18:30:00.000Z'); 53 | const delta = end.getTime() - start.getTime(); 54 | const result = new Date(); 55 | result.setTime(y2k.getTime() + delta); 56 | return result; 57 | }, 58 | ...p 59 | ); 60 | 61 | const returnsNaN = (...p) => 62 | recorderWrapper( 63 | { 64 | path: 'test_integration/flows/04_unserializeable/04_unserializeable.js', 65 | name: 'returnsNaN', 66 | paramIds: ['a'], 67 | injectionWhitelist: [], 68 | isDefault: false, 69 | isEcmaDefault: false, 70 | isAsync: false, 71 | isObject: false 72 | }, 73 | a => Number.parseFloat(a), 74 | ...p 75 | ); 76 | 77 | module.exports = { 78 | circularReference, 79 | returnAFunction, 80 | getElapsedTime, 81 | returnsNaN 82 | }; 83 | recordFileMeta({ 84 | path: 'test_integration/flows/04_unserializeable/04_unserializeable.js', 85 | mocks: [] 86 | }); 87 | -------------------------------------------------------------------------------- /test_integration/flows/05_dependency_injection/05_dependency_injection.js: -------------------------------------------------------------------------------- 1 | const getPostContent = (client, postId) => client.query('SELECT * FROM posts WHERE id=?', postId); 2 | 3 | const getModerator = (pool, postId) => pool.pooledQuery('SELECT * FROM users WHERE moderator=true AND postid=?', postId); 4 | 5 | const getPostComments = async (client, postId, redisCache) => { 6 | const votes = await redisCache(postId + 1); 7 | const regionId = await client.query('SELECT region_id FROM regions where post_id=?', postId); 8 | return client.pool.pooledQuery('SELECT * FROM comments WHERE post_id=? AND region_id=? AND votes=?', postId, regionId, votes); 9 | }; 10 | 11 | const getPost = async (dbClient, postId, redisCache) => { 12 | await getPostContent(dbClient, postId); 13 | const content = await getPostContent(dbClient, postId); 14 | const comments = await getPostComments(dbClient, postId, redisCache); 15 | const votes = await redisCache(postId); 16 | const moderator = await getModerator(dbClient.pool, postId); 17 | dbClient.commitSync(); 18 | return { 19 | content, comments, votes, moderator, 20 | }; 21 | }; 22 | 23 | const getActiveUserCount = async (dbClient, botCount) => { 24 | const totalUsers = await dbClient.query('SELECT COUNT(*) FROM active_users;'); 25 | return totalUsers - botCount; 26 | }; 27 | 28 | module.exports = { getPost, getPostComments, getActiveUserCount }; 29 | -------------------------------------------------------------------------------- /test_integration/flows/05_dependency_injection/05_dependency_injection_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const getPostContent = (client, postId) => 4 | client.testIntegrationFlows05DependencyInjection05DependencyInjectionJsQuery( 5 | 'SELECT * FROM posts WHERE id=?', 6 | postId 7 | ); 8 | 9 | const getModerator = (pool, postId) => 10 | pool.testIntegrationFlows05DependencyInjection05DependencyInjectionJsPooledQuery( 11 | 'SELECT * FROM users WHERE moderator=true AND postid=?', 12 | postId 13 | ); 14 | 15 | const getPostComments = async (...p) => 16 | recorderWrapper( 17 | { 18 | path: 19 | 'test_integration/flows/05_dependency_injection/05_dependency_injection.js', 20 | name: 'getPostComments', 21 | paramIds: ['client', 'postId', 'redisCache'], 22 | injectionWhitelist: ['query', 'pooledQuery', 'commitSync'], 23 | isDefault: false, 24 | isEcmaDefault: false, 25 | isAsync: true, 26 | isObject: false 27 | }, 28 | async (client, postId, redisCache) => { 29 | const votes = await redisCache(postId + 1); 30 | const regionId = await client.testIntegrationFlows05DependencyInjection05DependencyInjectionJsQuery( 31 | 'SELECT region_id FROM regions where post_id=?', 32 | postId 33 | ); 34 | return client.pool.testIntegrationFlows05DependencyInjection05DependencyInjectionJsPooledQuery( 35 | 'SELECT * FROM comments WHERE post_id=? AND region_id=? AND votes=?', 36 | postId, 37 | regionId, 38 | votes 39 | ); 40 | }, 41 | ...p 42 | ); 43 | 44 | const getPost = async (...p) => 45 | recorderWrapper( 46 | { 47 | path: 48 | 'test_integration/flows/05_dependency_injection/05_dependency_injection.js', 49 | name: 'getPost', 50 | paramIds: ['dbClient', 'postId', 'redisCache'], 51 | injectionWhitelist: ['query', 'pooledQuery', 'commitSync'], 52 | isDefault: false, 53 | isEcmaDefault: false, 54 | isAsync: true, 55 | isObject: false 56 | }, 57 | async (dbClient, postId, redisCache) => { 58 | await getPostContent(dbClient, postId); 59 | const content = await getPostContent(dbClient, postId); 60 | const comments = await getPostComments(dbClient, postId, redisCache); 61 | const votes = await redisCache(postId); 62 | const moderator = await getModerator(dbClient.pool, postId); 63 | dbClient.testIntegrationFlows05DependencyInjection05DependencyInjectionJsCommitSync(); 64 | return { 65 | content, 66 | comments, 67 | votes, 68 | moderator 69 | }; 70 | }, 71 | ...p 72 | ); 73 | 74 | const getActiveUserCount = async (...p) => 75 | recorderWrapper( 76 | { 77 | path: 78 | 'test_integration/flows/05_dependency_injection/05_dependency_injection.js', 79 | name: 'getActiveUserCount', 80 | paramIds: ['dbClient', 'botCount'], 81 | injectionWhitelist: ['query', 'pooledQuery', 'commitSync'], 82 | isDefault: false, 83 | isEcmaDefault: false, 84 | isAsync: true, 85 | isObject: false 86 | }, 87 | async (dbClient, botCount) => { 88 | const totalUsers = await dbClient.testIntegrationFlows05DependencyInjection05DependencyInjectionJsQuery( 89 | 'SELECT COUNT(*) FROM active_users;' 90 | ); 91 | return totalUsers - botCount; 92 | }, 93 | ...p 94 | ); 95 | 96 | module.exports = { getPost, getPostComments, getActiveUserCount }; 97 | recordFileMeta({ 98 | path: 99 | 'test_integration/flows/05_dependency_injection/05_dependency_injection.js', 100 | mocks: [] 101 | }); 102 | -------------------------------------------------------------------------------- /test_integration/flows/06_mocks/06_mocks.js: -------------------------------------------------------------------------------- 1 | const fileSystem = require('fs'); 2 | const { 3 | foo1, foo2: foo3, foo4, foo5, higherOrder, 4 | } = require('./auxilary1'); 5 | require('./auxilary2'); 6 | 7 | const expContinuationFn = () => JSON.parse(fileSystem.readFileSync('test_integration/flows/06_mocks/response.json', 'utf8').toString()); 8 | 9 | const getTodo = () => { 10 | fileSystem.readFileSync('test_integration/flows/06_mocks/response.json', 'utf8').toString(); 11 | const a = expContinuationFn(); 12 | const b = foo4(); 13 | return a.concat(b); 14 | }; 15 | 16 | const localMocksTest = async () => { 17 | const result = foo1() + foo1() + await foo3(); 18 | return result; 19 | }; 20 | 21 | // TODO: Not implemented 22 | const datesTest = () => foo5(); 23 | 24 | // TODO: Not implemented 25 | const higherOrderTest = () => { 26 | const gen = higherOrder(1); 27 | return gen(2); 28 | }; 29 | 30 | module.exports = { 31 | getTodo, localMocksTest, datesTest, higherOrderTest, expContinuationFn, 32 | }; 33 | -------------------------------------------------------------------------------- /test_integration/flows/06_mocks/06_mocks/expContinuationFn_0_fsReadFileSync0.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | '[\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n }\n]'; 3 | -------------------------------------------------------------------------------- /test_integration/flows/06_mocks/06_mocks/getTodo_0_fsReadFileSync0.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | '[\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n }\n]'; 3 | -------------------------------------------------------------------------------- /test_integration/flows/06_mocks/06_mocks/getTodo_0_fsReadFileSync1.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | '[\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n },\n {\n "userId": 1,\n "id": 1,\n "title": "delectus aut autem",\n "completed": false\n }\n]'; 3 | -------------------------------------------------------------------------------- /test_integration/flows/06_mocks/06_mocks_generated.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const auxilary1 = require('./auxilary1'); 3 | const auxilary2 = require('./auxilary2'); 4 | 5 | jest.mock('fs'); 6 | jest.mock('./auxilary1'); 7 | jest.mock('./auxilary2'); 8 | 9 | const { expContinuationFn } = require('./06_mocks'); 10 | const { getTodo } = require('./06_mocks'); 11 | const { localMocksTest } = require('./06_mocks'); 12 | 13 | const expContinuationFn0result = require('./06_mocks/expContinuationFn_0_result.mock'); 14 | const expContinuationFn0fsReadFileSync0 = require('./06_mocks/expContinuationFn_0_fsReadFileSync0.mock'); 15 | const getTodo0result = require('./06_mocks/getTodo_0_result.mock'); 16 | const getTodo0auxilary1Foo40 = require('./06_mocks/getTodo_0_auxilary1Foo40.mock'); 17 | const getTodo0fsReadFileSync0 = require('./06_mocks/getTodo_0_fsReadFileSync0.mock'); 18 | const getTodo0fsReadFileSync1 = require('./06_mocks/getTodo_0_fsReadFileSync1.mock'); 19 | 20 | describe('06_mocks', () => { 21 | describe('expContinuationFn', () => { 22 | it('should work for case 1', () => { 23 | let result = expContinuationFn0result; 24 | fs.readFileSync.mockReturnValueOnce(expContinuationFn0fsReadFileSync0); 25 | 26 | const actual = expContinuationFn(); 27 | expect(actual).toEqual(result); 28 | }); 29 | }); 30 | 31 | describe('getTodo', () => { 32 | it('should work for case 1', () => { 33 | let result = getTodo0result; 34 | auxilary1.foo4.mockReturnValueOnce(getTodo0auxilary1Foo40); 35 | fs.readFileSync.mockReturnValueOnce(getTodo0fsReadFileSync0); 36 | fs.readFileSync.mockReturnValueOnce(getTodo0fsReadFileSync1); 37 | 38 | const actual = getTodo(); 39 | expect(actual).toEqual(result); 40 | }); 41 | }); 42 | 43 | describe('localMocksTest', () => { 44 | it('should work for case 1', async () => { 45 | let result = 4; 46 | auxilary1.foo1.mockReturnValueOnce(1); 47 | auxilary1.foo1.mockReturnValueOnce(1); 48 | auxilary1.foo2.mockReturnValueOnce(2); 49 | 50 | const actual = await localMocksTest(); 51 | expect(actual).toEqual(result); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test_integration/flows/06_mocks/auxilary1.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const foo1 = () => 1; 4 | 5 | const foo2 = () => new Promise((resolve) => { 6 | setTimeout(() => { 7 | resolve(2); 8 | }, 1); 9 | }); 10 | 11 | const foo4 = () => JSON.parse(fs.readFileSync('test_integration/flows/06_mocks/response.json', 'utf8').toString()); 12 | 13 | const foo5 = () => new Date('2000-01-31T18:30:00.000Z'); 14 | 15 | const higherOrder = a => b => a + b; 16 | 17 | module.exports = { 18 | foo1, foo2, foo4, foo5, higherOrder, 19 | }; 20 | -------------------------------------------------------------------------------- /test_integration/flows/06_mocks/auxilary2.js: -------------------------------------------------------------------------------- 1 | // Does nothing 2 | -------------------------------------------------------------------------------- /test_integration/flows/07_large_payload/07_large_payload.js: -------------------------------------------------------------------------------- 1 | const getClickCountsHelper = (requestDataCb) => { 2 | const imgObjs = requestDataCb(); 3 | const result = imgObjs 4 | .map(imgObj => ({ ...imgObj, clicks: imgObj.imageId * 100 })); 5 | return result; 6 | }; 7 | 8 | const getClickCounts = () => { 9 | const requestDataCb = () => [...Array(100)].map((_, index) => ({ 10 | imageId: index, 11 | })); 12 | return getClickCountsHelper(requestDataCb); 13 | }; 14 | 15 | module.exports = { getClickCounts, getClickCountsHelper }; 16 | -------------------------------------------------------------------------------- /test_integration/flows/07_large_payload/07_large_payload_generated.test.js: -------------------------------------------------------------------------------- 1 | const { getClickCounts } = require('./07_large_payload'); 2 | const { getClickCountsHelper } = require('./07_large_payload'); 3 | 4 | const getClickCounts0result = require('./07_large_payload/getClickCounts_0_result.mock'); 5 | const getClickCountsHelper0result = require('./07_large_payload/getClickCountsHelper_0_result.mock'); 6 | const getClickCountsHelper0requestDataCb0 = require('./07_large_payload/getClickCountsHelper_0_requestDataCb0.mock'); 7 | 8 | describe('07_large_payload', () => { 9 | describe('getClickCounts', () => { 10 | it('should work for case 1', () => { 11 | let result = getClickCounts0result; 12 | 13 | const actual = getClickCounts(); 14 | expect(actual).toEqual(result); 15 | }); 16 | }); 17 | 18 | describe('getClickCountsHelper', () => { 19 | it('should work for case 1', () => { 20 | let requestDataCb = 21 | '() => [...Array(100)].map((_, index) => ({\n imageId: index\n }))'; 22 | let result = getClickCountsHelper0result; 23 | 24 | requestDataCb = jest.fn(); 25 | requestDataCb.mockReturnValueOnce(getClickCountsHelper0requestDataCb0); 26 | const actual = getClickCountsHelper(requestDataCb); 27 | expect(actual).toEqual(result); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test_integration/flows/07_large_payload/07_large_payload_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const getClickCountsHelper = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 'test_integration/flows/07_large_payload/07_large_payload.js', 7 | name: 'getClickCountsHelper', 8 | paramIds: ['requestDataCb'], 9 | injectionWhitelist: [], 10 | isDefault: false, 11 | isEcmaDefault: false, 12 | isAsync: false, 13 | isObject: false 14 | }, 15 | requestDataCb => { 16 | const imgObjs = requestDataCb(); 17 | const result = imgObjs.map(imgObj => ({ 18 | ...imgObj, 19 | clicks: imgObj.imageId * 100 20 | })); 21 | return result; 22 | }, 23 | ...p 24 | ); 25 | 26 | const getClickCounts = (...p) => 27 | recorderWrapper( 28 | { 29 | path: 'test_integration/flows/07_large_payload/07_large_payload.js', 30 | name: 'getClickCounts', 31 | paramIds: [], 32 | injectionWhitelist: [], 33 | isDefault: false, 34 | isEcmaDefault: false, 35 | isAsync: false, 36 | isObject: false 37 | }, 38 | () => { 39 | const requestDataCb = () => 40 | [...Array(100)].map((_, index) => ({ 41 | imageId: index 42 | })); 43 | 44 | return getClickCountsHelper(requestDataCb); 45 | }, 46 | ...p 47 | ); 48 | 49 | module.exports = { getClickCounts, getClickCountsHelper }; 50 | recordFileMeta({ 51 | path: 'test_integration/flows/07_large_payload/07_large_payload.js', 52 | mocks: [] 53 | }); 54 | -------------------------------------------------------------------------------- /test_integration/flows/08_this/08_this.js: -------------------------------------------------------------------------------- 1 | const newTarget = obj => new obj.InjectedPromise(resolve => resolve(42)); 2 | 3 | const sample = () => { }; 4 | 5 | function Foo() { 6 | this.bar = 1; 7 | } 8 | 9 | Foo.prototype.fun1 = function fun1() { 10 | this.bar += 1; 11 | return this.bar; 12 | }; 13 | 14 | Foo.prototype.fun2 = function fun2() { 15 | return this.fun1(); 16 | }; 17 | 18 | const protoOverwriteHelper = foo => foo.fun2(); 19 | 20 | const protoOverwrite = () => { 21 | const foo = new Foo(); 22 | return protoOverwriteHelper(foo); 23 | }; 24 | 25 | module.exports = { 26 | newTarget, sample, protoOverwrite, protoOverwriteHelper, 27 | }; 28 | -------------------------------------------------------------------------------- /test_integration/flows/08_this/08_this_generated.test.js: -------------------------------------------------------------------------------- 1 | const { newTarget } = require('./08_this'); 2 | const { protoOverwrite } = require('./08_this'); 3 | const { protoOverwriteHelper } = require('./08_this'); 4 | const { sample } = require('./08_this'); 5 | 6 | describe('08_this', () => { 7 | /* This function requires injection of Constructor (WIP) 8 | describe('newTarget',()=>{ 9 | 10 | it('should work for case 1', async ()=>{ 11 | let obj = {} 12 | let result = 42 13 | 14 | 15 | const actual = await newTarget(obj) 16 | expect(actual).toEqual(result) 17 | }) 18 | 19 | }) 20 | */ 21 | 22 | describe('protoOverwrite', () => { 23 | it('should work for case 1', () => { 24 | let result = 2; 25 | 26 | const actual = protoOverwrite(); 27 | expect(actual).toEqual(result); 28 | }); 29 | }); 30 | 31 | describe('protoOverwriteHelper', () => { 32 | it('should work for case 1', () => { 33 | let foo = { 34 | bar: 1 35 | }; 36 | let result = 2; 37 | 38 | foo.fun2 = jest.fn(); 39 | foo.fun2.mockReturnValueOnce(2); 40 | const actual = protoOverwriteHelper(foo); 41 | expect(actual).toEqual(result); 42 | }); 43 | }); 44 | 45 | describe('sample', () => { 46 | it('should work for case 1', () => { 47 | let result = undefined; 48 | 49 | const actual = sample(); 50 | expect(actual).toEqual(result); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test_integration/flows/08_this/08_this_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const newTarget = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 'test_integration/flows/08_this/08_this.js', 7 | name: 'newTarget', 8 | paramIds: ['obj'], 9 | injectionWhitelist: ['InjectedPromise', 'fun2'], 10 | isDefault: false, 11 | isEcmaDefault: false, 12 | isAsync: false, 13 | isObject: false 14 | }, 15 | obj => 16 | new obj.testIntegrationFlows08This08ThisJsInjectedPromise(resolve => 17 | resolve(42) 18 | ), 19 | ...p 20 | ); 21 | 22 | const sample = (...p) => 23 | recorderWrapper( 24 | { 25 | path: 'test_integration/flows/08_this/08_this.js', 26 | name: 'sample', 27 | paramIds: [], 28 | injectionWhitelist: ['InjectedPromise', 'fun2'], 29 | isDefault: false, 30 | isEcmaDefault: false, 31 | isAsync: false, 32 | isObject: false 33 | }, 34 | () => {}, 35 | ...p 36 | ); 37 | 38 | function Foo() { 39 | this.bar = 1; 40 | } 41 | 42 | Foo.prototype.fun1 = function fun1() { 43 | this.bar += 1; 44 | return this.bar; 45 | }; 46 | 47 | Foo.prototype.fun2 = function fun2() { 48 | return this.fun1(); 49 | }; 50 | 51 | const protoOverwriteHelper = (...p) => 52 | recorderWrapper( 53 | { 54 | path: 'test_integration/flows/08_this/08_this.js', 55 | name: 'protoOverwriteHelper', 56 | paramIds: ['foo'], 57 | injectionWhitelist: ['InjectedPromise', 'fun2'], 58 | isDefault: false, 59 | isEcmaDefault: false, 60 | isAsync: false, 61 | isObject: false 62 | }, 63 | foo => foo.testIntegrationFlows08This08ThisJsFun2(), 64 | ...p 65 | ); 66 | 67 | const protoOverwrite = (...p) => 68 | recorderWrapper( 69 | { 70 | path: 'test_integration/flows/08_this/08_this.js', 71 | name: 'protoOverwrite', 72 | paramIds: [], 73 | injectionWhitelist: ['InjectedPromise', 'fun2'], 74 | isDefault: false, 75 | isEcmaDefault: false, 76 | isAsync: false, 77 | isObject: false 78 | }, 79 | () => { 80 | const foo = new Foo(); 81 | return protoOverwriteHelper(foo); 82 | }, 83 | ...p 84 | ); 85 | 86 | module.exports = { 87 | newTarget, 88 | sample, 89 | protoOverwrite, 90 | protoOverwriteHelper 91 | }; 92 | recordFileMeta({ 93 | path: 'test_integration/flows/08_this/08_this.js', 94 | mocks: [] 95 | }); 96 | -------------------------------------------------------------------------------- /test_integration/flows/09_typescript_exports/09_typescript_exports.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports, '__esModule', { value: true }); 2 | 3 | const exportTest1 = a => a; 4 | exports.exportTest1 = exportTest1; 5 | 6 | const exportTest2 = a => b => [a, b]; 7 | 8 | exports.default = exportTest2; 9 | 10 | exports.exportTest3 = a => 2 * a; 11 | exports.fetchFromDb = client => client.query(); 12 | -------------------------------------------------------------------------------- /test_integration/flows/09_typescript_exports/09_typescript_exports_generated.test.js: -------------------------------------------------------------------------------- 1 | const { exportTest1 } = require('./09_typescript_exports'); 2 | const { default: exportTest2 } = require('./09_typescript_exports'); 3 | const { exportTest3 } = require('./09_typescript_exports'); 4 | const { fetchFromDb } = require('./09_typescript_exports'); 5 | 6 | describe('09_typescript_exports', () => { 7 | describe('exportTest1', () => { 8 | it('should work for case 1', () => { 9 | let a = 2; 10 | let result = 2; 11 | 12 | const actual = exportTest1(a); 13 | expect(actual).toEqual(result); 14 | }); 15 | }); 16 | 17 | describe('exportTest2', () => { 18 | it('should work for case 1', () => { 19 | let a = 3; 20 | let result = 'b => [a, b]'; 21 | 22 | const actual = exportTest2(a); 23 | expect(actual.toString()).toEqual(result); 24 | }); 25 | }); 26 | 27 | describe('exportTest3', () => { 28 | it('should work for case 1', () => { 29 | let a = 4; 30 | let result = 8; 31 | 32 | const actual = exportTest3(a); 33 | expect(actual).toEqual(result); 34 | }); 35 | }); 36 | 37 | describe('fetchFromDb', () => { 38 | it('should work for case 1', () => { 39 | let client = {}; 40 | let result = { 41 | rows: [ 42 | { 43 | a: 42 44 | } 45 | ] 46 | }; 47 | 48 | client.query = jest.fn(); 49 | client.query.mockReturnValueOnce({ 50 | rows: [ 51 | { 52 | a: 42 53 | } 54 | ] 55 | }); 56 | const actual = fetchFromDb(client); 57 | expect(actual).toMatchObject(result); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test_integration/flows/09_typescript_exports/09_typescript_exports_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | const exportTest1 = (...p) => 6 | recorderWrapper( 7 | { 8 | path: 9 | 'test_integration/flows/09_typescript_exports/09_typescript_exports.js', 10 | name: 'exportTest1', 11 | paramIds: ['a'], 12 | injectionWhitelist: ['query'], 13 | isDefault: false, 14 | isEcmaDefault: false, 15 | isAsync: false, 16 | isObject: false 17 | }, 18 | a => a, 19 | ...p 20 | ); 21 | exports.exportTest1 = exportTest1; 22 | 23 | const exportTest2 = (...p) => 24 | recorderWrapper( 25 | { 26 | path: 27 | 'test_integration/flows/09_typescript_exports/09_typescript_exports.js', 28 | name: 'exportTest2', 29 | paramIds: ['a'], 30 | injectionWhitelist: ['query'], 31 | isDefault: false, 32 | isEcmaDefault: true, 33 | isAsync: false, 34 | isObject: false 35 | }, 36 | a => b => [a, b], 37 | ...p 38 | ); 39 | 40 | exports.default = exportTest2; 41 | 42 | exports.exportTest3 = (...p) => 43 | recorderWrapper( 44 | { 45 | path: 46 | 'test_integration/flows/09_typescript_exports/09_typescript_exports.js', 47 | name: 'exportTest3', 48 | paramIds: ['a'], 49 | injectionWhitelist: ['query'], 50 | isDefault: false, 51 | isEcmaDefault: false, 52 | isAsync: false, 53 | isObject: false 54 | }, 55 | a => 2 * a, 56 | ...p 57 | ); 58 | exports.fetchFromDb = (...p) => 59 | recorderWrapper( 60 | { 61 | path: 62 | 'test_integration/flows/09_typescript_exports/09_typescript_exports.js', 63 | name: 'fetchFromDb', 64 | paramIds: ['client'], 65 | injectionWhitelist: ['query'], 66 | isDefault: false, 67 | isEcmaDefault: false, 68 | isAsync: false, 69 | isObject: false 70 | }, 71 | client => 72 | client.testIntegrationFlows09TypescriptExports09TypescriptExportsJsQuery(), 73 | ...p 74 | ); 75 | recordFileMeta({ 76 | path: 'test_integration/flows/09_typescript_exports/09_typescript_exports.js', 77 | mocks: [] 78 | }); 79 | -------------------------------------------------------------------------------- /test_integration/flows/10_anon_export_default/10_anon_export_default.js: -------------------------------------------------------------------------------- 1 | export default (a, b) => a + b; 2 | -------------------------------------------------------------------------------- /test_integration/flows/10_anon_export_default/10_anon_export_default_activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_integration/flows/10_anon_export_default/10_anon_export_default.js": { 3 | "exportedFunctions": { 4 | "defaultExport": { 5 | "captures": [ 6 | { 7 | "params": [ 8 | 1, 9 | 2 10 | ], 11 | "result": 3, 12 | "types": { 13 | "params": [ 14 | "Number", 15 | "Number" 16 | ], 17 | "result": "Number" 18 | } 19 | } 20 | ], 21 | "hashTable": { 22 | "XsRtb87OKK89hcJikSMaww==": true 23 | }, 24 | "meta": { 25 | "doesReturnPromise": false, 26 | "injectionWhitelist": [ 27 | ], 28 | "isAsync": false, 29 | "isDefault": true, 30 | "isEcmaDefault": true, 31 | "isObject": false, 32 | "name": "defaultExport", 33 | "paramIds": [ 34 | "a", 35 | "b" 36 | ], 37 | "path": "test_integration/flows/10_anon_export_default/10_anon_export_default.js" 38 | } 39 | } 40 | }, 41 | "meta": { 42 | "mocks": [ 43 | ], 44 | "path": "test_integration/flows/10_anon_export_default/10_anon_export_default.js" 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /test_integration/flows/10_anon_export_default/10_anon_export_default_generated.test.js: -------------------------------------------------------------------------------- 1 | const { default: defaultExport } = require('./10_anon_export_default'); 2 | 3 | describe('10_anon_export_default', () => { 4 | describe('defaultExport', () => { 5 | it('should work for case 1', () => { 6 | let a = 1; 7 | let b = 2; 8 | let result = 3; 9 | 10 | const actual = defaultExport(a, b); 11 | expect(actual).toEqual(result); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test_integration/flows/10_anon_export_default/10_anon_export_default_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | export default (...p) => 4 | recorderWrapper( 5 | { 6 | path: 7 | 'test_integration/flows/10_anon_export_default/10_anon_export_default.js', 8 | name: 'defaultExport', 9 | paramIds: ['a', 'b'], 10 | injectionWhitelist: [], 11 | isDefault: true, 12 | isEcmaDefault: true, 13 | isAsync: false, 14 | isObject: false 15 | }, 16 | (a, b) => a + b, 17 | ...p 18 | ); 19 | recordFileMeta({ 20 | path: 21 | 'test_integration/flows/10_anon_export_default/10_anon_export_default.js', 22 | mocks: [] 23 | }); 24 | -------------------------------------------------------------------------------- /test_integration/flows/11_higher_order/11_higher_order.js: -------------------------------------------------------------------------------- 1 | // These are not yet implemented 2 | // This test case is here to just make sure 3 | // that these type of code doesnt cause crash 4 | 5 | const base = param1 => param2 => param1.someFun() + param2.someOtherFun(); 6 | 7 | function base2(param1) { 8 | return function base3(param2) { 9 | return param1.someFun() + param2.someOtherFun(); 10 | }; 11 | } 12 | 13 | const validFun = param => param.someFun(); 14 | 15 | const rObj = { foo: f => p => f(p) }; 16 | const secondary1 = rObj.foo(p => p.someFun()); 17 | // eslint-disable-next-line 18 | const secondary2 = rObj.foo(function f(p){ 19 | return p.someFun(); 20 | }); 21 | 22 | module.exports = { 23 | base, base2, rObj, secondary1, secondary2, validFun, 24 | }; 25 | -------------------------------------------------------------------------------- /test_integration/flows/11_higher_order/11_higher_order_generated.test.js: -------------------------------------------------------------------------------- 1 | const { base } = require('./11_higher_order'); 2 | const { rObj } = require('./11_higher_order'); 3 | const { validFun } = require('./11_higher_order'); 4 | 5 | describe('11_higher_order', () => { 6 | describe('base', () => { 7 | it('should work for case 1', () => { 8 | let param1 = {}; 9 | let result = 'param2 => param1.someFun() + param2.someOtherFun()'; 10 | 11 | const actual = base(param1); 12 | expect(actual.toString()).toEqual(result); 13 | }); 14 | }); 15 | 16 | describe('rObj.foo', () => { 17 | it('should work for case 1', () => { 18 | let f = 'p => p.someFun()'; 19 | let result = 'p => f(p)'; 20 | 21 | const actual = rObj.foo(f); 22 | expect(actual.toString()).toEqual(result); 23 | }); 24 | 25 | it('should work for case 2', () => { 26 | let f = 'function f(p) {\n return p.someFun();\n}'; 27 | let result = 'p => f(p)'; 28 | 29 | const actual = rObj.foo(f); 30 | expect(actual.toString()).toEqual(result); 31 | }); 32 | }); 33 | 34 | describe('validFun', () => { 35 | it('should work for case 1', () => { 36 | let param = {}; 37 | let result = 3; 38 | 39 | param.someFun = jest.fn(); 40 | param.someFun.mockReturnValueOnce(3); 41 | const actual = validFun(param); 42 | expect(actual).toEqual(result); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test_integration/flows/11_higher_order/11_higher_order_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); // These are not yet implemented 3 | // This test case is here to just make sure 4 | // that these type of code doesnt cause crash 5 | 6 | const base = (...p) => 7 | recorderWrapper( 8 | { 9 | path: 'test_integration/flows/11_higher_order/11_higher_order.js', 10 | name: 'base', 11 | paramIds: ['param1'], 12 | injectionWhitelist: ['someFun'], 13 | isDefault: false, 14 | isEcmaDefault: false, 15 | isAsync: false, 16 | isObject: false 17 | }, 18 | param1 => param2 => param1.someFun() + param2.someOtherFun(), 19 | ...p 20 | ); 21 | const base2 = (...p) => 22 | recorderWrapper( 23 | { 24 | path: 'test_integration/flows/11_higher_order/11_higher_order.js', 25 | name: 'base2', 26 | paramIds: ['param1'], 27 | injectionWhitelist: ['someFun'], 28 | isDefault: false, 29 | isEcmaDefault: false, 30 | isAsync: false, 31 | isObject: false 32 | }, 33 | function base2(param1) { 34 | return function base3(param2) { 35 | return param1.someFun() + param2.someOtherFun(); 36 | }; 37 | }, 38 | ...p 39 | ); 40 | 41 | const validFun = (...p) => 42 | recorderWrapper( 43 | { 44 | path: 'test_integration/flows/11_higher_order/11_higher_order.js', 45 | name: 'validFun', 46 | paramIds: ['param'], 47 | injectionWhitelist: ['someFun'], 48 | isDefault: false, 49 | isEcmaDefault: false, 50 | isAsync: false, 51 | isObject: false 52 | }, 53 | param => param.testIntegrationFlows11HigherOrder11HigherOrderJsSomeFun(), 54 | ...p 55 | ); 56 | 57 | const rObj = { foo: f => p => f(p) }; 58 | const _foo = rObj.foo; 59 | rObj.foo = (...p) => 60 | recorderWrapper( 61 | { 62 | path: 'test_integration/flows/11_higher_order/11_higher_order.js', 63 | name: 'rObj.foo', 64 | paramIds: ['f'], 65 | injectionWhitelist: ['someFun'], 66 | isDefault: false, 67 | isEcmaDefault: false, 68 | isAsync: false, 69 | isObject: true 70 | }, 71 | _foo, 72 | ...p 73 | ); 74 | const secondary1 = rObj.foo(p => p.someFun()); 75 | // eslint-disable-next-line 76 | const secondary2 = rObj.foo(function f(p) { 77 | return p.someFun(); 78 | }); 79 | 80 | module.exports = { 81 | base, 82 | base2, 83 | rObj, 84 | secondary1, 85 | secondary2, 86 | validFun 87 | }; 88 | recordFileMeta({ 89 | path: 'test_integration/flows/11_higher_order/11_higher_order.js', 90 | mocks: [] 91 | }); 92 | -------------------------------------------------------------------------------- /test_integration/flows/12_unwanted_injections/12_unwanted_injections.js: -------------------------------------------------------------------------------- 1 | const fun = arr => arr.map(e => 2 * e); 2 | 3 | const fun2 = num => num.toLocaleString(); 4 | 5 | const fun3 = f => f.call(null, 2); 6 | 7 | module.exports = { fun, fun2, fun3 }; 8 | -------------------------------------------------------------------------------- /test_integration/flows/12_unwanted_injections/12_unwanted_injections_activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_integration/flows/12_unwanted_injections/12_unwanted_injections.js": { 3 | "exportedFunctions": { 4 | "fun": { 5 | "captures": [ 6 | { 7 | "params": [ 8 | [ 9 | 1, 10 | 2 11 | ] 12 | ], 13 | "result": [ 14 | 2, 15 | 4 16 | ], 17 | "types": { 18 | "params": [ 19 | "Array" 20 | ], 21 | "result": "Array" 22 | } 23 | } 24 | ], 25 | "hashTable": { 26 | "RfDVfJjbad8lhfB1GlNUIA==": true 27 | }, 28 | "meta": { 29 | "doesReturnPromise": false, 30 | "injectionWhitelist": [ 31 | ], 32 | "isAsync": false, 33 | "isDefault": false, 34 | "isEcmaDefault": false, 35 | "isObject": false, 36 | "name": "fun", 37 | "paramIds": [ 38 | "arr" 39 | ], 40 | "path": "test_integration/flows/12_unwanted_injections/12_unwanted_injections.js" 41 | } 42 | }, 43 | "fun2": { 44 | "captures": [ 45 | { 46 | "params": [ 47 | 2 48 | ], 49 | "result": "2", 50 | "types": { 51 | "params": [ 52 | "Number" 53 | ], 54 | "result": "String" 55 | } 56 | } 57 | ], 58 | "hashTable": { 59 | "vpBxCjOFctyIkzBQr+fWXQ==": true 60 | }, 61 | "meta": { 62 | "doesReturnPromise": false, 63 | "injectionWhitelist": [ 64 | ], 65 | "isAsync": false, 66 | "isDefault": false, 67 | "isEcmaDefault": false, 68 | "isObject": false, 69 | "name": "fun2", 70 | "paramIds": [ 71 | "num" 72 | ], 73 | "path": "test_integration/flows/12_unwanted_injections/12_unwanted_injections.js" 74 | } 75 | }, 76 | "fun3": { 77 | "captures": [ 78 | { 79 | "injections": { 80 | "f": { 81 | "captures": [ 82 | { 83 | "params": [ 84 | ], 85 | "result": 2, 86 | "types": { 87 | "params": [ 88 | ], 89 | "result": "Number" 90 | } 91 | } 92 | ] 93 | } 94 | }, 95 | "params": [ 96 | "a => a" 97 | ], 98 | "result": 2, 99 | "types": { 100 | "params": [ 101 | "Function" 102 | ], 103 | "result": "Number" 104 | } 105 | } 106 | ], 107 | "hashTable": { 108 | "+RBq4ZQThZP+QWcvwIzRcQ==": true 109 | }, 110 | "meta": { 111 | "doesReturnPromise": false, 112 | "injectionWhitelist": [ 113 | ], 114 | "isAsync": false, 115 | "isDefault": false, 116 | "isEcmaDefault": false, 117 | "isObject": false, 118 | "name": "fun3", 119 | "paramIds": [ 120 | "f" 121 | ], 122 | "path": "test_integration/flows/12_unwanted_injections/12_unwanted_injections.js" 123 | } 124 | } 125 | }, 126 | "meta": { 127 | "mocks": [ 128 | ], 129 | "path": "test_integration/flows/12_unwanted_injections/12_unwanted_injections.js" 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /test_integration/flows/12_unwanted_injections/12_unwanted_injections_generated.test.js: -------------------------------------------------------------------------------- 1 | const { fun } = require('./12_unwanted_injections'); 2 | const { fun2 } = require('./12_unwanted_injections'); 3 | const { fun3 } = require('./12_unwanted_injections'); 4 | 5 | describe('12_unwanted_injections', () => { 6 | describe('fun', () => { 7 | it('should work for case 1', () => { 8 | let arr = [1, 2]; 9 | let result = [2, 4]; 10 | 11 | const actual = fun(arr); 12 | expect(actual).toEqual(result); 13 | }); 14 | }); 15 | 16 | describe('fun2', () => { 17 | it('should work for case 1', () => { 18 | let num = 2; 19 | let result = '2'; 20 | 21 | const actual = fun2(num); 22 | expect(actual).toEqual(result); 23 | }); 24 | }); 25 | 26 | describe('fun3', () => { 27 | it('should work for case 1', () => { 28 | let f = 'a => a'; 29 | let result = 2; 30 | 31 | f = jest.fn(); 32 | f.mockReturnValueOnce(2); 33 | const actual = fun3(f); 34 | expect(actual).toEqual(result); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test_integration/flows/12_unwanted_injections/12_unwanted_injections_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const fun = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 7 | 'test_integration/flows/12_unwanted_injections/12_unwanted_injections.js', 8 | name: 'fun', 9 | paramIds: ['arr'], 10 | injectionWhitelist: [], 11 | isDefault: false, 12 | isEcmaDefault: false, 13 | isAsync: false, 14 | isObject: false 15 | }, 16 | arr => arr.map(e => 2 * e), 17 | ...p 18 | ); 19 | 20 | const fun2 = (...p) => 21 | recorderWrapper( 22 | { 23 | path: 24 | 'test_integration/flows/12_unwanted_injections/12_unwanted_injections.js', 25 | name: 'fun2', 26 | paramIds: ['num'], 27 | injectionWhitelist: [], 28 | isDefault: false, 29 | isEcmaDefault: false, 30 | isAsync: false, 31 | isObject: false 32 | }, 33 | num => num.toLocaleString(), 34 | ...p 35 | ); 36 | 37 | const fun3 = (...p) => 38 | recorderWrapper( 39 | { 40 | path: 41 | 'test_integration/flows/12_unwanted_injections/12_unwanted_injections.js', 42 | name: 'fun3', 43 | paramIds: ['f'], 44 | injectionWhitelist: [], 45 | isDefault: false, 46 | isEcmaDefault: false, 47 | isAsync: false, 48 | isObject: false 49 | }, 50 | f => f.call(null, 2), 51 | ...p 52 | ); 53 | 54 | module.exports = { fun, fun2, fun3 }; 55 | recordFileMeta({ 56 | path: 57 | 'test_integration/flows/12_unwanted_injections/12_unwanted_injections.js', 58 | mocks: [] 59 | }); 60 | -------------------------------------------------------------------------------- /test_integration/flows/13_anon_ts_export_default/13_anon_ts_export_default.js: -------------------------------------------------------------------------------- 1 | exports.default = (a, b) => a + b; 2 | -------------------------------------------------------------------------------- /test_integration/flows/13_anon_ts_export_default/13_anon_ts_export_default_activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_integration/flows/13_anon_ts_export_default/13_anon_ts_export_default.js": { 3 | "exportedFunctions": { 4 | "defaultExport": { 5 | "captures": [ 6 | { 7 | "params": [ 8 | 1, 9 | 2 10 | ], 11 | "result": 3, 12 | "types": { 13 | "params": [ 14 | "Number", 15 | "Number" 16 | ], 17 | "result": "Number" 18 | } 19 | } 20 | ], 21 | "hashTable": { 22 | "XsRtb87OKK89hcJikSMaww==": true 23 | }, 24 | "meta": { 25 | "doesReturnPromise": false, 26 | "injectionWhitelist": [ 27 | ], 28 | "isAsync": false, 29 | "isDefault": false, 30 | "isEcmaDefault": true, 31 | "isObject": false, 32 | "name": "defaultExport", 33 | "paramIds": [ 34 | "a", 35 | "b" 36 | ], 37 | "path": "test_integration/flows/13_anon_ts_export_default/13_anon_ts_export_default.js" 38 | } 39 | } 40 | }, 41 | "meta": { 42 | "mocks": [ 43 | ], 44 | "path": "test_integration/flows/13_anon_ts_export_default/13_anon_ts_export_default.js" 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /test_integration/flows/13_anon_ts_export_default/13_anon_ts_export_default_generated.test.js: -------------------------------------------------------------------------------- 1 | const { default: defaultExport } = require('./13_anon_ts_export_default'); 2 | 3 | describe('13_anon_ts_export_default', () => { 4 | describe('defaultExport', () => { 5 | it('should work for case 1', () => { 6 | let a = 1; 7 | let b = 2; 8 | let result = 3; 9 | 10 | const actual = defaultExport(a, b); 11 | expect(actual).toEqual(result); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test_integration/flows/13_anon_ts_export_default/13_anon_ts_export_default_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | exports.default = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 7 | 'test_integration/flows/13_anon_ts_export_default/13_anon_ts_export_default.js', 8 | name: 'defaultExport', 9 | paramIds: ['a', 'b'], 10 | injectionWhitelist: [], 11 | isDefault: false, 12 | isEcmaDefault: true, 13 | isAsync: false, 14 | isObject: false 15 | }, 16 | (a, b) => a + b, 17 | ...p 18 | ); 19 | recordFileMeta({ 20 | path: 21 | 'test_integration/flows/13_anon_ts_export_default/13_anon_ts_export_default.js', 22 | mocks: [] 23 | }); 24 | -------------------------------------------------------------------------------- /test_integration/flows/14_anon_module_exports_default/14_anon_module_exports_default.js: -------------------------------------------------------------------------------- 1 | module.exports = a => a; 2 | -------------------------------------------------------------------------------- /test_integration/flows/14_anon_module_exports_default/14_anon_module_exports_default_activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_integration/flows/14_anon_module_exports_default/14_anon_module_exports_default.js": { 3 | "exportedFunctions": { 4 | "defaultExport": { 5 | "captures": [ 6 | { 7 | "params": [ 8 | 1 9 | ], 10 | "result": 1, 11 | "types": { 12 | "params": [ 13 | "Number" 14 | ], 15 | "result": "Number" 16 | } 17 | } 18 | ], 19 | "hashTable": { 20 | "OJ+hJ1O9bhmPAa4D6zmzLQ==": true 21 | }, 22 | "meta": { 23 | "doesReturnPromise": false, 24 | "injectionWhitelist": [ 25 | ], 26 | "isAsync": false, 27 | "isDefault": true, 28 | "isEcmaDefault": false, 29 | "isObject": false, 30 | "name": "defaultExport", 31 | "paramIds": [ 32 | "a" 33 | ], 34 | "path": "test_integration/flows/14_anon_module_exports_default/14_anon_module_exports_default.js" 35 | } 36 | } 37 | }, 38 | "meta": { 39 | "mocks": [ 40 | ], 41 | "path": "test_integration/flows/14_anon_module_exports_default/14_anon_module_exports_default.js" 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /test_integration/flows/14_anon_module_exports_default/14_anon_module_exports_default_generated.test.js: -------------------------------------------------------------------------------- 1 | const defaultExport = require('./14_anon_module_exports_default'); 2 | 3 | describe('14_anon_module_exports_default', () => { 4 | describe('defaultExport', () => { 5 | it('should work for case 1', () => { 6 | let a = 1; 7 | let result = 1; 8 | 9 | const actual = defaultExport(a); 10 | expect(actual).toEqual(result); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test_integration/flows/14_anon_module_exports_default/14_anon_module_exports_default_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | module.exports = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 7 | 'test_integration/flows/14_anon_module_exports_default/14_anon_module_exports_default.js', 8 | name: 'defaultExport', 9 | paramIds: ['a'], 10 | injectionWhitelist: [], 11 | isDefault: true, 12 | isEcmaDefault: false, 13 | isAsync: false, 14 | isObject: false 15 | }, 16 | a => a, 17 | ...p 18 | ); 19 | recordFileMeta({ 20 | path: 21 | 'test_integration/flows/14_anon_module_exports_default/14_anon_module_exports_default.js', 22 | mocks: [] 23 | }); 24 | -------------------------------------------------------------------------------- /test_integration/flows/15_named_module_exports_default/15_named_module_exports_default.js: -------------------------------------------------------------------------------- 1 | const foo = a => a; 2 | 3 | module.exports = foo; 4 | -------------------------------------------------------------------------------- /test_integration/flows/15_named_module_exports_default/15_named_module_exports_default_activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_integration/flows/15_named_module_exports_default/15_named_module_exports_default.js": { 3 | "exportedFunctions": { 4 | "foo": { 5 | "captures": [ 6 | { 7 | "params": [ 8 | 1 9 | ], 10 | "result": 1, 11 | "types": { 12 | "params": [ 13 | "Number" 14 | ], 15 | "result": "Number" 16 | } 17 | } 18 | ], 19 | "hashTable": { 20 | "OJ+hJ1O9bhmPAa4D6zmzLQ==": true 21 | }, 22 | "meta": { 23 | "doesReturnPromise": false, 24 | "injectionWhitelist": [ 25 | ], 26 | "isAsync": false, 27 | "isDefault": true, 28 | "isEcmaDefault": false, 29 | "isObject": false, 30 | "name": "foo", 31 | "paramIds": [ 32 | "a" 33 | ], 34 | "path": "test_integration/flows/15_named_module_exports_default/15_named_module_exports_default.js" 35 | } 36 | } 37 | }, 38 | "meta": { 39 | "mocks": [ 40 | ], 41 | "path": "test_integration/flows/15_named_module_exports_default/15_named_module_exports_default.js" 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /test_integration/flows/15_named_module_exports_default/15_named_module_exports_default_generated.test.js: -------------------------------------------------------------------------------- 1 | const foo = require('./15_named_module_exports_default'); 2 | 3 | describe('15_named_module_exports_default', () => { 4 | describe('foo', () => { 5 | it('should work for case 1', () => { 6 | let a = 1; 7 | let result = 1; 8 | 9 | const actual = foo(a); 10 | expect(actual).toEqual(result); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test_integration/flows/15_named_module_exports_default/15_named_module_exports_default_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const foo = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 7 | 'test_integration/flows/15_named_module_exports_default/15_named_module_exports_default.js', 8 | name: 'foo', 9 | paramIds: ['a'], 10 | injectionWhitelist: [], 11 | isDefault: true, 12 | isEcmaDefault: false, 13 | isAsync: false, 14 | isObject: false 15 | }, 16 | a => a, 17 | ...p 18 | ); 19 | 20 | module.exports = foo; 21 | recordFileMeta({ 22 | path: 23 | 'test_integration/flows/15_named_module_exports_default/15_named_module_exports_default.js', 24 | mocks: [] 25 | }); 26 | -------------------------------------------------------------------------------- /test_integration/flows/16_exported_objects/16_exported_objects.js: -------------------------------------------------------------------------------- 1 | const obj1 = { 2 | async foo1(a, b) { 3 | const res = await a.someFun(); 4 | return res + b; 5 | }, 6 | foo2() { }, 7 | baz: 42, 8 | }; 9 | 10 | let obj2 = {}; 11 | obj2 = { 12 | bar: async (a, b) => a - b, 13 | deep: { fun: async a => a.anotherFun() }, 14 | higher: a => b => a * b, 15 | }; 16 | 17 | const largeObj = { 18 | largeFun: () => [...Array(1000).keys()], 19 | }; 20 | 21 | const obj3 = { poo: 1 }; 22 | 23 | const empty = {}; 24 | 25 | module.exports = { 26 | obj1, obj2, obj3, empty, largeObj, 27 | }; 28 | -------------------------------------------------------------------------------- /test_integration/flows/16_exported_objects/16_exported_objects_generated.test.js: -------------------------------------------------------------------------------- 1 | const { largeObj } = require('./16_exported_objects'); 2 | const { obj1 } = require('./16_exported_objects'); 3 | const { obj2 } = require('./16_exported_objects'); 4 | 5 | const largeObjLargeFun0result = require('./16_exported_objects/largeObjLargeFun_0_result.mock'); 6 | 7 | describe('16_exported_objects', () => { 8 | describe('largeObj.largeFun', () => { 9 | it('should work for case 1', () => { 10 | let result = largeObjLargeFun0result; 11 | 12 | const actual = largeObj.largeFun(); 13 | expect(actual).toEqual(result); 14 | }); 15 | }); 16 | 17 | describe('obj1.foo1', () => { 18 | it('should work for case 1', async () => { 19 | let a = {}; 20 | let b = 2; 21 | let result = 3; 22 | 23 | a.someFun = jest.fn(); 24 | a.someFun.mockReturnValueOnce(1); 25 | const actual = await obj1.foo1(a, b); 26 | expect(actual).toEqual(result); 27 | }); 28 | }); 29 | 30 | describe('obj1.foo2', () => { 31 | it('should work for case 1', () => { 32 | let result = undefined; 33 | 34 | const actual = obj1.foo2(); 35 | expect(actual).toEqual(result); 36 | }); 37 | }); 38 | 39 | describe('obj2.bar', () => { 40 | it('should work for case 1', async () => { 41 | let a = 2; 42 | let b = 1; 43 | let result = 1; 44 | 45 | const actual = await obj2.bar(a, b); 46 | expect(actual).toEqual(result); 47 | }); 48 | }); 49 | 50 | describe('obj2.deep.fun', () => { 51 | it('should work for case 1', async () => { 52 | let a = {}; 53 | let result = 1; 54 | 55 | a.anotherFun = jest.fn(); 56 | a.anotherFun.mockReturnValueOnce(1); 57 | const actual = await obj2.deep.fun(a); 58 | expect(actual).toEqual(result); 59 | }); 60 | }); 61 | 62 | describe('obj2.higher', () => { 63 | it('should work for case 1', () => { 64 | let a = 1; 65 | let result = 'b => a * b'; 66 | 67 | const actual = obj2.higher(a); 68 | expect(actual.toString()).toEqual(result); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test_integration/flows/16_exported_objects/16_exported_objects_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const obj1 = { 4 | async foo1(a, b) { 5 | const res = await a.testIntegrationFlows16ExportedObjects16ExportedObjectsJsSomeFun(); 6 | return res + b; 7 | }, 8 | foo2() {}, 9 | baz: 42 10 | }; 11 | const _foo2 = obj1.foo2; 12 | obj1.foo2 = (...p) => 13 | recorderWrapper( 14 | { 15 | path: 'test_integration/flows/16_exported_objects/16_exported_objects.js', 16 | name: 'obj1.foo2', 17 | paramIds: [], 18 | injectionWhitelist: ['someFun', 'anotherFun'], 19 | isDefault: false, 20 | isEcmaDefault: false, 21 | isAsync: false, 22 | isObject: true 23 | }, 24 | _foo2, 25 | ...p 26 | ); 27 | const _foo = obj1.foo1; 28 | obj1.foo1 = (...p) => 29 | recorderWrapper( 30 | { 31 | path: 'test_integration/flows/16_exported_objects/16_exported_objects.js', 32 | name: 'obj1.foo1', 33 | paramIds: ['a', 'b'], 34 | injectionWhitelist: ['someFun', 'anotherFun'], 35 | isDefault: false, 36 | isEcmaDefault: false, 37 | isAsync: true, 38 | isObject: true 39 | }, 40 | _foo, 41 | ...p 42 | ); 43 | 44 | let obj2 = {}; 45 | obj2 = { 46 | bar: async (a, b) => a - b, 47 | deep: { 48 | fun: async a => 49 | a.testIntegrationFlows16ExportedObjects16ExportedObjectsJsAnotherFun() 50 | }, 51 | higher: a => b => a * b 52 | }; 53 | const _higher = obj2.higher; 54 | obj2.higher = (...p) => 55 | recorderWrapper( 56 | { 57 | path: 'test_integration/flows/16_exported_objects/16_exported_objects.js', 58 | name: 'obj2.higher', 59 | paramIds: ['a'], 60 | injectionWhitelist: ['someFun', 'anotherFun'], 61 | isDefault: false, 62 | isEcmaDefault: false, 63 | isAsync: false, 64 | isObject: true 65 | }, 66 | _higher, 67 | ...p 68 | ); 69 | const _fun = obj2.deep.fun; 70 | obj2.deep.fun = (...p) => 71 | recorderWrapper( 72 | { 73 | path: 'test_integration/flows/16_exported_objects/16_exported_objects.js', 74 | name: 'obj2.deep.fun', 75 | paramIds: ['a'], 76 | injectionWhitelist: ['someFun', 'anotherFun'], 77 | isDefault: false, 78 | isEcmaDefault: false, 79 | isAsync: true, 80 | isObject: true 81 | }, 82 | _fun, 83 | ...p 84 | ); 85 | const _bar = obj2.bar; 86 | obj2.bar = (...p) => 87 | recorderWrapper( 88 | { 89 | path: 'test_integration/flows/16_exported_objects/16_exported_objects.js', 90 | name: 'obj2.bar', 91 | paramIds: ['a', 'b'], 92 | injectionWhitelist: ['someFun', 'anotherFun'], 93 | isDefault: false, 94 | isEcmaDefault: false, 95 | isAsync: true, 96 | isObject: true 97 | }, 98 | _bar, 99 | ...p 100 | ); 101 | 102 | const largeObj = { 103 | largeFun: () => [...Array(1000).keys()] 104 | }; 105 | const _largeFun = largeObj.largeFun; 106 | largeObj.largeFun = (...p) => 107 | recorderWrapper( 108 | { 109 | path: 'test_integration/flows/16_exported_objects/16_exported_objects.js', 110 | name: 'largeObj.largeFun', 111 | paramIds: [], 112 | injectionWhitelist: ['someFun', 'anotherFun'], 113 | isDefault: false, 114 | isEcmaDefault: false, 115 | isAsync: false, 116 | isObject: true 117 | }, 118 | _largeFun, 119 | ...p 120 | ); 121 | 122 | const obj3 = { poo: 1 }; 123 | 124 | const empty = {}; 125 | 126 | module.exports = { 127 | obj1, 128 | obj2, 129 | obj3, 130 | empty, 131 | largeObj 132 | }; 133 | recordFileMeta({ 134 | path: 'test_integration/flows/16_exported_objects/16_exported_objects.js', 135 | mocks: [] 136 | }); 137 | -------------------------------------------------------------------------------- /test_integration/flows/17_param_mutation/17_param_mutation.js: -------------------------------------------------------------------------------- 1 | const fun = (a) => { 2 | a[0] += 1; 3 | return a; 4 | }; 5 | 6 | module.exports = { fun }; 7 | -------------------------------------------------------------------------------- /test_integration/flows/17_param_mutation/17_param_mutation_activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_integration/flows/17_param_mutation/17_param_mutation.js": { 3 | "exportedFunctions": { 4 | "fun": { 5 | "captures": [ 6 | { 7 | "params": [ 8 | [ 9 | 1 10 | ] 11 | ], 12 | "result": [ 13 | 2 14 | ], 15 | "types": { 16 | "params": [ 17 | "Array" 18 | ], 19 | "result": "Array" 20 | } 21 | } 22 | ], 23 | "hashTable": { 24 | "yqoqNoYz7HZJNW28/qZN8Q==": true 25 | }, 26 | "meta": { 27 | "doesReturnPromise": false, 28 | "injectionWhitelist": [ 29 | ], 30 | "isAsync": false, 31 | "isDefault": false, 32 | "isEcmaDefault": false, 33 | "isObject": false, 34 | "name": "fun", 35 | "paramIds": [ 36 | "a" 37 | ], 38 | "path": "test_integration/flows/17_param_mutation/17_param_mutation.js" 39 | } 40 | } 41 | }, 42 | "meta": { 43 | "mocks": [ 44 | ], 45 | "path": "test_integration/flows/17_param_mutation/17_param_mutation.js" 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /test_integration/flows/17_param_mutation/17_param_mutation_generated.test.js: -------------------------------------------------------------------------------- 1 | const { fun } = require('./17_param_mutation'); 2 | 3 | describe('17_param_mutation', () => { 4 | describe('fun', () => { 5 | it('should work for case 1', () => { 6 | let a = [1]; 7 | let result = [2]; 8 | 9 | const actual = fun(a); 10 | expect(actual).toEqual(result); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test_integration/flows/17_param_mutation/17_param_mutation_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { recorderWrapper } = require('../../../src/recorder'); 3 | const fun = (...p) => 4 | recorderWrapper( 5 | { 6 | path: 'test_integration/flows/17_param_mutation/17_param_mutation.js', 7 | name: 'fun', 8 | paramIds: ['a'], 9 | injectionWhitelist: [], 10 | isDefault: false, 11 | isEcmaDefault: false, 12 | isAsync: false, 13 | isObject: false 14 | }, 15 | a => { 16 | a[0] += 1; 17 | return a; 18 | }, 19 | ...p 20 | ); 21 | 22 | module.exports = { fun }; 23 | recordFileMeta({ 24 | path: 'test_integration/flows/17_param_mutation/17_param_mutation.js', 25 | mocks: [] 26 | }); 27 | -------------------------------------------------------------------------------- /test_integration/flows/18_record_stub_params/18_record_stub_params.js: -------------------------------------------------------------------------------- 1 | const aux = require('./auxilary'); 2 | 3 | const fun = obj => obj.fun(1) + aux.fun(2); 4 | 5 | module.exports = { fun }; 6 | -------------------------------------------------------------------------------- /test_integration/flows/18_record_stub_params/18_record_stub_params_activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_integration/flows/18_record_stub_params/18_record_stub_params.js": { 3 | "exportedFunctions": { 4 | "fun": { 5 | "captures": [ 6 | { 7 | "injections": { 8 | "obj.fun": { 9 | "captures": [ 10 | { 11 | "params": [ 12 | 1 13 | ], 14 | "result": 1, 15 | "types": { 16 | "params": [ 17 | "Number" 18 | ], 19 | "result": "Number" 20 | } 21 | } 22 | ] 23 | } 24 | }, 25 | "mocks": { 26 | "./auxilary": { 27 | "fun": { 28 | "captures": [ 29 | { 30 | "params": [ 31 | 2 32 | ], 33 | "result": 2, 34 | "types": { 35 | "params": [ 36 | "Number" 37 | ], 38 | "result": "Number" 39 | } 40 | } 41 | ] 42 | } 43 | } 44 | }, 45 | "params": [ 46 | { 47 | } 48 | ], 49 | "result": 3, 50 | "types": { 51 | "params": [ 52 | "Object" 53 | ], 54 | "result": "Number" 55 | } 56 | } 57 | ], 58 | "hashTable": { 59 | "ptDMXfY3QQwk0LZqG8IhYA==": true 60 | }, 61 | "meta": { 62 | "doesReturnPromise": false, 63 | "injectionWhitelist": [ 64 | "fun" 65 | ], 66 | "isAsync": false, 67 | "isDefault": false, 68 | "isEcmaDefault": false, 69 | "isObject": false, 70 | "name": "fun", 71 | "paramIds": [ 72 | "obj" 73 | ], 74 | "path": "test_integration/flows/18_record_stub_params/18_record_stub_params.js" 75 | } 76 | } 77 | }, 78 | "meta": { 79 | "mocks": [ 80 | "./auxilary" 81 | ], 82 | "path": "test_integration/flows/18_record_stub_params/18_record_stub_params.js" 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /test_integration/flows/18_record_stub_params/18_record_stub_params_generated.test.js: -------------------------------------------------------------------------------- 1 | const auxilary = require('./auxilary'); 2 | 3 | jest.mock('./auxilary'); 4 | 5 | const { fun } = require('./18_record_stub_params'); 6 | 7 | describe('18_record_stub_params', () => { 8 | describe('fun', () => { 9 | it('should work for case 1', () => { 10 | let obj = {}; 11 | let result = 3; 12 | auxilary.fun.mockReturnValueOnce(2); 13 | obj.fun = jest.fn(); 14 | obj.fun.mockReturnValueOnce(1); 15 | const actual = fun(obj); 16 | expect(actual).toEqual(result); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test_integration/flows/18_record_stub_params/18_record_stub_params_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { mockRecorderWrapper } = require('../../../src/recorder'); 3 | const { recorderWrapper } = require('../../../src/recorder'); 4 | const aux = require('./auxilary'); 5 | aux.testIntegrationFlows18RecordStubParams18RecordStubParamsJsFun = (...p) => 6 | mockRecorderWrapper( 7 | { 8 | path: 9 | 'test_integration/flows/18_record_stub_params/18_record_stub_params.js', 10 | moduleName: './auxilary', 11 | name: 'fun' 12 | }, 13 | aux.fun, 14 | ...p 15 | ); 16 | 17 | const fun = (...p) => 18 | recorderWrapper( 19 | { 20 | path: 21 | 'test_integration/flows/18_record_stub_params/18_record_stub_params.js', 22 | name: 'fun', 23 | paramIds: ['obj'], 24 | injectionWhitelist: ['fun'], 25 | isDefault: false, 26 | isEcmaDefault: false, 27 | isAsync: false, 28 | isObject: false 29 | }, 30 | obj => 31 | obj.testIntegrationFlows18RecordStubParams18RecordStubParamsJsFun(1) + 32 | aux.testIntegrationFlows18RecordStubParams18RecordStubParamsJsFun(2), 33 | ...p 34 | ); 35 | 36 | module.exports = { fun }; 37 | recordFileMeta({ 38 | path: 'test_integration/flows/18_record_stub_params/18_record_stub_params.js', 39 | mocks: ['./auxilary'] 40 | }); 41 | -------------------------------------------------------------------------------- /test_integration/flows/18_record_stub_params/auxilary.js: -------------------------------------------------------------------------------- 1 | const fun = a => a; 2 | module.exports = { fun }; 3 | -------------------------------------------------------------------------------- /test_integration/flows/19_demo/19_demo.js: -------------------------------------------------------------------------------- 1 | const fileSystem = require('fs'); 2 | 3 | const getTodos = () => { 4 | const fileName = 'test_integration/flows/19_demo/sample.json'; 5 | const obj = JSON.parse(fileSystem.readFileSync(fileName, 'utf8').toString()); 6 | return obj; 7 | }; 8 | 9 | const getCompletedTodos = todos => todos.filter(todo => todo.done); 10 | 11 | const saveTodos = async (dbClient) => { 12 | const todos = getTodos(); 13 | const completedTodos = getCompletedTodos(todos); 14 | 15 | const result = await dbClient.bulkInsert(completedTodos); 16 | 17 | return result; 18 | }; 19 | 20 | module.exports = { 21 | getTodos, 22 | getCompletedTodos, 23 | saveTodos, 24 | }; 25 | -------------------------------------------------------------------------------- /test_integration/flows/19_demo/19_demo_generated.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | jest.mock('fs'); 4 | 5 | const { getCompletedTodos } = require('./19_demo'); 6 | const { getTodos } = require('./19_demo'); 7 | const { saveTodos } = require('./19_demo'); 8 | 9 | describe('19_demo', () => { 10 | describe('getCompletedTodos', () => { 11 | it('should work for case 1', () => { 12 | let todos = [ 13 | { 14 | done: true, 15 | title: 'Get eggs' 16 | }, 17 | { 18 | done: false, 19 | title: 'Boil eggs' 20 | } 21 | ]; 22 | let result = [ 23 | { 24 | done: true, 25 | title: 'Get eggs' 26 | } 27 | ]; 28 | 29 | const actual = getCompletedTodos(todos); 30 | expect(actual).toEqual(result); 31 | }); 32 | }); 33 | 34 | describe('getTodos', () => { 35 | it('should work for case 1', () => { 36 | let result = [ 37 | { 38 | done: true, 39 | title: 'Get eggs' 40 | }, 41 | { 42 | done: false, 43 | title: 'Boil eggs' 44 | } 45 | ]; 46 | fs.readFileSync.mockReturnValueOnce( 47 | '[\n {\n "title": "Get eggs",\n "done": true\n },\n {\n "title": "Boil eggs",\n "done": false\n }\n]' 48 | ); 49 | 50 | const actual = getTodos(); 51 | expect(actual).toEqual(result); 52 | }); 53 | }); 54 | 55 | describe('saveTodos', () => { 56 | it('should work for case 1', async () => { 57 | let dbClient = {}; 58 | let result = { 59 | message: '1 rows added' 60 | }; 61 | fs.readFileSync.mockReturnValueOnce( 62 | '[\n {\n "title": "Get eggs",\n "done": true\n },\n {\n "title": "Boil eggs",\n "done": false\n }\n]' 63 | ); 64 | dbClient.bulkInsert = jest.fn(); 65 | dbClient.bulkInsert.mockReturnValueOnce({ 66 | message: '1 rows added' 67 | }); 68 | const actual = await saveTodos(dbClient); 69 | expect(actual).toMatchObject(result); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test_integration/flows/19_demo/19_demo_instrumented.js: -------------------------------------------------------------------------------- 1 | const { recordFileMeta } = require('../../../src/recorder'); 2 | const { mockRecorderWrapper } = require('../../../src/recorder'); 3 | const { recorderWrapper } = require('../../../src/recorder'); 4 | const fileSystem = require('fs'); 5 | fileSystem.testIntegrationFlows19Demo19DemoJsReadFileSync = (...p) => 6 | mockRecorderWrapper( 7 | { 8 | path: 'test_integration/flows/19_demo/19_demo.js', 9 | moduleName: 'fs', 10 | name: 'readFileSync' 11 | }, 12 | fileSystem.readFileSync, 13 | ...p 14 | ); 15 | 16 | const getTodos = (...p) => 17 | recorderWrapper( 18 | { 19 | path: 'test_integration/flows/19_demo/19_demo.js', 20 | name: 'getTodos', 21 | paramIds: [], 22 | injectionWhitelist: ['bulkInsert'], 23 | isDefault: false, 24 | isEcmaDefault: false, 25 | isAsync: false, 26 | isObject: false 27 | }, 28 | () => { 29 | const fileName = 'test_integration/flows/19_demo/sample.json'; 30 | const obj = JSON.parse( 31 | fileSystem 32 | .testIntegrationFlows19Demo19DemoJsReadFileSync(fileName, 'utf8') 33 | .toString() 34 | ); 35 | return obj; 36 | }, 37 | ...p 38 | ); 39 | 40 | const getCompletedTodos = (...p) => 41 | recorderWrapper( 42 | { 43 | path: 'test_integration/flows/19_demo/19_demo.js', 44 | name: 'getCompletedTodos', 45 | paramIds: ['todos'], 46 | injectionWhitelist: ['bulkInsert'], 47 | isDefault: false, 48 | isEcmaDefault: false, 49 | isAsync: false, 50 | isObject: false 51 | }, 52 | todos => todos.filter(todo => todo.done), 53 | ...p 54 | ); 55 | 56 | const saveTodos = async (...p) => 57 | recorderWrapper( 58 | { 59 | path: 'test_integration/flows/19_demo/19_demo.js', 60 | name: 'saveTodos', 61 | paramIds: ['dbClient'], 62 | injectionWhitelist: ['bulkInsert'], 63 | isDefault: false, 64 | isEcmaDefault: false, 65 | isAsync: true, 66 | isObject: false 67 | }, 68 | async dbClient => { 69 | const todos = getTodos(); 70 | const completedTodos = getCompletedTodos(todos); 71 | 72 | const result = await dbClient.testIntegrationFlows19Demo19DemoJsBulkInsert( 73 | completedTodos 74 | ); 75 | 76 | return result; 77 | }, 78 | ...p 79 | ); 80 | 81 | module.exports = { 82 | getTodos, 83 | getCompletedTodos, 84 | saveTodos 85 | }; 86 | recordFileMeta({ 87 | path: 'test_integration/flows/19_demo/19_demo.js', 88 | mocks: ['fs'] 89 | }); 90 | -------------------------------------------------------------------------------- /test_integration/flows/19_demo/sample.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Get eggs", 4 | "done": true 5 | }, 6 | { 7 | "title": "Boil eggs", 8 | "done": false 9 | } 10 | ] -------------------------------------------------------------------------------- /test_integration/util/__snapshots__/walker.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`walker walk should return list of all files in directory 1`] = ` 4 | Array [ 5 | "test_integration/util/walking_test/a.js", 6 | "test_integration/util/walking_test/directory/b.jsx", 7 | ] 8 | `; 9 | -------------------------------------------------------------------------------- /test_integration/util/walker.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | walk, 3 | } = require('../../src/util/walker'); 4 | 5 | describe('walker', () => { 6 | describe('walk', () => { 7 | it('should return list of all files in directory', () => { 8 | const allFiles = walk('./test_integration/util/walking_test'); 9 | expect(allFiles).toMatchSnapshot(); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test_integration/util/walking_test/a.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ghost---Shadow/unit-test-recorder/053135e947b9e6287c33063ee6c8f4b11b786328/test_integration/util/walking_test/a.js -------------------------------------------------------------------------------- /test_integration/util/walking_test/d.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ghost---Shadow/unit-test-recorder/053135e947b9e6287c33063ee6c8f4b11b786328/test_integration/util/walking_test/d.exe -------------------------------------------------------------------------------- /test_integration/util/walking_test/directory/b.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ghost---Shadow/unit-test-recorder/053135e947b9e6287c33063ee6c8f4b11b786328/test_integration/util/walking_test/directory/b.jsx -------------------------------------------------------------------------------- /test_integration/util/walking_test/directory/e.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ghost---Shadow/unit-test-recorder/053135e947b9e6287c33063ee6c8f4b11b786328/test_integration/util/walking_test/directory/e.tsx -------------------------------------------------------------------------------- /test_integration/util/walking_test/f.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ghost---Shadow/unit-test-recorder/053135e947b9e6287c33063ee6c8f4b11b786328/test_integration/util/walking_test/f.ts --------------------------------------------------------------------------------