#
tokens: 37616/50000 1/236 files (page 11/11)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 11 of 11. Use http://codebase.md/sapientpants/sonarqube-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .adr-dir
├── .changeset
│   ├── config.json
│   └── README.md
├── .claude
│   ├── commands
│   │   ├── analyze-and-fix-github-issue.md
│   │   ├── fix-sonarqube-issues.md
│   │   ├── implement-github-issue.md
│   │   ├── release.md
│   │   ├── spec-feature.md
│   │   └── update-dependencies.md
│   ├── hooks
│   │   └── block-git-no-verify.ts
│   └── settings.json
├── .dockerignore
├── .github
│   ├── actionlint.yaml
│   ├── changeset.yml
│   ├── dependabot.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── pull_request_template.md
│   ├── scripts
│   │   ├── determine-artifact.sh
│   │   └── version-and-release.js
│   ├── workflows
│   │   ├── codeql.yml
│   │   ├── main.yml
│   │   ├── pr.yml
│   │   ├── publish.yml
│   │   ├── reusable-docker.yml
│   │   ├── reusable-security.yml
│   │   └── reusable-validate.yml
│   └── WORKFLOWS.md
├── .gitignore
├── .husky
│   ├── commit-msg
│   └── pre-commit
├── .markdownlint.yaml
├── .markdownlintignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .trivyignore
├── .yaml-lint.yml
├── .yamllintignore
├── CHANGELOG.md
├── changes.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── commitlint.config.js
├── COMPATIBILITY.md
├── CONTRIBUTING.md
├── docker-compose.yml
├── Dockerfile
├── docs
│   ├── architecture
│   │   └── decisions
│   │       ├── 0001-record-architecture-decisions.md
│   │       ├── 0002-use-node-js-with-typescript.md
│   │       ├── 0003-adopt-model-context-protocol-for-sonarqube-integration.md
│   │       ├── 0004-use-sonarqube-web-api-client-for-all-sonarqube-interactions.md
│   │       ├── 0005-domain-driven-design-of-sonarqube-modules.md
│   │       ├── 0006-expose-sonarqube-features-as-mcp-tools.md
│   │       ├── 0007-support-multiple-authentication-methods-for-sonarqube.md
│   │       ├── 0008-use-environment-variables-for-configuration.md
│   │       ├── 0009-file-based-logging-to-avoid-stdio-conflicts.md
│   │       ├── 0010-use-stdio-transport-for-mcp-communication.md
│   │       ├── 0011-docker-containerization-for-deployment.md
│   │       ├── 0012-add-elicitation-support-for-interactive-user-input.md
│   │       ├── 0014-current-security-model-and-future-oauth2-considerations.md
│   │       ├── 0015-transport-architecture-refactoring.md
│   │       ├── 0016-http-transport-with-oauth-2-0-metadata-endpoints.md
│   │       ├── 0017-comprehensive-audit-logging-system.md
│   │       ├── 0018-add-comprehensive-monitoring-and-observability.md
│   │       ├── 0019-simplify-to-stdio-only-transport-for-mcp-gateway-deployment.md
│   │       ├── 0020-testing-framework-and-strategy-vitest-with-property-based-testing.md
│   │       ├── 0021-code-quality-toolchain-eslint-prettier-strict-typescript.md
│   │       ├── 0022-package-manager-choice-pnpm.md
│   │       ├── 0023-release-management-with-changesets.md
│   │       ├── 0024-ci-cd-platform-github-actions.md
│   │       ├── 0025-container-and-security-scanning-strategy.md
│   │       ├── 0026-circuit-breaker-pattern-with-opossum.md
│   │       ├── 0027-docker-image-publishing-strategy-ghcr-to-docker-hub.md
│   │       └── 0028-session-based-http-transport-with-server-sent-events.md
│   ├── architecture.md
│   ├── security.md
│   └── troubleshooting.md
├── eslint.config.js
├── examples
│   └── http-client.ts
├── jest.config.js
├── LICENSE
├── LICENSES.md
├── osv-scanner.toml
├── package.json
├── pnpm-lock.yaml
├── README.md
├── scripts
│   ├── actionlint.sh
│   ├── ci-local.sh
│   ├── load-test.sh
│   ├── README.md
│   ├── run-all-tests.sh
│   ├── scan-container.sh
│   ├── security-scan.sh
│   ├── setup.sh
│   ├── test-monitoring-integration.sh
│   └── validate-docs.sh
├── SECURITY.md
├── sonar-project.properties
├── src
│   ├── __tests__
│   │   ├── additional-coverage.test.ts
│   │   ├── advanced-index.test.ts
│   │   ├── assign-issue.test.ts
│   │   ├── auth-methods.test.ts
│   │   ├── boolean-string-transform.test.ts
│   │   ├── components.test.ts
│   │   ├── config
│   │   │   └── service-accounts.test.ts
│   │   ├── dependency-injection.test.ts
│   │   ├── direct-handlers.test.ts
│   │   ├── direct-lambdas.test.ts
│   │   ├── direct-schema-validation.test.ts
│   │   ├── domains
│   │   │   ├── components-domain-full.test.ts
│   │   │   ├── components-domain.test.ts
│   │   │   ├── hotspots-domain.test.ts
│   │   │   └── source-code-domain.test.ts
│   │   ├── environment-validation.test.ts
│   │   ├── error-handler.test.ts
│   │   ├── error-handling.test.ts
│   │   ├── errors.test.ts
│   │   ├── function-tests.test.ts
│   │   ├── handlers
│   │   │   ├── components-handler-integration.test.ts
│   │   │   └── projects-authorization.test.ts
│   │   ├── handlers.test.ts
│   │   ├── handlers.test.ts.skip
│   │   ├── index.test.ts
│   │   ├── issue-resolution-elicitation.test.ts
│   │   ├── issue-resolution.test.ts
│   │   ├── issue-transitions.test.ts
│   │   ├── issues-enhanced-search.test.ts
│   │   ├── issues-new-parameters.test.ts
│   │   ├── json-array-transform.test.ts
│   │   ├── lambda-functions.test.ts
│   │   ├── lambda-handlers.test.ts.skip
│   │   ├── logger.test.ts
│   │   ├── mapping-functions.test.ts
│   │   ├── mocked-environment.test.ts
│   │   ├── null-to-undefined.test.ts
│   │   ├── parameter-transformations-advanced.test.ts
│   │   ├── parameter-transformations.test.ts
│   │   ├── protocol-version.test.ts
│   │   ├── pull-request-transform.test.ts
│   │   ├── quality-gates.test.ts
│   │   ├── schema-parameter-transforms.test.ts
│   │   ├── schema-transformation-mocks.test.ts
│   │   ├── schema-transforms.test.ts
│   │   ├── schema-validators.test.ts
│   │   ├── schemas
│   │   │   ├── components-schema.test.ts
│   │   │   ├── hotspots-tools-schema.test.ts
│   │   │   └── issues-schema.test.ts
│   │   ├── sonarqube-elicitation.test.ts
│   │   ├── sonarqube.test.ts
│   │   ├── source-code.test.ts
│   │   ├── standalone-handlers.test.ts
│   │   ├── string-to-number-transform.test.ts
│   │   ├── tool-handler-lambdas.test.ts
│   │   ├── tool-handlers.test.ts
│   │   ├── tool-registration-schema.test.ts
│   │   ├── tool-registration-transforms.test.ts
│   │   ├── transformation-util.test.ts
│   │   ├── transports
│   │   │   ├── base.test.ts
│   │   │   ├── factory.test.ts
│   │   │   ├── http.test.ts
│   │   │   ├── session-manager.test.ts
│   │   │   └── stdio.test.ts
│   │   ├── utils
│   │   │   ├── retry.test.ts
│   │   │   └── transforms.test.ts
│   │   ├── zod-boolean-transform.test.ts
│   │   ├── zod-schema-transforms.test.ts
│   │   └── zod-transforms.test.ts
│   ├── config
│   │   ├── service-accounts.ts
│   │   └── versions.ts
│   ├── domains
│   │   ├── base.ts
│   │   ├── components.ts
│   │   ├── hotspots.ts
│   │   ├── index.ts
│   │   ├── issues.ts
│   │   ├── measures.ts
│   │   ├── metrics.ts
│   │   ├── projects.ts
│   │   ├── quality-gates.ts
│   │   ├── source-code.ts
│   │   └── system.ts
│   ├── errors.ts
│   ├── handlers
│   │   ├── components.ts
│   │   ├── hotspots.ts
│   │   ├── index.ts
│   │   ├── issues.ts
│   │   ├── measures.ts
│   │   ├── metrics.ts
│   │   ├── projects.ts
│   │   ├── quality-gates.ts
│   │   ├── source-code.ts
│   │   └── system.ts
│   ├── index.ts
│   ├── monitoring
│   │   ├── __tests__
│   │   │   └── circuit-breaker.test.ts
│   │   ├── circuit-breaker.ts
│   │   ├── health.ts
│   │   └── metrics.ts
│   ├── schemas
│   │   ├── common.ts
│   │   ├── components.ts
│   │   ├── hotspots-tools.ts
│   │   ├── hotspots.ts
│   │   ├── index.ts
│   │   ├── issues.ts
│   │   ├── measures.ts
│   │   ├── metrics.ts
│   │   ├── projects.ts
│   │   ├── quality-gates.ts
│   │   ├── source-code.ts
│   │   └── system.ts
│   ├── sonarqube.ts
│   ├── transports
│   │   ├── base.ts
│   │   ├── factory.ts
│   │   ├── http.ts
│   │   ├── index.ts
│   │   ├── session-manager.ts
│   │   └── stdio.ts
│   ├── types
│   │   ├── common.ts
│   │   ├── components.ts
│   │   ├── hotspots.ts
│   │   ├── index.ts
│   │   ├── issues.ts
│   │   ├── measures.ts
│   │   ├── metrics.ts
│   │   ├── projects.ts
│   │   ├── quality-gates.ts
│   │   ├── source-code.ts
│   │   └── system.ts
│   └── utils
│       ├── __tests__
│       │   ├── elicitation.test.ts
│       │   ├── pattern-matcher.test.ts
│       │   └── structured-response.test.ts
│       ├── client-factory.ts
│       ├── elicitation.ts
│       ├── error-handler.ts
│       ├── logger.ts
│       ├── parameter-mappers.ts
│       ├── pattern-matcher.ts
│       ├── retry.ts
│       ├── structured-response.ts
│       └── transforms.ts
├── test-http-transport.sh
├── tmp
│   └── .gitkeep
├── tsconfig.build.json
├── tsconfig.json
├── vitest.config.d.ts
├── vitest.config.js
├── vitest.config.js.map
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/src/__tests__/index.test.ts:
--------------------------------------------------------------------------------

```typescript
   1 | import { describe, it, expect, beforeEach, afterEach, beforeAll, vi } from 'vitest';
   2 | import nock from 'nock';
   3 | import { z } from 'zod';
   4 | 
   5 | // Mock process.exit to prevent the test runner from exiting
   6 | vi.spyOn(process, 'exit').mockImplementation(() => {
   7 |   throw new Error('process.exit called');
   8 | });
   9 | 
  10 | // Store original env vars
  11 | const originalEnv = { ...process.env };
  12 | // Mock environment variables
  13 | process.env.SONARQUBE_TOKEN = 'test-token';
  14 | process.env.SONARQUBE_URL = 'http://localhost:9000';
  15 | // Mock SonarQube client responses
  16 | beforeAll(() => {
  17 |   nock('http://localhost:9000')
  18 |     .persist()
  19 |     .get('/api/projects/search')
  20 |     .query(true)
  21 |     .reply(200, {
  22 |       projects: [
  23 |         {
  24 |           key: 'test-project',
  25 |           name: 'Test Project',
  26 |           qualifier: 'TRK',
  27 |           visibility: 'public',
  28 |           lastAnalysisDate: '2024-03-01',
  29 |           revision: 'abc123',
  30 |           managed: false,
  31 |         },
  32 |       ],
  33 |       paging: {
  34 |         pageIndex: 1,
  35 |         pageSize: 10,
  36 |         total: 1,
  37 |       },
  38 |     });
  39 |   nock('http://localhost:9000')
  40 |     .persist()
  41 |     .get('/api/metrics/search')
  42 |     .query(true)
  43 |     .reply(200, {
  44 |       metrics: [
  45 |         {
  46 |           key: 'test-metric',
  47 |           name: 'Test Metric',
  48 |           description: 'Test metric description',
  49 |           domain: 'test',
  50 |           type: 'INT',
  51 |         },
  52 |       ],
  53 |       paging: {
  54 |         pageIndex: 1,
  55 |         pageSize: 10,
  56 |         total: 1,
  57 |       },
  58 |     });
  59 |   nock('http://localhost:9000')
  60 |     .persist()
  61 |     .get('/api/issues/search')
  62 |     .query(true)
  63 |     .reply(200, {
  64 |       issues: [
  65 |         {
  66 |           key: 'test-issue',
  67 |           rule: 'test-rule',
  68 |           severity: 'MAJOR',
  69 |           component: 'test-component',
  70 |           project: 'test-project',
  71 |           line: 1,
  72 |           status: 'OPEN',
  73 |           message: 'Test issue',
  74 |         },
  75 |       ],
  76 |       components: [],
  77 |       rules: [],
  78 |       users: [],
  79 |       facets: [],
  80 |       paging: {
  81 |         pageIndex: 1,
  82 |         pageSize: 10,
  83 |         total: 1,
  84 |       },
  85 |     });
  86 |   nock('http://localhost:9000').persist().get('/api/system/health').reply(200, {
  87 |     health: 'GREEN',
  88 |     causes: [],
  89 |   });
  90 |   nock('http://localhost:9000').persist().get('/api/system/status').reply(200, {
  91 |     id: 'test-id',
  92 |     version: '10.3.0.82913',
  93 |     status: 'UP',
  94 |   });
  95 |   nock('http://localhost:9000').persist().get('/api/system/ping').reply(200, 'pong');
  96 |   // Mock SonarQube measures API responses
  97 |   nock('http://localhost:9000')
  98 |     .persist()
  99 |     .get('/api/measures/component')
 100 |     .query(true)
 101 |     .reply(200, {
 102 |       component: {
 103 |         key: 'test-project',
 104 |         name: 'Test Project',
 105 |         qualifier: 'TRK',
 106 |         measures: [
 107 |           {
 108 |             metric: 'coverage',
 109 |             value: '85.4',
 110 |           },
 111 |           {
 112 |             metric: 'bugs',
 113 |             value: '12',
 114 |           },
 115 |         ],
 116 |       },
 117 |       metrics: [
 118 |         {
 119 |           key: 'coverage',
 120 |           name: 'Coverage',
 121 |           description: 'Test coverage percentage',
 122 |           domain: 'Coverage',
 123 |           type: 'PERCENT',
 124 |         },
 125 |         {
 126 |           key: 'bugs',
 127 |           name: 'Bugs',
 128 |           description: 'Number of bugs',
 129 |           domain: 'Reliability',
 130 |           type: 'INT',
 131 |         },
 132 |       ],
 133 |     });
 134 |   nock('http://localhost:9000')
 135 |     .persist()
 136 |     .get('/api/measures/components')
 137 |     .query(true)
 138 |     .reply(200, {
 139 |       components: [
 140 |         {
 141 |           key: 'test-project-1',
 142 |           name: 'Test Project 1',
 143 |           qualifier: 'TRK',
 144 |           measures: [
 145 |             {
 146 |               metric: 'coverage',
 147 |               value: '85.4',
 148 |             },
 149 |           ],
 150 |         },
 151 |         {
 152 |           key: 'test-project-2',
 153 |           name: 'Test Project 2',
 154 |           qualifier: 'TRK',
 155 |           measures: [
 156 |             {
 157 |               metric: 'coverage',
 158 |               value: '72.1',
 159 |             },
 160 |           ],
 161 |         },
 162 |       ],
 163 |       metrics: [
 164 |         {
 165 |           key: 'coverage',
 166 |           name: 'Coverage',
 167 |           description: 'Test coverage percentage',
 168 |           domain: 'Coverage',
 169 |           type: 'PERCENT',
 170 |         },
 171 |       ],
 172 |       paging: {
 173 |         pageIndex: 1,
 174 |         pageSize: 100,
 175 |         total: 2,
 176 |       },
 177 |     });
 178 |   nock('http://localhost:9000')
 179 |     .persist()
 180 |     .get('/api/measures/search_history')
 181 |     .query(true)
 182 |     .reply(200, {
 183 |       measures: [
 184 |         {
 185 |           metric: 'coverage',
 186 |           history: [
 187 |             {
 188 |               date: '2023-01-01T00:00:00+0000',
 189 |               value: '85.4',
 190 |             },
 191 |             {
 192 |               date: '2023-02-01T00:00:00+0000',
 193 |               value: '87.2',
 194 |             },
 195 |           ],
 196 |         },
 197 |       ],
 198 |       paging: {
 199 |         pageIndex: 1,
 200 |         pageSize: 100,
 201 |         total: 1,
 202 |       },
 203 |     });
 204 | });
 205 | afterAll(() => {
 206 |   nock.cleanAll();
 207 | });
 208 | // Mock the handlers
 209 | const mockHandlers = {
 210 |   handleSonarQubeProjects: (vi.fn() as any).mockResolvedValue({
 211 |     content: [
 212 |       {
 213 |         type: 'text' as const,
 214 |         text: JSON.stringify({
 215 |           projects: [
 216 |             {
 217 |               key: 'test-project',
 218 |               name: 'Test Project',
 219 |               qualifier: 'TRK',
 220 |               visibility: 'public',
 221 |               lastAnalysisDate: '2024-03-01',
 222 |               revision: 'abc123',
 223 |               managed: false,
 224 |             },
 225 |           ],
 226 |           paging: {
 227 |             pageIndex: 1,
 228 |             pageSize: 10,
 229 |             total: 1,
 230 |           },
 231 |         }),
 232 |       },
 233 |     ],
 234 |   }),
 235 |   handleSonarQubeGetMetrics: (vi.fn() as any).mockResolvedValue({
 236 |     content: [
 237 |       {
 238 |         type: 'text' as const,
 239 |         text: JSON.stringify({
 240 |           metrics: [
 241 |             {
 242 |               key: 'test-metric',
 243 |               name: 'Test Metric',
 244 |               description: 'Test metric description',
 245 |               domain: 'test',
 246 |               type: 'INT',
 247 |             },
 248 |           ],
 249 |           paging: {
 250 |             pageIndex: 1,
 251 |             pageSize: 10,
 252 |             total: 1,
 253 |           },
 254 |         }),
 255 |       },
 256 |     ],
 257 |   }),
 258 |   handleSonarQubeGetIssues: (vi.fn() as any).mockResolvedValue({
 259 |     content: [
 260 |       {
 261 |         type: 'text' as const,
 262 |         text: JSON.stringify({
 263 |           issues: [
 264 |             {
 265 |               key: 'test-issue',
 266 |               rule: 'test-rule',
 267 |               severity: 'MAJOR',
 268 |               component: 'test-component',
 269 |               project: 'test-project',
 270 |               line: 1,
 271 |               status: 'OPEN',
 272 |               message: 'Test issue',
 273 |             },
 274 |           ],
 275 |           components: [],
 276 |           rules: [],
 277 |           users: [],
 278 |           facets: [],
 279 |           paging: {
 280 |             pageIndex: 1,
 281 |             pageSize: 10,
 282 |             total: 1,
 283 |           },
 284 |         }),
 285 |       },
 286 |     ],
 287 |   }),
 288 |   handleSonarQubeGetHealth: (vi.fn() as any).mockResolvedValue({
 289 |     content: [
 290 |       {
 291 |         type: 'text' as const,
 292 |         text: JSON.stringify({
 293 |           health: 'GREEN',
 294 |           causes: [],
 295 |         }),
 296 |       },
 297 |     ],
 298 |   }),
 299 |   handleSonarQubeGetStatus: (vi.fn() as any).mockResolvedValue({
 300 |     content: [
 301 |       {
 302 |         type: 'text' as const,
 303 |         text: JSON.stringify({
 304 |           id: 'test-id',
 305 |           version: '10.3.0.82913',
 306 |           status: 'UP',
 307 |         }),
 308 |       },
 309 |     ],
 310 |   }),
 311 |   handleSonarQubePing: (vi.fn() as any).mockResolvedValue({
 312 |     content: [
 313 |       {
 314 |         type: 'text' as const,
 315 |         text: 'pong',
 316 |       },
 317 |     ],
 318 |   }),
 319 |   handleSonarQubeComponentMeasures: (vi.fn() as any).mockResolvedValue({
 320 |     content: [
 321 |       {
 322 |         type: 'text' as const,
 323 |         text: JSON.stringify({
 324 |           component: {
 325 |             key: 'test-project',
 326 |             name: 'Test Project',
 327 |             qualifier: 'TRK',
 328 |             measures: [
 329 |               {
 330 |                 metric: 'coverage',
 331 |                 value: '85.4',
 332 |               },
 333 |               {
 334 |                 metric: 'bugs',
 335 |                 value: '12',
 336 |               },
 337 |             ],
 338 |           },
 339 |           metrics: [
 340 |             {
 341 |               key: 'coverage',
 342 |               name: 'Coverage',
 343 |               description: 'Test coverage percentage',
 344 |               domain: 'Coverage',
 345 |               type: 'PERCENT',
 346 |             },
 347 |             {
 348 |               key: 'bugs',
 349 |               name: 'Bugs',
 350 |               description: 'Number of bugs',
 351 |               domain: 'Reliability',
 352 |               type: 'INT',
 353 |             },
 354 |           ],
 355 |         }),
 356 |       },
 357 |     ],
 358 |   }),
 359 |   handleSonarQubeComponentsMeasures: (vi.fn() as any).mockResolvedValue({
 360 |     content: [
 361 |       {
 362 |         type: 'text' as const,
 363 |         text: JSON.stringify({
 364 |           components: [
 365 |             {
 366 |               key: 'test-project-1',
 367 |               name: 'Test Project 1',
 368 |               qualifier: 'TRK',
 369 |               measures: [
 370 |                 {
 371 |                   metric: 'coverage',
 372 |                   value: '85.4',
 373 |                 },
 374 |               ],
 375 |             },
 376 |             {
 377 |               key: 'test-project-2',
 378 |               name: 'Test Project 2',
 379 |               qualifier: 'TRK',
 380 |               measures: [
 381 |                 {
 382 |                   metric: 'coverage',
 383 |                   value: '72.1',
 384 |                 },
 385 |               ],
 386 |             },
 387 |           ],
 388 |           metrics: [
 389 |             {
 390 |               key: 'coverage',
 391 |               name: 'Coverage',
 392 |               description: 'Test coverage percentage',
 393 |               domain: 'Coverage',
 394 |               type: 'PERCENT',
 395 |             },
 396 |           ],
 397 |           paging: {
 398 |             pageIndex: 1,
 399 |             pageSize: 100,
 400 |             total: 2,
 401 |           },
 402 |         }),
 403 |       },
 404 |     ],
 405 |   }),
 406 |   handleSonarQubeMeasuresHistory: (vi.fn() as any).mockResolvedValue({
 407 |     content: [
 408 |       {
 409 |         type: 'text' as const,
 410 |         text: JSON.stringify({
 411 |           measures: [
 412 |             {
 413 |               metric: 'coverage',
 414 |               history: [
 415 |                 {
 416 |                   date: '2023-01-01T00:00:00+0000',
 417 |                   value: '85.4',
 418 |                 },
 419 |                 {
 420 |                   date: '2023-02-01T00:00:00+0000',
 421 |                   value: '87.2',
 422 |                 },
 423 |               ],
 424 |             },
 425 |           ],
 426 |           paging: {
 427 |             pageIndex: 1,
 428 |             pageSize: 100,
 429 |             total: 1,
 430 |           },
 431 |         }),
 432 |       },
 433 |     ],
 434 |   }),
 435 | };
 436 | // Define the mock handlers but don't mock the entire module
 437 | vi.mock('../index.js', async () => {
 438 |   // Get the original module
 439 |   const originalModule = await vi.importActual('../index.js');
 440 |   return {
 441 |     // Return everything from the original module
 442 |     ...originalModule,
 443 |     // But override these specific functions for tests that need mocks
 444 |     mcpServer: {
 445 |       ...(originalModule.mcpServer as Record<string, unknown>),
 446 |       connect: vi.fn(),
 447 |     },
 448 |   };
 449 | });
 450 | // Save environment variables
 451 | // Using the originalEnv declared at the top of the file
 452 | let mcpServer: any;
 453 | let nullToUndefined: any;
 454 | let handleSonarQubeProjects: any;
 455 | let mapToSonarQubeParams: any;
 456 | let handleSonarQubeGetIssues: any;
 457 | let handleSonarQubeGetMetrics: any;
 458 | let handleSonarQubeGetHealth: any;
 459 | let handleSonarQubeGetStatus: any;
 460 | let handleSonarQubePing: any;
 461 | let handleSonarQubeComponentMeasures: any;
 462 | let handleSonarQubeComponentsMeasures: any;
 463 | let handleSonarQubeMeasuresHistory: any;
 464 | let handleSonarQubeHotspots: any;
 465 | let handleSonarQubeHotspot: any;
 466 | let handleSonarQubeUpdateHotspotStatus: any;
 467 | let qualityGateHandler: any;
 468 | let qualityGateStatusHandler: any;
 469 | let hotspotHandler: any;
 470 | let updateHotspotStatusHandler: any;
 471 | describe('MCP Server', () => {
 472 |   beforeAll(async () => {
 473 |     const module = await import('../index.js');
 474 |     mcpServer = module.mcpServer;
 475 |     nullToUndefined = module.nullToUndefined;
 476 |     handleSonarQubeProjects = module.handleSonarQubeProjects;
 477 |     mapToSonarQubeParams = module.mapToSonarQubeParams;
 478 |     handleSonarQubeGetIssues = module.handleSonarQubeGetIssues;
 479 |     handleSonarQubeGetMetrics = module.handleSonarQubeGetMetrics;
 480 |     handleSonarQubeGetHealth = module.handleSonarQubeGetHealth;
 481 |     handleSonarQubeGetStatus = module.handleSonarQubeGetStatus;
 482 |     handleSonarQubePing = module.handleSonarQubePing;
 483 |     handleSonarQubeComponentMeasures = module.handleSonarQubeComponentMeasures;
 484 |     handleSonarQubeComponentsMeasures = module.handleSonarQubeComponentsMeasures;
 485 |     handleSonarQubeMeasuresHistory = module.handleSonarQubeMeasuresHistory;
 486 |     handleSonarQubeHotspots = module.handleSonarQubeHotspots;
 487 |     handleSonarQubeHotspot = module.handleSonarQubeHotspot;
 488 |     handleSonarQubeUpdateHotspotStatus = module.handleSonarQubeUpdateHotspotStatus;
 489 |     qualityGateHandler = module.qualityGateHandler;
 490 |     qualityGateStatusHandler = module.qualityGateStatusHandler;
 491 |     hotspotHandler = module.hotspotHandler;
 492 |     updateHotspotStatusHandler = module.updateHotspotStatusHandler;
 493 |   });
 494 |   beforeEach(() => {
 495 |     vi.resetModules();
 496 |     process.env = { ...originalEnv };
 497 |     // Ensure test environment variables are set
 498 |     process.env.SONARQUBE_TOKEN = 'test-token';
 499 |     process.env.SONARQUBE_URL = 'http://localhost:9000';
 500 |     nock.cleanAll();
 501 |   });
 502 |   afterEach(() => {
 503 |     process.env = originalEnv;
 504 |     vi.restoreAllMocks();
 505 |     nock.cleanAll();
 506 |   });
 507 |   it('should have initialized the MCP server', () => {
 508 |     expect(mcpServer).toBeDefined();
 509 |     expect(mcpServer.server).toBeDefined();
 510 |   });
 511 |   describe('Tool registration', () => {
 512 |     let testServer: any;
 513 |     let registeredTools: Map<string, any>;
 514 |     beforeEach(() => {
 515 |       registeredTools = new Map();
 516 |       testServer = {
 517 |         tool: vi.fn((name: string, description: string, schema: any, handler: any) => {
 518 |           registeredTools.set(name, { description, schema, handler });
 519 |         }),
 520 |       };
 521 |       // Register tools
 522 |       testServer.tool(
 523 |         'projects',
 524 |         'List all SonarQube projects',
 525 |         { page: {}, page_size: {} },
 526 |         mockHandlers.handleSonarQubeProjects
 527 |       );
 528 |       testServer.tool(
 529 |         'metrics',
 530 |         'Get available metrics from SonarQube',
 531 |         { page: {}, page_size: {} },
 532 |         mockHandlers.handleSonarQubeGetMetrics
 533 |       );
 534 |       testServer.tool(
 535 |         'issues',
 536 |         'Get issues for a SonarQube project',
 537 |         {
 538 |           project_key: {},
 539 |           severity: {},
 540 |           page: {},
 541 |           page_size: {},
 542 |           statuses: {},
 543 |           resolutions: {},
 544 |           resolved: {},
 545 |           types: {},
 546 |           rules: {},
 547 |           tags: {},
 548 |         },
 549 |         mockHandlers.handleSonarQubeGetIssues
 550 |       );
 551 |       testServer.tool(
 552 |         'system_health',
 553 |         'Get the health status of the SonarQube instance',
 554 |         {},
 555 |         mockHandlers.handleSonarQubeGetHealth
 556 |       );
 557 |       testServer.tool(
 558 |         'system_status',
 559 |         'Get the status of the SonarQube instance',
 560 |         {},
 561 |         mockHandlers.handleSonarQubeGetStatus
 562 |       );
 563 |       testServer.tool(
 564 |         'system_ping',
 565 |         'Ping the SonarQube instance to check if it is up',
 566 |         {},
 567 |         mockHandlers.handleSonarQubePing
 568 |       );
 569 |       testServer.tool(
 570 |         'measures_component',
 571 |         'Get measures for a specific component',
 572 |         {
 573 |           component: {},
 574 |           metric_keys: {},
 575 |           additional_fields: {},
 576 |           branch: {},
 577 |           pull_request: {},
 578 |           period: {},
 579 |         },
 580 |         mockHandlers.handleSonarQubeComponentMeasures
 581 |       );
 582 |       testServer.tool(
 583 |         'measures_components',
 584 |         'Get measures for multiple components',
 585 |         {
 586 |           component_keys: {},
 587 |           metric_keys: {},
 588 |           additional_fields: {},
 589 |           branch: {},
 590 |           pull_request: {},
 591 |           period: {},
 592 |           page: {},
 593 |           page_size: {},
 594 |         },
 595 |         mockHandlers.handleSonarQubeComponentsMeasures
 596 |       );
 597 |       testServer.tool(
 598 |         'measures_history',
 599 |         'Get measures history for a component',
 600 |         {
 601 |           component: {},
 602 |           metrics: {},
 603 |           from: {},
 604 |           to: {},
 605 |           branch: {},
 606 |           pull_request: {},
 607 |           page: {},
 608 |           page_size: {},
 609 |         },
 610 |         mockHandlers.handleSonarQubeMeasuresHistory
 611 |       );
 612 |     });
 613 |     it('should register all required tools', () => {
 614 |       expect(registeredTools.size).toBe(9);
 615 |       expect(registeredTools.has('projects')).toBe(true);
 616 |       expect(registeredTools.has('metrics')).toBe(true);
 617 |       expect(registeredTools.has('issues')).toBe(true);
 618 |       expect(registeredTools.has('system_health')).toBe(true);
 619 |       expect(registeredTools.has('system_status')).toBe(true);
 620 |       expect(registeredTools.has('system_ping')).toBe(true);
 621 |       expect(registeredTools.has('measures_component')).toBe(true);
 622 |       expect(registeredTools.has('measures_components')).toBe(true);
 623 |       expect(registeredTools.has('measures_history')).toBe(true);
 624 |     });
 625 |     it('should register tools with correct descriptions', () => {
 626 |       expect(registeredTools.get('projects').description).toBe('List all SonarQube projects');
 627 |       expect(registeredTools.get('metrics').description).toBe(
 628 |         'Get available metrics from SonarQube'
 629 |       );
 630 |       expect(registeredTools.get('issues').description).toBe('Get issues for a SonarQube project');
 631 |       expect(registeredTools.get('system_health').description).toBe(
 632 |         'Get the health status of the SonarQube instance'
 633 |       );
 634 |       expect(registeredTools.get('system_status').description).toBe(
 635 |         'Get the status of the SonarQube instance'
 636 |       );
 637 |       expect(registeredTools.get('system_ping').description).toBe(
 638 |         'Ping the SonarQube instance to check if it is up'
 639 |       );
 640 |       expect(registeredTools.get('measures_component').description).toBe(
 641 |         'Get measures for a specific component'
 642 |       );
 643 |       expect(registeredTools.get('measures_components').description).toBe(
 644 |         'Get measures for multiple components'
 645 |       );
 646 |       expect(registeredTools.get('measures_history').description).toBe(
 647 |         'Get measures history for a component'
 648 |       );
 649 |     });
 650 |     it('should register tools with correct handlers', () => {
 651 |       expect(registeredTools.get('projects').handler).toBe(mockHandlers.handleSonarQubeProjects);
 652 |       expect(registeredTools.get('metrics').handler).toBe(mockHandlers.handleSonarQubeGetMetrics);
 653 |       expect(registeredTools.get('issues').handler).toBe(mockHandlers.handleSonarQubeGetIssues);
 654 |       expect(registeredTools.get('system_health').handler).toBe(
 655 |         mockHandlers.handleSonarQubeGetHealth
 656 |       );
 657 |       expect(registeredTools.get('system_status').handler).toBe(
 658 |         mockHandlers.handleSonarQubeGetStatus
 659 |       );
 660 |       expect(registeredTools.get('system_ping').handler).toBe(mockHandlers.handleSonarQubePing);
 661 |       expect(registeredTools.get('measures_component').handler).toBe(
 662 |         mockHandlers.handleSonarQubeComponentMeasures
 663 |       );
 664 |       expect(registeredTools.get('measures_components').handler).toBe(
 665 |         mockHandlers.handleSonarQubeComponentsMeasures
 666 |       );
 667 |       expect(registeredTools.get('measures_history').handler).toBe(
 668 |         mockHandlers.handleSonarQubeMeasuresHistory
 669 |       );
 670 |     });
 671 |   });
 672 |   describe('nullToUndefined', () => {
 673 |     it('should return undefined for null', () => {
 674 |       expect(nullToUndefined(null)).toBeUndefined();
 675 |     });
 676 |     it('should return the value for non-null', () => {
 677 |       expect(nullToUndefined('value')).toBe('value');
 678 |     });
 679 |   });
 680 |   describe('handleSonarQubeProjects', () => {
 681 |     it('should fetch and return a list of projects', async () => {
 682 |       nock('http://localhost:9000')
 683 |         .get('/api/projects/search')
 684 |         .query(true)
 685 |         .reply(200, {
 686 |           components: [
 687 |             {
 688 |               key: 'project1',
 689 |               name: 'Project 1',
 690 |               qualifier: 'TRK',
 691 |               visibility: 'public',
 692 |               lastAnalysisDate: '2024-03-01',
 693 |               revision: 'abc123',
 694 |               managed: false,
 695 |             },
 696 |           ],
 697 |           paging: { pageIndex: 1, pageSize: 1, total: 1 },
 698 |         });
 699 |       const response = await handleSonarQubeProjects({ page: 1, page_size: 1 });
 700 |       expect(response.content[0].text).toContain('project1');
 701 |     });
 702 |   });
 703 |   describe('mapToSonarQubeParams', () => {
 704 |     it('should map MCP tool parameters to SonarQube client parameters', () => {
 705 |       const params = mapToSonarQubeParams({ project_key: 'key', severity: 'MAJOR' });
 706 |       expect(params.projectKey).toBe('key');
 707 |       expect(params.severity).toBe('MAJOR');
 708 |     });
 709 |   });
 710 |   describe('handleSonarQubeGetIssues', () => {
 711 |     it('should fetch and return a list of issues', async () => {
 712 |       nock('http://localhost:9000')
 713 |         .get('/api/issues/search')
 714 |         .query(true)
 715 |         .reply(200, {
 716 |           issues: [
 717 |             {
 718 |               key: 'issue1',
 719 |               rule: 'rule1',
 720 |               severity: 'MAJOR',
 721 |               component: 'comp1',
 722 |               project: 'proj1',
 723 |               line: 1,
 724 |               status: 'OPEN',
 725 |               message: 'Test issue',
 726 |             },
 727 |           ],
 728 |           components: [],
 729 |           rules: [],
 730 |           paging: { pageIndex: 1, pageSize: 1, total: 1 },
 731 |         });
 732 |       const response = await handleSonarQubeGetIssues({ projectKey: 'key' });
 733 |       expect(response.content[0].text).toContain('issue');
 734 |     });
 735 |   });
 736 |   describe('handleSonarQubeGetMetrics', () => {
 737 |     it('should fetch and return a list of metrics', async () => {
 738 |       nock('http://localhost:9000')
 739 |         .get('/api/metrics/search')
 740 |         .query(true)
 741 |         .reply(200, {
 742 |           metrics: [
 743 |             {
 744 |               key: 'metric1',
 745 |               name: 'Metric 1',
 746 |               description: 'Test metric',
 747 |               domain: 'domain1',
 748 |               type: 'INT',
 749 |             },
 750 |           ],
 751 |           paging: { pageIndex: 1, pageSize: 1, total: 1 },
 752 |         });
 753 |       const response = await handleSonarQubeGetMetrics({ page: 1, pageSize: 1 });
 754 |       expect(response.content[0].text).toContain('metric');
 755 |     });
 756 |   });
 757 |   describe('handleSonarQubeGetHealth', () => {
 758 |     it('should fetch and return health status', async () => {
 759 |       nock('http://localhost:9000').get('/api/v2/system/health').reply(200, {
 760 |         status: 'GREEN',
 761 |         checkedAt: '2023-12-01T10:00:00Z',
 762 |       });
 763 |       const response = await handleSonarQubeGetHealth();
 764 |       expect(response.content[0].text).toContain('GREEN');
 765 |     });
 766 |   });
 767 |   describe('handleSonarQubeGetStatus', () => {
 768 |     it('should fetch and return system status', async () => {
 769 |       nock('http://localhost:9000').get('/api/system/status').reply(200, {
 770 |         id: 'test-id',
 771 |         version: '10.3.0.82913',
 772 |         status: 'UP',
 773 |       });
 774 |       const response = await handleSonarQubeGetStatus();
 775 |       expect(response.content[0].text).toContain('UP');
 776 |     });
 777 |   });
 778 |   describe('handleSonarQubePing', () => {
 779 |     it('should ping the system and return the result', async () => {
 780 |       nock('http://localhost:9000').get('/api/system/ping').reply(200, 'pong');
 781 |       const response = await handleSonarQubePing();
 782 |       expect(response.content[0].text).toBe('pong');
 783 |     });
 784 |   });
 785 |   describe('Conditional server start', () => {
 786 |     it('should not start the server if NODE_ENV is test', () => {
 787 |       process.env.NODE_ENV = 'test';
 788 |       const mcpConnectSpy = vi.spyOn(mcpServer, 'connect');
 789 |       // Since the server doesn't start in test mode, we verify that connect is not called
 790 |       expect(mcpConnectSpy).not.toHaveBeenCalled();
 791 |       mcpConnectSpy.mockRestore();
 792 |     });
 793 |     it('should use transport factory in production mode', () => {
 794 |       // Test that our transport factory is used (covered by integration)
 795 |       // The actual server startup is tested manually or in integration tests
 796 |       // since we can't easily test the module-level code execution
 797 |       expect(true).toBe(true);
 798 |     });
 799 |   });
 800 |   describe('Schema transformations', () => {
 801 |     it('should handle page and page_size transformations correctly', () => {
 802 |       // Use the actual schema from the tool registration
 803 |       const pageSchema = z
 804 |         .string()
 805 |         .optional()
 806 |         .transform((val: any) => (val ? parseInt(val, 10) || null : null));
 807 |       // Test valid number strings
 808 |       expect(pageSchema.parse('10')).toBe(10);
 809 |       expect(pageSchema.parse('20')).toBe(20);
 810 |       // Test invalid number strings
 811 |       expect(pageSchema.parse('invalid')).toBe(null);
 812 |       expect(pageSchema.parse('not-a-number')).toBe(null);
 813 |       // Test empty/undefined values
 814 |       expect(pageSchema.parse(undefined)).toBe(null);
 815 |       expect(pageSchema.parse('')).toBe(null);
 816 |     });
 817 |     it('should handle boolean transformations correctly', () => {
 818 |       const booleanSchema = z
 819 |         .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
 820 |         .nullable()
 821 |         .optional();
 822 |       // Test string values
 823 |       expect(booleanSchema.parse('true')).toBe(true);
 824 |       expect(booleanSchema.parse('false')).toBe(false);
 825 |       // Test boolean values
 826 |       expect(booleanSchema.parse(true)).toBe(true);
 827 |       expect(booleanSchema.parse(false)).toBe(false);
 828 |       // Test null/undefined values
 829 |       expect(booleanSchema.parse(null)).toBe(null);
 830 |       expect(booleanSchema.parse(undefined)).toBe(undefined);
 831 |     });
 832 |     it('should handle array transformations correctly', () => {
 833 |       const stringArraySchema = z.array(z.string()).nullable().optional();
 834 |       const statusSchema = z
 835 |         .array(
 836 |           z.enum([
 837 |             'OPEN',
 838 |             'CONFIRMED',
 839 |             'REOPENED',
 840 |             'RESOLVED',
 841 |             'CLOSED',
 842 |             'TO_REVIEW',
 843 |             'IN_REVIEW',
 844 |             'REVIEWED',
 845 |           ])
 846 |         )
 847 |         .nullable()
 848 |         .optional();
 849 |       const resolutionSchema = z
 850 |         .array(z.enum(['FALSE-POSITIVE', 'WONTFIX', 'FIXED', 'REMOVED']))
 851 |         .nullable()
 852 |         .optional();
 853 |       const typeSchema = z
 854 |         .array(z.enum(['CODE_SMELL', 'BUG', 'VULNERABILITY', 'SECURITY_HOTSPOT']))
 855 |         .nullable()
 856 |         .optional();
 857 |       // Test valid arrays
 858 |       expect(statusSchema.parse(['OPEN', 'CONFIRMED'])).toEqual(['OPEN', 'CONFIRMED']);
 859 |       expect(resolutionSchema.parse(['FALSE-POSITIVE', 'WONTFIX'])).toEqual([
 860 |         'FALSE-POSITIVE',
 861 |         'WONTFIX',
 862 |       ]);
 863 |       expect(typeSchema.parse(['CODE_SMELL', 'BUG'])).toEqual(['CODE_SMELL', 'BUG']);
 864 |       expect(stringArraySchema.parse(['value1', 'value2'])).toEqual(['value1', 'value2']);
 865 |       // Test null/undefined values
 866 |       expect(statusSchema.parse(null)).toBe(null);
 867 |       expect(resolutionSchema.parse(null)).toBe(null);
 868 |       expect(typeSchema.parse(null)).toBe(null);
 869 |       expect(stringArraySchema.parse(null)).toBe(null);
 870 |       expect(statusSchema.parse(undefined)).toBe(undefined);
 871 |       expect(resolutionSchema.parse(undefined)).toBe(undefined);
 872 |       expect(typeSchema.parse(undefined)).toBe(undefined);
 873 |       expect(stringArraySchema.parse(undefined)).toBe(undefined);
 874 |       // Test invalid values
 875 |       expect(() => statusSchema.parse(['INVALID'])).toThrow();
 876 |       expect(() => resolutionSchema.parse(['INVALID'])).toThrow();
 877 |       expect(() => typeSchema.parse(['INVALID'])).toThrow();
 878 |     });
 879 |     it('should handle severity schema correctly', () => {
 880 |       const severitySchema = z
 881 |         .enum(['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'])
 882 |         .nullable()
 883 |         .optional();
 884 |       // Test valid values
 885 |       expect(severitySchema.parse('INFO')).toBe('INFO');
 886 |       expect(severitySchema.parse('MINOR')).toBe('MINOR');
 887 |       expect(severitySchema.parse('MAJOR')).toBe('MAJOR');
 888 |       expect(severitySchema.parse('CRITICAL')).toBe('CRITICAL');
 889 |       expect(severitySchema.parse('BLOCKER')).toBe('BLOCKER');
 890 |       // Test null/undefined values
 891 |       expect(severitySchema.parse(null)).toBe(null);
 892 |       expect(severitySchema.parse(undefined)).toBe(undefined);
 893 |       // Test invalid values
 894 |       expect(() => severitySchema.parse('INVALID')).toThrow();
 895 |     });
 896 |     it('should handle date parameters correctly', () => {
 897 |       const dateSchema = z.string().nullable().optional();
 898 |       // Test valid dates
 899 |       expect(dateSchema.parse('2024-01-01')).toBe('2024-01-01');
 900 |       expect(dateSchema.parse('2024-12-31')).toBe('2024-12-31');
 901 |       // Test null/undefined values
 902 |       expect(dateSchema.parse(null)).toBe(null);
 903 |       expect(dateSchema.parse(undefined)).toBe(undefined);
 904 |     });
 905 |     it('should handle hotspot search boolean transformations correctly', () => {
 906 |       // Test string to boolean transformation schemas used in hotspot search
 907 |       const hotspotBooleanSchema = z
 908 |         .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
 909 |         .nullable()
 910 |         .optional();
 911 |       // Test boolean values
 912 |       expect(hotspotBooleanSchema.parse(true)).toBe(true);
 913 |       expect(hotspotBooleanSchema.parse(false)).toBe(false);
 914 |       // Test string values
 915 |       expect(hotspotBooleanSchema.parse('true')).toBe(true);
 916 |       expect(hotspotBooleanSchema.parse('false')).toBe(false);
 917 |       expect(hotspotBooleanSchema.parse('any')).toBe(false);
 918 |       // Test null/undefined values
 919 |       expect(hotspotBooleanSchema.parse(null)).toBe(null);
 920 |       expect(hotspotBooleanSchema.parse(undefined)).toBe(undefined);
 921 |     });
 922 |     it('should handle complex parameter combinations', () => {
 923 |       // Mock SonarQube API response
 924 |       nock('http://localhost:9000')
 925 |         .get('/api/issues/search')
 926 |         .query(true)
 927 |         .reply(200, {
 928 |           issues: [],
 929 |           components: [],
 930 |           rules: [],
 931 |           paging: { pageIndex: 1, pageSize: 100, total: 0 },
 932 |         });
 933 |       const params = {
 934 |         project_key: 'test-project',
 935 |         severity: 'MAJOR',
 936 |         statuses: ['OPEN', 'CONFIRMED'],
 937 |         resolutions: ['FALSE-POSITIVE', 'WONTFIX'],
 938 |         types: ['CODE_SMELL', 'BUG'],
 939 |         rules: ['rule1', 'rule2'],
 940 |         tags: ['tag1', 'tag2'],
 941 |         created_after: '2024-01-01',
 942 |         created_before: '2024-12-31',
 943 |         created_at: '2024-06-15',
 944 |         created_in_last: '7d',
 945 |         assignees: ['user1', 'user2'],
 946 |         authors: ['author1', 'author2'],
 947 |         cwe: ['cwe1', 'cwe2'],
 948 |         languages: ['java', 'typescript'],
 949 |         owasp_top10: ['a1', 'a2'],
 950 |         sans_top25: ['sans1', 'sans2'],
 951 |         sonarsource_security: ['sec1', 'sec2'],
 952 |         on_component_only: true,
 953 |         facets: ['facet1', 'facet2'],
 954 |         since_leak_period: true,
 955 |         in_new_code_period: true,
 956 |       };
 957 |       // Verify parameters are properly mapped
 958 |       const mappedParams = mapToSonarQubeParams(params);
 959 |       expect(mappedParams).toBeDefined();
 960 |       expect(mappedParams.projectKey).toBe('test-project');
 961 |     });
 962 |   });
 963 |   describe('Tool handlers', () => {
 964 |     beforeEach(() => {
 965 |       vi.resetAllMocks();
 966 |     });
 967 |     describe('handleSonarQubeComponentMeasures', () => {
 968 |       it('should fetch and return component measures', async () => {
 969 |         nock('http://localhost:9000')
 970 |           .get('/api/measures/component')
 971 |           .query(true)
 972 |           .reply(200, {
 973 |             component: {
 974 |               key: 'test-component',
 975 |               name: 'Test Component',
 976 |               qualifier: 'TRK',
 977 |               measures: [
 978 |                 {
 979 |                   metric: 'coverage',
 980 |                   value: '85.4',
 981 |                 },
 982 |               ],
 983 |             },
 984 |             metrics: [
 985 |               {
 986 |                 key: 'coverage',
 987 |                 name: 'Coverage',
 988 |                 description: 'Test coverage',
 989 |                 domain: 'Coverage',
 990 |                 type: 'PERCENT',
 991 |               },
 992 |             ],
 993 |           });
 994 |         const response = await handleSonarQubeComponentMeasures({
 995 |           component: 'test-component',
 996 |           metricKeys: ['coverage'],
 997 |         });
 998 |         expect(response.content[0].text).toContain('test-component');
 999 |         expect(response.content[0].text).toContain('coverage');
1000 |         expect(response.content[0].text).toContain('85.4');
1001 |       });
1002 |       it('should fetch component measures with all optional parameters', async () => {
1003 |         nock('http://localhost:9000')
1004 |           .get('/api/measures/component')
1005 |           .query((queryObject) => {
1006 |             return (
1007 |               queryObject.component === 'test-component' &&
1008 |               queryObject.metricKeys === 'coverage,bugs' &&
1009 |               queryObject.additionalFields === 'periods,metrics' &&
1010 |               queryObject.branch === 'main' &&
1011 |               queryObject.pullRequest === 'pr-123'
1012 |             );
1013 |           })
1014 |           .matchHeader('authorization', 'Bearer test-token')
1015 |           .reply(200, {
1016 |             component: {
1017 |               key: 'test-component',
1018 |               name: 'Test Component',
1019 |               qualifier: 'TRK',
1020 |               measures: [
1021 |                 {
1022 |                   metric: 'coverage',
1023 |                   value: '85.4',
1024 |                   period: { index: 1, value: '+5.4' },
1025 |                 },
1026 |                 {
1027 |                   metric: 'bugs',
1028 |                   value: '10',
1029 |                   period: { index: 1, value: '-2' },
1030 |                 },
1031 |               ],
1032 |               periods: [{ index: 1, mode: 'previous_version', date: '2023-01-01T00:00:00+0000' }],
1033 |             },
1034 |             metrics: [
1035 |               {
1036 |                 key: 'coverage',
1037 |                 name: 'Coverage',
1038 |                 description: 'Test coverage',
1039 |                 domain: 'Coverage',
1040 |                 type: 'PERCENT',
1041 |               },
1042 |               {
1043 |                 key: 'bugs',
1044 |                 name: 'Bugs',
1045 |                 description: 'Number of bugs',
1046 |                 domain: 'Reliability',
1047 |                 type: 'INT',
1048 |               },
1049 |             ],
1050 |           });
1051 |         const response = await handleSonarQubeComponentMeasures({
1052 |           component: 'test-component',
1053 |           metricKeys: ['coverage', 'bugs'],
1054 |           additionalFields: ['periods', 'metrics'],
1055 |           branch: 'main',
1056 |           pullRequest: 'pr-123',
1057 |           period: '1',
1058 |         });
1059 |         const result = JSON.parse(response.content[0].text);
1060 |         expect(result.component.key).toBe('test-component');
1061 |         expect(result.component.measures).toHaveLength(2);
1062 |         expect(result.component.periods).toBeDefined();
1063 |         expect(result.metrics).toHaveLength(2);
1064 |         expect(result.component.measures[0].period).toBeDefined();
1065 |         expect(result.component.measures[0].period.index).toBe(1);
1066 |       });
1067 |     });
1068 |     describe('handleSonarQubeComponentsMeasures', () => {
1069 |       it('should fetch and return measures for multiple components', async () => {
1070 |         // Mock individual component measure calls
1071 |         nock('http://localhost:9000')
1072 |           .get('/api/measures/component')
1073 |           .query({
1074 |             component: 'test-component-1',
1075 |             metricKeys: 'bugs',
1076 |           })
1077 |           .matchHeader('authorization', 'Bearer test-token')
1078 |           .reply(200, {
1079 |             component: {
1080 |               key: 'test-component-1',
1081 |               name: 'Test Component 1',
1082 |               qualifier: 'TRK',
1083 |               measures: [
1084 |                 {
1085 |                   metric: 'bugs',
1086 |                   value: '10',
1087 |                 },
1088 |               ],
1089 |             },
1090 |             metrics: [
1091 |               {
1092 |                 key: 'bugs',
1093 |                 name: 'Bugs',
1094 |                 description: 'Number of bugs',
1095 |                 domain: 'Reliability',
1096 |                 type: 'INT',
1097 |               },
1098 |             ],
1099 |           });
1100 |         nock('http://localhost:9000')
1101 |           .get('/api/measures/component')
1102 |           .query({
1103 |             component: 'test-component-2',
1104 |             metricKeys: 'bugs',
1105 |           })
1106 |           .matchHeader('authorization', 'Bearer test-token')
1107 |           .reply(200, {
1108 |             component: {
1109 |               key: 'test-component-2',
1110 |               name: 'Test Component 2',
1111 |               qualifier: 'TRK',
1112 |               measures: [
1113 |                 {
1114 |                   metric: 'bugs',
1115 |                   value: '5',
1116 |                 },
1117 |               ],
1118 |             },
1119 |             metrics: [
1120 |               {
1121 |                 key: 'bugs',
1122 |                 name: 'Bugs',
1123 |                 description: 'Number of bugs',
1124 |                 domain: 'Reliability',
1125 |                 type: 'INT',
1126 |               },
1127 |             ],
1128 |           });
1129 |         // Mock the additional call to get metrics from first component
1130 |         nock('http://localhost:9000')
1131 |           .get('/api/measures/component')
1132 |           .query({
1133 |             component: 'test-component-1',
1134 |             metricKeys: 'bugs',
1135 |           })
1136 |           .matchHeader('authorization', 'Bearer test-token')
1137 |           .reply(200, {
1138 |             component: {
1139 |               key: 'test-component-1',
1140 |               name: 'Test Component 1',
1141 |               qualifier: 'TRK',
1142 |               measures: [
1143 |                 {
1144 |                   metric: 'bugs',
1145 |                   value: '10',
1146 |                 },
1147 |               ],
1148 |             },
1149 |             metrics: [
1150 |               {
1151 |                 key: 'bugs',
1152 |                 name: 'Bugs',
1153 |                 description: 'Number of bugs',
1154 |                 domain: 'Reliability',
1155 |                 type: 'INT',
1156 |               },
1157 |             ],
1158 |           });
1159 |         const response = await handleSonarQubeComponentsMeasures({
1160 |           componentKeys: ['test-component-1', 'test-component-2'],
1161 |           metricKeys: ['bugs'],
1162 |           page: 1,
1163 |           pageSize: 100,
1164 |         });
1165 |         expect(response.content[0].text).toContain('test-component-1');
1166 |         expect(response.content[0].text).toContain('test-component-2');
1167 |         expect(response.content[0].text).toContain('bugs');
1168 |       });
1169 |       it('should fetch components measures with all optional parameters', async () => {
1170 |         // Mock individual component measure calls with optional parameters
1171 |         nock('http://localhost:9000')
1172 |           .get('/api/measures/component')
1173 |           .query({
1174 |             component: 'test-component-1',
1175 |             metricKeys: 'coverage,bugs',
1176 |             additionalFields: 'periods,metrics',
1177 |             branch: 'develop',
1178 |             pullRequest: 'pr-456',
1179 |           })
1180 |           .matchHeader('authorization', 'Bearer test-token')
1181 |           .reply(200, {
1182 |             component: {
1183 |               key: 'test-component-1',
1184 |               name: 'Test Component 1',
1185 |               qualifier: 'TRK',
1186 |               measures: [
1187 |                 {
1188 |                   metric: 'coverage',
1189 |                   value: '85.4',
1190 |                   period: { index: 2, value: '+5.4' },
1191 |                 },
1192 |                 {
1193 |                   metric: 'bugs',
1194 |                   value: '10',
1195 |                   period: { index: 2, value: '-2' },
1196 |                 },
1197 |               ],
1198 |               periods: [{ index: 2, mode: 'previous_version', date: '2023-01-01T00:00:00+0000' }],
1199 |             },
1200 |             metrics: [
1201 |               {
1202 |                 key: 'coverage',
1203 |                 name: 'Coverage',
1204 |                 description: 'Test coverage',
1205 |                 domain: 'Coverage',
1206 |                 type: 'PERCENT',
1207 |               },
1208 |               {
1209 |                 key: 'bugs',
1210 |                 name: 'Bugs',
1211 |                 description: 'Number of bugs',
1212 |                 domain: 'Reliability',
1213 |                 type: 'INT',
1214 |               },
1215 |             ],
1216 |             paging: {
1217 |               pageIndex: 3,
1218 |               pageSize: 25,
1219 |               total: 50,
1220 |             },
1221 |           });
1222 |         // Mock the additional call to get metrics from first component
1223 |         nock('http://localhost:9000')
1224 |           .get('/api/measures/component')
1225 |           .query({
1226 |             component: 'test-component-1',
1227 |             metricKeys: 'coverage,bugs',
1228 |             additionalFields: 'periods,metrics',
1229 |             branch: 'develop',
1230 |             pullRequest: 'pr-456',
1231 |           })
1232 |           .matchHeader('authorization', 'Bearer test-token')
1233 |           .reply(200, {
1234 |             component: {
1235 |               key: 'test-component-1',
1236 |               name: 'Test Component 1',
1237 |               qualifier: 'TRK',
1238 |               measures: [
1239 |                 {
1240 |                   metric: 'coverage',
1241 |                   value: '85.4',
1242 |                   period: { index: 2, value: '+5.4' },
1243 |                 },
1244 |                 {
1245 |                   metric: 'bugs',
1246 |                   value: '10',
1247 |                   period: { index: 2, value: '-2' },
1248 |                 },
1249 |               ],
1250 |               periods: [{ index: 2, mode: 'previous_version', date: '2023-01-01T00:00:00+0000' }],
1251 |             },
1252 |             metrics: [
1253 |               {
1254 |                 key: 'coverage',
1255 |                 name: 'Coverage',
1256 |                 description: 'Test coverage',
1257 |                 domain: 'Coverage',
1258 |                 type: 'PERCENT',
1259 |               },
1260 |               {
1261 |                 key: 'bugs',
1262 |                 name: 'Bugs',
1263 |                 description: 'Number of bugs',
1264 |                 domain: 'Reliability',
1265 |                 type: 'INT',
1266 |               },
1267 |             ],
1268 |             period: {
1269 |               index: 2,
1270 |               mode: 'previous_version',
1271 |               date: '2023-01-01T00:00:00+0000',
1272 |             },
1273 |           });
1274 |         // Mock second component
1275 |         nock('http://localhost:9000')
1276 |           .get('/api/measures/component')
1277 |           .query({
1278 |             component: 'test-component-2',
1279 |             metricKeys: 'coverage,bugs',
1280 |             additionalFields: 'periods,metrics',
1281 |             branch: 'develop',
1282 |             pullRequest: 'pr-456',
1283 |           })
1284 |           .matchHeader('authorization', 'Bearer test-token')
1285 |           .reply(200, {
1286 |             component: {
1287 |               key: 'test-component-2',
1288 |               name: 'Test Component 2',
1289 |               qualifier: 'TRK',
1290 |               measures: [
1291 |                 {
1292 |                   metric: 'coverage',
1293 |                   value: '78.2',
1294 |                   period: { index: 2, value: '+3.1' },
1295 |                 },
1296 |                 {
1297 |                   metric: 'bugs',
1298 |                   value: '5',
1299 |                   period: { index: 2, value: '-1' },
1300 |                 },
1301 |               ],
1302 |               periods: [{ index: 2, mode: 'previous_version', date: '2023-01-01T00:00:00+0000' }],
1303 |             },
1304 |             metrics: [
1305 |               {
1306 |                 key: 'coverage',
1307 |                 name: 'Coverage',
1308 |                 description: 'Test coverage',
1309 |                 domain: 'Coverage',
1310 |                 type: 'PERCENT',
1311 |               },
1312 |               {
1313 |                 key: 'bugs',
1314 |                 name: 'Bugs',
1315 |                 description: 'Number of bugs',
1316 |                 domain: 'Reliability',
1317 |                 type: 'INT',
1318 |               },
1319 |             ],
1320 |             period: {
1321 |               index: 2,
1322 |               mode: 'previous_version',
1323 |               date: '2023-01-01T00:00:00+0000',
1324 |             },
1325 |           });
1326 |         const response = await handleSonarQubeComponentsMeasures({
1327 |           componentKeys: ['test-component-1', 'test-component-2'],
1328 |           metricKeys: ['coverage', 'bugs'],
1329 |           additionalFields: ['periods', 'metrics'],
1330 |           branch: 'develop',
1331 |           pullRequest: 'pr-456',
1332 |           period: '2',
1333 |           page: 1,
1334 |           pageSize: 25,
1335 |         });
1336 |         const result = JSON.parse(response.content[0].text);
1337 |         expect(result.components).toHaveLength(2);
1338 |         expect(result.metrics).toHaveLength(2);
1339 |         expect(result.paging.pageIndex).toBe(1);
1340 |         expect(result.paging.pageSize).toBe(25);
1341 |         expect(result.paging.total).toBe(2);
1342 |         expect(result.components[0].key).toBe('test-component-1');
1343 |         expect(result.components[0].measures).toHaveLength(2);
1344 |         expect(result.components[0].periods).toBeDefined();
1345 |         expect(result.components[0].measures[0].period).toBeDefined();
1346 |         expect(result.components[0].measures[0].period.index).toBe(2);
1347 |       });
1348 |     });
1349 |     describe('handleSonarQubeMeasuresHistory', () => {
1350 |       it('should fetch and return measures history', async () => {
1351 |         nock('http://localhost:9000')
1352 |           .get('/api/measures/search_history')
1353 |           .query(true)
1354 |           .reply(200, {
1355 |             measures: [
1356 |               {
1357 |                 metric: 'coverage',
1358 |                 history: [
1359 |                   {
1360 |                     date: '2023-01-01T00:00:00+0000',
1361 |                     value: '80.0',
1362 |                   },
1363 |                   {
1364 |                     date: '2023-02-01T00:00:00+0000',
1365 |                     value: '85.0',
1366 |                   },
1367 |                 ],
1368 |               },
1369 |             ],
1370 |             paging: {
1371 |               pageIndex: 1,
1372 |               pageSize: 100,
1373 |               total: 1,
1374 |             },
1375 |           });
1376 |         const response = await handleSonarQubeMeasuresHistory({
1377 |           component: 'test-component',
1378 |           metrics: ['coverage'],
1379 |           from: '2023-01-01',
1380 |           to: '2023-02-01',
1381 |         });
1382 |         expect(response.content[0].text).toContain('coverage');
1383 |         expect(response.content[0].text).toContain('history');
1384 |         expect(response.content[0].text).toContain('2023-01-01');
1385 |         expect(response.content[0].text).toContain('2023-02-01');
1386 |       });
1387 |       it('should fetch measures history with all optional parameters', async () => {
1388 |         nock('http://localhost:9000')
1389 |           .get('/api/measures/search_history')
1390 |           .query((queryObject) => {
1391 |             return (
1392 |               queryObject.component === 'test-component' &&
1393 |               queryObject.metrics === 'coverage,bugs,code_smells' &&
1394 |               queryObject.from === '2023-01-01' &&
1395 |               queryObject.to === '2023-12-31' &&
1396 |               queryObject.branch === 'release' &&
1397 |               queryObject.pullRequest === 'pr-789' &&
1398 |               queryObject.ps === '30' &&
1399 |               queryObject.p === '2'
1400 |             );
1401 |           })
1402 |           .reply(200, {
1403 |             measures: [
1404 |               {
1405 |                 metric: 'coverage',
1406 |                 history: [
1407 |                   {
1408 |                     date: '2023-01-01T00:00:00+0000',
1409 |                     value: '80.0',
1410 |                   },
1411 |                   {
1412 |                     date: '2023-03-01T00:00:00+0000',
1413 |                     value: '83.5',
1414 |                   },
1415 |                   {
1416 |                     date: '2023-06-01T00:00:00+0000',
1417 |                     value: '85.0',
1418 |                   },
1419 |                   {
1420 |                     date: '2023-09-01T00:00:00+0000',
1421 |                     value: '87.2',
1422 |                   },
1423 |                   {
1424 |                     date: '2023-12-01T00:00:00+0000',
1425 |                     value: '90.1',
1426 |                   },
1427 |                 ],
1428 |               },
1429 |               {
1430 |                 metric: 'bugs',
1431 |                 history: [
1432 |                   {
1433 |                     date: '2023-01-01T00:00:00+0000',
1434 |                     value: '15',
1435 |                   },
1436 |                   {
1437 |                     date: '2023-03-01T00:00:00+0000',
1438 |                     value: '12',
1439 |                   },
1440 |                   {
1441 |                     date: '2023-06-01T00:00:00+0000',
1442 |                     value: '10',
1443 |                   },
1444 |                   {
1445 |                     date: '2023-09-01T00:00:00+0000',
1446 |                     value: '7',
1447 |                   },
1448 |                   {
1449 |                     date: '2023-12-01T00:00:00+0000',
1450 |                     value: '5',
1451 |                   },
1452 |                 ],
1453 |               },
1454 |               {
1455 |                 metric: 'code_smells',
1456 |                 history: [
1457 |                   {
1458 |                     date: '2023-01-01T00:00:00+0000',
1459 |                     value: '50',
1460 |                   },
1461 |                   {
1462 |                     date: '2023-03-01T00:00:00+0000',
1463 |                     value: '45',
1464 |                   },
1465 |                   {
1466 |                     date: '2023-06-01T00:00:00+0000',
1467 |                     value: '40',
1468 |                   },
1469 |                   {
1470 |                     date: '2023-09-01T00:00:00+0000',
1471 |                     value: '35',
1472 |                   },
1473 |                   {
1474 |                     date: '2023-12-01T00:00:00+0000',
1475 |                     value: '30',
1476 |                   },
1477 |                 ],
1478 |               },
1479 |             ],
1480 |             paging: {
1481 |               pageIndex: 2,
1482 |               pageSize: 30,
1483 |               total: 60,
1484 |             },
1485 |           });
1486 |         const response = await handleSonarQubeMeasuresHistory({
1487 |           component: 'test-component',
1488 |           metrics: ['coverage', 'bugs', 'code_smells'],
1489 |           from: '2023-01-01',
1490 |           to: '2023-12-31',
1491 |           branch: 'release',
1492 |           pullRequest: 'pr-789',
1493 |           page: 2,
1494 |           pageSize: 30,
1495 |         });
1496 |         const result = JSON.parse(response.content[0].text);
1497 |         expect(result.measures).toHaveLength(3);
1498 |         expect(result.paging.pageIndex).toBe(2);
1499 |         expect(result.paging.pageSize).toBe(30);
1500 |         expect(result.paging.total).toBe(60);
1501 |         // Check coverage metric
1502 |         expect(result.measures[0].metric).toBe('coverage');
1503 |         expect(result.measures[0].history).toHaveLength(5);
1504 |         expect(result.measures[0].history[0].date).toBe('2023-01-01T00:00:00+0000');
1505 |         expect(result.measures[0].history[0].value).toBe('80.0');
1506 |         expect(result.measures[0].history[4].date).toBe('2023-12-01T00:00:00+0000');
1507 |         expect(result.measures[0].history[4].value).toBe('90.1');
1508 |         // Check bugs metric
1509 |         expect(result.measures[1].metric).toBe('bugs');
1510 |         expect(result.measures[1].history).toHaveLength(5);
1511 |         expect(result.measures[1].history[0].value).toBe('15');
1512 |         expect(result.measures[1].history[4].value).toBe('5');
1513 |         // Check code_smells metric
1514 |         expect(result.measures[2].metric).toBe('code_smells');
1515 |         expect(result.measures[2].history).toHaveLength(5);
1516 |         expect(result.measures[2].history[0].value).toBe('50');
1517 |         expect(result.measures[2].history[4].value).toBe('30');
1518 |       });
1519 |     });
1520 |     describe('measures_component tool lambda', () => {
1521 |       it('should call handleSonarQubeComponentMeasures with correct parameters', async () => {
1522 |         // Create a simulated lambda function that mimics the tool handler
1523 |         const componentMeasuresLambda = async (params: Record<string, unknown>) => {
1524 |           return await handleSonarQubeComponentMeasures({
1525 |             component: params.component as string,
1526 |             metricKeys: Array.isArray(params.metric_keys)
1527 |               ? (params.metric_keys as string[])
1528 |               : [params.metric_keys as string],
1529 |             additionalFields: params.additional_fields as string[] | undefined,
1530 |             branch: params.branch as string | undefined,
1531 |             pullRequest: params.pull_request as string | undefined,
1532 |             period: params.period as string | undefined,
1533 |           });
1534 |         };
1535 |         // Mock the handleSonarQubeComponentMeasures function
1536 |         const mockHandler = (vi.fn() as any).mockResolvedValue({
1537 |           content: [{ type: 'text', text: '{"component":{}}' }],
1538 |         });
1539 |         const originalHandler = handleSonarQubeComponentMeasures;
1540 |         handleSonarQubeComponentMeasures = mockHandler;
1541 |         // Test with string metrics parameter
1542 |         await componentMeasuresLambda({
1543 |           component: 'my-project',
1544 |           metric_keys: 'coverage',
1545 |           branch: 'main',
1546 |         });
1547 |         // Test with array metrics parameter
1548 |         await componentMeasuresLambda({
1549 |           component: 'my-project',
1550 |           metric_keys: ['coverage', 'bugs'],
1551 |           additional_fields: ['periods'],
1552 |           pull_request: 'pr-123',
1553 |           period: '1',
1554 |         });
1555 |         // Check that the handler was called with the correct parameters
1556 |         expect(mockHandler).toHaveBeenCalledTimes(2);
1557 |         // Check first call with string parameter
1558 |         expect(mockHandler.mock.calls[0][0]).toEqual({
1559 |           component: 'my-project',
1560 |           metricKeys: ['coverage'],
1561 |           branch: 'main',
1562 |           additionalFields: undefined,
1563 |           pullRequest: undefined,
1564 |           period: undefined,
1565 |         });
1566 |         // Check second call with array parameter
1567 |         expect(mockHandler.mock.calls[1][0]).toEqual({
1568 |           component: 'my-project',
1569 |           metricKeys: ['coverage', 'bugs'],
1570 |           additionalFields: ['periods'],
1571 |           branch: undefined,
1572 |           pullRequest: 'pr-123',
1573 |           period: '1',
1574 |         });
1575 |         // Restore the original handler
1576 |         handleSonarQubeComponentMeasures = originalHandler;
1577 |       });
1578 |     });
1579 |     describe('measures_components tool lambda', () => {
1580 |       it('should call handleSonarQubeComponentsMeasures with correct parameters', async () => {
1581 |         // Create a simulated lambda function that mimics the tool handler
1582 |         const componentsMeasuresLambda = async (params: Record<string, unknown>) => {
1583 |           return await handleSonarQubeComponentsMeasures({
1584 |             componentKeys: Array.isArray(params.component_keys)
1585 |               ? (params.component_keys as string[])
1586 |               : [params.component_keys as string],
1587 |             metricKeys: Array.isArray(params.metric_keys)
1588 |               ? (params.metric_keys as string[])
1589 |               : [params.metric_keys as string],
1590 |             additionalFields: params.additional_fields as string[] | undefined,
1591 |             branch: params.branch as string | undefined,
1592 |             pullRequest: params.pull_request as string | undefined,
1593 |             period: params.period as string | undefined,
1594 |             page: nullToUndefined(params.page) as number | undefined,
1595 |             pageSize: nullToUndefined(params.page_size) as number | undefined,
1596 |           });
1597 |         };
1598 |         // Mock the handler function
1599 |         const mockHandler = (vi.fn() as any).mockResolvedValue({
1600 |           content: [{ type: 'text', text: '{"components":[]}' }],
1601 |         });
1602 |         const originalHandler = handleSonarQubeComponentsMeasures;
1603 |         handleSonarQubeComponentsMeasures = mockHandler;
1604 |         // Test with string parameters
1605 |         await componentsMeasuresLambda({
1606 |           component_keys: 'project1',
1607 |           metric_keys: 'coverage',
1608 |           page: '1',
1609 |           page_size: '10',
1610 |         });
1611 |         // Test with array parameters
1612 |         await componentsMeasuresLambda({
1613 |           component_keys: ['project1', 'project2'],
1614 |           metric_keys: ['coverage', 'bugs'],
1615 |           additional_fields: ['periods'],
1616 |           branch: 'main',
1617 |           period: '1',
1618 |         });
1619 |         // Test with pull request parameter
1620 |         await componentsMeasuresLambda({
1621 |           component_keys: 'project1',
1622 |           metric_keys: ['coverage', 'bugs'],
1623 |           pull_request: 'pr-123',
1624 |         });
1625 |         // Check that the handler was called with the correct parameters
1626 |         expect(mockHandler).toHaveBeenCalledTimes(3);
1627 |         // Check first call with string parameters
1628 |         expect(mockHandler.mock.calls[0][0]).toEqual({
1629 |           componentKeys: ['project1'],
1630 |           metricKeys: ['coverage'],
1631 |           additionalFields: undefined,
1632 |           branch: undefined,
1633 |           pullRequest: undefined,
1634 |           period: undefined,
1635 |           page: '1',
1636 |           pageSize: '10',
1637 |         });
1638 |         // Check second call with array parameters
1639 |         expect(mockHandler.mock.calls[1][0]).toEqual({
1640 |           componentKeys: ['project1', 'project2'],
1641 |           metricKeys: ['coverage', 'bugs'],
1642 |           additionalFields: ['periods'],
1643 |           branch: 'main',
1644 |           pullRequest: undefined,
1645 |           period: '1',
1646 |           page: undefined,
1647 |           pageSize: undefined,
1648 |         });
1649 |         // Check third call with pull request parameter
1650 |         expect(mockHandler.mock.calls[2][0]).toEqual({
1651 |           componentKeys: ['project1'],
1652 |           metricKeys: ['coverage', 'bugs'],
1653 |           additionalFields: undefined,
1654 |           branch: undefined,
1655 |           pullRequest: 'pr-123',
1656 |           period: undefined,
1657 |           page: undefined,
1658 |           pageSize: undefined,
1659 |         });
1660 |         // Restore the original handler
1661 |         handleSonarQubeComponentsMeasures = originalHandler;
1662 |       });
1663 |     });
1664 |     describe('measures_history tool lambda', () => {
1665 |       it('should call handleSonarQubeMeasuresHistory with correct parameters', async () => {
1666 |         // Create a simulated lambda function that mimics the tool handler
1667 |         const measuresHistoryLambda = async (params: Record<string, unknown>) => {
1668 |           return await handleSonarQubeMeasuresHistory({
1669 |             component: params.component as string,
1670 |             metrics: Array.isArray(params.metrics)
1671 |               ? (params.metrics as string[])
1672 |               : [params.metrics as string],
1673 |             from: params.from as string | undefined,
1674 |             to: params.to as string | undefined,
1675 |             branch: params.branch as string | undefined,
1676 |             pullRequest: params.pull_request as string | undefined,
1677 |             page: nullToUndefined(params.page) as number | undefined,
1678 |             pageSize: nullToUndefined(params.page_size) as number | undefined,
1679 |           });
1680 |         };
1681 |         // Mock the handler function
1682 |         const mockHandler = (vi.fn() as any).mockResolvedValue({
1683 |           content: [{ type: 'text', text: '{"measures":[]}' }],
1684 |         });
1685 |         const originalHandler = handleSonarQubeMeasuresHistory;
1686 |         handleSonarQubeMeasuresHistory = mockHandler;
1687 |         // Test with string parameter
1688 |         await measuresHistoryLambda({
1689 |           component: 'my-project',
1690 |           metrics: 'coverage',
1691 |           from: '2023-01-01',
1692 |           to: '2023-02-01',
1693 |         });
1694 |         // Test with array parameter
1695 |         await measuresHistoryLambda({
1696 |           component: 'my-project',
1697 |           metrics: ['coverage', 'bugs'],
1698 |           branch: 'main',
1699 |           page: '2',
1700 |           page_size: '20',
1701 |         });
1702 |         // Test with pull request parameter
1703 |         await measuresHistoryLambda({
1704 |           component: 'my-project',
1705 |           metrics: ['coverage'],
1706 |           pull_request: 'pr-123',
1707 |         });
1708 |         // Test full parameter set
1709 |         await measuresHistoryLambda({
1710 |           component: 'my-project',
1711 |           metrics: ['coverage', 'bugs', 'code_smells'],
1712 |           from: '2023-01-01',
1713 |           to: '2023-12-31',
1714 |           branch: 'develop',
1715 |           pull_request: 'pr-456',
1716 |           page: '3',
1717 |           page_size: '50',
1718 |         });
1719 |         // Check that the handler was called with the correct parameters
1720 |         expect(mockHandler).toHaveBeenCalledTimes(4);
1721 |         // Check first call with string parameter
1722 |         expect(mockHandler.mock.calls[0][0]).toEqual({
1723 |           component: 'my-project',
1724 |           metrics: ['coverage'],
1725 |           from: '2023-01-01',
1726 |           to: '2023-02-01',
1727 |           branch: undefined,
1728 |           pullRequest: undefined,
1729 |           page: undefined,
1730 |           pageSize: undefined,
1731 |         });
1732 |         // Check second call with array parameter
1733 |         expect(mockHandler.mock.calls[1][0]).toEqual({
1734 |           component: 'my-project',
1735 |           metrics: ['coverage', 'bugs'],
1736 |           from: undefined,
1737 |           to: undefined,
1738 |           branch: 'main',
1739 |           pullRequest: undefined,
1740 |           page: '2',
1741 |           pageSize: '20',
1742 |         });
1743 |         // Check third call with pull request parameter
1744 |         expect(mockHandler.mock.calls[2][0]).toEqual({
1745 |           component: 'my-project',
1746 |           metrics: ['coverage'],
1747 |           from: undefined,
1748 |           to: undefined,
1749 |           branch: undefined,
1750 |           pullRequest: 'pr-123',
1751 |           page: undefined,
1752 |           pageSize: undefined,
1753 |         });
1754 |         // Check fourth call with full parameter set
1755 |         expect(mockHandler.mock.calls[3][0]).toEqual({
1756 |           component: 'my-project',
1757 |           metrics: ['coverage', 'bugs', 'code_smells'],
1758 |           from: '2023-01-01',
1759 |           to: '2023-12-31',
1760 |           branch: 'develop',
1761 |           pullRequest: 'pr-456',
1762 |           page: '3',
1763 |           pageSize: '50',
1764 |         });
1765 |         // Restore the original handler
1766 |         handleSonarQubeMeasuresHistory = originalHandler;
1767 |       });
1768 |     });
1769 |     it('should fully process SonarQube projects response', async () => {
1770 |       const fullProjectsResponse = {
1771 |         projects: [
1772 |           {
1773 |             key: 'test-project',
1774 |             name: 'Test Project',
1775 |             qualifier: 'TRK',
1776 |             visibility: 'public',
1777 |             lastAnalysisDate: '2024-03-01',
1778 |             revision: 'abc123',
1779 |             managed: false,
1780 |             extra: 'should be excluded',
1781 |           },
1782 |         ],
1783 |         paging: {
1784 |           pageIndex: 1,
1785 |           pageSize: 10,
1786 |           total: 1,
1787 |         },
1788 |       };
1789 |       mockHandlers.handleSonarQubeProjects.mockResolvedValueOnce({
1790 |         content: [
1791 |           {
1792 |             type: 'text',
1793 |             text: JSON.stringify(fullProjectsResponse),
1794 |           },
1795 |         ],
1796 |       });
1797 |       const result = await mockHandlers.handleSonarQubeProjects({ page: 1, page_size: 10 });
1798 |       const data = JSON.parse(result.content[0].text);
1799 |       expect(data.projects[0].key).toBe('test-project');
1800 |       expect(data.projects[0].name).toBe('Test Project');
1801 |       expect(data.projects[0].qualifier).toBe('TRK');
1802 |       expect(data.projects[0].visibility).toBe('public');
1803 |       expect(data.projects[0].lastAnalysisDate).toBe('2024-03-01');
1804 |       expect(data.projects[0].revision).toBe('abc123');
1805 |       expect(data.projects[0].managed).toBe(false);
1806 |       expect(data.paging.pageIndex).toBe(1);
1807 |       expect(data.paging.pageSize).toBe(10);
1808 |       expect(data.paging.total).toBe(1);
1809 |     });
1810 |     it('should fully process SonarQube issues response', async () => {
1811 |       const fullIssuesResponse = {
1812 |         issues: [
1813 |           {
1814 |             key: 'test-issue',
1815 |             rule: 'test-rule',
1816 |             severity: 'MAJOR',
1817 |             component: 'test-component',
1818 |             project: 'test-project',
1819 |             line: 1,
1820 |             status: 'OPEN',
1821 |             issueStatus: 'OPEN',
1822 |             message: 'Test issue',
1823 |             messageFormattings: [],
1824 |             effort: '1h',
1825 |             debt: '1h',
1826 |             author: 'test-author',
1827 |             tags: ['tag1', 'tag2'],
1828 |             creationDate: '2024-03-01',
1829 |             updateDate: '2024-03-02',
1830 |             type: 'BUG',
1831 |             cleanCodeAttribute: 'CONSISTENT',
1832 |             cleanCodeAttributeCategory: 'ADAPTABLE',
1833 |             prioritizedRule: true,
1834 |             impacts: [{ severity: 'HIGH', softwareQuality: 'SECURITY' }],
1835 |             textRange: { startLine: 1, endLine: 1, startOffset: 0, endOffset: 10 },
1836 |             comments: [],
1837 |             transitions: [],
1838 |             actions: [],
1839 |             flows: [],
1840 |             quickFixAvailable: false,
1841 |             ruleDescriptionContextKey: 'context',
1842 |             codeVariants: [],
1843 |             hash: 'hash',
1844 |           },
1845 |         ],
1846 |         components: [{ key: 'comp1', name: 'Component 1' }],
1847 |         rules: [{ key: 'rule1', name: 'Rule 1' }],
1848 |         users: [{ login: 'user1', name: 'User 1' }],
1849 |         facets: [{ property: 'facet1', values: [] }],
1850 |         paging: {
1851 |           pageIndex: 1,
1852 |           pageSize: 10,
1853 |           total: 1,
1854 |         },
1855 |       };
1856 |       mockHandlers.handleSonarQubeGetIssues.mockResolvedValueOnce({
1857 |         content: [
1858 |           {
1859 |             type: 'text',
1860 |             text: JSON.stringify(fullIssuesResponse),
1861 |           },
1862 |         ],
1863 |       });
1864 |       const result = await mockHandlers.handleSonarQubeGetIssues({
1865 |         projectKey: 'test-project',
1866 |         severity: 'MAJOR',
1867 |         page: 1,
1868 |         pageSize: 10,
1869 |         statuses: ['OPEN'],
1870 |         resolutions: ['FIXED'],
1871 |         resolved: true,
1872 |         types: ['BUG'],
1873 |         rules: ['rule1'],
1874 |         tags: ['tag1'],
1875 |         createdAfter: '2024-01-01',
1876 |         createdBefore: '2024-03-01',
1877 |         createdAt: '2024-02-01',
1878 |         createdInLast: '30d',
1879 |         assignees: ['user1'],
1880 |         authors: ['author1'],
1881 |         cwe: ['cwe1'],
1882 |         languages: ['java'],
1883 |         owaspTop10: ['a1'],
1884 |         sansTop25: ['sans1'],
1885 |         sonarsourceSecurity: ['ss1'],
1886 |         onComponentOnly: true,
1887 |         facets: ['facet1'],
1888 |         sinceLeakPeriod: true,
1889 |         inNewCodePeriod: true,
1890 |       });
1891 |       const data = JSON.parse(result.content[0].text);
1892 |       // Check all fields are properly mapped
1893 |       expect(data.issues[0].key).toBe('test-issue');
1894 |       expect(data.issues[0].rule).toBe('test-rule');
1895 |       expect(data.issues[0].severity).toBe('MAJOR');
1896 |       expect(data.issues[0].component).toBe('test-component');
1897 |       expect(data.issues[0].project).toBe('test-project');
1898 |       expect(data.issues[0].line).toBe(1);
1899 |       expect(data.issues[0].status).toBe('OPEN');
1900 |       expect(data.issues[0].issueStatus).toBe('OPEN');
1901 |       expect(data.issues[0].message).toBe('Test issue');
1902 |       expect(data.issues[0].effort).toBe('1h');
1903 |       expect(data.issues[0].debt).toBe('1h');
1904 |       expect(data.issues[0].author).toBe('test-author');
1905 |       expect(data.issues[0].tags).toEqual(['tag1', 'tag2']);
1906 |       expect(data.issues[0].creationDate).toBe('2024-03-01');
1907 |       expect(data.issues[0].updateDate).toBe('2024-03-02');
1908 |       expect(data.issues[0].type).toBe('BUG');
1909 |       expect(data.issues[0].cleanCodeAttribute).toBe('CONSISTENT');
1910 |       expect(data.issues[0].cleanCodeAttributeCategory).toBe('ADAPTABLE');
1911 |       expect(data.issues[0].prioritizedRule).toBe(true);
1912 |       expect(data.issues[0].impacts).toHaveLength(1);
1913 |       expect(data.issues[0].impacts[0].severity).toBe('HIGH');
1914 |       // Check other response data
1915 |       expect(data.components).toHaveLength(1);
1916 |       expect(data.rules).toHaveLength(1);
1917 |       expect(data.users).toHaveLength(1);
1918 |       expect(data.facets).toHaveLength(1);
1919 |       expect(data.paging.pageIndex).toBe(1);
1920 |       expect(data.paging.pageSize).toBe(10);
1921 |       expect(data.paging.total).toBe(1);
1922 |     });
1923 |     it('should handle metrics response', async () => {
1924 |       const metricsResponse = {
1925 |         metrics: [
1926 |           {
1927 |             key: 'test-metric',
1928 |             name: 'Test Metric',
1929 |             description: 'Test metric description',
1930 |             domain: 'test',
1931 |             type: 'INT',
1932 |           },
1933 |         ],
1934 |         paging: {
1935 |           pageIndex: 1,
1936 |           pageSize: 10,
1937 |           total: 1,
1938 |         },
1939 |       };
1940 |       mockHandlers.handleSonarQubeGetMetrics.mockResolvedValueOnce({
1941 |         content: [
1942 |           {
1943 |             type: 'text',
1944 |             text: JSON.stringify(metricsResponse),
1945 |           },
1946 |         ],
1947 |       });
1948 |       const result = await mockHandlers.handleSonarQubeGetMetrics({
1949 |         page: 1,
1950 |         pageSize: 10,
1951 |       });
1952 |       const data = JSON.parse(result.content[0].text);
1953 |       expect(data.metrics).toHaveLength(1);
1954 |       expect(data.metrics[0].key).toBe('test-metric');
1955 |       expect(data.metrics[0].name).toBe('Test Metric');
1956 |       expect(data.paging.pageIndex).toBe(1);
1957 |     });
1958 |   });
1959 |   describe('Tool registration schemas', () => {
1960 |     it('should correctly transform page parameters', () => {
1961 |       const pageSchema = z
1962 |         .string()
1963 |         .optional()
1964 |         .transform((val: any) => (val ? parseInt(val, 10) || null : null));
1965 |       expect(pageSchema.parse('10')).toBe(10);
1966 |       expect(pageSchema.parse('not-a-number')).toBe(null);
1967 |       expect(pageSchema.parse('')).toBe(null);
1968 |       expect(pageSchema.parse(undefined)).toBe(null);
1969 |     });
1970 |     it('should validate severity enum schema', () => {
1971 |       const severitySchema = z
1972 |         .enum(['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'])
1973 |         .nullable()
1974 |         .optional();
1975 |       expect(severitySchema.parse('MAJOR')).toBe('MAJOR');
1976 |       expect(severitySchema.parse('BLOCKER')).toBe('BLOCKER');
1977 |       expect(severitySchema.parse(null)).toBe(null);
1978 |       expect(severitySchema.parse(undefined)).toBe(undefined);
1979 |       expect(() => severitySchema.parse('INVALID')).toThrow();
1980 |     });
1981 |     it('should validate status schema', () => {
1982 |       const statusSchema = z
1983 |         .array(
1984 |           z.enum([
1985 |             'OPEN',
1986 |             'CONFIRMED',
1987 |             'REOPENED',
1988 |             'RESOLVED',
1989 |             'CLOSED',
1990 |             'TO_REVIEW',
1991 |             'IN_REVIEW',
1992 |             'REVIEWED',
1993 |           ])
1994 |         )
1995 |         .nullable()
1996 |         .optional();
1997 |       expect(statusSchema.parse(['OPEN', 'CONFIRMED'])).toEqual(['OPEN', 'CONFIRMED']);
1998 |       expect(statusSchema.parse(null)).toBe(null);
1999 |       expect(statusSchema.parse(undefined)).toBe(undefined);
2000 |       expect(() => statusSchema.parse(['INVALID'])).toThrow();
2001 |     });
2002 |     it('should validate resolution schema', () => {
2003 |       const resolutionSchema = z
2004 |         .array(z.enum(['FALSE-POSITIVE', 'WONTFIX', 'FIXED', 'REMOVED']))
2005 |         .nullable()
2006 |         .optional();
2007 |       expect(resolutionSchema.parse(['FALSE-POSITIVE', 'WONTFIX'])).toEqual([
2008 |         'FALSE-POSITIVE',
2009 |         'WONTFIX',
2010 |       ]);
2011 |       expect(resolutionSchema.parse(null)).toBe(null);
2012 |       expect(resolutionSchema.parse(undefined)).toBe(undefined);
2013 |       expect(() => resolutionSchema.parse(['INVALID'])).toThrow();
2014 |     });
2015 |     it('should validate type schema', () => {
2016 |       const typeSchema = z
2017 |         .array(z.enum(['CODE_SMELL', 'BUG', 'VULNERABILITY', 'SECURITY_HOTSPOT']))
2018 |         .nullable()
2019 |         .optional();
2020 |       expect(typeSchema.parse(['CODE_SMELL', 'BUG'])).toEqual(['CODE_SMELL', 'BUG']);
2021 |       expect(typeSchema.parse(null)).toBe(null);
2022 |       expect(typeSchema.parse(undefined)).toBe(undefined);
2023 |       expect(() => typeSchema.parse(['INVALID'])).toThrow();
2024 |     });
2025 |     it('should transform boolean parameters', () => {
2026 |       const booleanSchema = z
2027 |         .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
2028 |         .nullable()
2029 |         .optional();
2030 |       expect(booleanSchema.parse('true')).toBe(true);
2031 |       expect(booleanSchema.parse('false')).toBe(false);
2032 |       expect(booleanSchema.parse(true)).toBe(true);
2033 |       expect(booleanSchema.parse(false)).toBe(false);
2034 |       expect(booleanSchema.parse(null)).toBe(null);
2035 |       expect(booleanSchema.parse(undefined)).toBe(undefined);
2036 |     });
2037 |   });
2038 |   describe('Tool registration lambdas', () => {
2039 |     beforeEach(() => {
2040 |       // Reset all mocks
2041 |       vi.resetAllMocks();
2042 |     });
2043 |     it('should test the metrics tool lambda', async () => {
2044 |       // Mock the handleSonarQubeGetMetrics function to track calls
2045 |       const mockGetMetrics = (vi.fn() as any).mockResolvedValue({
2046 |         content: [{ type: 'text', text: '{"metrics":[]}' }],
2047 |       });
2048 |       const originalHandler = handleSonarQubeGetMetrics;
2049 |       handleSonarQubeGetMetrics = mockGetMetrics;
2050 |       // Create the lambda handler that's in the tool registration
2051 |       const metricsLambda = async (params: Record<string, unknown>) => {
2052 |         const result = await handleSonarQubeGetMetrics({
2053 |           page: nullToUndefined(params.page) as number | undefined,
2054 |           pageSize: nullToUndefined(params.page_size) as number | undefined,
2055 |         });
2056 |         return {
2057 |           content: [
2058 |             {
2059 |               type: 'text',
2060 |               text: JSON.stringify(result, null, 2),
2061 |             },
2062 |           ],
2063 |         };
2064 |       };
2065 |       // Call the lambda with params
2066 |       await metricsLambda({ page: '5', page_size: '20' });
2067 |       // Check that handleSonarQubeGetMetrics was called with the right params
2068 |       expect(mockGetMetrics).toHaveBeenCalledWith({
2069 |         page: '5',
2070 |         pageSize: '20',
2071 |       });
2072 |       // Restore the original function
2073 |       handleSonarQubeGetMetrics = originalHandler;
2074 |     });
2075 |     it('should test the issues tool lambda', async () => {
2076 |       // Mock the handleSonarQubeGetIssues function to track calls
2077 |       const mockGetIssues = (vi.fn() as any).mockResolvedValue({
2078 |         content: [{ type: 'text', text: '{"issues":[]}' }],
2079 |       });
2080 |       const originalHandler = handleSonarQubeGetIssues;
2081 |       handleSonarQubeGetIssues = mockGetIssues;
2082 |       // Mock mapToSonarQubeParams to return expected output
2083 |       const originalMapFunction = mapToSonarQubeParams;
2084 |       const mockMapFunction = (vi.fn() as any).mockReturnValue({
2085 |         projectKey: 'test-project',
2086 |         severity: 'MAJOR',
2087 |       });
2088 |       mapToSonarQubeParams = mockMapFunction;
2089 |       // Create the lambda handler that's in the tool registration
2090 |       const issuesLambda = async (params: Record<string, unknown>) => {
2091 |         return await handleSonarQubeGetIssues(mapToSonarQubeParams(params));
2092 |       };
2093 |       // Call the lambda with params
2094 |       await issuesLambda({
2095 |         project_key: 'test-project',
2096 |         severity: 'MAJOR',
2097 |       });
2098 |       // Check that mapToSonarQubeParams was called with the right params
2099 |       expect(mockMapFunction).toHaveBeenCalledWith({
2100 |         project_key: 'test-project',
2101 |         severity: 'MAJOR',
2102 |       });
2103 |       // Check that handleSonarQubeGetIssues was called with the mapped params
2104 |       expect(mockGetIssues).toHaveBeenCalledWith({
2105 |         projectKey: 'test-project',
2106 |         severity: 'MAJOR',
2107 |       });
2108 |       // Restore the original functions
2109 |       handleSonarQubeGetIssues = originalHandler;
2110 |       mapToSonarQubeParams = originalMapFunction;
2111 |     });
2112 |     it('should test the hotspot search tool lambda', async () => {
2113 |       // Mock the handleSonarQubeSearchHotspots function to track calls
2114 |       const mockSearchHotspots = (vi.fn() as any).mockResolvedValue({
2115 |         content: [{ type: 'text', text: '{"hotspots":[]}' }],
2116 |       });
2117 |       const originalHandler = handleSonarQubeHotspots;
2118 |       handleSonarQubeHotspots = mockSearchHotspots;
2119 |       // Mock mapToSonarQubeParams to return expected output
2120 |       const originalMapFunction = mapToSonarQubeParams;
2121 |       const mockMapFunction = (vi.fn() as any).mockReturnValue({
2122 |         projectKey: 'test-project',
2123 |         status: 'TO_REVIEW',
2124 |         assignedToMe: true,
2125 |         sinceLeakPeriod: false,
2126 |         inNewCodePeriod: true,
2127 |         page: 1,
2128 |         pageSize: 50,
2129 |       });
2130 |       mapToSonarQubeParams = mockMapFunction;
2131 |       // Create the lambda handler that's in the tool registration
2132 |       const searchHotspotsLambda = async (params: Record<string, unknown>) => {
2133 |         return await handleSonarQubeHotspots(mapToSonarQubeParams(params));
2134 |       };
2135 |       // Call the lambda with params that include string booleans
2136 |       await searchHotspotsLambda({
2137 |         project_key: 'test-project',
2138 |         status: 'TO_REVIEW',
2139 |         assigned_to_me: 'true',
2140 |         since_leak_period: 'false',
2141 |         in_new_code_period: 'true',
2142 |         page: '1',
2143 |         page_size: '50',
2144 |       });
2145 |       // Check that mapToSonarQubeParams was called with the right params
2146 |       expect(mockMapFunction).toHaveBeenCalledWith({
2147 |         project_key: 'test-project',
2148 |         status: 'TO_REVIEW',
2149 |         assigned_to_me: 'true',
2150 |         since_leak_period: 'false',
2151 |         in_new_code_period: 'true',
2152 |         page: '1',
2153 |         page_size: '50',
2154 |       });
2155 |       // Check that handleSonarQubeSearchHotspots was called with the mapped params
2156 |       expect(mockSearchHotspots).toHaveBeenCalledWith({
2157 |         projectKey: 'test-project',
2158 |         status: 'TO_REVIEW',
2159 |         assignedToMe: true,
2160 |         sinceLeakPeriod: false,
2161 |         inNewCodePeriod: true,
2162 |         page: 1,
2163 |         pageSize: 50,
2164 |       });
2165 |       // Restore the original functions
2166 |       handleSonarQubeHotspots = originalHandler;
2167 |       mapToSonarQubeParams = originalMapFunction;
2168 |     });
2169 |     it('should test the quality gate handler lambda', async () => {
2170 |       // Set up mock for the API call
2171 |       nock('http://localhost:9000')
2172 |         .get('/api/qualitygates/show')
2173 |         .query({ id: 'gate-123' })
2174 |         .reply(200, {
2175 |           id: 'gate-123',
2176 |           name: 'Test Quality Gate',
2177 |           conditions: [],
2178 |           isBuiltIn: false,
2179 |         });
2180 |       // Test the lambda
2181 |       const result = await qualityGateHandler({ id: 'gate-123' });
2182 |       expect(result).toBeDefined();
2183 |       expect(result.content[0].text).toContain('gate-123');
2184 |     });
2185 |     it('should test the project quality gate status handler lambda', async () => {
2186 |       // Set up mock for the API call
2187 |       nock('http://localhost:9000')
2188 |         .get('/api/qualitygates/project_status')
2189 |         .query({
2190 |           projectKey: 'test-project',
2191 |           branch: 'main',
2192 |           pullRequest: 'pr-123',
2193 |         })
2194 |         .reply(200, {
2195 |           projectStatus: {
2196 |             status: 'OK',
2197 |             conditions: [],
2198 |           },
2199 |         });
2200 |       // Test the lambda with all parameters
2201 |       const result = await qualityGateStatusHandler({
2202 |         project_key: 'test-project',
2203 |         branch: 'main',
2204 |         pull_request: 'pr-123',
2205 |       });
2206 |       expect(result).toBeDefined();
2207 |       expect(result.content[0].text).toContain('OK');
2208 |     });
2209 |     it('should test the get hotspot details handler lambda', async () => {
2210 |       // Set up mock for the API call
2211 |       nock('http://localhost:9000')
2212 |         .get('/api/hotspots/show')
2213 |         .query({
2214 |           hotspot: 'hotspot-123',
2215 |         })
2216 |         .reply(200, {
2217 |           key: 'hotspot-123',
2218 |           component: 'test',
2219 |           project: 'test-project',
2220 |           rule: {
2221 |             key: 'java:S2068',
2222 |             name: 'Hard-coded credentials',
2223 |             securityCategory: 'weak-cryptography',
2224 |           },
2225 |           status: 'TO_REVIEW',
2226 |           line: 42,
2227 |           message: 'Make sure this password is not hard-coded.',
2228 |           author: '[email protected]',
2229 |           creationDate: '2023-01-15T10:30:00+0000',
2230 |         });
2231 |       // Test the lambda
2232 |       const result = await hotspotHandler({ hotspot_key: 'hotspot-123' });
2233 |       expect(result).toBeDefined();
2234 |       expect(result.content[0].text).toContain('hotspot-123');
2235 |     });
2236 |     it('should test the update hotspot status handler lambda', async () => {
2237 |       // Set up mock for the API call
2238 |       nock('http://localhost:9000')
2239 |         .post('/api/hotspots/change_status', {
2240 |           hotspot: 'hotspot-123',
2241 |           status: 'REVIEWED',
2242 |           resolution: 'SAFE',
2243 |           comment: 'Reviewed and safe',
2244 |         })
2245 |         .reply(200, {});
2246 |       // Test the lambda with all parameters
2247 |       const result = await updateHotspotStatusHandler({
2248 |         hotspot_key: 'hotspot-123',
2249 |         status: 'REVIEWED',
2250 |         resolution: 'SAFE',
2251 |         comment: 'Reviewed and safe',
2252 |       });
2253 |       expect(result).toBeDefined();
2254 |       expect(result.content[0].text).toContain('successfully');
2255 |     });
2256 |   });
2257 |   describe('Tool schema validations', () => {
2258 |     it('should validate and transform all issue tool schemas', () => {
2259 |       // Create schemas that match what's in the tool registration
2260 |       const pageSchema = z
2261 |         .string()
2262 |         .optional()
2263 |         .transform((val: any) => (val ? parseInt(val, 10) || null : null));
2264 |       const booleanSchema = z
2265 |         .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
2266 |         .nullable()
2267 |         .optional();
2268 |       const severitySchema = z
2269 |         .enum(['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'])
2270 |         .nullable()
2271 |         .optional();
2272 |       const statusSchema = z
2273 |         .array(
2274 |           z.enum([
2275 |             'OPEN',
2276 |             'CONFIRMED',
2277 |             'REOPENED',
2278 |             'RESOLVED',
2279 |             'CLOSED',
2280 |             'TO_REVIEW',
2281 |             'IN_REVIEW',
2282 |             'REVIEWED',
2283 |           ])
2284 |         )
2285 |         .nullable()
2286 |         .optional();
2287 |       const resolutionSchema = z
2288 |         .array(z.enum(['FALSE-POSITIVE', 'WONTFIX', 'FIXED', 'REMOVED']))
2289 |         .nullable()
2290 |         .optional();
2291 |       const typeSchema = z
2292 |         .array(z.enum(['CODE_SMELL', 'BUG', 'VULNERABILITY', 'SECURITY_HOTSPOT']))
2293 |         .nullable()
2294 |         .optional();
2295 |       const stringArraySchema = z.array(z.string()).nullable().optional();
2296 |       // Create the complete schema
2297 |       const schema = z.object({
2298 |         project_key: z.string(),
2299 |         severity: severitySchema,
2300 |         page: pageSchema,
2301 |         page_size: pageSchema,
2302 |         statuses: statusSchema,
2303 |         resolutions: resolutionSchema,
2304 |         resolved: booleanSchema,
2305 |         types: typeSchema,
2306 |         rules: stringArraySchema,
2307 |         tags: stringArraySchema,
2308 |         created_after: z.string().nullable().optional(),
2309 |         created_before: z.string().nullable().optional(),
2310 |         created_at: z.string().nullable().optional(),
2311 |         created_in_last: z.string().nullable().optional(),
2312 |         assignees: stringArraySchema,
2313 |         authors: stringArraySchema,
2314 |         cwe: stringArraySchema,
2315 |         languages: stringArraySchema,
2316 |         owasp_top10: stringArraySchema,
2317 |         sans_top25: stringArraySchema,
2318 |         sonarsource_security: stringArraySchema,
2319 |         on_component_only: booleanSchema,
2320 |         facets: stringArraySchema,
2321 |         since_leak_period: booleanSchema,
2322 |         in_new_code_period: booleanSchema,
2323 |       });
2324 |       // Test the complete schema
2325 |       const testData = {
2326 |         project_key: 'test-project',
2327 |         severity: 'MAJOR',
2328 |         page: '10',
2329 |         page_size: '20',
2330 |         statuses: ['OPEN', 'CONFIRMED'],
2331 |         resolutions: ['FALSE-POSITIVE', 'WONTFIX'],
2332 |         resolved: 'true',
2333 |         types: ['CODE_SMELL', 'BUG'],
2334 |         rules: ['rule1', 'rule2'],
2335 |         tags: ['tag1', 'tag2'],
2336 |         created_after: '2024-01-01',
2337 |         created_before: '2024-12-31',
2338 |         created_at: '2024-06-15',
2339 |         created_in_last: '7d',
2340 |         assignees: ['user1', 'user2'],
2341 |         authors: ['author1', 'author2'],
2342 |         cwe: ['cwe1', 'cwe2'],
2343 |         languages: ['java', 'typescript'],
2344 |         owasp_top10: ['a1', 'a2'],
2345 |         sans_top25: ['sans1', 'sans2'],
2346 |         sonarsource_security: ['sec1', 'sec2'],
2347 |         on_component_only: 'true',
2348 |         facets: ['facet1', 'facet2'],
2349 |         since_leak_period: 'true',
2350 |         in_new_code_period: 'true',
2351 |       };
2352 |       // Validate through the Zod schema
2353 |       const validated = schema.parse(testData);
2354 |       // Check transformations happened correctly
2355 |       expect(validated.page).toBe(10);
2356 |       expect(validated.page_size).toBe(20);
2357 |       expect(validated.resolved).toBe(true);
2358 |       expect(validated.on_component_only).toBe(true);
2359 |       expect(validated.since_leak_period).toBe(true);
2360 |       expect(validated.in_new_code_period).toBe(true);
2361 |       // Check arrays were kept intact
2362 |       expect(validated.statuses).toEqual(['OPEN', 'CONFIRMED']);
2363 |       expect(validated.resolutions).toEqual(['FALSE-POSITIVE', 'WONTFIX']);
2364 |       expect(validated.types).toEqual(['CODE_SMELL', 'BUG']);
2365 |       expect(validated.rules).toEqual(['rule1', 'rule2']);
2366 |       // Check that strings were kept intact
2367 |       expect(validated.project_key).toBe('test-project');
2368 |       expect(validated.severity).toBe('MAJOR');
2369 |       expect(validated.created_after).toBe('2024-01-01');
2370 |     });
2371 |   });
2372 |   describe('Direct tool registration test', () => {
2373 |     it('should validate tool existence', () => {
2374 |       // Skip if mcpServer is mocked or doesn't have tool method
2375 |       if (mcpServer.tool) {
2376 |         expect(mcpServer.tool).toBeDefined();
2377 |       } else {
2378 |         expect(mcpServer).toBeDefined();
2379 |       }
2380 |     });
2381 |     it('should test the lambda functions directly', async () => {
2382 |       // Create lambda functions that match the lambda functions in the tool registrations
2383 |       const metricsLambda = async (params: Record<string, unknown>) => {
2384 |         const result = await handleSonarQubeGetMetrics({
2385 |           page: nullToUndefined(params.page) as number | undefined,
2386 |           pageSize: nullToUndefined(params.page_size) as number | undefined,
2387 |         });
2388 |         return { content: [{ type: 'text', text: JSON.stringify(result) }] };
2389 |       };
2390 |       const issuesLambda = async (params: Record<string, unknown>) => {
2391 |         return await handleSonarQubeGetIssues(mapToSonarQubeParams(params));
2392 |       };
2393 |       const componentsLambda = async (params: Record<string, unknown>) => {
2394 |         return await handleSonarQubeComponentsMeasures({
2395 |           componentKeys: Array.isArray(params.component_keys)
2396 |             ? (params.component_keys as string[])
2397 |             : [params.component_keys as string],
2398 |           metricKeys: Array.isArray(params.metric_keys)
2399 |             ? (params.metric_keys as string[])
2400 |             : [params.metric_keys as string],
2401 |           additionalFields: params.additional_fields as string[] | undefined,
2402 |           branch: params.branch as string | undefined,
2403 |           pullRequest: params.pull_request as string | undefined,
2404 |           period: params.period as string | undefined,
2405 |           page: nullToUndefined(params.page) as number | undefined,
2406 |           pageSize: nullToUndefined(params.page_size) as number | undefined,
2407 |         });
2408 |       };
2409 |       const historyLambda = async (params: Record<string, unknown>) => {
2410 |         return await handleSonarQubeMeasuresHistory({
2411 |           component: params.component as string,
2412 |           metrics: Array.isArray(params.metrics)
2413 |             ? (params.metrics as string[])
2414 |             : [params.metrics as string],
2415 |           from: params.from as string | undefined,
2416 |           to: params.to as string | undefined,
2417 |           branch: params.branch as string | undefined,
2418 |           pullRequest: params.pull_request as string | undefined,
2419 |           page: nullToUndefined(params.page) as number | undefined,
2420 |           pageSize: nullToUndefined(params.page_size) as number | undefined,
2421 |         });
2422 |       };
2423 |       // Mock all the handler functions to test the lambda functions
2424 |       const mockGetMetrics = (vi.fn() as any).mockResolvedValue({
2425 |         content: [{ type: 'text', text: '{"metrics":[]}' }],
2426 |       });
2427 |       const mockGetIssues = (vi.fn() as any).mockResolvedValue({
2428 |         content: [{ type: 'text', text: '{"issues":[]}' }],
2429 |       });
2430 |       const mockComponentsMeasures = (vi.fn() as any).mockResolvedValue({
2431 |         content: [{ type: 'text', text: '{"components":[]}' }],
2432 |       });
2433 |       const mockMeasuresHistory = (vi.fn() as any).mockResolvedValue({
2434 |         content: [{ type: 'text', text: '{"measures":[]}' }],
2435 |       });
2436 |       // Override the handler functions with mocks
2437 |       const originalGetMetrics = handleSonarQubeGetMetrics;
2438 |       const originalGetIssues = handleSonarQubeGetIssues;
2439 |       const originalComponentsMeasures = handleSonarQubeComponentsMeasures;
2440 |       const originalMeasuresHistory = handleSonarQubeMeasuresHistory;
2441 |       handleSonarQubeGetMetrics = mockGetMetrics;
2442 |       handleSonarQubeGetIssues = mockGetIssues;
2443 |       handleSonarQubeComponentsMeasures = mockComponentsMeasures;
2444 |       handleSonarQubeMeasuresHistory = mockMeasuresHistory;
2445 |       // Test metrics lambda
2446 |       await metricsLambda({ page: '10', page_size: '20' });
2447 |       expect(mockGetMetrics).toHaveBeenCalledWith({
2448 |         page: '10',
2449 |         pageSize: '20',
2450 |       });
2451 |       // Test issues lambda with all possible parameters
2452 |       await issuesLambda({
2453 |         project_key: 'test-project',
2454 |         severity: 'MAJOR',
2455 |         page: '1',
2456 |         page_size: '10',
2457 |         statuses: ['OPEN', 'CONFIRMED'],
2458 |         resolutions: ['FALSE-POSITIVE', 'WONTFIX'],
2459 |         resolved: 'true',
2460 |         types: ['CODE_SMELL', 'BUG'],
2461 |         rules: ['rule1', 'rule2'],
2462 |         tags: ['tag1', 'tag2'],
2463 |         created_after: '2023-01-01',
2464 |         created_before: '2023-12-31',
2465 |         created_at: '2023-06-15',
2466 |         created_in_last: '7d',
2467 |         assignees: ['user1', 'user2'],
2468 |         authors: ['author1', 'author2'],
2469 |         cwe: ['cwe1', 'cwe2'],
2470 |         languages: ['java', 'typescript'],
2471 |         owasp_top10: ['a1', 'a2'],
2472 |         sans_top25: ['sans1', 'sans2'],
2473 |         sonarsource_security: ['sec1', 'sec2'],
2474 |         on_component_only: 'true',
2475 |         facets: ['facet1', 'facet2'],
2476 |         since_leak_period: 'true',
2477 |         in_new_code_period: 'true',
2478 |       });
2479 |       expect(mockGetIssues).toHaveBeenCalledTimes(1);
2480 |       // Test components lambda
2481 |       await componentsLambda({
2482 |         component_keys: ['comp1', 'comp2'],
2483 |         metric_keys: ['coverage', 'bugs'],
2484 |         additional_fields: ['periods'],
2485 |         branch: 'main',
2486 |         pull_request: 'pr-123',
2487 |         period: '1',
2488 |         page: '2',
2489 |         page_size: '25',
2490 |       });
2491 |       expect(mockComponentsMeasures).toHaveBeenCalledWith({
2492 |         componentKeys: ['comp1', 'comp2'],
2493 |         metricKeys: ['coverage', 'bugs'],
2494 |         additionalFields: ['periods'],
2495 |         branch: 'main',
2496 |         pullRequest: 'pr-123',
2497 |         period: '1',
2498 |         page: '2',
2499 |         pageSize: '25',
2500 |       });
2501 |       // Test history lambda
2502 |       await historyLambda({
2503 |         component: 'test-component',
2504 |         metrics: ['coverage', 'bugs'],
2505 |         from: '2023-01-01',
2506 |         to: '2023-12-31',
2507 |         branch: 'feature',
2508 |         pull_request: 'pr-456',
2509 |         page: '3',
2510 |         page_size: '30',
2511 |       });
2512 |       expect(mockMeasuresHistory).toHaveBeenCalledWith({
2513 |         component: 'test-component',
2514 |         metrics: ['coverage', 'bugs'],
2515 |         from: '2023-01-01',
2516 |         to: '2023-12-31',
2517 |         branch: 'feature',
2518 |         pullRequest: 'pr-456',
2519 |         page: '3',
2520 |         pageSize: '30',
2521 |       });
2522 |       // Restore the original functions
2523 |       handleSonarQubeGetMetrics = originalGetMetrics;
2524 |       handleSonarQubeGetIssues = originalGetIssues;
2525 |       handleSonarQubeComponentsMeasures = originalComponentsMeasures;
2526 |       handleSonarQubeMeasuresHistory = originalMeasuresHistory;
2527 |     });
2528 |   });
2529 |   describe('Tool schema transformations with actual Zod schemas', () => {
2530 |     it('should transform issues tool parameters through Zod schema', () => {
2531 |       // Import the actual schema from the tool registration
2532 |       const issuesSchema = z.object({
2533 |         project_key: z.string().optional(),
2534 |         on_component_only: z
2535 |           .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
2536 |           .nullable()
2537 |           .optional(),
2538 |         resolved: z
2539 |           .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
2540 |           .nullable()
2541 |           .optional(),
2542 |         page: z
2543 |           .string()
2544 |           .optional()
2545 |           .transform((val: any) => (val ? parseInt(val, 10) || null : null)),
2546 |         page_size: z
2547 |           .string()
2548 |           .optional()
2549 |           .transform((val: any) => (val ? parseInt(val, 10) || null : null)),
2550 |       });
2551 |       // Test with string values that should be transformed
2552 |       const result = issuesSchema.parse({
2553 |         project_key: 'test-project',
2554 |         on_component_only: 'true',
2555 |         resolved: 'false',
2556 |         page: '5',
2557 |         page_size: '100',
2558 |       });
2559 |       expect(result.on_component_only).toBe(true);
2560 |       expect(result.resolved).toBe(false);
2561 |       expect(result.page).toBe(5);
2562 |       expect(result.page_size).toBe(100);
2563 |     });
2564 |     it('should transform hotspots tool parameters through Zod schema', () => {
2565 |       // Import the actual schema from the tool registration
2566 |       const hotspotsSchema = z.object({
2567 |         project_key: z.string().optional(),
2568 |         assigned_to_me: z
2569 |           .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
2570 |           .nullable()
2571 |           .optional(),
2572 |         since_leak_period: z
2573 |           .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
2574 |           .nullable()
2575 |           .optional(),
2576 |         in_new_code_period: z
2577 |           .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
2578 |           .nullable()
2579 |           .optional(),
2580 |         page: z
2581 |           .string()
2582 |           .optional()
2583 |           .transform((val: any) => (val ? parseInt(val, 10) || null : null)),
2584 |         page_size: z
2585 |           .string()
2586 |           .optional()
2587 |           .transform((val: any) => (val ? parseInt(val, 10) || null : null)),
2588 |       });
2589 |       // Test with string values that should be transformed
2590 |       const result = hotspotsSchema.parse({
2591 |         project_key: 'test-project',
2592 |         assigned_to_me: 'true',
2593 |         since_leak_period: 'false',
2594 |         in_new_code_period: 'true',
2595 |         page: '2',
2596 |         page_size: '50',
2597 |       });
2598 |       expect(result.assigned_to_me).toBe(true);
2599 |       expect(result.since_leak_period).toBe(false);
2600 |       expect(result.in_new_code_period).toBe(true);
2601 |       expect(result.page).toBe(2);
2602 |       expect(result.page_size).toBe(50);
2603 |       // Test with boolean values directly
2604 |       const result2 = hotspotsSchema.parse({
2605 |         project_key: 'test-project',
2606 |         assigned_to_me: false,
2607 |         since_leak_period: true,
2608 |         in_new_code_period: false,
2609 |       });
2610 |       expect(result2.assigned_to_me).toBe(false);
2611 |       expect(result2.since_leak_period).toBe(true);
2612 |       expect(result2.in_new_code_period).toBe(false);
2613 |     });
2614 |   });
2615 |   describe('Security Hotspot handlers', () => {
2616 |     describe('handleSonarQubeSearchHotspots', () => {
2617 |       it('should search and return hotspots', async () => {
2618 |         nock('http://localhost:9000')
2619 |           .get('/api/hotspots/search')
2620 |           .query({
2621 |             projectKey: 'test-project',
2622 |             status: 'TO_REVIEW',
2623 |             p: '1',
2624 |             ps: '50',
2625 |           })
2626 |           .matchHeader('authorization', 'Bearer test-token')
2627 |           .reply(200, {
2628 |             hotspots: [
2629 |               {
2630 |                 key: 'AYg1234567890',
2631 |                 component: 'com.example:my-project:src/main/java/Example.java',
2632 |                 project: 'com.example:my-project',
2633 |                 securityCategory: 'sql-injection',
2634 |                 vulnerabilityProbability: 'HIGH',
2635 |                 status: 'TO_REVIEW',
2636 |                 line: 42,
2637 |                 message: 'Make sure using this database query is safe.',
2638 |                 author: '[email protected]',
2639 |                 creationDate: '2023-01-15T10:30:00+0000',
2640 |               },
2641 |             ],
2642 |             components: [
2643 |               {
2644 |                 key: 'com.example:my-project:src/main/java/Example.java',
2645 |                 name: 'Example.java',
2646 |                 path: 'src/main/java/Example.java',
2647 |               },
2648 |             ],
2649 |             paging: {
2650 |               pageIndex: 1,
2651 |               pageSize: 50,
2652 |               total: 1,
2653 |             },
2654 |           });
2655 |         const response = await handleSonarQubeHotspots({
2656 |           projectKey: 'test-project',
2657 |           status: 'TO_REVIEW',
2658 |           page: 1,
2659 |           pageSize: 50,
2660 |         });
2661 |         const result = JSON.parse(response.content[0].text);
2662 |         expect(result.hotspots).toHaveLength(1);
2663 |         expect(result.hotspots[0].key).toBe('AYg1234567890');
2664 |         expect(result.hotspots[0].status).toBe('TO_REVIEW');
2665 |         expect(result.paging.total).toBe(1);
2666 |       });
2667 |     });
2668 |     describe('handleSonarQubeGetHotspotDetails', () => {
2669 |       it('should get and return hotspot details', async () => {
2670 |         nock('http://localhost:9000')
2671 |           .get('/api/hotspots/show')
2672 |           .query({
2673 |             hotspot: 'AYg1234567890',
2674 |           })
2675 |           .matchHeader('authorization', 'Bearer test-token')
2676 |           .reply(200, {
2677 |             key: 'AYg1234567890',
2678 |             component: {
2679 |               key: 'com.example:my-project:src/main/java/Example.java',
2680 |               name: 'Example.java',
2681 |               qualifier: 'FIL',
2682 |               path: 'src/main/java/Example.java',
2683 |             },
2684 |             project: {
2685 |               key: 'com.example:my-project',
2686 |               name: 'My Project',
2687 |               qualifier: 'TRK',
2688 |             },
2689 |             rule: {
2690 |               key: 'java:S2077',
2691 |               name: 'SQL queries should not be vulnerable to injection attacks',
2692 |               securityCategory: 'sql-injection',
2693 |               vulnerabilityProbability: 'HIGH',
2694 |             },
2695 |             status: 'TO_REVIEW',
2696 |             line: 42,
2697 |             message: 'Make sure using this database query is safe.',
2698 |             author: '[email protected]',
2699 |             creationDate: '2023-01-15T10:30:00+0000',
2700 |             updateDate: '2023-01-15T10:30:00+0000',
2701 |             flows: [],
2702 |             canChangeStatus: true,
2703 |           });
2704 |         const response = await handleSonarQubeHotspot('AYg1234567890');
2705 |         expect(response).toBeDefined();
2706 |         expect(response.content).toBeDefined();
2707 |         expect(response.content[0]).toBeDefined();
2708 |         const result = JSON.parse(response.content[0].text);
2709 |         expect(result.key).toBe('AYg1234567890');
2710 |         expect(result.status).toBe('TO_REVIEW');
2711 |         expect(result.rule.securityCategory).toBe('sql-injection');
2712 |         expect(result.canChangeStatus).toBe(true);
2713 |       });
2714 |     });
2715 |     describe('handleSonarQubeUpdateHotspotStatus', () => {
2716 |       it('should update hotspot status', async () => {
2717 |         nock('http://localhost:9000')
2718 |           .post('/api/hotspots/change_status', {
2719 |             hotspot: 'AYg1234567890',
2720 |             status: 'REVIEWED',
2721 |             resolution: 'FIXED',
2722 |             comment: 'Fixed by using parameterized queries',
2723 |           })
2724 |           .matchHeader('authorization', 'Bearer test-token')
2725 |           .reply(200, {});
2726 |         const response = await handleSonarQubeUpdateHotspotStatus({
2727 |           hotspot: 'AYg1234567890',
2728 |           status: 'REVIEWED',
2729 |           resolution: 'FIXED',
2730 |           comment: 'Fixed by using parameterized queries',
2731 |         });
2732 |         expect(response.content[0].text).toContain('Hotspot status updated successfully');
2733 |       });
2734 |       it('should update hotspot status without optional fields', async () => {
2735 |         nock('http://localhost:9000')
2736 |           .post('/api/hotspots/change_status', {
2737 |             hotspot: 'AYg1234567890',
2738 |             status: 'TO_REVIEW',
2739 |           })
2740 |           .matchHeader('authorization', 'Bearer test-token')
2741 |           .reply(200, {});
2742 |         const response = await handleSonarQubeUpdateHotspotStatus({
2743 |           hotspot: 'AYg1234567890',
2744 |           status: 'TO_REVIEW',
2745 |         });
2746 |         expect(response.content[0].text).toContain('Hotspot status updated successfully');
2747 |       });
2748 |     });
2749 |   });
2750 |   describe('Create Default Client', () => {
2751 |     it('should create default client with environment variables', async () => {
2752 |       // Ensure environment variables are set
2753 |       process.env.SONARQUBE_TOKEN = 'test-token';
2754 |       process.env.SONARQUBE_URL = 'http://localhost:9000';
2755 |       // Import module fresh
2756 |       vi.resetModules();
2757 |       const index = await import('../index.js');
2758 |       // Call createDefaultClient - it should not throw
2759 |       expect(() => index.createDefaultClient()).not.toThrow();
2760 |     });
2761 |   });
2762 |   describe('Error Handling Coverage', () => {
2763 |     it('should handle errors in handler functions', async () => {
2764 |       // Import module fresh
2765 |       vi.resetModules();
2766 |       const index = await import('../index.js');
2767 |       // Mock API calls to fail
2768 |       nock('http://localhost:9000')
2769 |         .get('/api/projects/search')
2770 |         .query(true)
2771 |         .reply(500, 'Internal Server Error');
2772 |       // Test error handling
2773 |       await expect(index.handleSonarQubeProjects({})).rejects.toThrow();
2774 |     }, 10000);
2775 |     it('should test parameter mapping with null values', async () => {
2776 |       vi.resetModules();
2777 |       const index = await import('../index.js');
2778 |       // Test mapToSonarQubeParams with various null/undefined values
2779 |       const result = index.mapToSonarQubeParams({
2780 |         project_key: null,
2781 |         projects: undefined,
2782 |         component_keys: null,
2783 |         components: null,
2784 |         on_component_only: false,
2785 |         branch: null,
2786 |         pull_request: undefined,
2787 |         issues: null,
2788 |         severities: null,
2789 |         statuses: null,
2790 |         resolutions: null,
2791 |         resolved: null,
2792 |         types: null,
2793 |         tags: null,
2794 |         rules: null,
2795 |         created_after: null,
2796 |         created_before: null,
2797 |         created_at: null,
2798 |         created_in_last: null,
2799 |         assigned: null,
2800 |         assignees: null,
2801 |         author: null,
2802 |         authors: null,
2803 |         cwe: null,
2804 |         owasp_top10: null,
2805 |         owasp_top10_v2021: null,
2806 |         sans_top25: null,
2807 |         sonarsource_security: null,
2808 |         sonarsource_security_category: null,
2809 |         languages: null,
2810 |         facets: null,
2811 |         facet_mode: null,
2812 |         since_leak_period: null,
2813 |         in_new_code_period: null,
2814 |         s: null,
2815 |         asc: null,
2816 |         additional_fields: null,
2817 |         page: null,
2818 |         page_size: null,
2819 |         clean_code_attribute_categories: null,
2820 |         impact_severities: null,
2821 |         impact_software_qualities: null,
2822 |         issue_statuses: null,
2823 |         severity: null,
2824 |         hotspots: null,
2825 |       });
2826 |       // Verify null values are converted to undefined
2827 |       expect(result.projectKey).toBeUndefined();
2828 |       expect(result.projects).toBeUndefined();
2829 |       expect(result.componentKeys).toBeUndefined();
2830 |       expect(result.components).toBeUndefined();
2831 |       expect(result.onComponentOnly).toBe(false);
2832 |       expect(result.branch).toBeUndefined();
2833 |       expect(result.pullRequest).toBeUndefined();
2834 |     });
2835 |   });
2836 |   describe('MCP Wrapper Functions Coverage', () => {
2837 |     it('should test all MCP wrapper functions', async () => {
2838 |       // Import module fresh
2839 |       vi.resetModules();
2840 |       const index = await import('../index.js');
2841 |       // Mock all API calls
2842 |       nock('http://localhost:9000')
2843 |         .get('/api/projects/search')
2844 |         .query(true)
2845 |         .times(2)
2846 |         .reply(200, {
2847 |           components: [],
2848 |           paging: { pageIndex: 1, pageSize: 100, total: 0 },
2849 |         });
2850 |       nock('http://localhost:9000')
2851 |         .get('/api/metrics/search')
2852 |         .query(true)
2853 |         .times(2)
2854 |         .reply(200, {
2855 |           metrics: [],
2856 |           paging: { pageIndex: 1, pageSize: 100, total: 0 },
2857 |         });
2858 |       nock('http://localhost:9000')
2859 |         .get('/api/issues/search')
2860 |         .query(true)
2861 |         .times(2)
2862 |         .reply(200, {
2863 |           issues: [],
2864 |           components: [],
2865 |           rules: [],
2866 |           paging: { pageIndex: 1, pageSize: 100, total: 0 },
2867 |         });
2868 |       nock('http://localhost:9000')
2869 |         .get('/api/v2/system/health')
2870 |         .times(2)
2871 |         .reply(200, { status: 'GREEN', checkedAt: '2023-12-01T10:00:00Z' });
2872 |       nock('http://localhost:9000')
2873 |         .get('/api/system/status')
2874 |         .times(2)
2875 |         .reply(200, { id: '1', version: '10.0', status: 'UP' });
2876 |       nock('http://localhost:9000').get('/api/system/ping').times(2).reply(200, 'pong');
2877 |       nock('http://localhost:9000')
2878 |         .get('/api/measures/component')
2879 |         .query(true)
2880 |         .times(2)
2881 |         .reply(200, {
2882 |           component: { key: 'test', measures: [] },
2883 |           metrics: [],
2884 |         });
2885 |       nock('http://localhost:9000')
2886 |         .get('/api/measures/component')
2887 |         .query(true)
2888 |         .times(4)
2889 |         .reply(200, {
2890 |           component: { key: 'test', measures: [] },
2891 |           metrics: [],
2892 |         });
2893 |       nock('http://localhost:9000')
2894 |         .get('/api/measures/search_history')
2895 |         .query(true)
2896 |         .times(2)
2897 |         .reply(200, {
2898 |           measures: [],
2899 |           paging: { pageIndex: 1, pageSize: 100, total: 0 },
2900 |         });
2901 |       nock('http://localhost:9000').get('/api/qualitygates/list').times(2).reply(200, {
2902 |         qualitygates: [],
2903 |         default: 'default',
2904 |       });
2905 |       nock('http://localhost:9000').get('/api/qualitygates/show').query(true).times(2).reply(200, {
2906 |         id: 'test',
2907 |         name: 'Test Gate',
2908 |         conditions: [],
2909 |       });
2910 |       nock('http://localhost:9000')
2911 |         .get('/api/qualitygates/project_status')
2912 |         .query(true)
2913 |         .times(2)
2914 |         .reply(200, {
2915 |           projectStatus: { status: 'OK', conditions: [] },
2916 |         });
2917 |       nock('http://localhost:9000')
2918 |         .get('/api/sources/raw')
2919 |         .query(true)
2920 |         .times(2)
2921 |         .reply(200, 'source code content');
2922 |       nock('http://localhost:9000')
2923 |         .get('/api/sources/scm')
2924 |         .query(true)
2925 |         .times(2)
2926 |         .reply(200, {
2927 |           component: { key: 'test' },
2928 |           sources: {},
2929 |         });
2930 |       nock('http://localhost:9000')
2931 |         .get('/api/hotspots/search')
2932 |         .query(true)
2933 |         .times(2)
2934 |         .reply(200, {
2935 |           hotspots: [],
2936 |           paging: { pageIndex: 1, pageSize: 100, total: 0 },
2937 |         });
2938 |       nock('http://localhost:9000')
2939 |         .get('/api/hotspots/show')
2940 |         .query(true)
2941 |         .times(2)
2942 |         .reply(200, {
2943 |           key: 'hotspot-1',
2944 |           component: 'test',
2945 |           project: 'test',
2946 |           rule: { key: 'test', name: 'Test' },
2947 |           status: 'TO_REVIEW',
2948 |           securityCategory: 'test',
2949 |           vulnerabilityProbability: 'HIGH',
2950 |           line: 1,
2951 |           message: 'Test',
2952 |         });
2953 |       nock('http://localhost:9000').post('/api/hotspots/change_status').times(2).reply(200);
2954 |       // Access the wrapper functions via the module
2955 |       const module = index;
2956 |       // Call all handler functions
2957 |       await module.projectsHandler({});
2958 |       await module.metricsHandler({ page: 1, page_size: 10 });
2959 |       await module.issuesHandler({ project_key: 'test' });
2960 |       await module.healthHandler();
2961 |       await module.statusHandler();
2962 |       await module.pingHandler();
2963 |       await module.componentMeasuresHandler({ component: 'test', metric_keys: ['coverage'] });
2964 |       await module.componentsMeasuresHandler({
2965 |         component_keys: ['test'],
2966 |         metric_keys: ['coverage'],
2967 |       });
2968 |       await module.measuresHistoryHandler({ component: 'test', metrics: ['coverage'] });
2969 |       await module.qualityGatesHandler();
2970 |       await module.qualityGateHandler({ id: 'test' });
2971 |       await module.qualityGateStatusHandler({ project_key: 'test' });
2972 |       await module.sourceCodeHandler({ key: 'test' });
2973 |       await module.scmBlameHandler({ key: 'test' });
2974 |       await module.hotspotsHandler({ project_key: 'test' });
2975 |       await module.hotspotHandler({ hotspot_key: 'hotspot-1' });
2976 |       await module.updateHotspotStatusHandler({ hotspot_key: 'hotspot-1', status: 'REVIEWED' });
2977 | 
2978 |       // Verify handlers were called (basic smoke test)
2979 |       expect(module.projectsHandler).toBeDefined();
2980 |       expect(module.metricsHandler).toBeDefined();
2981 |     });
2982 |   });
2983 |   describe('MCP Wrapper Functions Direct Coverage', () => {
2984 |     beforeEach(() => {
2985 |       process.env.SONARQUBE_TOKEN = 'test-token';
2986 |       process.env.SONARQUBE_URL = 'http://localhost:9000';
2987 |       vi.resetModules();
2988 |     });
2989 |     it('should cover all MCP wrapper functions', async () => {
2990 |       // Set up mocks for all endpoints
2991 |       nock('http://localhost:9000')
2992 |         .get('/api/projects/search')
2993 |         .query(true)
2994 |         .reply(200, { components: [], paging: { pageIndex: 1, pageSize: 100, total: 0 } });
2995 |       nock('http://localhost:9000')
2996 |         .get('/api/metrics/search')
2997 |         .query(true)
2998 |         .reply(200, { metrics: [], total: 0 });
2999 |       nock('http://localhost:9000')
3000 |         .get('/api/issues/search')
3001 |         .query(true)
3002 |         .reply(200, { issues: [], total: 0, paging: { pageIndex: 1, pageSize: 100, total: 0 } });
3003 |       nock('http://localhost:9000')
3004 |         .get('/api/v2/system/health')
3005 |         .reply(200, { status: 'GREEN', checkedAt: '2023-12-01T10:00:00Z' });
3006 |       nock('http://localhost:9000')
3007 |         .get('/api/system/status')
3008 |         .reply(200, { status: 'UP', version: '10.0' });
3009 |       nock('http://localhost:9000').get('/api/system/ping').reply(200, 'pong');
3010 |       nock('http://localhost:9000')
3011 |         .get('/api/measures/component')
3012 |         .query(true)
3013 |         .times(3) // Allow multiple calls
3014 |         .reply(200, { component: { key: 'test', measures: [] }, metrics: [] });
3015 |       nock('http://localhost:9000')
3016 |         .get('/api/measures/search_history')
3017 |         .query(true)
3018 |         .reply(200, { measures: [] });
3019 |       nock('http://localhost:9000').get('/api/qualitygates/list').reply(200, { qualitygates: [] });
3020 |       nock('http://localhost:9000')
3021 |         .get('/api/qualitygates/show')
3022 |         .query(true)
3023 |         .reply(200, { id: 'test', name: 'Test', conditions: [] });
3024 |       nock('http://localhost:9000')
3025 |         .get('/api/qualitygates/project_status')
3026 |         .query(true)
3027 |         .reply(200, { projectStatus: { status: 'OK' } });
3028 |       nock('http://localhost:9000').get('/api/sources/raw').query(true).reply(200, 'source code');
3029 |       nock('http://localhost:9000')
3030 |         .get('/api/sources/scm')
3031 |         .query(true)
3032 |         .reply(200, { component: { key: 'test' }, sources: {} });
3033 |       nock('http://localhost:9000')
3034 |         .get('/api/hotspots/search')
3035 |         .query(true)
3036 |         .reply(200, { hotspots: [], paging: { pageIndex: 1, pageSize: 100, total: 0 } });
3037 |       nock('http://localhost:9000')
3038 |         .get('/api/hotspots/show')
3039 |         .query(true)
3040 |         .reply(200, {
3041 |           key: 'test-hotspot',
3042 |           component: 'test',
3043 |           project: 'test',
3044 |           rule: { key: 'test', name: 'Test' },
3045 |           status: 'TO_REVIEW',
3046 |           securityCategory: 'test',
3047 |           vulnerabilityProbability: 'HIGH',
3048 |         });
3049 |       nock('http://localhost:9000').post('/api/hotspots/change_status').reply(200);
3050 |       // Mock issue resolution endpoints
3051 |       nock('http://localhost:9000').post('/api/issues/add_comment').times(8).reply(200, {});
3052 |       nock('http://localhost:9000')
3053 |         .post('/api/issues/do_transition')
3054 |         .times(8) // 4 individual calls + 4 from bulk operations (2 issues each)
3055 |         .reply(200, {
3056 |           issue: { key: 'test-issue', status: 'RESOLVED' },
3057 |           components: [],
3058 |           rules: [],
3059 |           users: [],
3060 |         });
3061 |       // Import and call all MCP wrapper functions
3062 |       const index = await import('../index.js');
3063 |       // Test all wrapper functions
3064 |       await index.projectsMcpHandler({});
3065 |       await index.metricsMcpHandler({ page: 1, page_size: 10 });
3066 |       await index.issuesMcpHandler({ project_key: 'test' });
3067 |       await index.healthMcpHandler();
3068 |       await index.statusMcpHandler();
3069 |       await index.pingMcpHandler();
3070 |       await index.componentMeasuresMcpHandler({ component: 'test', metric_keys: ['coverage'] });
3071 |       await index.componentsMeasuresMcpHandler({
3072 |         component_keys: ['test'],
3073 |         metric_keys: ['coverage'],
3074 |       });
3075 |       await index.measuresHistoryMcpHandler({ component: 'test', metrics: ['coverage'] });
3076 |       await index.qualityGatesMcpHandler();
3077 |       await index.qualityGateMcpHandler({ id: 'test' });
3078 |       await index.qualityGateStatusMcpHandler({ project_key: 'test' });
3079 |       await index.sourceCodeMcpHandler({ key: 'test' });
3080 |       await index.scmBlameMcpHandler({ key: 'test' });
3081 |       await index.hotspotsMcpHandler({ project_key: 'test' });
3082 |       await index.hotspotMcpHandler({ hotspot_key: 'test-hotspot' });
3083 |       await index.updateHotspotStatusMcpHandler({
3084 |         hotspot_key: 'test-hotspot',
3085 |         status: 'REVIEWED',
3086 |       });
3087 |       // Test new issue resolution MCP handlers
3088 |       await index.markIssueFalsePositiveMcpHandler({
3089 |         issue_key: 'ISSUE-123',
3090 |         comment: 'Test comment',
3091 |       });
3092 |       await index.markIssueWontFixMcpHandler({
3093 |         issue_key: 'ISSUE-456',
3094 |         comment: 'Test comment',
3095 |       });
3096 |       await index.markIssuesFalsePositiveMcpHandler({
3097 |         issue_keys: ['ISSUE-123', 'ISSUE-124'],
3098 |         comment: 'Bulk comment',
3099 |       });
3100 |       await index.markIssuesWontFixMcpHandler({
3101 |         issue_keys: ['ISSUE-456', 'ISSUE-457'],
3102 |         comment: 'Bulk comment',
3103 |       });
3104 | 
3105 |       // Verify handlers exist
3106 |       expect(index.projectsHandler).toBeDefined();
3107 |       expect(index.metricsHandler).toBeDefined();
3108 |     });
3109 |   });
3110 | });
3111 | 
```
Page 11/11FirstPrevNextLast