diff --git a/tests/integration/specs/api/api.test.ts b/tests/integration/specs/api/api.test.ts index 230c6df1d..166dc37aa 100644 --- a/tests/integration/specs/api/api.test.ts +++ b/tests/integration/specs/api/api.test.ts @@ -46,28 +46,27 @@ describe('api', () => { { timeout: 15000 }, ); - it( - 'exits with code 2 for an invalid HTTP method', - async () => { - harness.withAuth('http://localhost:19999', 'fake-token'); - - const result = await harness.run('api trace /api/system/status'); - - expect(result.exitCode).toBe(2); - expect(result.stdout + result.stderr).toContain("Invalid HTTP method 'trace'"); - }, - { timeout: 15000 }, - ); - - it( - 'exits with code 2 when endpoint does not start with /', - async () => { + it.each([ + ['for an invalid HTTP method', 'api trace /api/system/status', "Invalid HTTP method 'trace'"], + [ + 'when endpoint does not start with /', + 'api get api/system/status', + "Endpoint must start with '/'", + ], + [ + 'when --data is not valid JSON', + 'api post /api/system/status --data not-json', + '--data must be valid JSON', + ], + ])( + 'exits with code 2 %s', + async (_, command, expectedError) => { harness.withAuth('http://localhost:19999', 'fake-token'); - const result = await harness.run('api get api/system/status'); + const result = await harness.run(command); expect(result.exitCode).toBe(2); - expect(result.stdout + result.stderr).toContain("Endpoint must start with '/'"); + expect(result.stdout + result.stderr).toContain(expectedError); }, { timeout: 15000 }, ); @@ -98,19 +97,6 @@ describe('api', () => { { timeout: 15000 }, ); - it( - 'exits with code 2 when --data is not valid JSON', - async () => { - harness.withAuth('http://localhost:19999', 'fake-token'); - - const result = await harness.run('api post /api/system/status --data not-json'); - - expect(result.exitCode).toBe(2); - expect(result.stdout + result.stderr).toContain('--data must be valid JSON'); - }, - { timeout: 15000 }, - ); - it( 'returns response body from a successful GET request', async () => { diff --git a/tests/integration/specs/list/list-issues.test.ts b/tests/integration/specs/list/list-issues.test.ts index d3619fb81..5946a34e7 100644 --- a/tests/integration/specs/list/list-issues.test.ts +++ b/tests/integration/specs/list/list-issues.test.ts @@ -711,16 +711,32 @@ describe('list issues — argument validation', () => { { timeout: 15000 }, ); - it( - 'exits with code 2 when --page-size is greater than 500', - async () => { + it.each([ + [ + '--page-size is greater than 500', + 'list issues --project my-project --page-size 501', + 'Invalid --page-size', + ], + [ + '--severities is not a recognised value', + 'list issues --project my-project --severities UNKNOWN', + 'Invalid severity', + ], + [ + '--statuses is not a recognised value', + 'list issues --project my-project --statuses UNKNOWN', + 'Invalid status(es)', + ], + ])( + 'exits with code 2 when %s', + async (_, command, expectedError) => { // Validation runs inside the handler — auth must pass first harness.withAuth('http://localhost:19999', 'fake-token'); - const result = await harness.run('list issues --project my-project --page-size 501'); + const result = await harness.run(command); expect(result.exitCode).toBe(2); - expect(result.stdout + result.stderr).toContain('Invalid --page-size'); + expect(result.stdout + result.stderr).toContain(expectedError); }, { timeout: 15000 }, ); @@ -741,19 +757,6 @@ describe('list issues — argument validation', () => { { timeout: 15000 }, ); - it( - 'exits with code 2 when --severities is not a recognised value', - async () => { - harness.withAuth('http://localhost:19999', 'fake-token'); - - const result = await harness.run('list issues --project my-project --severities UNKNOWN'); - - expect(result.exitCode).toBe(2); - expect(result.stdout + result.stderr).toContain('Invalid severity'); - }, - { timeout: 15000 }, - ); - it( 'sends impactSeverities param on MQR server', async () => { @@ -865,20 +868,6 @@ describe('list issues — argument validation', () => { { timeout: 15000 }, ); - it( - 'exits with code 2 when --statuses is not a recognised value', - async () => { - // Validation runs inside the handler — auth must pass first - harness.withAuth('http://localhost:19999', 'fake-token'); - - const result = await harness.run('list issues --project my-project --statuses UNKNOWN'); - - expect(result.exitCode).toBe(2); - expect(result.stdout + result.stderr).toContain('Invalid status(es)'); - }, - { timeout: 15000 }, - ); - it('passes multiple statuses to API when --statuses is provided with multiple values', async () => { const server = await harness .newFakeServer() diff --git a/tests/integration/specs/run/mcp.test.ts b/tests/integration/specs/run/mcp.test.ts index 04743d522..1d54a9dac 100644 --- a/tests/integration/specs/run/mcp.test.ts +++ b/tests/integration/specs/run/mcp.test.ts @@ -156,56 +156,25 @@ describe('run mcp', () => { { timeout: 15000 }, ); - it( - 'passes SONARQUBE_DEBUG_ENABLED=true to docker when --debug is set', - async () => { - const server = await harness.newFakeServer().withAuthToken('test-token').start(); - harness.withAuth(server.baseUrl(), 'test-token'); - const fakeBinDir = setupFakeDocker(); - - const result = await harness.run('run mcp --debug', { - extraEnv: { PATH: `${fakeBinDir}${PATH_SEP}${process.env.PATH ?? ''}` }, - }); - - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('-e SONARQUBE_DEBUG_ENABLED'); - expect(result.stdout).toContain('ENV_DEBUG_ENABLED=true'); - }, - { timeout: 15000 }, - ); - - it( - 'passes SONARQUBE_READ_ONLY=true to docker when --read-only is set', - async () => { - const server = await harness.newFakeServer().withAuthToken('test-token').start(); - harness.withAuth(server.baseUrl(), 'test-token'); - const fakeBinDir = setupFakeDocker(); - - const result = await harness.run('run mcp --read-only', { - extraEnv: { PATH: `${fakeBinDir}${PATH_SEP}${process.env.PATH ?? ''}` }, - }); - - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('-e SONARQUBE_READ_ONLY'); - expect(result.stdout).toContain('ENV_READ_ONLY=true'); - }, - { timeout: 15000 }, - ); - - it( - 'passes SONARQUBE_TOOLSETS to docker when --toolsets is set', - async () => { + it.each([ + ['--debug', '-e SONARQUBE_DEBUG_ENABLED', 'ENV_DEBUG_ENABLED=true'], + ['--read-only', '-e SONARQUBE_READ_ONLY', 'ENV_READ_ONLY=true'], + ['--toolsets issues,rules', '-e SONARQUBE_TOOLSETS', 'ENV_TOOLSETS=issues,rules'], + ['--toolsets cag,vortex,issues', '-e SONARQUBE_TOOLSETS', 'ENV_TOOLSETS=cag,vortex,issues'], + ])( + 'passes correct env var to docker for: %s', + async (flag, envFlag, envValue) => { const server = await harness.newFakeServer().withAuthToken('test-token').start(); harness.withAuth(server.baseUrl(), 'test-token'); const fakeBinDir = setupFakeDocker(); - const result = await harness.run('run mcp --toolsets issues,rules', { + const result = await harness.run(`run mcp ${flag}`, { extraEnv: { PATH: `${fakeBinDir}${PATH_SEP}${process.env.PATH ?? ''}` }, }); expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('-e SONARQUBE_TOOLSETS'); - expect(result.stdout).toContain('ENV_TOOLSETS=issues,rules'); + expect(result.stdout).toContain(envFlag); + expect(result.stdout).toContain(envValue); }, { timeout: 15000 }, ); @@ -235,24 +204,6 @@ describe('run mcp', () => { { timeout: 15000 }, ); - it( - 'passes cag/vortex through when explicitly included in --toolsets', - async () => { - const server = await harness.newFakeServer().withAuthToken('test-token').start(); - harness.withAuth(server.baseUrl(), 'test-token'); - const fakeBinDir = setupFakeDocker(); - - const result = await harness.run('run mcp --toolsets cag,vortex,issues', { - extraEnv: { PATH: `${fakeBinDir}${PATH_SEP}${process.env.PATH ?? ''}` }, - }); - - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('-e SONARQUBE_TOOLSETS'); - expect(result.stdout).toContain('ENV_TOOLSETS=cag,vortex,issues'); - }, - { timeout: 15000 }, - ); - it( 'propagates non-zero container exit code to CLI exit code', async () => { diff --git a/tests/unit/commands/integrate/antigravity/health.test.ts b/tests/unit/commands/integrate/antigravity/health.test.ts index c40dce985..a4f2eb924 100644 --- a/tests/unit/commands/integrate/antigravity/health.test.ts +++ b/tests/unit/commands/integrate/antigravity/health.test.ts @@ -64,7 +64,11 @@ describe('checkAntigravitySecretsHookFile', () => { expect(checkAntigravitySecretsHookFile(join(tempDir, 'hooks.json'))).toBe('not_configured'); }); - it('returns configured when sonar-secrets block and script exist (absolute command path)', () => { + it.each([ + ['configured', true, 'view_file'], + ['not_configured', false, 'view_file'], + ['invalid', true, 'edit_file'], + ] as const)('returns %s when enabled=%s and matcher=%s', (expected, enabled, matcher) => { tempDir = mkdtempSync(join(tmpdir(), 'sonar-antigravity-health-')); const scriptPath = join(tempDir, hookScriptName()); writeFileSync(scriptPath, '#!/bin/bash\n'); @@ -72,10 +76,10 @@ describe('checkAntigravitySecretsHookFile', () => { join(tempDir, 'hooks.json'), JSON.stringify({ 'sonar-secrets': { - enabled: true, + enabled, PreToolUse: [ { - matcher: 'view_file', + matcher, hooks: [{ command: formatAntigravityHookCommand(scriptPath) }], }, ], @@ -83,7 +87,7 @@ describe('checkAntigravitySecretsHookFile', () => { }), ); - expect(checkAntigravitySecretsHookFile(join(tempDir, 'hooks.json'))).toBe('configured'); + expect(checkAntigravitySecretsHookFile(join(tempDir, 'hooks.json'))).toBe(expected); }); it('returns configured when the hook command path is relative to hooks.json', () => { @@ -149,54 +153,10 @@ describe('checkAntigravitySecretsHookFile', () => { expect(checkAntigravitySecretsHookFile(join(tempDir, 'hooks.json'))).toBe('invalid'); }); - it('returns not_configured when the sonar-secrets block is disabled', () => { - tempDir = mkdtempSync(join(tmpdir(), 'sonar-antigravity-health-')); - const scriptPath = join(tempDir, hookScriptName()); - writeFileSync(scriptPath, '#!/bin/bash\n'); - writeFileSync( - join(tempDir, 'hooks.json'), - JSON.stringify({ - 'sonar-secrets': { - enabled: false, - PreToolUse: [ - { - matcher: 'view_file', - hooks: [{ command: formatAntigravityHookCommand(scriptPath) }], - }, - ], - }, - }), - ); - - expect(checkAntigravitySecretsHookFile(join(tempDir, 'hooks.json'))).toBe('not_configured'); - }); - it('returns not_configured when the sonar-secrets block is absent', () => { tempDir = mkdtempSync(join(tmpdir(), 'sonar-antigravity-health-')); writeFileSync(join(tempDir, 'hooks.json'), JSON.stringify({ other: { enabled: true } })); expect(checkAntigravitySecretsHookFile(join(tempDir, 'hooks.json'))).toBe('not_configured'); }); - - it('returns invalid when the PreToolUse matcher is not view_file', () => { - tempDir = mkdtempSync(join(tmpdir(), 'sonar-antigravity-health-')); - const scriptPath = join(tempDir, hookScriptName()); - writeFileSync(scriptPath, '#!/bin/bash\n'); - writeFileSync( - join(tempDir, 'hooks.json'), - JSON.stringify({ - 'sonar-secrets': { - enabled: true, - PreToolUse: [ - { - matcher: 'edit_file', - hooks: [{ command: formatAntigravityHookCommand(scriptPath) }], - }, - ], - }, - }), - ); - - expect(checkAntigravitySecretsHookFile(join(tempDir, 'hooks.json'))).toBe('invalid'); - }); }); diff --git a/tests/unit/core/host/connectivity/network-config.test.ts b/tests/unit/core/host/connectivity/network-config.test.ts index 593496570..e125d3dbc 100644 --- a/tests/unit/core/host/connectivity/network-config.test.ts +++ b/tests/unit/core/host/connectivity/network-config.test.ts @@ -347,30 +347,21 @@ describe('buildFetchNetworkOptions', () => { }); describe('noProxy bypass', () => { - it('skips proxy when hostname matches noProxy exactly', () => { + it.each([ + [ + 'hostname matches exactly', + 'sonar.internal.corp.com', + 'https://sonar.internal.corp.com/api', + ], + ['hostname matches suffix', 'corp.com', 'https://sonar.corp.com/api'], + ['noProxy is wildcard *', '*', 'https://sonar.example.com/api'], + ['* in comma-separated list', 'localhost,*', 'https://anything.example.com/api'], + ])('skips proxy when %s', (_, noProxy, url) => { const config = makeConfig({ SONAR_HTTPS_PROXY_URL: 'https://proxy:8080', - SONAR_NO_PROXY: 'sonar.internal.corp.com', + SONAR_NO_PROXY: noProxy, }); - const opts = buildFetchNetworkOptions('https://sonar.internal.corp.com/api', config); - expect(opts.proxy).toBeUndefined(); - }); - - it('skips proxy when hostname matches noProxy suffix', () => { - const config = makeConfig({ - SONAR_HTTPS_PROXY_URL: 'https://proxy:8080', - SONAR_NO_PROXY: 'corp.com', - }); - const opts = buildFetchNetworkOptions('https://sonar.corp.com/api', config); - expect(opts.proxy).toBeUndefined(); - }); - - it('skips proxy when noProxy is wildcard *', () => { - const config = makeConfig({ - SONAR_HTTPS_PROXY_URL: 'https://proxy:8080', - SONAR_NO_PROXY: '*', - }); - const opts = buildFetchNetworkOptions('https://sonar.example.com/api', config); + const opts = buildFetchNetworkOptions(url, config); expect(opts.proxy).toBeUndefined(); }); @@ -421,15 +412,6 @@ describe('buildFetchNetworkOptions', () => { ); }); - it('* as one entry in a comma-separated list matches everything', () => { - const config = makeConfig({ - SONAR_HTTPS_PROXY_URL: 'https://proxy:8080', - SONAR_NO_PROXY: 'localhost,*', - }); - const opts = buildFetchNetworkOptions('https://anything.example.com/api', config); - expect(opts.proxy).toBeUndefined(); - }); - it('port-specific noProxy entry only bypasses matching port', () => { const config = makeConfig({ SONAR_HTTPS_PROXY_URL: 'https://proxy:8080',