ai-first-cli 1.3.0 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ai-dev/index.db +0 -0
- package/.github/workflows/publish.yml +4 -1
- package/BETA_EVALUATION_REPORT.md +151 -0
- package/CHANGELOG.md +178 -0
- package/PHASE1_USER_SIMULATION.md +56 -0
- package/PHASE2_USER_SIMULATION.md +81 -0
- package/PHASE3_USER_SIMULATION.md +176 -0
- package/README.es.md +18 -0
- package/README.md +80 -1
- package/ai/graph/knowledge-graph.json +10 -0
- package/ai-context/ai_context.md +130 -0
- package/{test-projects/react-app/.ai-dev → ai-context}/ai_rules.md +10 -5
- package/ai-context/architecture.md +136 -0
- package/ai-context/context/features/src.json +69 -0
- package/ai-context/context/features/test-projects.json +69 -0
- package/ai-context/context/flows/App.json +17 -0
- package/ai-context/context/flows/DashboardPage.json +14 -0
- package/ai-context/context/flows/LoginPage.json +14 -0
- package/ai-context/context/flows/admin.json +10 -0
- package/ai-context/context/flows/ai-first.json +9 -0
- package/ai-context/context/flows/androidresources.json +11 -0
- package/ai-context/context/flows/auth.json +13 -0
- package/ai-context/context/flows/authController.json +14 -0
- package/ai-context/context/flows/doctor.json +9 -0
- package/ai-context/context/flows/entrypoints.json +9 -0
- package/ai-context/context/flows/explore.json +9 -0
- package/ai-context/context/flows/fastapiAdapter.json +14 -0
- package/ai-context/context/flows/fastapiadapter.json +11 -0
- package/ai-context/context/flows/index.json +19 -0
- package/ai-context/context/flows/indexer.json +9 -0
- package/ai-context/context/flows/indexstate.json +9 -0
- package/ai-context/context/flows/init.json +22 -0
- package/ai-context/context/flows/main.json +18 -0
- package/ai-context/context/flows/mainactivity.json +9 -0
- package/ai-context/context/flows/models.json +15 -0
- package/ai-context/context/flows/posts.json +15 -0
- package/ai-context/context/flows/repoMapper.json +20 -0
- package/ai-context/context/flows/repomapper.json +11 -0
- package/ai-context/context/flows/routes.json +15 -0
- package/ai-context/context/flows/serializers.json +10 -0
- package/ai-context/context/flows/user.json +23 -0
- package/ai-context/context/flows/views.json +12 -0
- package/{test-projects/react-app/.ai-dev → ai-context}/conventions.md +3 -2
- package/ai-context/dependencies.json +3360 -0
- package/ai-context/entrypoints.md +45 -0
- package/ai-context/index-state.json +196 -0
- package/ai-context/modules.json +901 -0
- package/ai-context/project.json +33 -0
- package/ai-context/repo_map.json +8857 -0
- package/ai-context/repo_map.md +2002 -0
- package/{test-projects/flask-app/.ai-dev → ai-context}/schema.json +1 -1
- package/ai-context/summary.md +46 -0
- package/ai-context/symbols.json +82467 -0
- package/{test-projects/react-app/.ai-dev → ai-context}/tech_stack.md +15 -7
- package/ai-context-evaluation-report-1774223059505.md +206 -0
- package/dist/analyzers/architecture.d.ts.map +1 -1
- package/dist/analyzers/architecture.js +6 -0
- package/dist/analyzers/architecture.js.map +1 -1
- package/dist/analyzers/entrypoints.d.ts.map +1 -1
- package/dist/analyzers/entrypoints.js +105 -0
- package/dist/analyzers/entrypoints.js.map +1 -1
- package/dist/analyzers/symbols.d.ts.map +1 -1
- package/dist/analyzers/symbols.js +72 -1
- package/dist/analyzers/symbols.js.map +1 -1
- package/dist/analyzers/techStack.d.ts +8 -0
- package/dist/analyzers/techStack.d.ts.map +1 -1
- package/dist/analyzers/techStack.js +75 -0
- package/dist/analyzers/techStack.js.map +1 -1
- package/dist/scripts/ai-context-evaluator.js +367 -0
- package/package.json +1 -1
- package/quick-evaluation-report-1774396002305.md +64 -0
- package/quick-evaluator.ts +200 -0
- package/scripts/ai-context-evaluator.ts +440 -0
- package/src/analyzers/architecture.ts +8 -0
- package/src/analyzers/entrypoints.ts +115 -0
- package/src/analyzers/symbols.ts +77 -1
- package/src/analyzers/techStack.ts +93 -0
- package/test_adapters.mjs +11 -11
- package/tests/apex-parser.test.ts +193 -0
- package/tests/cli-commands-batch1.test.ts +808 -0
- package/tests/cli-commands-batch2.test.ts +1113 -0
- package/tests/cli-commands-batch3.test.ts +1128 -0
- package/tests/cli-index.test.ts +1007 -0
- package/tests/cli-init.test.ts +761 -0
- package/tests/salesforce-apex-classes.test.ts +713 -0
- package/tests/salesforce-apex-triggers.test.ts +871 -0
- package/tests/salesforce-custom-objects.test.ts +918 -0
- package/tests/salesforce-flows.test.ts +710 -0
- package/tests/salesforce-lwc.test.ts +963 -0
- package/tests/salesforce-sfdx-integration.test.ts +1125 -0
- package/CONTRIBUTING.md +0 -89
- package/FLOW.md +0 -129
- package/install.sh +0 -188
- package/run-all-tests.sh +0 -184
- package/test-projects/django-app/.ai-dev/ai_context.md +0 -92
- package/test-projects/django-app/.ai-dev/ai_rules.md +0 -47
- package/test-projects/django-app/.ai-dev/architecture.md +0 -57
- package/test-projects/django-app/.ai-dev/cache.json +0 -169
- package/test-projects/django-app/.ai-dev/context/flows/views.json +0 -10
- package/test-projects/django-app/.ai-dev/conventions.md +0 -51
- package/test-projects/django-app/.ai-dev/dependencies.json +0 -312
- package/test-projects/django-app/.ai-dev/entrypoints.md +0 -4
- package/test-projects/django-app/.ai-dev/files.json +0 -209
- package/test-projects/django-app/.ai-dev/graph/knowledge-graph.json +0 -36
- package/test-projects/django-app/.ai-dev/graph/module-graph.json +0 -145
- package/test-projects/django-app/.ai-dev/graph/symbol-graph.json +0 -1488
- package/test-projects/django-app/.ai-dev/graph/symbol-references.json +0 -1
- package/test-projects/django-app/.ai-dev/index-state.json +0 -294
- package/test-projects/django-app/.ai-dev/modules.json +0 -35
- package/test-projects/django-app/.ai-dev/project.json +0 -11
- package/test-projects/django-app/.ai-dev/repo_map.json +0 -412
- package/test-projects/django-app/.ai-dev/repo_map.md +0 -105
- package/test-projects/django-app/.ai-dev/schema.json +0 -5
- package/test-projects/django-app/.ai-dev/summary.md +0 -15
- package/test-projects/django-app/.ai-dev/symbols.json +0 -1
- package/test-projects/django-app/.ai-dev/tech_stack.md +0 -32
- package/test-projects/django-app/README.md +0 -91
- package/test-projects/django-app/blog/__init__.py +0 -0
- package/test-projects/django-app/blog/admin.py +0 -31
- package/test-projects/django-app/blog/models.py +0 -55
- package/test-projects/django-app/blog/serializers.py +0 -69
- package/test-projects/django-app/blog/urls.py +0 -14
- package/test-projects/django-app/blog/views.py +0 -96
- package/test-projects/django-app/django_app/__init__.py +0 -0
- package/test-projects/django-app/django_app/settings.py +0 -90
- package/test-projects/django-app/django_app/urls.py +0 -11
- package/test-projects/django-app/django_app/wsgi.py +0 -9
- package/test-projects/django-app/manage.py +0 -23
- package/test-projects/django-app/requirements.txt +0 -3
- package/test-projects/django-app/users/__init__.py +0 -0
- package/test-projects/django-app/users/admin.py +0 -42
- package/test-projects/django-app/users/models.py +0 -54
- package/test-projects/django-app/users/serializers.py +0 -113
- package/test-projects/django-app/users/urls.py +0 -13
- package/test-projects/django-app/users/views.py +0 -135
- package/test-projects/express-api/.ai-dev/ai_context.md +0 -112
- package/test-projects/express-api/.ai-dev/ai_rules.md +0 -50
- package/test-projects/express-api/.ai-dev/architecture.md +0 -62
- package/test-projects/express-api/.ai-dev/context/features/controllers.json +0 -13
- package/test-projects/express-api/.ai-dev/context/features/services.json +0 -13
- package/test-projects/express-api/.ai-dev/context/flows/auth.json +0 -12
- package/test-projects/express-api/.ai-dev/context/flows/user.json +0 -13
- package/test-projects/express-api/.ai-dev/conventions.md +0 -51
- package/test-projects/express-api/.ai-dev/dependencies.json +0 -54
- package/test-projects/express-api/.ai-dev/entrypoints.md +0 -17
- package/test-projects/express-api/.ai-dev/modules.json +0 -30
- package/test-projects/express-api/.ai-dev/project.json +0 -15
- package/test-projects/express-api/.ai-dev/repo_map.json +0 -100
- package/test-projects/express-api/.ai-dev/repo_map.md +0 -36
- package/test-projects/express-api/.ai-dev/schema.json +0 -5
- package/test-projects/express-api/.ai-dev/summary.md +0 -14
- package/test-projects/express-api/.ai-dev/symbols.json +0 -7
- package/test-projects/express-api/.ai-dev/tech_stack.md +0 -38
- package/test-projects/express-api/.ai-dev/tools.json +0 -10
- package/test-projects/express-api/controllers/authController.js +0 -32
- package/test-projects/express-api/controllers/userController.js +0 -51
- package/test-projects/express-api/index.js +0 -30
- package/test-projects/express-api/middleware/authMiddleware.js +0 -30
- package/test-projects/express-api/models/userRepository.js +0 -25
- package/test-projects/express-api/package.json +0 -18
- package/test-projects/express-api/services/authService.js +0 -17
- package/test-projects/express-api/services/userService.js +0 -28
- package/test-projects/fastapi-app/.ai-dev/ai_context.md +0 -89
- package/test-projects/fastapi-app/.ai-dev/ai_rules.md +0 -47
- package/test-projects/fastapi-app/.ai-dev/architecture.md +0 -39
- package/test-projects/fastapi-app/.ai-dev/cache.json +0 -125
- package/test-projects/fastapi-app/.ai-dev/conventions.md +0 -51
- package/test-projects/fastapi-app/.ai-dev/dependencies.json +0 -244
- package/test-projects/fastapi-app/.ai-dev/entrypoints.md +0 -4
- package/test-projects/fastapi-app/.ai-dev/files.json +0 -154
- package/test-projects/fastapi-app/.ai-dev/graph/knowledge-graph.json +0 -15
- package/test-projects/fastapi-app/.ai-dev/graph/module-graph.json +0 -78
- package/test-projects/fastapi-app/.ai-dev/graph/symbol-graph.json +0 -1724
- package/test-projects/fastapi-app/.ai-dev/graph/symbol-references.json +0 -51
- package/test-projects/fastapi-app/.ai-dev/index-state.json +0 -217
- package/test-projects/fastapi-app/.ai-dev/modules.json +0 -16
- package/test-projects/fastapi-app/.ai-dev/project.json +0 -9
- package/test-projects/fastapi-app/.ai-dev/repo_map.json +0 -298
- package/test-projects/fastapi-app/.ai-dev/repo_map.md +0 -74
- package/test-projects/fastapi-app/.ai-dev/schema.json +0 -5
- package/test-projects/fastapi-app/.ai-dev/summary.md +0 -12
- package/test-projects/fastapi-app/.ai-dev/symbols.json +0 -1
- package/test-projects/fastapi-app/.ai-dev/tech_stack.md +0 -32
- package/test-projects/fastapi-app/.ai-dev/tools.json +0 -10
- package/test-projects/fastapi-app/README.md +0 -118
- package/test-projects/fastapi-app/app/database.py +0 -21
- package/test-projects/fastapi-app/app/dependencies.py +0 -107
- package/test-projects/fastapi-app/app/main.py +0 -47
- package/test-projects/fastapi-app/app/models.py +0 -149
- package/test-projects/fastapi-app/app/routers/auth.py +0 -117
- package/test-projects/fastapi-app/app/routers/posts.py +0 -272
- package/test-projects/fastapi-app/app/schemas.py +0 -191
- package/test-projects/fastapi-app/requirements.txt +0 -10
- package/test-projects/flask-app/.ai-dev/ai_context.md +0 -94
- package/test-projects/flask-app/.ai-dev/ai_rules.md +0 -47
- package/test-projects/flask-app/.ai-dev/architecture.md +0 -49
- package/test-projects/flask-app/.ai-dev/cache.json +0 -157
- package/test-projects/flask-app/.ai-dev/context/features/app.json +0 -25
- package/test-projects/flask-app/.ai-dev/context/flows/routes.json +0 -14
- package/test-projects/flask-app/.ai-dev/conventions.md +0 -51
- package/test-projects/flask-app/.ai-dev/dependencies.json +0 -298
- package/test-projects/flask-app/.ai-dev/entrypoints.md +0 -4
- package/test-projects/flask-app/.ai-dev/files.json +0 -194
- package/test-projects/flask-app/.ai-dev/graph/knowledge-graph.json +0 -60
- package/test-projects/flask-app/.ai-dev/graph/module-graph.json +0 -95
- package/test-projects/flask-app/.ai-dev/graph/symbol-graph.json +0 -1448
- package/test-projects/flask-app/.ai-dev/graph/symbol-references.json +0 -45
- package/test-projects/flask-app/.ai-dev/index-state.json +0 -273
- package/test-projects/flask-app/.ai-dev/modules.json +0 -21
- package/test-projects/flask-app/.ai-dev/project.json +0 -13
- package/test-projects/flask-app/.ai-dev/repo_map.json +0 -400
- package/test-projects/flask-app/.ai-dev/repo_map.md +0 -98
- package/test-projects/flask-app/.ai-dev/summary.md +0 -13
- package/test-projects/flask-app/.ai-dev/symbols.json +0 -1
- package/test-projects/flask-app/.ai-dev/tech_stack.md +0 -32
- package/test-projects/flask-app/.ai-dev/tools.json +0 -10
- package/test-projects/flask-app/README.md +0 -129
- package/test-projects/flask-app/app/__init__.py +0 -46
- package/test-projects/flask-app/app/api/__init__.py +0 -7
- package/test-projects/flask-app/app/api/routes.py +0 -122
- package/test-projects/flask-app/app/auth/__init__.py +0 -7
- package/test-projects/flask-app/app/auth/forms.py +0 -52
- package/test-projects/flask-app/app/auth/routes.py +0 -68
- package/test-projects/flask-app/app/blog/__init__.py +0 -7
- package/test-projects/flask-app/app/blog/forms.py +0 -35
- package/test-projects/flask-app/app/blog/routes.py +0 -140
- package/test-projects/flask-app/app/main/__init__.py +0 -7
- package/test-projects/flask-app/app/main/routes.py +0 -88
- package/test-projects/flask-app/app/models.py +0 -177
- package/test-projects/flask-app/config.py +0 -64
- package/test-projects/flask-app/requirements.txt +0 -10
- package/test-projects/laravel-app/.ai-dev/ai_context.md +0 -97
- package/test-projects/laravel-app/.ai-dev/ai_rules.md +0 -47
- package/test-projects/laravel-app/.ai-dev/architecture.md +0 -60
- package/test-projects/laravel-app/.ai-dev/cache.json +0 -161
- package/test-projects/laravel-app/.ai-dev/context/features/app.json +0 -21
- package/test-projects/laravel-app/.ai-dev/context/flows/.json +0 -9
- package/test-projects/laravel-app/.ai-dev/context/flows/category.json +0 -12
- package/test-projects/laravel-app/.ai-dev/context/flows/comment.json +0 -12
- package/test-projects/laravel-app/.ai-dev/context/flows/post.json +0 -12
- package/test-projects/laravel-app/.ai-dev/context/flows/unnamed.json +0 -9
- package/test-projects/laravel-app/.ai-dev/conventions.md +0 -51
- package/test-projects/laravel-app/.ai-dev/dependencies.json +0 -6
- package/test-projects/laravel-app/.ai-dev/entrypoints.md +0 -4
- package/test-projects/laravel-app/.ai-dev/files.json +0 -199
- package/test-projects/laravel-app/.ai-dev/graph/knowledge-graph.json +0 -98
- package/test-projects/laravel-app/.ai-dev/graph/module-graph.json +0 -30
- package/test-projects/laravel-app/.ai-dev/graph/symbol-graph.json +0 -5
- package/test-projects/laravel-app/.ai-dev/graph/symbol-references.json +0 -1
- package/test-projects/laravel-app/.ai-dev/index-state.json +0 -280
- package/test-projects/laravel-app/.ai-dev/modules.json +0 -29
- package/test-projects/laravel-app/.ai-dev/project.json +0 -17
- package/test-projects/laravel-app/.ai-dev/repo_map.json +0 -419
- package/test-projects/laravel-app/.ai-dev/repo_map.md +0 -106
- package/test-projects/laravel-app/.ai-dev/schema.json +0 -5
- package/test-projects/laravel-app/.ai-dev/summary.md +0 -15
- package/test-projects/laravel-app/.ai-dev/symbols.json +0 -1
- package/test-projects/laravel-app/.ai-dev/tech_stack.md +0 -34
- package/test-projects/laravel-app/.ai-dev/tools.json +0 -10
- package/test-projects/laravel-app/README.md +0 -107
- package/test-projects/laravel-app/app/Http/Controllers/Api/CategoryController.php +0 -88
- package/test-projects/laravel-app/app/Http/Controllers/Api/CommentController.php +0 -56
- package/test-projects/laravel-app/app/Http/Controllers/Api/PostController.php +0 -174
- package/test-projects/laravel-app/app/Http/Controllers/Controller.php +0 -12
- package/test-projects/laravel-app/app/Models/Category.php +0 -34
- package/test-projects/laravel-app/app/Models/Comment.php +0 -51
- package/test-projects/laravel-app/app/Models/Post.php +0 -108
- package/test-projects/laravel-app/app/Models/User.php +0 -85
- package/test-projects/laravel-app/bootstrap/app.php +0 -25
- package/test-projects/laravel-app/composer.json +0 -35
- package/test-projects/laravel-app/routes/api.php +0 -40
- package/test-projects/nestjs-backend/.ai-dev/ai_context.md +0 -111
- package/test-projects/nestjs-backend/.ai-dev/ai_rules.md +0 -52
- package/test-projects/nestjs-backend/.ai-dev/architecture.md +0 -49
- package/test-projects/nestjs-backend/.ai-dev/cache.json +0 -169
- package/test-projects/nestjs-backend/.ai-dev/context/features/src.json +0 -23
- package/test-projects/nestjs-backend/.ai-dev/context/flows/auth.controller.json +0 -14
- package/test-projects/nestjs-backend/.ai-dev/context/flows/auth.json +0 -10
- package/test-projects/nestjs-backend/.ai-dev/context/flows/users..json +0 -10
- package/test-projects/nestjs-backend/.ai-dev/context/flows/users.controller.json +0 -14
- package/test-projects/nestjs-backend/.ai-dev/context/flows/users.json +0 -10
- package/test-projects/nestjs-backend/.ai-dev/conventions.md +0 -52
- package/test-projects/nestjs-backend/.ai-dev/dependencies.json +0 -152
- package/test-projects/nestjs-backend/.ai-dev/entrypoints.md +0 -18
- package/test-projects/nestjs-backend/.ai-dev/files.json +0 -209
- package/test-projects/nestjs-backend/.ai-dev/graph/knowledge-graph.json +0 -132
- package/test-projects/nestjs-backend/.ai-dev/graph/module-graph.json +0 -29
- package/test-projects/nestjs-backend/.ai-dev/graph/symbol-graph.json +0 -304
- package/test-projects/nestjs-backend/.ai-dev/graph/symbol-references.json +0 -5
- package/test-projects/nestjs-backend/.ai-dev/index-state.json +0 -294
- package/test-projects/nestjs-backend/.ai-dev/modules.json +0 -19
- package/test-projects/nestjs-backend/.ai-dev/project.json +0 -18
- package/test-projects/nestjs-backend/.ai-dev/repo_map.json +0 -427
- package/test-projects/nestjs-backend/.ai-dev/repo_map.md +0 -104
- package/test-projects/nestjs-backend/.ai-dev/schema.json +0 -5
- package/test-projects/nestjs-backend/.ai-dev/summary.md +0 -13
- package/test-projects/nestjs-backend/.ai-dev/symbols.json +0 -1
- package/test-projects/nestjs-backend/.ai-dev/tech_stack.md +0 -38
- package/test-projects/nestjs-backend/.ai-dev/tools.json +0 -10
- package/test-projects/nestjs-backend/package.json +0 -22
- package/test-projects/nestjs-backend/src/app.module.ts +0 -8
- package/test-projects/nestjs-backend/src/auth/auth.controller.ts +0 -22
- package/test-projects/nestjs-backend/src/auth/auth.module.ts +0 -11
- package/test-projects/nestjs-backend/src/auth/auth.service.ts +0 -28
- package/test-projects/nestjs-backend/src/auth/dto/login.dto.ts +0 -4
- package/test-projects/nestjs-backend/src/auth/strategies/jwt.strategy.ts +0 -18
- package/test-projects/nestjs-backend/src/main.ts +0 -9
- package/test-projects/nestjs-backend/src/users/users.controller.ts +0 -32
- package/test-projects/nestjs-backend/src/users/users.module.ts +0 -10
- package/test-projects/nestjs-backend/src/users/users.service.ts +0 -42
- package/test-projects/nestjs-backend/tsconfig.json +0 -21
- package/test-projects/python-cli/.ai-dev/ai_context.md +0 -95
- package/test-projects/python-cli/.ai-dev/ai_rules.md +0 -47
- package/test-projects/python-cli/.ai-dev/architecture.md +0 -55
- package/test-projects/python-cli/.ai-dev/cache.json +0 -149
- package/test-projects/python-cli/.ai-dev/context/features/cli.json +0 -16
- package/test-projects/python-cli/.ai-dev/context/flows/list_.json +0 -9
- package/test-projects/python-cli/.ai-dev/context/flows/remove_.json +0 -9
- package/test-projects/python-cli/.ai-dev/conventions.md +0 -51
- package/test-projects/python-cli/.ai-dev/dependencies.json +0 -66
- package/test-projects/python-cli/.ai-dev/entrypoints.md +0 -4
- package/test-projects/python-cli/.ai-dev/files.json +0 -184
- package/test-projects/python-cli/.ai-dev/graph/knowledge-graph.json +0 -83
- package/test-projects/python-cli/.ai-dev/graph/module-graph.json +0 -31
- package/test-projects/python-cli/.ai-dev/graph/symbol-graph.json +0 -358
- package/test-projects/python-cli/.ai-dev/graph/symbol-references.json +0 -11
- package/test-projects/python-cli/.ai-dev/index-state.json +0 -259
- package/test-projects/python-cli/.ai-dev/modules.json +0 -21
- package/test-projects/python-cli/.ai-dev/project.json +0 -15
- package/test-projects/python-cli/.ai-dev/repo_map.json +0 -367
- package/test-projects/python-cli/.ai-dev/repo_map.md +0 -93
- package/test-projects/python-cli/.ai-dev/schema.json +0 -5
- package/test-projects/python-cli/.ai-dev/summary.md +0 -14
- package/test-projects/python-cli/.ai-dev/symbols.json +0 -1
- package/test-projects/python-cli/.ai-dev/tech_stack.md +0 -32
- package/test-projects/python-cli/.ai-dev/tools.json +0 -10
- package/test-projects/python-cli/__init__.py +0 -1
- package/test-projects/python-cli/cli/__init__.py +0 -1
- package/test-projects/python-cli/cli/add_command.py +0 -6
- package/test-projects/python-cli/cli/list_command.py +0 -7
- package/test-projects/python-cli/cli/remove_command.py +0 -6
- package/test-projects/python-cli/main.py +0 -34
- package/test-projects/python-cli/models/__init__.py +0 -2
- package/test-projects/python-cli/models/task.py +0 -19
- package/test-projects/python-cli/models/task_repository.py +0 -44
- package/test-projects/rails-app/.ai-dev/ai_context.md +0 -94
- package/test-projects/rails-app/.ai-dev/ai_rules.md +0 -47
- package/test-projects/rails-app/.ai-dev/architecture.md +0 -49
- package/test-projects/rails-app/.ai-dev/cache.json +0 -193
- package/test-projects/rails-app/.ai-dev/context/features/app.json +0 -24
- package/test-projects/rails-app/.ai-dev/context/features/config.json +0 -13
- package/test-projects/rails-app/.ai-dev/context/flows/application.json +0 -9
- package/test-projects/rails-app/.ai-dev/context/flows/application_.json +0 -9
- package/test-projects/rails-app/.ai-dev/context/flows/comments.json +0 -11
- package/test-projects/rails-app/.ai-dev/context/flows/comments_.json +0 -11
- package/test-projects/rails-app/.ai-dev/context/flows/posts.json +0 -11
- package/test-projects/rails-app/.ai-dev/context/flows/posts_.json +0 -11
- package/test-projects/rails-app/.ai-dev/context/flows/routes.json +0 -9
- package/test-projects/rails-app/.ai-dev/context/flows/users.json +0 -11
- package/test-projects/rails-app/.ai-dev/context/flows/users_.json +0 -11
- package/test-projects/rails-app/.ai-dev/conventions.md +0 -51
- package/test-projects/rails-app/.ai-dev/dependencies.json +0 -6
- package/test-projects/rails-app/.ai-dev/entrypoints.md +0 -4
- package/test-projects/rails-app/.ai-dev/files.json +0 -239
- package/test-projects/rails-app/.ai-dev/graph/knowledge-graph.json +0 -130
- package/test-projects/rails-app/.ai-dev/graph/module-graph.json +0 -27
- package/test-projects/rails-app/.ai-dev/graph/symbol-graph.json +0 -5
- package/test-projects/rails-app/.ai-dev/graph/symbol-references.json +0 -1
- package/test-projects/rails-app/.ai-dev/index-state.json +0 -336
- package/test-projects/rails-app/.ai-dev/modules.json +0 -26
- package/test-projects/rails-app/.ai-dev/project.json +0 -22
- package/test-projects/rails-app/.ai-dev/repo_map.json +0 -486
- package/test-projects/rails-app/.ai-dev/repo_map.md +0 -117
- package/test-projects/rails-app/.ai-dev/schema.json +0 -5
- package/test-projects/rails-app/.ai-dev/summary.md +0 -13
- package/test-projects/rails-app/.ai-dev/symbols.json +0 -1
- package/test-projects/rails-app/.ai-dev/tech_stack.md +0 -32
- package/test-projects/rails-app/.ai-dev/tools.json +0 -10
- package/test-projects/rails-app/Gemfile +0 -38
- package/test-projects/rails-app/README.md +0 -140
- package/test-projects/rails-app/Rakefile +0 -8
- package/test-projects/rails-app/app/controllers/api/comments_controller.rb +0 -75
- package/test-projects/rails-app/app/controllers/api/posts_controller.rb +0 -68
- package/test-projects/rails-app/app/controllers/api/users_controller.rb +0 -54
- package/test-projects/rails-app/app/controllers/application_controller.rb +0 -31
- package/test-projects/rails-app/app/models/comment.rb +0 -34
- package/test-projects/rails-app/app/models/post.rb +0 -36
- package/test-projects/rails-app/app/models/user.rb +0 -28
- package/test-projects/rails-app/app/services/post_service.rb +0 -92
- package/test-projects/rails-app/app/services/user_service.rb +0 -76
- package/test-projects/rails-app/config/application.rb +0 -27
- package/test-projects/rails-app/config/environment.rb +0 -7
- package/test-projects/rails-app/config/routes.rb +0 -15
- package/test-projects/react-app/.ai-dev/ai_context.md +0 -96
- package/test-projects/react-app/.ai-dev/architecture.md +0 -39
- package/test-projects/react-app/.ai-dev/cache.json +0 -153
- package/test-projects/react-app/.ai-dev/context/features/src.json +0 -18
- package/test-projects/react-app/.ai-dev/context/flows/UsersPage.json +0 -14
- package/test-projects/react-app/.ai-dev/context/flows/dashboard.json +0 -9
- package/test-projects/react-app/.ai-dev/context/flows/login.json +0 -9
- package/test-projects/react-app/.ai-dev/context/flows/users.json +0 -9
- package/test-projects/react-app/.ai-dev/dependencies.json +0 -128
- package/test-projects/react-app/.ai-dev/entrypoints.md +0 -4
- package/test-projects/react-app/.ai-dev/files.json +0 -189
- package/test-projects/react-app/.ai-dev/graph/knowledge-graph.json +0 -112
- package/test-projects/react-app/.ai-dev/graph/module-graph.json +0 -31
- package/test-projects/react-app/.ai-dev/graph/symbol-graph.json +0 -868
- package/test-projects/react-app/.ai-dev/graph/symbol-references.json +0 -31
- package/test-projects/react-app/.ai-dev/index-state.json +0 -266
- package/test-projects/react-app/.ai-dev/modules.json +0 -17
- package/test-projects/react-app/.ai-dev/project.json +0 -16
- package/test-projects/react-app/.ai-dev/repo_map.json +0 -391
- package/test-projects/react-app/.ai-dev/repo_map.md +0 -94
- package/test-projects/react-app/.ai-dev/schema.json +0 -5
- package/test-projects/react-app/.ai-dev/summary.md +0 -13
- package/test-projects/react-app/.ai-dev/symbols.json +0 -1
- package/test-projects/react-app/.ai-dev/tools.json +0 -10
- package/test-projects/react-app/package.json +0 -16
- package/test-projects/react-app/src/App.tsx +0 -21
- package/test-projects/react-app/src/context/AuthContext.tsx +0 -41
- package/test-projects/react-app/src/hooks/useAuth.ts +0 -10
- package/test-projects/react-app/src/main.tsx +0 -10
- package/test-projects/react-app/src/pages/DashboardPage.tsx +0 -17
- package/test-projects/react-app/src/pages/LoginPage.tsx +0 -41
- package/test-projects/react-app/src/pages/UsersPage.tsx +0 -36
- package/test-projects/react-app/src/services/userService.ts +0 -37
- package/test-projects/salesforce-cli/.ai-dev/ai_context.md +0 -89
- package/test-projects/salesforce-cli/.ai-dev/ai_rules.md +0 -47
- package/test-projects/salesforce-cli/.ai-dev/architecture.md +0 -39
- package/test-projects/salesforce-cli/.ai-dev/cache.json +0 -125
- package/test-projects/salesforce-cli/.ai-dev/context/features/force-app.json +0 -14
- package/test-projects/salesforce-cli/.ai-dev/context/flows/account.json +0 -9
- package/test-projects/salesforce-cli/.ai-dev/context/flows/opportunity.json +0 -9
- package/test-projects/salesforce-cli/.ai-dev/conventions.md +0 -51
- package/test-projects/salesforce-cli/.ai-dev/dependencies.json +0 -6
- package/test-projects/salesforce-cli/.ai-dev/entrypoints.md +0 -4
- package/test-projects/salesforce-cli/.ai-dev/files.json +0 -154
- package/test-projects/salesforce-cli/.ai-dev/graph/knowledge-graph.json +0 -64
- package/test-projects/salesforce-cli/.ai-dev/graph/module-graph.json +0 -13
- package/test-projects/salesforce-cli/.ai-dev/graph/symbol-graph.json +0 -148
- package/test-projects/salesforce-cli/.ai-dev/graph/symbol-references.json +0 -1
- package/test-projects/salesforce-cli/.ai-dev/index-state.json +0 -217
- package/test-projects/salesforce-cli/.ai-dev/modules.json +0 -12
- package/test-projects/salesforce-cli/.ai-dev/project.json +0 -14
- package/test-projects/salesforce-cli/.ai-dev/repo_map.json +0 -328
- package/test-projects/salesforce-cli/.ai-dev/repo_map.md +0 -80
- package/test-projects/salesforce-cli/.ai-dev/schema.json +0 -5
- package/test-projects/salesforce-cli/.ai-dev/summary.md +0 -13
- package/test-projects/salesforce-cli/.ai-dev/symbols.json +0 -1
- package/test-projects/salesforce-cli/.ai-dev/tech_stack.md +0 -31
- package/test-projects/salesforce-cli/.ai-dev/tools.json +0 -10
- package/test-projects/salesforce-cli/.forceignore +0 -27
- package/test-projects/salesforce-cli/force-app/main/default/classes/AccountController.cls +0 -24
- package/test-projects/salesforce-cli/force-app/main/default/classes/OpportunityController.cls +0 -25
- package/test-projects/salesforce-cli/force-app/main/default/objects/Project__c.object.xml +0 -45
- package/test-projects/salesforce-cli/force-app/main/default/triggers/AccountTrigger.trigger +0 -33
- package/test-projects/salesforce-cli/sfdx-project.json +0 -11
- package/test-projects/spring-boot-app/.ai-dev/ai_context.md +0 -91
- package/test-projects/spring-boot-app/.ai-dev/ai_rules.md +0 -48
- package/test-projects/spring-boot-app/.ai-dev/architecture.md +0 -39
- package/test-projects/spring-boot-app/.ai-dev/cache.json +0 -173
- package/test-projects/spring-boot-app/.ai-dev/context/features/src.json +0 -26
- package/test-projects/spring-boot-app/.ai-dev/context/flows/PostController.json +0 -19
- package/test-projects/spring-boot-app/.ai-dev/context/flows/UserController.json +0 -19
- package/test-projects/spring-boot-app/.ai-dev/context/flows/comment.json +0 -11
- package/test-projects/spring-boot-app/.ai-dev/context/flows/post.json +0 -14
- package/test-projects/spring-boot-app/.ai-dev/context/flows/user.json +0 -14
- package/test-projects/spring-boot-app/.ai-dev/conventions.md +0 -52
- package/test-projects/spring-boot-app/.ai-dev/dependencies.json +0 -326
- package/test-projects/spring-boot-app/.ai-dev/entrypoints.md +0 -4
- package/test-projects/spring-boot-app/.ai-dev/files.json +0 -214
- package/test-projects/spring-boot-app/.ai-dev/graph/knowledge-graph.json +0 -231
- package/test-projects/spring-boot-app/.ai-dev/graph/module-graph.json +0 -22
- package/test-projects/spring-boot-app/.ai-dev/graph/symbol-graph.json +0 -794
- package/test-projects/spring-boot-app/.ai-dev/graph/symbol-references.json +0 -70
- package/test-projects/spring-boot-app/.ai-dev/index-state.json +0 -301
- package/test-projects/spring-boot-app/.ai-dev/modules.json +0 -21
- package/test-projects/spring-boot-app/.ai-dev/project.json +0 -17
- package/test-projects/spring-boot-app/.ai-dev/repo_map.json +0 -461
- package/test-projects/spring-boot-app/.ai-dev/repo_map.md +0 -109
- package/test-projects/spring-boot-app/.ai-dev/schema.json +0 -5
- package/test-projects/spring-boot-app/.ai-dev/summary.md +0 -12
- package/test-projects/spring-boot-app/.ai-dev/symbols.json +0 -1
- package/test-projects/spring-boot-app/.ai-dev/tech_stack.md +0 -32
- package/test-projects/spring-boot-app/.ai-dev/tools.json +0 -10
- package/test-projects/spring-boot-app/.classpath +0 -57
- package/test-projects/spring-boot-app/.factorypath +0 -69
- package/test-projects/spring-boot-app/.project +0 -34
- package/test-projects/spring-boot-app/.settings/org.eclipse.core.resources.prefs +0 -4
- package/test-projects/spring-boot-app/.settings/org.eclipse.jdt.apt.core.prefs +0 -4
- package/test-projects/spring-boot-app/.settings/org.eclipse.jdt.core.prefs +0 -10
- package/test-projects/spring-boot-app/.settings/org.eclipse.m2e.core.prefs +0 -4
- package/test-projects/spring-boot-app/README.md +0 -122
- package/test-projects/spring-boot-app/pom.xml +0 -79
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/DemoApplication.java +0 -12
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/controllers/CommentController.java +0 -89
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/controllers/PostController.java +0 -92
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/controllers/UserController.java +0 -84
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/models/Comment.java +0 -38
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/models/Post.java +0 -56
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/models/User.java +0 -44
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/repositories/CommentRepository.java +0 -21
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/repositories/PostRepository.java +0 -18
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/repositories/UserRepository.java +0 -15
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/services/PostService.java +0 -83
- package/test-projects/spring-boot-app/src/main/java/com/example/demo/services/UserService.java +0 -62
- package/test-projects/spring-boot-app/src/main/resources/application.properties +0 -22
- package/test-projects/spring-boot-app/target/classes/com/example/demo/DemoApplication.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/controllers/CommentController$CommentCreateRequest.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/controllers/CommentController$CommentUpdateRequest.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/controllers/CommentController.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/controllers/PostController$PostCreateRequest.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/controllers/PostController$PostUpdateRequest.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/controllers/PostController.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/controllers/UserController$UserCreateRequest.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/controllers/UserController$UserUpdateRequest.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/controllers/UserController.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/models/Comment.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/models/Post.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/models/User.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/repositories/CommentRepository.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/repositories/PostRepository.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/repositories/UserRepository.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/services/PostService.class +0 -0
- package/test-projects/spring-boot-app/target/classes/com/example/demo/services/UserService.class +0 -0
- package/tests/e2e/run-e2e.sh +0 -88
- /package/{test-projects/django-app/.ai-dev → ai-context}/tools.json +0 -0
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { extractSymbols } from "../src/analyzers/symbols";
|
|
3
|
+
import { FileInfo } from "../src/core/repoScanner";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
|
|
8
|
+
const SALESFORCE_ENTERPRISE_PATH = path.join(process.cwd(), "test-projects/salesforce-enterprise");
|
|
9
|
+
|
|
10
|
+
describe("Salesforce Apex Triggers Detection", () => {
|
|
11
|
+
let tempDir: string;
|
|
12
|
+
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "apex-triggers-test-"));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const createFileInfo = (filePath: string, extension: string, content: string): FileInfo => {
|
|
22
|
+
const fullPath = path.join(tempDir, filePath);
|
|
23
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
24
|
+
fs.writeFileSync(fullPath, content);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
path: fullPath,
|
|
28
|
+
relativePath: filePath,
|
|
29
|
+
extension,
|
|
30
|
+
name: filePath.split("/").pop()?.replace(`.${extension}`, "") || "",
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// =========================================================================
|
|
35
|
+
// INTEGRATION TESTS - Using real Salesforce Enterprise test project
|
|
36
|
+
// =========================================================================
|
|
37
|
+
|
|
38
|
+
describe("Integration: Real Salesforce Enterprise Project", () => {
|
|
39
|
+
it("should detect trigger files in the enterprise project", () => {
|
|
40
|
+
const triggersDir = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers");
|
|
41
|
+
const files = fs.readdirSync(triggersDir).filter(f => f.endsWith(".trigger"));
|
|
42
|
+
|
|
43
|
+
expect(files).toHaveLength(1);
|
|
44
|
+
expect(files).toContain("OpportunityTrigger.trigger");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should extract OpportunityTrigger symbol from real file", () => {
|
|
48
|
+
const triggerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger");
|
|
49
|
+
const content = fs.readFileSync(triggerPath, "utf-8");
|
|
50
|
+
|
|
51
|
+
const file: FileInfo = {
|
|
52
|
+
path: triggerPath,
|
|
53
|
+
relativePath: "force-app/main/default/triggers/OpportunityTrigger.trigger",
|
|
54
|
+
extension: "trigger",
|
|
55
|
+
name: "OpportunityTrigger",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const result = extractSymbols([file]);
|
|
59
|
+
const triggerSymbol = result.symbols.find(s => s.name === "OpportunityTrigger");
|
|
60
|
+
|
|
61
|
+
expect(triggerSymbol).toBeDefined();
|
|
62
|
+
expect(triggerSymbol?.type).toBe("function");
|
|
63
|
+
expect(triggerSymbol?.export).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// =========================================================================
|
|
68
|
+
// OpportunityTrigger - Main trigger under test
|
|
69
|
+
// =========================================================================
|
|
70
|
+
|
|
71
|
+
describe("OpportunityTrigger", () => {
|
|
72
|
+
it("should have trigger name 'OpportunityTrigger'", () => {
|
|
73
|
+
const content = fs.readFileSync(
|
|
74
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
75
|
+
"utf-8"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const file = createFileInfo("force-app/main/default/triggers/OpportunityTrigger.trigger", "trigger", content);
|
|
79
|
+
const result = extractSymbols([file]);
|
|
80
|
+
|
|
81
|
+
const triggerSymbol = result.symbols.find(s => s.name === "OpportunityTrigger");
|
|
82
|
+
expect(triggerSymbol).toBeDefined();
|
|
83
|
+
expect(triggerSymbol?.name).toBe("OpportunityTrigger");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should fire on 'Opportunity' object", () => {
|
|
87
|
+
const content = fs.readFileSync(
|
|
88
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
89
|
+
"utf-8"
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(content).toContain("trigger OpportunityTrigger on Opportunity");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should detect all 7 trigger events", () => {
|
|
96
|
+
const content = fs.readFileSync(
|
|
97
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
98
|
+
"utf-8"
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// All 7 events: before insert, before update, before delete, after insert, after update, after delete, after undelete
|
|
102
|
+
expect(content).toContain("before insert");
|
|
103
|
+
expect(content).toContain("before update");
|
|
104
|
+
expect(content).toContain("before delete");
|
|
105
|
+
expect(content).toContain("after insert");
|
|
106
|
+
expect(content).toContain("after update");
|
|
107
|
+
expect(content).toContain("after delete");
|
|
108
|
+
expect(content).toContain("after undelete");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should have all 7 events in single trigger definition", () => {
|
|
112
|
+
const content = fs.readFileSync(
|
|
113
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
114
|
+
"utf-8"
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// The trigger definition should contain all events
|
|
118
|
+
const triggerDefinitionMatch = content.match(/trigger\s+\w+\s+on\s+\w+\s*\(([^)]+)\)/);
|
|
119
|
+
expect(triggerDefinitionMatch).toBeDefined();
|
|
120
|
+
|
|
121
|
+
const events = triggerDefinitionMatch?.[1] || "";
|
|
122
|
+
expect(events).toContain("before insert");
|
|
123
|
+
expect(events).toContain("before update");
|
|
124
|
+
expect(events).toContain("before delete");
|
|
125
|
+
expect(events).toContain("after insert");
|
|
126
|
+
expect(events).toContain("after update");
|
|
127
|
+
expect(events).toContain("after delete");
|
|
128
|
+
expect(events).toContain("after undelete");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should use handler pattern with OpportunityTriggerHandler", () => {
|
|
132
|
+
const content = fs.readFileSync(
|
|
133
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
134
|
+
"utf-8"
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(content).toContain("OpportunityTriggerHandler");
|
|
138
|
+
expect(content).toContain("new OpportunityTriggerHandler()");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should have bypass logic with TriggerManagement.isTriggerBypassed", () => {
|
|
142
|
+
const content = fs.readFileSync(
|
|
143
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
144
|
+
"utf-8"
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
expect(content).toContain("TriggerManagement.isTriggerBypassed");
|
|
148
|
+
expect(content).toContain("isTriggerBypassed('Opportunity')");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should use Trigger.isBefore and Trigger.isAfter context variables", () => {
|
|
152
|
+
const content = fs.readFileSync(
|
|
153
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
154
|
+
"utf-8"
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(content).toContain("Trigger.isBefore");
|
|
158
|
+
expect(content).toContain("Trigger.isAfter");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should use Trigger.isInsert, Trigger.isUpdate, Trigger.isDelete, Trigger.isUndelete", () => {
|
|
162
|
+
const content = fs.readFileSync(
|
|
163
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
164
|
+
"utf-8"
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
expect(content).toContain("Trigger.isInsert");
|
|
168
|
+
expect(content).toContain("Trigger.isUpdate");
|
|
169
|
+
expect(content).toContain("Trigger.isDelete");
|
|
170
|
+
expect(content).toContain("Trigger.isUndelete");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should use Trigger.new and Trigger.oldMap context variables", () => {
|
|
174
|
+
const content = fs.readFileSync(
|
|
175
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
176
|
+
"utf-8"
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect(content).toContain("Trigger.new");
|
|
180
|
+
expect(content).toContain("Trigger.oldMap");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should have before insert handler", () => {
|
|
184
|
+
const content = fs.readFileSync(
|
|
185
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
186
|
+
"utf-8"
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(content).toContain("onBeforeInsert");
|
|
190
|
+
expect(content).toContain("Trigger.new");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should have before update handler", () => {
|
|
194
|
+
const content = fs.readFileSync(
|
|
195
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
196
|
+
"utf-8"
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(content).toContain("onBeforeUpdate");
|
|
200
|
+
expect(content).toContain("Trigger.new");
|
|
201
|
+
expect(content).toContain("Trigger.oldMap");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should have before delete handler", () => {
|
|
205
|
+
const content = fs.readFileSync(
|
|
206
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
207
|
+
"utf-8"
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(content).toContain("onBeforeDelete");
|
|
211
|
+
expect(content).toContain("Trigger.oldMap");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should have after insert handler", () => {
|
|
215
|
+
const content = fs.readFileSync(
|
|
216
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
217
|
+
"utf-8"
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
expect(content).toContain("onAfterInsert");
|
|
221
|
+
expect(content).toContain("Trigger.new");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should have after update handler", () => {
|
|
225
|
+
const content = fs.readFileSync(
|
|
226
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
227
|
+
"utf-8"
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
expect(content).toContain("onAfterUpdate");
|
|
231
|
+
expect(content).toContain("Trigger.new");
|
|
232
|
+
expect(content).toContain("Trigger.oldMap");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should have after delete handler", () => {
|
|
236
|
+
const content = fs.readFileSync(
|
|
237
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
238
|
+
"utf-8"
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(content).toContain("onAfterDelete");
|
|
242
|
+
expect(content).toContain("Trigger.oldMap");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("should have after undelete handler", () => {
|
|
246
|
+
const content = fs.readFileSync(
|
|
247
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
248
|
+
"utf-8"
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
expect(content).toContain("onAfterUndelete");
|
|
252
|
+
expect(content).toContain("Trigger.new");
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// =========================================================================
|
|
257
|
+
// UNIT TESTS - Apex trigger parsing edge cases
|
|
258
|
+
// =========================================================================
|
|
259
|
+
|
|
260
|
+
describe("Unit: Apex trigger parsing edge cases", () => {
|
|
261
|
+
it("should parse trigger with single event", () => {
|
|
262
|
+
const content = `trigger AccountTrigger on Account (before insert) {
|
|
263
|
+
// handler logic
|
|
264
|
+
}`;
|
|
265
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
266
|
+
const result = extractSymbols([file]);
|
|
267
|
+
|
|
268
|
+
const triggerSymbol = result.symbols.find(s => s.name === "AccountTrigger");
|
|
269
|
+
expect(triggerSymbol).toBeDefined();
|
|
270
|
+
expect(triggerSymbol?.type).toBe("function");
|
|
271
|
+
expect(triggerSymbol?.export).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should parse trigger with multiple events on same line", () => {
|
|
275
|
+
const content = `trigger ContactTrigger on Contact (before insert, before update, after insert, after update) {
|
|
276
|
+
// handler logic
|
|
277
|
+
}`;
|
|
278
|
+
const file = createFileInfo("ContactTrigger.trigger", "trigger", content);
|
|
279
|
+
const result = extractSymbols([file]);
|
|
280
|
+
|
|
281
|
+
const triggerSymbol = result.symbols.find(s => s.name === "ContactTrigger");
|
|
282
|
+
expect(triggerSymbol).toBeDefined();
|
|
283
|
+
expect(triggerSymbol?.type).toBe("function");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should parse trigger with before events only", () => {
|
|
287
|
+
const content = `trigger CaseTrigger on Case (before insert, before update, before delete) {
|
|
288
|
+
// before event handlers
|
|
289
|
+
}`;
|
|
290
|
+
const file = createFileInfo("CaseTrigger.trigger", "trigger", content);
|
|
291
|
+
const result = extractSymbols([file]);
|
|
292
|
+
|
|
293
|
+
const triggerSymbol = result.symbols.find(s => s.name === "CaseTrigger");
|
|
294
|
+
expect(triggerSymbol).toBeDefined();
|
|
295
|
+
expect(triggerSymbol?.type).toBe("function");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("should parse trigger with after events only", () => {
|
|
299
|
+
const content = `trigger LeadTrigger on Lead (after insert, after update, after delete, after undelete) {
|
|
300
|
+
// after event handlers
|
|
301
|
+
}`;
|
|
302
|
+
const file = createFileInfo("LeadTrigger.trigger", "trigger", content);
|
|
303
|
+
const result = extractSymbols([file]);
|
|
304
|
+
|
|
305
|
+
const triggerSymbol = result.symbols.find(s => s.name === "LeadTrigger");
|
|
306
|
+
expect(triggerSymbol).toBeDefined();
|
|
307
|
+
expect(triggerSymbol?.type).toBe("function");
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("should parse trigger on different SObjects (Account)", () => {
|
|
311
|
+
const content = `trigger AccountTrigger on Account (before insert, before update) {
|
|
312
|
+
for(Account a : Trigger.new) {
|
|
313
|
+
// process
|
|
314
|
+
}
|
|
315
|
+
}`;
|
|
316
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
317
|
+
const result = extractSymbols([file]);
|
|
318
|
+
|
|
319
|
+
const triggerSymbol = result.symbols.find(s => s.name === "AccountTrigger");
|
|
320
|
+
expect(triggerSymbol).toBeDefined();
|
|
321
|
+
|
|
322
|
+
const content2 = fs.readFileSync(file.path, "utf-8");
|
|
323
|
+
expect(content2).toContain("on Account");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("should parse trigger on different SObjects (Contact)", () => {
|
|
327
|
+
const content = `trigger ContactTrigger on Contact (before insert, before update) {
|
|
328
|
+
for(Contact c : Trigger.new) {
|
|
329
|
+
// process
|
|
330
|
+
}
|
|
331
|
+
}`;
|
|
332
|
+
const file = createFileInfo("ContactTrigger.trigger", "trigger", content);
|
|
333
|
+
const result = extractSymbols([file]);
|
|
334
|
+
|
|
335
|
+
const triggerSymbol = result.symbols.find(s => s.name === "ContactTrigger");
|
|
336
|
+
expect(triggerSymbol).toBeDefined();
|
|
337
|
+
|
|
338
|
+
const content2 = fs.readFileSync(file.path, "utf-8");
|
|
339
|
+
expect(content2).toContain("on Contact");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("should parse trigger on custom object", () => {
|
|
343
|
+
const content = `trigger CustomObjectTrigger on CustomObject__c (before insert) {
|
|
344
|
+
// custom object trigger
|
|
345
|
+
}`;
|
|
346
|
+
const file = createFileInfo("CustomObjectTrigger.trigger", "trigger", content);
|
|
347
|
+
const result = extractSymbols([file]);
|
|
348
|
+
|
|
349
|
+
const triggerSymbol = result.symbols.find(s => s.name === "CustomObjectTrigger");
|
|
350
|
+
expect(triggerSymbol).toBeDefined();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should detect trigger with handler instantiation", () => {
|
|
354
|
+
const content = `trigger OrderTrigger on Order (before insert) {
|
|
355
|
+
OrderTriggerHandler handler = new OrderTriggerHandler();
|
|
356
|
+
if (Trigger.isBefore) {
|
|
357
|
+
handler.onBeforeInsert(Trigger.new);
|
|
358
|
+
}
|
|
359
|
+
}`;
|
|
360
|
+
const file = createFileInfo("OrderTrigger.trigger", "trigger", content);
|
|
361
|
+
const result = extractSymbols([file]);
|
|
362
|
+
|
|
363
|
+
const triggerSymbol = result.symbols.find(s => s.name === "OrderTrigger");
|
|
364
|
+
expect(triggerSymbol).toBeDefined();
|
|
365
|
+
|
|
366
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
367
|
+
expect(fileContent).toContain("new OrderTriggerHandler()");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("should detect trigger with bypass check at start", () => {
|
|
371
|
+
const content = `trigger ProductTrigger on Product2 (before insert) {
|
|
372
|
+
if (TriggerManagement.isTriggerBypassed('Product2')) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
// trigger logic
|
|
376
|
+
}`;
|
|
377
|
+
const file = createFileInfo("ProductTrigger.trigger", "trigger", content);
|
|
378
|
+
const result = extractSymbols([file]);
|
|
379
|
+
|
|
380
|
+
const triggerSymbol = result.symbols.find(s => s.name === "ProductTrigger");
|
|
381
|
+
expect(triggerSymbol).toBeDefined();
|
|
382
|
+
|
|
383
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
384
|
+
expect(fileContent).toContain("isTriggerBypassed");
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("should parse trigger with Trigger.new", () => {
|
|
388
|
+
const content = `trigger AssetTrigger on Asset (before insert, before update) {
|
|
389
|
+
for(Asset a : Trigger.new) {
|
|
390
|
+
a.Description = 'Updated';
|
|
391
|
+
}
|
|
392
|
+
}`;
|
|
393
|
+
const file = createFileInfo("AssetTrigger.trigger", "trigger", content);
|
|
394
|
+
const result = extractSymbols([file]);
|
|
395
|
+
|
|
396
|
+
expect(result.symbols.find(s => s.name === "AssetTrigger")).toBeDefined();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("should parse trigger with Trigger.oldMap", () => {
|
|
400
|
+
const content = `trigger CampaignTrigger on Campaign (before delete) {
|
|
401
|
+
Map<Id, Campaign> oldMap = Trigger.oldMap;
|
|
402
|
+
// process deletions
|
|
403
|
+
}`;
|
|
404
|
+
const file = createFileInfo("CampaignTrigger.trigger", "trigger", content);
|
|
405
|
+
const result = extractSymbols([file]);
|
|
406
|
+
|
|
407
|
+
expect(result.symbols.find(s => s.name === "CampaignTrigger")).toBeDefined();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("should detect trigger as entrypoint type", () => {
|
|
411
|
+
const content = fs.readFileSync(
|
|
412
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
413
|
+
"utf-8"
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
// Trigger files are entrypoints in Salesforce
|
|
417
|
+
expect(content).toContain("trigger");
|
|
418
|
+
expect(content).toContain("Trigger.isBefore");
|
|
419
|
+
expect(content).toContain("Trigger.isAfter");
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// =========================================================================
|
|
424
|
+
// TRIGGER EVENTS VERIFICATION
|
|
425
|
+
// =========================================================================
|
|
426
|
+
|
|
427
|
+
describe("Trigger Events Verification", () => {
|
|
428
|
+
it("should correctly identify before insert event", () => {
|
|
429
|
+
const content = `trigger AccountTrigger on Account (before insert) {
|
|
430
|
+
if (Trigger.isBefore && Trigger.isInsert) {
|
|
431
|
+
// before insert logic
|
|
432
|
+
}
|
|
433
|
+
}`;
|
|
434
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
435
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
436
|
+
|
|
437
|
+
expect(fileContent).toContain("before insert");
|
|
438
|
+
expect(fileContent).toContain("Trigger.isBefore");
|
|
439
|
+
expect(fileContent).toContain("Trigger.isInsert");
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("should correctly identify before update event", () => {
|
|
443
|
+
const content = `trigger AccountTrigger on Account (before update) {
|
|
444
|
+
if (Trigger.isBefore && Trigger.isUpdate) {
|
|
445
|
+
// before update logic
|
|
446
|
+
}
|
|
447
|
+
}`;
|
|
448
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
449
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
450
|
+
|
|
451
|
+
expect(fileContent).toContain("before update");
|
|
452
|
+
expect(fileContent).toContain("Trigger.isBefore");
|
|
453
|
+
expect(fileContent).toContain("Trigger.isUpdate");
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("should correctly identify before delete event", () => {
|
|
457
|
+
const content = `trigger AccountTrigger on Account (before delete) {
|
|
458
|
+
if (Trigger.isBefore && Trigger.isDelete) {
|
|
459
|
+
// before delete logic
|
|
460
|
+
}
|
|
461
|
+
}`;
|
|
462
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
463
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
464
|
+
|
|
465
|
+
expect(fileContent).toContain("before delete");
|
|
466
|
+
expect(fileContent).toContain("Trigger.isBefore");
|
|
467
|
+
expect(fileContent).toContain("Trigger.isDelete");
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it("should correctly identify after insert event", () => {
|
|
471
|
+
const content = `trigger AccountTrigger on Account (after insert) {
|
|
472
|
+
if (Trigger.isAfter && Trigger.isInsert) {
|
|
473
|
+
// after insert logic
|
|
474
|
+
}
|
|
475
|
+
}`;
|
|
476
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
477
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
478
|
+
|
|
479
|
+
expect(fileContent).toContain("after insert");
|
|
480
|
+
expect(fileContent).toContain("Trigger.isAfter");
|
|
481
|
+
expect(fileContent).toContain("Trigger.isInsert");
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("should correctly identify after update event", () => {
|
|
485
|
+
const content = `trigger AccountTrigger on Account (after update) {
|
|
486
|
+
if (Trigger.isAfter && Trigger.isUpdate) {
|
|
487
|
+
// after update logic
|
|
488
|
+
}
|
|
489
|
+
}`;
|
|
490
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
491
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
492
|
+
|
|
493
|
+
expect(fileContent).toContain("after update");
|
|
494
|
+
expect(fileContent).toContain("Trigger.isAfter");
|
|
495
|
+
expect(fileContent).toContain("Trigger.isUpdate");
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("should correctly identify after delete event", () => {
|
|
499
|
+
const content = `trigger AccountTrigger on Account (after delete) {
|
|
500
|
+
if (Trigger.isAfter && Trigger.isDelete) {
|
|
501
|
+
// after delete logic
|
|
502
|
+
}
|
|
503
|
+
}`;
|
|
504
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
505
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
506
|
+
|
|
507
|
+
expect(fileContent).toContain("after delete");
|
|
508
|
+
expect(fileContent).toContain("Trigger.isAfter");
|
|
509
|
+
expect(fileContent).toContain("Trigger.isDelete");
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("should correctly identify after undelete event", () => {
|
|
513
|
+
const content = `trigger AccountTrigger on Account (after undelete) {
|
|
514
|
+
if (Trigger.isAfter && Trigger.isUndelete) {
|
|
515
|
+
// after undelete logic
|
|
516
|
+
}
|
|
517
|
+
}`;
|
|
518
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
519
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
520
|
+
|
|
521
|
+
expect(fileContent).toContain("after undelete");
|
|
522
|
+
expect(fileContent).toContain("Trigger.isAfter");
|
|
523
|
+
expect(fileContent).toContain("Trigger.isUndelete");
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// =========================================================================
|
|
528
|
+
// TRIGGER CONTEXT VARIABLES VERIFICATION
|
|
529
|
+
// =========================================================================
|
|
530
|
+
|
|
531
|
+
describe("Trigger Context Variables Verification", () => {
|
|
532
|
+
it("should verify Trigger.new is used correctly", () => {
|
|
533
|
+
const content = fs.readFileSync(
|
|
534
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
535
|
+
"utf-8"
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
expect(content).toMatch(/Trigger\.new/);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("should verify Trigger.old is used correctly", () => {
|
|
542
|
+
const content = `trigger AccountTrigger on Account (before update) {
|
|
543
|
+
for(Account oldAcc : Trigger.old) {
|
|
544
|
+
// use old values
|
|
545
|
+
}
|
|
546
|
+
}`;
|
|
547
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
548
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
549
|
+
|
|
550
|
+
expect(fileContent).toMatch(/Trigger\.old/);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("should verify Trigger.oldMap is used correctly", () => {
|
|
554
|
+
const content = fs.readFileSync(
|
|
555
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
556
|
+
"utf-8"
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
expect(content).toMatch(/Trigger\.oldMap/);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("should verify Trigger.newMap is used correctly", () => {
|
|
563
|
+
const content = `trigger AccountTrigger on Account (after update) {
|
|
564
|
+
Map<Id, Account> newMap = Trigger.newMap;
|
|
565
|
+
// process with new map
|
|
566
|
+
}`;
|
|
567
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
568
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
569
|
+
|
|
570
|
+
expect(fileContent).toMatch(/Trigger\.newMap/);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("should verify Trigger.isExecuting is used", () => {
|
|
574
|
+
const content = `trigger AccountTrigger on Account (before insert) {
|
|
575
|
+
if (Trigger.isExecuting) {
|
|
576
|
+
// running in trigger context
|
|
577
|
+
}
|
|
578
|
+
}`;
|
|
579
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
580
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
581
|
+
|
|
582
|
+
expect(fileContent).toMatch(/Trigger\.isExecuting/);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it("should verify Trigger.operationType is used", () => {
|
|
586
|
+
const content = `trigger AccountTrigger on Account (before insert) {
|
|
587
|
+
System.Debug('Operation: ' + Trigger.operationType);
|
|
588
|
+
}`;
|
|
589
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
590
|
+
const fileContent = fs.readFileSync(file.path, "utf-8");
|
|
591
|
+
|
|
592
|
+
expect(fileContent).toMatch(/Trigger\.operationType/);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// =========================================================================
|
|
597
|
+
// TRIGGER HANDLER PATTERN VERIFICATION
|
|
598
|
+
// =========================================================================
|
|
599
|
+
|
|
600
|
+
describe("Trigger Handler Pattern Verification", () => {
|
|
601
|
+
it("should have trigger handler class for OpportunityTrigger", () => {
|
|
602
|
+
const handlerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/OpportunityTriggerHandler.cls");
|
|
603
|
+
expect(fs.existsSync(handlerPath)).toBe(true);
|
|
604
|
+
|
|
605
|
+
const content = fs.readFileSync(handlerPath, "utf-8");
|
|
606
|
+
expect(content).toContain("class OpportunityTriggerHandler");
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it("should verify handler has onBeforeInsert method", () => {
|
|
610
|
+
const handlerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/OpportunityTriggerHandler.cls");
|
|
611
|
+
const content = fs.readFileSync(handlerPath, "utf-8");
|
|
612
|
+
|
|
613
|
+
expect(content).toContain("public void onBeforeInsert");
|
|
614
|
+
expect(content).toContain("onBeforeInsert");
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("should verify handler has onBeforeUpdate method", () => {
|
|
618
|
+
const handlerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/OpportunityTriggerHandler.cls");
|
|
619
|
+
const content = fs.readFileSync(handlerPath, "utf-8");
|
|
620
|
+
|
|
621
|
+
expect(content).toContain("public void onBeforeUpdate");
|
|
622
|
+
expect(content).toContain("onBeforeUpdate");
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it("should verify handler has onBeforeDelete method", () => {
|
|
626
|
+
const handlerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/OpportunityTriggerHandler.cls");
|
|
627
|
+
const content = fs.readFileSync(handlerPath, "utf-8");
|
|
628
|
+
|
|
629
|
+
expect(content).toContain("public void onBeforeDelete");
|
|
630
|
+
expect(content).toContain("onBeforeDelete");
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("should verify handler has onAfterInsert method", () => {
|
|
634
|
+
const handlerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/OpportunityTriggerHandler.cls");
|
|
635
|
+
const content = fs.readFileSync(handlerPath, "utf-8");
|
|
636
|
+
|
|
637
|
+
expect(content).toContain("public void onAfterInsert");
|
|
638
|
+
expect(content).toContain("onAfterInsert");
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it("should verify handler has onAfterUpdate method", () => {
|
|
642
|
+
const handlerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/OpportunityTriggerHandler.cls");
|
|
643
|
+
const content = fs.readFileSync(handlerPath, "utf-8");
|
|
644
|
+
|
|
645
|
+
expect(content).toContain("public void onAfterUpdate");
|
|
646
|
+
expect(content).toContain("onAfterUpdate");
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it("should verify handler has onAfterDelete method", () => {
|
|
650
|
+
const handlerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/OpportunityTriggerHandler.cls");
|
|
651
|
+
const content = fs.readFileSync(handlerPath, "utf-8");
|
|
652
|
+
|
|
653
|
+
expect(content).toContain("public void onAfterDelete");
|
|
654
|
+
expect(content).toContain("onAfterDelete");
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it("should verify handler has onAfterUndelete method", () => {
|
|
658
|
+
const handlerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/OpportunityTriggerHandler.cls");
|
|
659
|
+
const content = fs.readFileSync(handlerPath, "utf-8");
|
|
660
|
+
|
|
661
|
+
expect(content).toContain("public void onAfterUndelete");
|
|
662
|
+
expect(content).toContain("onAfterUndelete");
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it("should use handler pattern in trigger", () => {
|
|
666
|
+
const content = fs.readFileSync(
|
|
667
|
+
path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger"),
|
|
668
|
+
"utf-8"
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
// Verify handler instantiation and usage pattern
|
|
672
|
+
expect(content).toContain("OpportunityTriggerHandler handler = new OpportunityTriggerHandler()");
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// =========================================================================
|
|
677
|
+
// TRIGGER BYPASS MANAGEMENT VERIFICATION
|
|
678
|
+
// =========================================================================
|
|
679
|
+
|
|
680
|
+
describe("Trigger Bypass Management Verification", () => {
|
|
681
|
+
it("should have TriggerManagement class", () => {
|
|
682
|
+
const managementPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/TriggerManagement.cls");
|
|
683
|
+
expect(fs.existsSync(managementPath)).toBe(true);
|
|
684
|
+
|
|
685
|
+
const content = fs.readFileSync(managementPath, "utf-8");
|
|
686
|
+
expect(content).toContain("class TriggerManagement");
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it("should have isTriggerBypassed method", () => {
|
|
690
|
+
const managementPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/classes/TriggerManagement.cls");
|
|
691
|
+
const content = fs.readFileSync(managementPath, "utf-8");
|
|
692
|
+
|
|
693
|
+
expect(content).toContain("isTriggerBypassed");
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it("should use isTriggerBypassed in trigger", () => {
|
|
697
|
+
const triggerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger");
|
|
698
|
+
const content = fs.readFileSync(triggerPath, "utf-8");
|
|
699
|
+
|
|
700
|
+
expect(content).toContain("TriggerManagement.isTriggerBypassed('Opportunity')");
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it("should return early when trigger is bypassed", () => {
|
|
704
|
+
const triggerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger");
|
|
705
|
+
const content = fs.readFileSync(triggerPath, "utf-8");
|
|
706
|
+
|
|
707
|
+
expect(content).toContain("return;");
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
// =========================================================================
|
|
712
|
+
// SYMBOL EXTRACTION ACCURACY
|
|
713
|
+
// =========================================================================
|
|
714
|
+
|
|
715
|
+
describe("Symbol Extraction Accuracy", () => {
|
|
716
|
+
it("should extract OpportunityTrigger as function symbol", () => {
|
|
717
|
+
const triggerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger");
|
|
718
|
+
const content = fs.readFileSync(triggerPath, "utf-8");
|
|
719
|
+
|
|
720
|
+
const file: FileInfo = {
|
|
721
|
+
path: triggerPath,
|
|
722
|
+
relativePath: "force-app/main/default/triggers/OpportunityTrigger.trigger",
|
|
723
|
+
extension: "trigger",
|
|
724
|
+
name: "OpportunityTrigger",
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const result = extractSymbols([file]);
|
|
728
|
+
const triggerSymbols = result.symbols.filter(s => s.type === "function");
|
|
729
|
+
|
|
730
|
+
expect(triggerSymbols.length).toBeGreaterThanOrEqual(1);
|
|
731
|
+
expect(triggerSymbols.some(s => s.name === "OpportunityTrigger")).toBe(true);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it("should have correct line number for OpportunityTrigger", () => {
|
|
735
|
+
const triggerPath = path.join(SALESFORCE_ENTERPRISE_PATH, "force-app/main/default/triggers/OpportunityTrigger.trigger");
|
|
736
|
+
const content = fs.readFileSync(triggerPath, "utf-8");
|
|
737
|
+
|
|
738
|
+
const file: FileInfo = {
|
|
739
|
+
path: triggerPath,
|
|
740
|
+
relativePath: "force-app/main/default/triggers/OpportunityTrigger.trigger",
|
|
741
|
+
extension: "trigger",
|
|
742
|
+
name: "OpportunityTrigger",
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
const result = extractSymbols([file]);
|
|
746
|
+
const triggerSymbol = result.symbols.find(s => s.name === "OpportunityTrigger");
|
|
747
|
+
|
|
748
|
+
expect(triggerSymbol).toBeDefined();
|
|
749
|
+
expect(triggerSymbol?.line).toBe(7); // trigger definition starts at line 7
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it("should extract all trigger symbols from multiple trigger files", () => {
|
|
753
|
+
const trigger1 = `trigger AccountTrigger on Account (before insert) {}`;
|
|
754
|
+
const trigger2 = `trigger ContactTrigger on Contact (before insert) {}`;
|
|
755
|
+
const trigger3 = `trigger LeadTrigger on Lead (before insert) {}`;
|
|
756
|
+
|
|
757
|
+
const files = [
|
|
758
|
+
createFileInfo("AccountTrigger.trigger", "trigger", trigger1),
|
|
759
|
+
createFileInfo("ContactTrigger.trigger", "trigger", trigger2),
|
|
760
|
+
createFileInfo("LeadTrigger.trigger", "trigger", trigger3),
|
|
761
|
+
];
|
|
762
|
+
|
|
763
|
+
const result = extractSymbols(files);
|
|
764
|
+
const triggerSymbols = result.symbols.filter(s => s.type === "function");
|
|
765
|
+
|
|
766
|
+
expect(triggerSymbols.length).toBe(3);
|
|
767
|
+
expect(triggerSymbols.some(s => s.name === "AccountTrigger")).toBe(true);
|
|
768
|
+
expect(triggerSymbols.some(s => s.name === "ContactTrigger")).toBe(true);
|
|
769
|
+
expect(triggerSymbols.some(s => s.name === "LeadTrigger")).toBe(true);
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// =========================================================================
|
|
774
|
+
// EDGE CASES
|
|
775
|
+
// =========================================================================
|
|
776
|
+
|
|
777
|
+
describe("Edge Cases", () => {
|
|
778
|
+
it("should handle trigger without comments", () => {
|
|
779
|
+
const content = `trigger SimpleTrigger on Account (before insert) {
|
|
780
|
+
// no comments
|
|
781
|
+
}`;
|
|
782
|
+
const file = createFileInfo("SimpleTrigger.trigger", "trigger", content);
|
|
783
|
+
const result = extractSymbols([file]);
|
|
784
|
+
|
|
785
|
+
expect(result.symbols.find(s => s.name === "SimpleTrigger")).toBeDefined();
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
it("should handle trigger with multi-line events definition", () => {
|
|
789
|
+
const content = `trigger AccountTrigger on Account (
|
|
790
|
+
before insert,
|
|
791
|
+
before update,
|
|
792
|
+
before delete,
|
|
793
|
+
after insert,
|
|
794
|
+
after update,
|
|
795
|
+
after delete,
|
|
796
|
+
after undelete
|
|
797
|
+
) {
|
|
798
|
+
// all events
|
|
799
|
+
}`;
|
|
800
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
801
|
+
const result = extractSymbols([file]);
|
|
802
|
+
|
|
803
|
+
expect(result.symbols.find(s => s.name === "AccountTrigger")).toBeDefined();
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it("should handle trigger on object with namespace prefix", () => {
|
|
807
|
+
const content = `trigger CustomTrigger on MyNamespace__CustomObject__c (before insert) {
|
|
808
|
+
// namespaced object
|
|
809
|
+
}`;
|
|
810
|
+
const file = createFileInfo("CustomTrigger.trigger", "trigger", content);
|
|
811
|
+
const result = extractSymbols([file]);
|
|
812
|
+
|
|
813
|
+
expect(result.symbols.find(s => s.name === "CustomTrigger")).toBeDefined();
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it("should handle trigger with no explicit handler (inline logic)", () => {
|
|
817
|
+
const content = `trigger InlineTrigger on Account (before insert) {
|
|
818
|
+
for(Account a : Trigger.new) {
|
|
819
|
+
a.Description = 'Auto-generated';
|
|
820
|
+
}
|
|
821
|
+
}`;
|
|
822
|
+
const file = createFileInfo("InlineTrigger.trigger", "trigger", content);
|
|
823
|
+
const result = extractSymbols([file]);
|
|
824
|
+
|
|
825
|
+
expect(result.symbols.find(s => s.name === "InlineTrigger")).toBeDefined();
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it("should handle trigger with try-catch block", () => {
|
|
829
|
+
const content = `trigger SafeTrigger on Account (before insert) {
|
|
830
|
+
try {
|
|
831
|
+
// might fail
|
|
832
|
+
} catch (Exception e) {
|
|
833
|
+
System.debug(e.getMessage());
|
|
834
|
+
}
|
|
835
|
+
}`;
|
|
836
|
+
const file = createFileInfo("SafeTrigger.trigger", "trigger", content);
|
|
837
|
+
const result = extractSymbols([file]);
|
|
838
|
+
|
|
839
|
+
expect(result.symbols.find(s => s.name === "SafeTrigger")).toBeDefined();
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it("should handle trigger with SOQL query", () => {
|
|
843
|
+
const content = `trigger AccountTrigger on Account (before insert) {
|
|
844
|
+
List<Account> related = [SELECT Id FROM Account WHERE ParentId = null LIMIT 1];
|
|
845
|
+
}`;
|
|
846
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
847
|
+
const result = extractSymbols([file]);
|
|
848
|
+
|
|
849
|
+
expect(result.symbols.find(s => s.name === "AccountTrigger")).toBeDefined();
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
it("should handle trigger with DML statement", () => {
|
|
853
|
+
const content = `trigger AccountTrigger on Account (before insert) {
|
|
854
|
+
insert new Account(Name = 'Test');
|
|
855
|
+
}`;
|
|
856
|
+
const file = createFileInfo("AccountTrigger.trigger", "trigger", content);
|
|
857
|
+
const result = extractSymbols([file]);
|
|
858
|
+
|
|
859
|
+
expect(result.symbols.find(s => s.name === "AccountTrigger")).toBeDefined();
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it("should handle empty trigger body", () => {
|
|
863
|
+
const content = `trigger EmptyTrigger on Account (before insert) {
|
|
864
|
+
}`;
|
|
865
|
+
const file = createFileInfo("EmptyTrigger.trigger", "trigger", content);
|
|
866
|
+
const result = extractSymbols([file]);
|
|
867
|
+
|
|
868
|
+
expect(result.symbols.find(s => s.name === "EmptyTrigger")).toBeDefined();
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
});
|