diff --git a/src/config.test.ts b/src/config.test.ts index 5409df18..3a16e59a 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -1,112 +1,36 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { exportedForTesting, ONE_HOUR_IN_MS, TEN_MINUTES_IN_MS } from './config.js'; +import { stubDefaultEnvVars } from './testShared.js'; describe('Config', () => { const { Config, parseNumber } = exportedForTesting; - const originalEnv = process.env; - - const defaultEnvVars = { - SERVER: 'https://test-server.com', - SITE_NAME: 'test-site', - PAT_NAME: 'test-pat-name', - PAT_VALUE: 'test-pat-value', - } as const; - beforeEach(() => { vi.resetModules(); - process.env = { - ...originalEnv, - AUTH: undefined, - TRANSPORT: undefined, - HTTP_PORT_ENV_VAR_NAME: undefined, - PORT: undefined, - CUSTOM_PORT: undefined, - CORS_ORIGIN_CONFIG: undefined, - TRUST_PROXY_CONFIG: undefined, - SERVER: undefined, - SITE_NAME: undefined, - PAT_NAME: undefined, - PAT_VALUE: undefined, - JWT_SUB_CLAIM: undefined, - CONNECTED_APP_CLIENT_ID: undefined, - CONNECTED_APP_SECRET_ID: undefined, - CONNECTED_APP_SECRET_VALUE: undefined, - UAT_TENANT_ID: undefined, - UAT_ISSUER: undefined, - UAT_USERNAME_CLAIM: undefined, - UAT_USERNAME_CLAIM_NAME: undefined, - UAT_PRIVATE_KEY: undefined, - UAT_PRIVATE_KEY_PATH: undefined, - UAT_KEY_ID: undefined, - JWT_ADDITIONAL_PAYLOAD: undefined, - DATASOURCE_CREDENTIALS: undefined, - DEFAULT_LOG_LEVEL: undefined, - DISABLE_LOG_MASKING: undefined, - INCLUDE_TOOLS: undefined, - EXCLUDE_TOOLS: undefined, - MAX_REQUEST_TIMEOUT_MS: undefined, - MAX_RESULT_LIMIT: undefined, - MAX_RESULT_LIMITS: undefined, - DISABLE_QUERY_DATASOURCE_VALIDATION_REQUESTS: undefined, - DISABLE_METADATA_API_REQUESTS: undefined, - DISABLE_SESSION_MANAGEMENT: undefined, - ENABLE_SERVER_LOGGING: undefined, - SERVER_LOG_DIRECTORY: undefined, - INCLUDE_PROJECT_IDS: undefined, - INCLUDE_DATASOURCE_IDS: undefined, - INCLUDE_WORKBOOK_IDS: undefined, - INCLUDE_TAGS: undefined, - TABLEAU_SERVER_VERSION_CHECK_INTERVAL_IN_HOURS: undefined, - DANGEROUSLY_DISABLE_OAUTH: undefined, - OAUTH_ISSUER: undefined, - OAUTH_REDIRECT_URI: undefined, - OAUTH_LOCK_SITE: undefined, - OAUTH_JWE_PRIVATE_KEY: undefined, - OAUTH_JWE_PRIVATE_KEY_PATH: undefined, - OAUTH_JWE_PRIVATE_KEY_PASSPHRASE: undefined, - OAUTH_CIMD_DNS_SERVERS: undefined, - OAUTH_ACCESS_TOKEN_TIMEOUT_MS: undefined, - OAUTH_AUTHORIZATION_CODE_TIMEOUT_MS: undefined, - OAUTH_REFRESH_TOKEN_TIMEOUT_MS: undefined, - OAUTH_CLIENT_ID_SECRET_PAIRS: undefined, - }; + vi.unstubAllEnvs(); + stubDefaultEnvVars(); }); afterEach(() => { - process.env = { ...originalEnv }; + vi.unstubAllEnvs(); }); it('should throw error when SERVER is missing', () => { - process.env = { - ...process.env, - SERVER: undefined, - SITE_NAME: 'test-site', - }; + vi.stubEnv('SERVER', undefined); expect(() => new Config()).toThrow('The environment variable SERVER is not set'); }); it('should accept HTTP URLs for SERVER', () => { - process.env = { - ...process.env, - SERVER: 'http://foo.com', - PAT_NAME: 'test-pat-name', - PAT_VALUE: 'test-pat-value', - SITE_NAME: 'test-site', - }; + vi.stubEnv('SERVER', 'http://foo.com'); const config = new Config(); expect(config.server).toBe('http://foo.com'); }); it('should throw error when SERVER is not HTTP/HTTPS', () => { - process.env = { - ...process.env, - SERVER: 'gopher://foo.com', - SITE_NAME: 'test-site', - }; + vi.stubEnv('SERVER', 'gopher://foo.com'); expect(() => new Config()).toThrow( 'The environment variable SERVER must start with "http://" or "https://": gopher://foo.com', @@ -114,11 +38,7 @@ describe('Config', () => { }); it('should throw error when SERVER is not a valid URL', () => { - process.env = { - ...process.env, - SERVER: 'https://', - SITE_NAME: 'test-site', - }; + vi.stubEnv('SERVER', 'https://'); expect(() => new Config()).toThrow( 'The environment variable SERVER is not a valid URL: https:// -- Invalid URL', @@ -126,252 +46,144 @@ describe('Config', () => { }); it('should set siteName to empty string when SITE_NAME is "${user_config.site_name}"', () => { - process.env = { - ...process.env, - SERVER: 'https://test-server.com', - PAT_NAME: 'test-pat-name', - PAT_VALUE: 'test-pat-value', - SITE_NAME: '${user_config.site_name}', - }; + vi.stubEnv('SITE_NAME', '${user_config.site_name}'); const config = new Config(); expect(config.siteName).toBe(''); }); it('should throw error when PAT_NAME is missing', () => { - process.env = { - ...process.env, - SERVER: 'https://test-server.com', - SITE_NAME: 'test-site', - PAT_NAME: undefined, - PAT_VALUE: 'test-pat-value', - }; + vi.stubEnv('PAT_NAME', undefined); expect(() => new Config()).toThrow('The environment variable PAT_NAME is not set'); }); it('should throw error when PAT_VALUE is missing', () => { - process.env = { - ...process.env, - SERVER: 'https://test-server.com', - SITE_NAME: 'test-site', - PAT_NAME: 'test-pat-name', - PAT_VALUE: undefined, - }; + vi.stubEnv('PAT_VALUE', undefined); expect(() => new Config()).toThrow('The environment variable PAT_VALUE is not set'); }); it('should configure PAT authentication when PAT credentials are provided', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); - expect(config.patName).toBe('test-pat-name'); - expect(config.patValue).toBe('test-pat-value'); - expect(config.siteName).toBe('test-site'); + expect(config.patName).toBe('sponge'); + expect(config.patValue).toBe('bob'); + expect(config.siteName).toBe('tc25'); }); it('should set default log level to debug when not specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.defaultLogLevel).toBe('debug'); }); it('should set custom log level when specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - DEFAULT_LOG_LEVEL: 'info', - }; + vi.stubEnv('DEFAULT_LOG_LEVEL', 'info'); const config = new Config(); expect(config.defaultLogLevel).toBe('info'); }); it('should set disableLogMasking to false by default', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.disableLogMasking).toBe(false); }); it('should set disableLogMasking to true when specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - DISABLE_LOG_MASKING: 'true', - }; + vi.stubEnv('DISABLE_LOG_MASKING', 'true'); const config = new Config(); expect(config.disableLogMasking).toBe(true); }); it('should set maxRequestTimeoutMs to the default value when not specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.maxRequestTimeoutMs).toBe(10 * 60 * 1000); }); it('should set maxRequestTimeoutMs to the specified value when specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_REQUEST_TIMEOUT_MS: '123456', - }; + vi.stubEnv('MAX_REQUEST_TIMEOUT_MS', '123456'); const config = new Config(); expect(config.maxRequestTimeoutMs).toBe(123456); }); it('should set maxRequestTimeoutMs to the default value when specified as a non-number', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_REQUEST_TIMEOUT_MS: 'abc', - }; + vi.stubEnv('MAX_REQUEST_TIMEOUT_MS', 'abc'); const config = new Config(); expect(config.maxRequestTimeoutMs).toBe(TEN_MINUTES_IN_MS); }); it('should set maxRequestTimeoutMs to the default value when specified as a negative number', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_REQUEST_TIMEOUT_MS: '-100', - }; + vi.stubEnv('MAX_REQUEST_TIMEOUT_MS', '-100'); const config = new Config(); expect(config.maxRequestTimeoutMs).toBe(TEN_MINUTES_IN_MS); }); it('should set maxRequestTimeoutMs to the default value when specified as a number greater than one hour', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_REQUEST_TIMEOUT_MS: `${ONE_HOUR_IN_MS + 1}`, - }; + vi.stubEnv('MAX_REQUEST_TIMEOUT_MS', `${ONE_HOUR_IN_MS + 1}`); const config = new Config(); expect(config.maxRequestTimeoutMs).toBe(TEN_MINUTES_IN_MS); }); it('should set disableQueryDatasourceValidationRequests to false by default', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.disableQueryDatasourceValidationRequests).toBe(false); }); it('should set disableQueryDatasourceValidationRequests to true when specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - DISABLE_QUERY_DATASOURCE_VALIDATION_REQUESTS: 'true', - }; + vi.stubEnv('DISABLE_QUERY_DATASOURCE_VALIDATION_REQUESTS', 'true'); const config = new Config(); expect(config.disableQueryDatasourceValidationRequests).toBe(true); }); it('should set disableMetadataApiRequests to false by default', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.disableMetadataApiRequests).toBe(false); }); it('should set disableMetadataApiRequests to true when specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - DISABLE_METADATA_API_REQUESTS: 'true', - }; + vi.stubEnv('DISABLE_METADATA_API_REQUESTS', 'true'); const config = new Config(); expect(config.disableMetadataApiRequests).toBe(true); }); it('should set disableSessionManagement to false by default', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.disableSessionManagement).toBe(false); }); it('should set disableMetadataApiRequests to true when specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - DISABLE_SESSION_MANAGEMENT: 'true', - }; + vi.stubEnv('DISABLE_SESSION_MANAGEMENT', 'true'); const config = new Config(); expect(config.disableSessionManagement).toBe(true); }); it('should default transport to stdio when not specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.transport).toBe('stdio'); }); it('should set transport to http when specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - TRANSPORT: 'http', - DANGEROUSLY_DISABLE_OAUTH: 'true', - }; + vi.stubEnv('TRANSPORT', 'http'); + vi.stubEnv('DANGEROUSLY_DISABLE_OAUTH', 'true'); const config = new Config(); expect(config.transport).toBe('http'); }); it('should set tableauServerVersionCheckIntervalInHours to default when not specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - TABLEAU_SERVER_VERSION_CHECK_INTERVAL_IN_HOURS: undefined, - }; - const config = new Config(); expect(config.tableauServerVersionCheckIntervalInHours).toBe(1); }); it('should set tableauServerVersionCheckIntervalInHours to the specified value when specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - TABLEAU_SERVER_VERSION_CHECK_INTERVAL_IN_HOURS: '2', - }; + vi.stubEnv('TABLEAU_SERVER_VERSION_CHECK_INTERVAL_IN_HOURS', '2'); const config = new Config(); expect(config.tableauServerVersionCheckIntervalInHours).toBe(2); @@ -379,209 +191,130 @@ describe('Config', () => { describe('Tool filtering', () => { it('should set empty arrays for includeTools and excludeTools when not specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.includeTools).toEqual([]); expect(config.excludeTools).toEqual([]); }); it('should parse INCLUDE_TOOLS into an array of valid tool names', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_TOOLS: 'query-datasource,get-datasource-metadata', - }; + vi.stubEnv('INCLUDE_TOOLS', 'query-datasource,get-datasource-metadata'); const config = new Config(); expect(config.includeTools).toEqual(['query-datasource', 'get-datasource-metadata']); }); it('should parse INCLUDE_TOOLS into an array of valid tool names when tool group names are used', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_TOOLS: 'query-datasource,workbook', - }; + vi.stubEnv('INCLUDE_TOOLS', 'query-datasource,workbook'); const config = new Config(); expect(config.includeTools).toEqual(['query-datasource', 'list-workbooks', 'get-workbook']); }); it('should parse EXCLUDE_TOOLS into an array of valid tool names', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - EXCLUDE_TOOLS: 'query-datasource', - }; + vi.stubEnv('EXCLUDE_TOOLS', 'query-datasource'); const config = new Config(); expect(config.excludeTools).toEqual(['query-datasource']); }); it('should parse EXCLUDE_TOOLS into an array of valid tool names when tool group names are used', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - EXCLUDE_TOOLS: 'query-datasource,workbook', - }; + vi.stubEnv('EXCLUDE_TOOLS', 'query-datasource,workbook'); const config = new Config(); expect(config.excludeTools).toEqual(['query-datasource', 'list-workbooks', 'get-workbook']); }); it('should filter out invalid tool names from INCLUDE_TOOLS', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_TOOLS: 'query-datasource,order-hamburgers', - }; + vi.stubEnv('INCLUDE_TOOLS', 'query-datasource,order-hamburgers'); const config = new Config(); expect(config.includeTools).toEqual(['query-datasource']); }); it('should filter out invalid tool names from EXCLUDE_TOOLS', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - EXCLUDE_TOOLS: 'query-datasource,order-hamburgers', - }; + vi.stubEnv('EXCLUDE_TOOLS', 'query-datasource,order-hamburgers'); const config = new Config(); expect(config.excludeTools).toEqual(['query-datasource']); }); it('should throw error when both INCLUDE_TOOLS and EXCLUDE_TOOLS are specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_TOOLS: 'query-datasource', - EXCLUDE_TOOLS: 'get-datasource-metadata', - }; + vi.stubEnv('INCLUDE_TOOLS', 'query-datasource'); + vi.stubEnv('EXCLUDE_TOOLS', 'get-datasource-metadata'); expect(() => new Config()).toThrow('Cannot include and exclude tools simultaneously'); }); it('should throw error when both INCLUDE_TOOLS and EXCLUDE_TOOLS are specified with tool group names', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_TOOLS: 'datasource', - EXCLUDE_TOOLS: 'workbook', - }; + vi.stubEnv('INCLUDE_TOOLS', 'datasource'); + vi.stubEnv('EXCLUDE_TOOLS', 'workbook'); + expect(() => new Config()).toThrow('Cannot include and exclude tools simultaneously'); }); }); describe('HTTP server config parsing', () => { it('should set sslKey to default when SSL_KEY is not set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.sslKey).toBe(''); }); it('should set sslKey to the specified value when SSL_KEY is set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - SSL_KEY: 'path/to/ssl-key.pem', - }; + vi.stubEnv('SSL_KEY', 'path/to/ssl-key.pem'); const config = new Config(); expect(config.sslKey).toBe('path/to/ssl-key.pem'); }); it('should set sslCert to default when SSL_CERT is not set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.sslCert).toBe(''); }); it('should set sslCert to the specified value when SSL_CERT is set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - SSL_CERT: 'path/to/ssl-cert.pem', - }; + vi.stubEnv('SSL_CERT', 'path/to/ssl-cert.pem'); const config = new Config(); expect(config.sslCert).toBe('path/to/ssl-cert.pem'); }); it('should set httpPort to default when HTTP_PORT_ENV_VAR_NAME and PORT are not set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.httpPort).toBe(3927); }); it('should set httpPort to the value of PORT when set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - PORT: '8080', - }; + vi.stubEnv('PORT', '8080'); const config = new Config(); expect(config.httpPort).toBe(8080); }); it('should set httpPort to the value of the environment variable specified by HTTP_PORT_ENV_VAR_NAME when set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - HTTP_PORT_ENV_VAR_NAME: 'CUSTOM_PORT', - CUSTOM_PORT: '41664', - }; + vi.stubEnv('HTTP_PORT_ENV_VAR_NAME', 'CUSTOM_PORT'); + vi.stubEnv('CUSTOM_PORT', '41664'); const config = new Config(); expect(config.httpPort).toBe(41664); }); it('should set httpPort to default when HTTP_PORT_ENV_VAR_NAME is set and custom port is not set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - HTTP_PORT_ENV_VAR_NAME: 'CUSTOM_PORT', - }; + vi.stubEnv('HTTP_PORT_ENV_VAR_NAME', 'CUSTOM_PORT'); const config = new Config(); expect(config.httpPort).toBe(3927); }); it('should set httpPort to default when PORT is set to an invalid value', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - PORT: 'invalid', - }; + vi.stubEnv('PORT', 'invalid'); const config = new Config(); expect(config.httpPort).toBe(3927); }); it('should set httpPort to default when HTTP_PORT_ENV_VAR_NAME is set and custom port is invalid', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - HTTP_PORT_ENV_VAR_NAME: 'CUSTOM_PORT', - CUSTOM_PORT: 'invalid', - }; + vi.stubEnv('HTTP_PORT_ENV_VAR_NAME', 'CUSTOM_PORT'); + vi.stubEnv('CUSTOM_PORT', 'invalid'); const config = new Config(); expect(config.httpPort).toBe(3927); @@ -590,76 +323,47 @@ describe('Config', () => { describe('CORS origin config parsing', () => { it('should set corsOriginConfig to true when CORS_ORIGIN_CONFIG is not set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.corsOriginConfig).toBe(true); }); it('should set corsOriginConfig to true when CORS_ORIGIN_CONFIG is "true"', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - CORS_ORIGIN_CONFIG: 'true', - }; + vi.stubEnv('CORS_ORIGIN_CONFIG', 'true'); const config = new Config(); expect(config.corsOriginConfig).toBe(true); }); it('should set corsOriginConfig to "*" when CORS_ORIGIN_CONFIG is "*"', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - CORS_ORIGIN_CONFIG: '*', - }; + vi.stubEnv('CORS_ORIGIN_CONFIG', '*'); const config = new Config(); expect(config.corsOriginConfig).toBe('*'); }); it('should set corsOriginConfig to false when CORS_ORIGIN_CONFIG is "false"', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - CORS_ORIGIN_CONFIG: 'false', - }; + vi.stubEnv('CORS_ORIGIN_CONFIG', 'false'); const config = new Config(); expect(config.corsOriginConfig).toBe(false); }); it('should set corsOriginConfig to the specified origin when CORS_ORIGIN_CONFIG is a valid URL', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - CORS_ORIGIN_CONFIG: 'https://example.com:8080', - }; + vi.stubEnv('CORS_ORIGIN_CONFIG', 'https://example.com:8080'); const config = new Config(); expect(config.corsOriginConfig).toBe('https://example.com:8080'); }); it('should set corsOriginConfig to the specified origins when CORS_ORIGIN_CONFIG is an array of URLs', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - CORS_ORIGIN_CONFIG: '["https://example.com", "https://example.org"]', - }; + vi.stubEnv('CORS_ORIGIN_CONFIG', '["https://example.com", "https://example.org"]'); const config = new Config(); expect(config.corsOriginConfig).toEqual(['https://example.com', 'https://example.org']); }); it('should throw error when CORS_ORIGIN_CONFIG is not a valid URL', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - CORS_ORIGIN_CONFIG: 'invalid', - }; + vi.stubEnv('CORS_ORIGIN_CONFIG', 'invalid'); expect(() => new Config()).toThrow( 'The environment variable CORS_ORIGIN_CONFIG is not a valid URL: invalid', @@ -667,11 +371,7 @@ describe('Config', () => { }); it('should throw error when CORS_ORIGIN_CONFIG is not a valid array of URLs', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - CORS_ORIGIN_CONFIG: '["https://example.com", "invalid"]', - }; + vi.stubEnv('CORS_ORIGIN_CONFIG', '["https://example.com", "invalid"]'); expect(() => new Config()).toThrow( 'The environment variable CORS_ORIGIN_CONFIG is not a valid array of URLs: ["https://example.com", "invalid"]', @@ -681,54 +381,33 @@ describe('Config', () => { describe('Trust proxy config parsing', () => { it('should set trustProxyConfig to null when TRUST_PROXY_CONFIG is not set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.trustProxyConfig).toBe(null); }); it('should set trustProxyConfig to true when TRUST_PROXY_CONFIG is "true"', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - TRUST_PROXY_CONFIG: 'true', - }; + vi.stubEnv('TRUST_PROXY_CONFIG', 'true'); const config = new Config(); expect(config.trustProxyConfig).toBe(true); }); it('should set trustProxyConfig to false when TRUST_PROXY_CONFIG is "false"', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - TRUST_PROXY_CONFIG: 'false', - }; + vi.stubEnv('TRUST_PROXY_CONFIG', 'false'); const config = new Config(); expect(config.trustProxyConfig).toBe(false); }); it('should set trustProxyConfig to the specified number when TRUST_PROXY_CONFIG is a valid number', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - TRUST_PROXY_CONFIG: '1', - }; + vi.stubEnv('TRUST_PROXY_CONFIG', '1'); const config = new Config(); expect(config.trustProxyConfig).toBe(1); }); it('should set trustProxyConfig to the specified string when TRUST_PROXY_CONFIG is a valid string', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - TRUST_PROXY_CONFIG: 'loopback, linklocal, uniquelocal', - }; + vi.stubEnv('TRUST_PROXY_CONFIG', 'loopback, linklocal, uniquelocal'); const config = new Config(); expect(config.trustProxyConfig).toBe('loopback, linklocal, uniquelocal'); @@ -736,21 +415,19 @@ describe('Config', () => { }); describe('Connected App config parsing', () => { - const defaultDirectTrustEnvVars = { - ...defaultEnvVars, - AUTH: 'direct-trust', - JWT_SUB_CLAIM: 'test-jwt-sub-claim', - CONNECTED_APP_CLIENT_ID: 'test-client-id', - CONNECTED_APP_SECRET_ID: 'test-secret-id', - CONNECTED_APP_SECRET_VALUE: 'test-secret-value', - } as const; + function stubDefaultDirectTrustEnvVars(): void { + vi.stubEnv('AUTH', 'direct-trust'); + vi.stubEnv('JWT_SUB_CLAIM', 'test-jwt-sub-claim'); + vi.stubEnv('CONNECTED_APP_CLIENT_ID', 'test-client-id'); + vi.stubEnv('CONNECTED_APP_SECRET_ID', 'test-secret-id'); + vi.stubEnv('CONNECTED_APP_SECRET_VALUE', 'test-secret-value'); + } - it('should configure direct-trust authentication when all required variables are provided', () => { - process.env = { - ...process.env, - ...defaultDirectTrustEnvVars, - }; + beforeEach(() => { + stubDefaultDirectTrustEnvVars(); + }); + it('should configure direct-trust authentication when all required variables are provided', () => { const config = new Config(); expect(config.auth).toBe('direct-trust'); expect(config.jwtUsername).toBe('test-jwt-sub-claim'); @@ -761,32 +438,20 @@ describe('Config', () => { }); it('should set jwtAdditionalPayload to the specified value when JWT_ADDITIONAL_PAYLOAD is set', () => { - process.env = { - ...process.env, - ...defaultDirectTrustEnvVars, - JWT_ADDITIONAL_PAYLOAD: '{"custom":"payload"}', - }; + vi.stubEnv('JWT_ADDITIONAL_PAYLOAD', '{"custom":"payload"}'); const config = new Config(); expect(JSON.parse(config.jwtAdditionalPayload)).toEqual({ custom: 'payload' }); }); it('should throw error when JWT_SUB_CLAIM is missing for direct-trust auth', () => { - process.env = { - ...process.env, - ...defaultDirectTrustEnvVars, - JWT_SUB_CLAIM: undefined, - }; + vi.stubEnv('JWT_SUB_CLAIM', undefined); expect(() => new Config()).toThrow('The environment variable JWT_SUB_CLAIM is not set'); }); it('should throw error when CONNECTED_APP_CLIENT_ID is missing for direct-trust auth', () => { - process.env = { - ...process.env, - ...defaultDirectTrustEnvVars, - CONNECTED_APP_CLIENT_ID: undefined, - }; + vi.stubEnv('CONNECTED_APP_CLIENT_ID', undefined); expect(() => new Config()).toThrow( 'The environment variable CONNECTED_APP_CLIENT_ID is not set', @@ -794,11 +459,7 @@ describe('Config', () => { }); it('should throw error when CONNECTED_APP_SECRET_ID is missing for direct-trust auth', () => { - process.env = { - ...process.env, - ...defaultDirectTrustEnvVars, - CONNECTED_APP_SECRET_ID: undefined, - }; + vi.stubEnv('CONNECTED_APP_SECRET_ID', undefined); expect(() => new Config()).toThrow( 'The environment variable CONNECTED_APP_SECRET_ID is not set', @@ -806,11 +467,7 @@ describe('Config', () => { }); it('should throw error when CONNECTED_APP_SECRET_VALUE is missing for direct-trust auth', () => { - process.env = { - ...process.env, - ...defaultDirectTrustEnvVars, - CONNECTED_APP_SECRET_VALUE: undefined, - }; + vi.stubEnv('CONNECTED_APP_SECRET_VALUE', undefined); expect(() => new Config()).toThrow( 'The environment variable CONNECTED_APP_SECRET_VALUE is not set', @@ -818,12 +475,8 @@ describe('Config', () => { }); it('should allow PAT_NAME and PAT_VALUE to be empty when AUTH is "direct-trust"', () => { - process.env = { - ...process.env, - ...defaultDirectTrustEnvVars, - PAT_NAME: undefined, - PAT_VALUE: undefined, - }; + vi.stubEnv('PAT_NAME', undefined); + vi.stubEnv('PAT_VALUE', undefined); const config = new Config(); expect(config.patName).toBe(''); @@ -831,11 +484,12 @@ describe('Config', () => { }); it('should allow all direct-trust fields to be empty when AUTH is not "direct-trust"', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - AUTH: 'pat', - }; + vi.stubEnv('AUTH', 'pat'); + vi.stubEnv('JWT_SUB_CLAIM', undefined); + vi.stubEnv('CONNECTED_APP_CLIENT_ID', undefined); + vi.stubEnv('CONNECTED_APP_SECRET_ID', undefined); + vi.stubEnv('CONNECTED_APP_SECRET_VALUE', undefined); + vi.stubEnv('JWT_ADDITIONAL_PAYLOAD', undefined); const config = new Config(); expect(config.auth).toBe('pat'); @@ -848,22 +502,20 @@ describe('Config', () => { }); describe('UAT configuration config parsing', () => { - const defaultUatEnvVars = { - ...defaultEnvVars, - AUTH: 'uat', - UAT_TENANT_ID: 'test-tenant-id', - UAT_ISSUER: 'test-issuer', - UAT_USERNAME_CLAIM: 'test-username', - UAT_PRIVATE_KEY: 'test-private-key', - UAT_KEY_ID: 'test-key-id', - } as const; + function stubDefaultUatEnvVars(): void { + vi.stubEnv('AUTH', 'uat'); + vi.stubEnv('UAT_TENANT_ID', 'test-tenant-id'); + vi.stubEnv('UAT_ISSUER', 'test-issuer'); + vi.stubEnv('UAT_USERNAME_CLAIM', 'test-username'); + vi.stubEnv('UAT_PRIVATE_KEY', 'test-private-key'); + vi.stubEnv('UAT_KEY_ID', 'test-key-id'); + } - it('should configure uat authentication when all required variables are provided', () => { - process.env = { - ...process.env, - ...defaultUatEnvVars, - }; + beforeEach(() => { + stubDefaultUatEnvVars(); + }); + it('should configure uat authentication when all required variables are provided', () => { const config = new Config(); expect(config.auth).toBe('uat'); expect(config.uatTenantId).toBe('test-tenant-id'); @@ -875,55 +527,35 @@ describe('Config', () => { }); it('should fall back to JWT_SUB_CLAIM when UAT_USERNAME_CLAIM is not set', () => { - process.env = { - ...process.env, - ...defaultUatEnvVars, - UAT_USERNAME_CLAIM: undefined, - JWT_SUB_CLAIM: 'test-jwt-sub-claim', - }; + vi.stubEnv('UAT_USERNAME_CLAIM', undefined); + vi.stubEnv('JWT_SUB_CLAIM', 'test-jwt-sub-claim'); const config = new Config(); expect(config.jwtUsername).toBe('test-jwt-sub-claim'); }); it('should set uatUsernameClaimName to the specified value when UAT_USERNAME_CLAIM_NAME is set', () => { - process.env = { - ...process.env, - ...defaultUatEnvVars, - UAT_USERNAME_CLAIM_NAME: 'test-username-claim-name', - }; + vi.stubEnv('UAT_USERNAME_CLAIM_NAME', 'test-username-claim-name'); const config = new Config(); expect(config.uatUsernameClaimName).toBe('test-username-claim-name'); }); it('should throw error when UAT_TENANT_ID is missing', () => { - process.env = { - ...process.env, - ...defaultUatEnvVars, - UAT_TENANT_ID: undefined, - }; + vi.stubEnv('UAT_TENANT_ID', undefined); expect(() => new Config()).toThrow('The environment variable UAT_TENANT_ID is not set'); }); it('should throw error when UAT_ISSUER is missing', () => { - process.env = { - ...process.env, - ...defaultUatEnvVars, - UAT_ISSUER: undefined, - }; + vi.stubEnv('UAT_ISSUER', undefined); expect(() => new Config()).toThrow('The environment variable UAT_ISSUER is not set'); }); it('should throw error when UAT_USERNAME_CLAIM is missing and JWT_SUB_CLAIM is not set', () => { - process.env = { - ...process.env, - ...defaultUatEnvVars, - UAT_USERNAME_CLAIM: undefined, - JWT_SUB_CLAIM: undefined, - }; + vi.stubEnv('UAT_USERNAME_CLAIM', undefined); + vi.stubEnv('JWT_SUB_CLAIM', undefined); expect(() => new Config()).toThrow( 'One of the environment variables: UAT_USERNAME_CLAIM or JWT_SUB_CLAIM must be set', @@ -931,12 +563,8 @@ describe('Config', () => { }); it('should throw error when UAT_PRIVATE_KEY and UAT_PRIVATE_KEY_PATH is not set', () => { - process.env = { - ...process.env, - ...defaultUatEnvVars, - UAT_PRIVATE_KEY: undefined, - UAT_PRIVATE_KEY_PATH: undefined, - }; + vi.stubEnv('UAT_PRIVATE_KEY', undefined); + vi.stubEnv('UAT_PRIVATE_KEY_PATH', undefined); expect(() => new Config()).toThrow( 'One of the environment variables: UAT_PRIVATE_KEY_PATH or UAT_PRIVATE_KEY must be set', @@ -944,12 +572,8 @@ describe('Config', () => { }); it('should throw error when UAT_PRIVATE_KEY and UAT_PRIVATE_KEY_PATH are both set', () => { - process.env = { - ...process.env, - ...defaultUatEnvVars, - UAT_PRIVATE_KEY: 'hamburgers', - UAT_PRIVATE_KEY_PATH: 'hotdogs', - }; + vi.stubEnv('UAT_PRIVATE_KEY', 'hamburgers'); + vi.stubEnv('UAT_PRIVATE_KEY_PATH', 'hotdogs'); expect(() => new Config()).toThrow( 'Only one of the environment variables: UAT_PRIVATE_KEY or UAT_PRIVATE_KEY_PATH must be set', @@ -959,11 +583,6 @@ describe('Config', () => { describe('Bounded context parsing', () => { it('should set boundedContext to null sets when no project, datasource, or workbook IDs are provided', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.boundedContext).toEqual({ projectIds: null, @@ -974,14 +593,10 @@ describe('Config', () => { }); it('should set boundedContext to the specified tags and project, datasource, and workbook IDs when provided', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_PROJECT_IDS: ' 123, 456, 123 ', // spacing is intentional here to test trimming - INCLUDE_DATASOURCE_IDS: '789,101', - INCLUDE_WORKBOOK_IDS: '112,113', - INCLUDE_TAGS: 'tag1,tag2', - }; + vi.stubEnv('INCLUDE_PROJECT_IDS', ' 123, 456, 123 '); // spacing is intentional here to test trimming + vi.stubEnv('INCLUDE_DATASOURCE_IDS', '789,101'); + vi.stubEnv('INCLUDE_WORKBOOK_IDS', '112,113'); + vi.stubEnv('INCLUDE_TAGS', 'tag1,tag2'); const config = new Config(); expect(config.boundedContext).toEqual({ @@ -993,11 +608,7 @@ describe('Config', () => { }); it('should throw error when INCLUDE_PROJECT_IDS is set to an empty string', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_PROJECT_IDS: '', - }; + vi.stubEnv('INCLUDE_PROJECT_IDS', ''); expect(() => new Config()).toThrow( 'When set, the environment variable INCLUDE_PROJECT_IDS must have at least one value', @@ -1005,11 +616,7 @@ describe('Config', () => { }); it('should throw error when INCLUDE_DATASOURCE_IDS is set to an empty string', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_DATASOURCE_IDS: '', - }; + vi.stubEnv('INCLUDE_DATASOURCE_IDS', ''); expect(() => new Config()).toThrow( 'When set, the environment variable INCLUDE_DATASOURCE_IDS must have at least one value', @@ -1017,11 +624,7 @@ describe('Config', () => { }); it('should throw error when INCLUDE_WORKBOOK_IDS is set to an empty string', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_WORKBOOK_IDS: '', - }; + vi.stubEnv('INCLUDE_WORKBOOK_IDS', ''); expect(() => new Config()).toThrow( 'When set, the environment variable INCLUDE_WORKBOOK_IDS must have at least one value', @@ -1029,11 +632,7 @@ describe('Config', () => { }); it('should throw error when INCLUDE_TAGS is set to an empty string', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - INCLUDE_TAGS: '', - }; + vi.stubEnv('INCLUDE_TAGS', ''); expect(() => new Config()).toThrow( 'When set, the environment variable INCLUDE_TAGS must have at least one value', @@ -1042,11 +641,11 @@ describe('Config', () => { }); describe('OAuth configuration', () => { - const defaultOAuthEnvVars = { - ...defaultEnvVars, - OAUTH_ISSUER: 'https://example.com', - OAUTH_JWE_PRIVATE_KEY_PATH: 'path/to/private.pem', - } as const; + function stubDefaultOAuthEnvVars(): void { + vi.stubEnv('OAUTH_ISSUER', 'https://example.com'); + vi.stubEnv('OAUTH_JWE_PRIVATE_KEY_PATH', 'path/to/private.pem'); + vi.stubEnv('TABLEAU_MCP_TEST', 'true'); + } const defaultOAuthTimeoutMs = { authzCodeTimeoutMs: 10 * 60 * 1000, @@ -1057,22 +656,17 @@ describe('Config', () => { const defaultOAuthConfig = { enabled: true, clientIdSecretPairs: null, - issuer: defaultOAuthEnvVars.OAUTH_ISSUER, - redirectUri: `${defaultOAuthEnvVars.OAUTH_ISSUER}/Callback`, + issuer: 'https://example.com', + redirectUri: 'https://example.com/Callback', lockSite: true, jwePrivateKey: '', - jwePrivateKeyPath: defaultOAuthEnvVars.OAUTH_JWE_PRIVATE_KEY_PATH, + jwePrivateKeyPath: 'path/to/private.pem', jwePrivateKeyPassphrase: undefined, dnsServers: ['1.1.1.1', '1.0.0.1'], ...defaultOAuthTimeoutMs, } as const; it('should default to disabled', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - const config = new Config(); expect(config.oauth).toEqual({ enabled: false, @@ -1089,32 +683,22 @@ describe('Config', () => { }); it('should enable OAuth when OAUTH_ISSUER is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - }; + stubDefaultOAuthEnvVars(); const config = new Config(); expect(config.oauth).toEqual(defaultOAuthConfig); }); it('should disable OAuth when DANGEROUSLY_DISABLE_OAUTH is "true"', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - DANGEROUSLY_DISABLE_OAUTH: 'true', - }; + vi.stubEnv('DANGEROUSLY_DISABLE_OAUTH', 'true'); const config = new Config(); expect(config.oauth.enabled).toEqual(false); }); it('should set redirectUri to the specified value when OAUTH_REDIRECT_URI is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_REDIRECT_URI: 'https://example.com/CustomCallback', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('OAUTH_REDIRECT_URI', 'https://example.com/CustomCallback'); const config = new Config(); expect(config.oauth).toEqual({ @@ -1124,25 +708,19 @@ describe('Config', () => { }); it('should set redirectUri to the default value when OAUTH_REDIRECT_URI is not set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_REDIRECT_URI: '', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('OAUTH_REDIRECT_URI', ''); const config = new Config(); expect(config.oauth).toEqual({ ...defaultOAuthConfig, - redirectUri: `${defaultOAuthEnvVars.OAUTH_ISSUER}/Callback`, + redirectUri: 'https://example.com/Callback', }); }); it('should set lockSite to the specified value when OAUTH_LOCK_SITE is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_LOCK_SITE: 'false', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('OAUTH_LOCK_SITE', 'false'); const config = new Config(); expect(config.oauth).toEqual({ @@ -1152,12 +730,9 @@ describe('Config', () => { }); it('should set jwePrivateKey to the specified value when OAUTH_JWE_PRIVATE_KEY is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_JWE_PRIVATE_KEY: 'hamburgers', - OAUTH_JWE_PRIVATE_KEY_PATH: '', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('OAUTH_JWE_PRIVATE_KEY', 'hamburgers'); + vi.stubEnv('OAUTH_JWE_PRIVATE_KEY_PATH', ''); const config = new Config(); expect(config.oauth).toEqual({ @@ -1169,11 +744,8 @@ describe('Config', () => { }); it('should set authzCodeTimeoutMs to the specified value when OAUTH_AUTHORIZATION_CODE_TIMEOUT_MS is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_AUTHORIZATION_CODE_TIMEOUT_MS: '5678', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('OAUTH_AUTHORIZATION_CODE_TIMEOUT_MS', '5678'); const config = new Config(); expect(config.oauth).toEqual({ @@ -1183,11 +755,8 @@ describe('Config', () => { }); it('should set accessTokenTimeoutMs to the specified value when OAUTH_ACCESS_TOKEN_TIMEOUT_MS is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_ACCESS_TOKEN_TIMEOUT_MS: '1234', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('OAUTH_ACCESS_TOKEN_TIMEOUT_MS', '1234'); const config = new Config(); expect(config.oauth).toEqual({ @@ -1197,23 +766,15 @@ describe('Config', () => { }); it('should set refreshTokenTimeoutMs to the specified value when OAUTH_REFRESH_TOKEN_TIMEOUT_MS is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_REFRESH_TOKEN_TIMEOUT_MS: '1234', - }; + vi.stubEnv('OAUTH_REFRESH_TOKEN_TIMEOUT_MS', '1234'); const config = new Config(); expect(config.oauth.refreshTokenTimeoutMs).toBe(1234); }); it('should throw error when TRANSPORT is "http" and OAUTH_ISSUER is not set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - TRANSPORT: 'http', - OAUTH_ISSUER: undefined, - }; + vi.stubEnv('TRANSPORT', 'http'); + vi.stubEnv('OAUTH_ISSUER', undefined); expect(() => new Config()).toThrow( 'OAUTH_ISSUER must be set when TRANSPORT is "http" unless DANGEROUSLY_DISABLE_OAUTH is "true"', @@ -1221,11 +782,8 @@ describe('Config', () => { }); it('should throw error when OAUTH_JWE_PRIVATE_KEY and OAUTH_JWE_PRIVATE_KEY_PATH is not set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_JWE_PRIVATE_KEY_PATH: '', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('OAUTH_JWE_PRIVATE_KEY_PATH', ''); expect(() => new Config()).toThrow( 'One of the environment variables: OAUTH_JWE_PRIVATE_KEY_PATH or OAUTH_JWE_PRIVATE_KEY must be set', @@ -1233,12 +791,9 @@ describe('Config', () => { }); it('should throw error when OAUTH_JWE_PRIVATE_KEY and OAUTH_JWE_PRIVATE_KEY_PATH are both set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_JWE_PRIVATE_KEY: 'hamburgers', - OAUTH_JWE_PRIVATE_KEY_PATH: 'hotdogs', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('OAUTH_JWE_PRIVATE_KEY', 'hamburgers'); + vi.stubEnv('OAUTH_JWE_PRIVATE_KEY_PATH', 'hotdogs'); expect(() => new Config()).toThrow( 'Only one of the environment variables: OAUTH_JWE_PRIVATE_KEY or OAUTH_JWE_PRIVATE_KEY_PATH must be set', @@ -1246,23 +801,15 @@ describe('Config', () => { }); it('should throw error when AUTH is "oauth" and OAUTH_ISSUER is not set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - AUTH: 'oauth', - OAUTH_ISSUER: '', - }; + vi.stubEnv('AUTH', 'oauth'); + vi.stubEnv('OAUTH_ISSUER', ''); expect(() => new Config()).toThrow('When AUTH is "oauth", OAUTH_ISSUER must be set'); }); it('should throw error when AUTH is "oauth" and DANGEROUSLY_DISABLE_OAUTH is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - AUTH: 'oauth', - DANGEROUSLY_DISABLE_OAUTH: 'true', - }; + vi.stubEnv('AUTH', 'oauth'); + vi.stubEnv('DANGEROUSLY_DISABLE_OAUTH', 'true'); expect(() => new Config()).toThrow( 'When AUTH is "oauth", DANGEROUSLY_DISABLE_OAUTH cannot be "true"', @@ -1270,44 +817,31 @@ describe('Config', () => { }); it('should default transport to "http" when OAUTH_ISSUER is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - TRANSPORT: undefined, - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('TRANSPORT', undefined); const config = new Config(); expect(config.transport).toBe('http'); }); it('should default auth to "oauth" when OAUTH_ISSUER is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - }; - + stubDefaultOAuthEnvVars(); const config = new Config(); expect(config.auth).toBe('oauth'); }); it('should throw error when transport is stdio and auth is "oauth"', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - TRANSPORT: 'stdio', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('TRANSPORT', 'stdio'); expect(() => new Config()).toThrow('TRANSPORT must be "http" when OAUTH_ISSUER is set'); }); it('should allow PAT_NAME and PAT_VALUE to be empty when AUTH is "oauth"', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - PAT_NAME: undefined, - PAT_VALUE: undefined, - AUTH: 'oauth', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('PAT_NAME', undefined); + vi.stubEnv('PAT_VALUE', undefined); + vi.stubEnv('AUTH', 'oauth'); const config = new Config(); expect(config.patName).toBe(''); @@ -1315,23 +849,16 @@ describe('Config', () => { }); it('should allow SITE_NAME to be empty when AUTH is "oauth"', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - AUTH: 'oauth', - SITE_NAME: '', - }; + stubDefaultOAuthEnvVars(); + vi.stubEnv('AUTH', 'oauth'); + vi.stubEnv('SITE_NAME', ''); const config = new Config(); expect(config.siteName).toBe(''); }); it('should set clientIdSecretPairs to the specified value when OAUTH_CLIENT_ID_SECRET_PAIRS is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_CLIENT_ID_SECRET_PAIRS: 'client1:secret1,client2:secret2', - }; + vi.stubEnv('OAUTH_CLIENT_ID_SECRET_PAIRS', 'client1:secret1,client2:secret2'); const config = new Config(); expect(config.oauth.clientIdSecretPairs).toEqual({ @@ -1341,11 +868,7 @@ describe('Config', () => { }); it('should set dnsServers to the specified value when OAUTH_CIMD_DNS_SERVERS is set', () => { - process.env = { - ...process.env, - ...defaultOAuthEnvVars, - OAUTH_CIMD_DNS_SERVERS: '8.8.8.8,8.8.4.4', - }; + vi.stubEnv('OAUTH_CIMD_DNS_SERVERS', '8.8.8.8,8.8.4.4'); const config = new Config(); expect(config.oauth.dnsServers).toEqual(['8.8.8.8', '8.8.4.4']); @@ -1451,96 +974,59 @@ describe('Config', () => { describe('Max results limit parsing', () => { it('should return null when MAX_RESULT_LIMIT and MAX_RESULT_LIMITS are not set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - }; - expect(new Config().getMaxResultLimit('query-datasource')).toBeNull(); }); it('should return the max result limit when MAX_RESULT_LIMITS has a single tool', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_RESULT_LIMITS: 'query-datasource:100', - }; + vi.stubEnv('MAX_RESULT_LIMITS', 'query-datasource:100'); expect(new Config().getMaxResultLimit('query-datasource')).toEqual(100); }); it('should return the max result limit when MAX_RESULT_LIMITS has a single tool group', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_RESULT_LIMITS: 'datasource:200', - }; + vi.stubEnv('MAX_RESULT_LIMITS', 'datasource:200'); expect(new Config().getMaxResultLimit('query-datasource')).toEqual(200); }); it('should return the max result limit for the tool when a tool and a tool group are both specified', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_RESULT_LIMITS: 'query-datasource:100,datasource:200', - }; + vi.stubEnv('MAX_RESULT_LIMITS', 'query-datasource:100,datasource:200'); expect(new Config().getMaxResultLimit('query-datasource')).toEqual(100); expect(new Config().getMaxResultLimit('list-datasources')).toEqual(200); }); it('should fallback to MAX_RESULT_LIMIT when a tool-specific max result limit is not set', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_RESULT_LIMITS: 'query-datasource:100', - MAX_RESULT_LIMIT: '300', - }; + vi.stubEnv('MAX_RESULT_LIMITS', 'query-datasource:100'); + vi.stubEnv('MAX_RESULT_LIMIT', '300'); expect(new Config().getMaxResultLimit('query-datasource')).toEqual(100); expect(new Config().getMaxResultLimit('list-datasources')).toEqual(300); }); it('should return null when MAX_RESULT_LIMITS has a non-number', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_RESULT_LIMITS: 'query-datasource:abc', - }; + vi.stubEnv('MAX_RESULT_LIMITS', 'query-datasource:abc'); const config = new Config(); expect(config.getMaxResultLimit('query-datasource')).toBe(null); }); it('should return null when MAX_RESULT_LIMIT is specified as a non-number', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_RESULT_LIMIT: 'abc', - }; + vi.stubEnv('MAX_RESULT_LIMIT', 'abc'); const config = new Config(); expect(config.getMaxResultLimit('query-datasource')).toBe(null); }); it('should return null when MAX_RESULT_LIMITS has a negative number', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_RESULT_LIMITS: 'query-datasource:-100', - }; + vi.stubEnv('MAX_RESULT_LIMITS', 'query-datasource:-100'); const config = new Config(); expect(config.getMaxResultLimit('query-datasource')).toBe(null); }); it('should return null when MAX_RESULT_LIMIT is specified as a negative number', () => { - process.env = { - ...process.env, - ...defaultEnvVars, - MAX_RESULT_LIMIT: '-100', - }; + vi.stubEnv('MAX_RESULT_LIMIT', '-100'); const config = new Config(); expect(config.getMaxResultLimit('query-datasource')).toBe(null); diff --git a/src/telemetry/init.test.ts b/src/telemetry/init.test.ts index 6c80f4ab..6b435ca0 100644 --- a/src/telemetry/init.test.ts +++ b/src/telemetry/init.test.ts @@ -1,31 +1,23 @@ import { MockInstance } from 'vitest'; +import { stubDefaultEnvVars } from '../testShared.js'; import { initializeTelemetry } from './init.js'; -import { TelemetryConfig } from './types.js'; - const mocks = vi.hoisted(() => ({ - mockGetConfig: vi.fn(), MockNoOpTelemetryProvider: vi.fn(), })); -vi.mock('../config.js', () => ({ - getConfig: mocks.mockGetConfig, -})); - vi.mock('./noop.js', () => ({ NoOpTelemetryProvider: mocks.MockNoOpTelemetryProvider, })); describe('initializeTelemetry', () => { - const defaultTelemetryConfig: TelemetryConfig = { - provider: 'noop', - }; - let consoleErrorSpy: MockInstance; let consoleWarnSpy: MockInstance; beforeEach(() => { vi.clearAllMocks(); + vi.unstubAllEnvs(); + stubDefaultEnvVars(); // Suppress console output consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); @@ -41,23 +33,21 @@ describe('initializeTelemetry', () => { afterEach(() => { consoleErrorSpy.mockRestore(); consoleWarnSpy.mockRestore(); + vi.unstubAllEnvs(); }); // NoOp tests it('returns NoOpTelemetryProvider when provider is "noop"', () => { - mocks.mockGetConfig.mockReturnValue({ - telemetry: { ...defaultTelemetryConfig, provider: 'noop' }, - }); + vi.stubEnv('TELEMETRY_PROVIDER', 'noop'); initializeTelemetry(); expect(mocks.MockNoOpTelemetryProvider).toHaveBeenCalled(); }); - it('returns NoOpTelemetryProvider for unknown provider with warning', () => { - mocks.mockGetConfig.mockReturnValue({ - telemetry: { ...defaultTelemetryConfig, provider: 'unknown-provider' }, - }); + it('returns NoOpTelemetryProvider when provider is "custom" and module path is invalid', () => { + vi.stubEnv('TELEMETRY_PROVIDER', 'custom'); + vi.stubEnv('TELEMETRY_PROVIDER_CONFIG', '{"module":"./invalid-module.js"}'); initializeTelemetry(); diff --git a/src/testSetup.ts b/src/testSetup.ts index e82d2e30..28019c51 100644 --- a/src/testSetup.ts +++ b/src/testSetup.ts @@ -1,10 +1,6 @@ -import { testProductVersion } from './testShared.js'; +import { stubDefaultEnvVars, testProductVersion } from './testShared.js'; -vi.stubEnv('SERVER', 'https://my-tableau-server.com'); -vi.stubEnv('SITE_NAME', 'tc25'); -vi.stubEnv('PAT_NAME', 'sponge'); -vi.stubEnv('PAT_VALUE', 'bob'); -vi.stubEnv('TABLEAU_MCP_TEST', 'true'); +stubDefaultEnvVars(); vi.mock('./server.js', async (importOriginal) => ({ ...(await importOriginal()), diff --git a/src/testShared.ts b/src/testShared.ts index 4f58b6e6..2397ece5 100644 --- a/src/testShared.ts +++ b/src/testShared.ts @@ -4,3 +4,12 @@ export const testProductVersion = { value: '2025.3.0', build: '20253.25.0903.0012', } satisfies ProductVersion; + +export function stubDefaultEnvVars(): void { + vi.stubEnv('SERVER', 'https://my-tableau-server.com'); + vi.stubEnv('SITE_NAME', 'tc25'); + vi.stubEnv('PAT_NAME', 'sponge'); + vi.stubEnv('PAT_VALUE', 'bob'); + vi.stubEnv('TABLEAU_MCP_TEST', 'true'); + vi.stubEnv('PRODUCT_TELEMETRY_ENABLED', 'false'); +} diff --git a/src/tools/getDatasourceMetadata/getDatasourceMetadata.test.ts b/src/tools/getDatasourceMetadata/getDatasourceMetadata.test.ts index ca0de454..b304a03a 100644 --- a/src/tools/getDatasourceMetadata/getDatasourceMetadata.test.ts +++ b/src/tools/getDatasourceMetadata/getDatasourceMetadata.test.ts @@ -2,6 +2,7 @@ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { Err, Ok } from 'ts-results-es'; import { Server } from '../../server.js'; +import { stubDefaultEnvVars } from '../../testShared.js'; import invariant from '../../utils/invariant.js'; import { Provider } from '../../utils/provider.js'; import { getVizqlDataServiceDisabledError } from '../getVizqlDataServiceDisabledError.js'; @@ -190,7 +191,6 @@ const mockListFieldsResponses = vi.hoisted(() => ({ const mocks = vi.hoisted(() => ({ mockReadMetadata: vi.fn(), mockGraphql: vi.fn(), - mockGetConfig: vi.fn(), })); vi.mock('../../restApiInstance.js', () => ({ @@ -206,28 +206,16 @@ vi.mock('../../restApiInstance.js', () => ({ ), })); -vi.mock('../../config.js', () => ({ - getConfig: mocks.mockGetConfig, -})); - describe('getDatasourceMetadataTool', () => { beforeEach(() => { vi.clearAllMocks(); - // Set default config for existing tests + vi.unstubAllEnvs(); + stubDefaultEnvVars(); resetResourceAccessCheckerSingleton(); - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - disableMetadataApiRequests: false, - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); }); it('should create a tool instance with correct properties', () => { @@ -646,20 +634,7 @@ describe('getDatasourceMetadataTool', () => { }); it('should return only readMetadata result when disableMetadataApiRequests is true and readMetadata succeeds', async () => { - // Configure to disable metadata API requests - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - disableMetadataApiRequests: true, - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - }); + vi.stubEnv('DISABLE_METADATA_API_REQUESTS', 'true'); mocks.mockReadMetadata.mockResolvedValue(new Ok(mockReadMetadataResponses.success)); mocks.mockGraphql.mockResolvedValue(mockListFieldsResponses.success); @@ -705,20 +680,7 @@ describe('getDatasourceMetadataTool', () => { }); it('should return error when disableMetadataApiRequests is true and readMetadata fails', async () => { - // Configure to disable metadata API requests - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - disableMetadataApiRequests: true, - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - }); + vi.stubEnv('DISABLE_METADATA_API_REQUESTS', 'true'); const errorMessage = 'ReadMetadata API Error'; mocks.mockReadMetadata.mockRejectedValue(new Error(errorMessage)); @@ -752,18 +714,7 @@ describe('getDatasourceMetadataTool', () => { }); it('should return data source not allowed error when datasource is not allowed', async () => { - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - boundedContext: { - projectIds: null, - datasourceIds: new Set(['some-other-datasource-luid']), - workbookIds: null, - tags: null, - }, - }); + vi.stubEnv('INCLUDE_DATASOURCE_IDS', 'some-other-datasource-luid'); const result = await getToolResult(); expect(result.isError).toBe(true); diff --git a/src/tools/pulse/generateInsightBrief/generatePulseInsightBriefTool.test.ts b/src/tools/pulse/generateInsightBrief/generatePulseInsightBriefTool.test.ts index 6854b274..e8922d75 100644 --- a/src/tools/pulse/generateInsightBrief/generatePulseInsightBriefTool.test.ts +++ b/src/tools/pulse/generateInsightBrief/generatePulseInsightBriefTool.test.ts @@ -3,6 +3,7 @@ import { Err, Ok } from 'ts-results-es'; import { PulseDisabledError } from '../../../sdks/tableau/methods/pulseMethods.js'; import { Server } from '../../../server.js'; +import { stubDefaultEnvVars } from '../../../testShared.js'; import invariant from '../../../utils/invariant.js'; import { Provider } from '../../../utils/provider.js'; import { exportedForTesting as resourceAccessCheckerExportedForTesting } from '../../resourceAccessChecker.js'; @@ -12,7 +13,6 @@ const { resetResourceAccessCheckerSingleton } = resourceAccessCheckerExportedFor const mocks = vi.hoisted(() => ({ mockGeneratePulseInsightBrief: vi.fn(), - mockGetConfig: vi.fn(), })); vi.mock('../../../restApiInstance.js', () => ({ @@ -25,10 +25,6 @@ vi.mock('../../../restApiInstance.js', () => ({ ), })); -vi.mock('../../../config.js', () => ({ - getConfig: mocks.mockGetConfig, -})); - describe('getGeneratePulseInsightBriefTool', () => { const briefRequest = { language: 'LANGUAGE_EN_US' as const, @@ -129,19 +125,13 @@ describe('getGeneratePulseInsightBriefTool', () => { beforeEach(() => { vi.clearAllMocks(); + vi.unstubAllEnvs(); + stubDefaultEnvVars(); resetResourceAccessCheckerSingleton(); - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); }); it('should have correct tool name', () => { @@ -307,18 +297,7 @@ describe('getGeneratePulseInsightBriefTool', () => { ], }; - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - boundedContext: { - projectIds: null, - datasourceIds: new Set([allowedDatasourceId]), - workbookIds: null, - tags: null, - }, - }); + vi.stubEnv('INCLUDE_DATASOURCE_IDS', allowedDatasourceId); mocks.mockGeneratePulseInsightBrief.mockResolvedValue(new Ok(mockBriefResponse)); const result = await getToolResult(twoMetricRequest); @@ -337,18 +316,7 @@ describe('getGeneratePulseInsightBriefTool', () => { }); it('should return an error when all metrics are filtered out', async () => { - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - boundedContext: { - projectIds: null, - datasourceIds: new Set(['ALLOWED-DATASOURCE-ID']), - workbookIds: null, - tags: null, - }, - }); + vi.stubEnv('INCLUDE_DATASOURCE_IDS', 'some-other-datasource-luid'); mocks.mockGeneratePulseInsightBrief.mockResolvedValue(new Ok(mockBriefResponse)); const result = await getToolResult(); diff --git a/src/tools/pulse/generateMetricValueInsightBundle/generatePulseMetricValueInsightBundleTool.test.ts b/src/tools/pulse/generateMetricValueInsightBundle/generatePulseMetricValueInsightBundleTool.test.ts index 29e89f2d..67304083 100644 --- a/src/tools/pulse/generateMetricValueInsightBundle/generatePulseMetricValueInsightBundleTool.test.ts +++ b/src/tools/pulse/generateMetricValueInsightBundle/generatePulseMetricValueInsightBundleTool.test.ts @@ -4,6 +4,7 @@ import { Err, Ok } from 'ts-results-es'; import { PulseDisabledError } from '../../../sdks/tableau/methods/pulseMethods.js'; import { PulseInsightBundleType } from '../../../sdks/tableau/types/pulse.js'; import { Server } from '../../../server.js'; +import { stubDefaultEnvVars } from '../../../testShared.js'; import invariant from '../../../utils/invariant.js'; import { Provider } from '../../../utils/provider.js'; import { exportedForTesting as resourceAccessCheckerExportedForTesting } from '../../resourceAccessChecker.js'; @@ -12,7 +13,6 @@ import { getGeneratePulseMetricValueInsightBundleTool } from './generatePulseMet const { resetResourceAccessCheckerSingleton } = resourceAccessCheckerExportedForTesting; const mocks = vi.hoisted(() => ({ mockGeneratePulseMetricValueInsightBundle: vi.fn(), - mockGetConfig: vi.fn(), })); vi.mock('../../../restApiInstance.js', () => ({ @@ -25,10 +25,6 @@ vi.mock('../../../restApiInstance.js', () => ({ ), })); -vi.mock('../../../config.js', () => ({ - getConfig: mocks.mockGetConfig, -})); - describe('getGeneratePulseMetricValueInsightBundleTool', () => { const bundleRequest = { bundle_request: { @@ -115,21 +111,13 @@ describe('getGeneratePulseMetricValueInsightBundleTool', () => { beforeEach(() => { vi.clearAllMocks(); - // Set default config for existing tests + vi.unstubAllEnvs(); + stubDefaultEnvVars(); resetResourceAccessCheckerSingleton(); - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - disableMetadataApiRequests: false, - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); }); it('should call generatePulseMetricValueInsightBundle without bundleType and return Ok result', async () => { @@ -229,18 +217,7 @@ describe('getGeneratePulseMetricValueInsightBundleTool', () => { }); it('should return data source not allowed error when datasource is not allowed', async () => { - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - boundedContext: { - projectIds: null, - datasourceIds: new Set(['some-other-datasource-luid']), - workbookIds: null, - tags: null, - }, - }); + vi.stubEnv('INCLUDE_DATASOURCE_IDS', 'some-other-datasource-luid'); const result = await getToolResult(); expect(result.isError).toBe(true); diff --git a/src/tools/queryDatasource/datasourceCredentials.test.ts b/src/tools/queryDatasource/datasourceCredentials.test.ts index b8ec1381..74018a8b 100644 --- a/src/tools/queryDatasource/datasourceCredentials.test.ts +++ b/src/tools/queryDatasource/datasourceCredentials.test.ts @@ -1,3 +1,4 @@ +import { stubDefaultEnvVars } from '../../testShared.js'; import { exportedForTesting as datasourceCredentialsExportedForTesting, getDatasourceCredentials, @@ -5,20 +6,15 @@ import { const { resetDatasourceCredentials } = datasourceCredentialsExportedForTesting; -const mocks = vi.hoisted(() => ({ - mockGetConfig: vi.fn(), -})); - -vi.mock('../../config.js', () => ({ - getConfig: mocks.mockGetConfig, -})); - describe('getDatasourceCredentials', () => { beforeEach(() => { + vi.unstubAllEnvs(); + stubDefaultEnvVars(); resetDatasourceCredentials(); - mocks.mockGetConfig.mockReturnValue({ - datasourceCredentials: undefined, - }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); }); it('should return undefined when DATASOURCE_CREDENTIALS is not set', () => { @@ -30,11 +26,12 @@ describe('getDatasourceCredentials', () => { }); it('should return credentials for a valid datasource LUID', () => { - mocks.mockGetConfig.mockReturnValue({ - datasourceCredentials: JSON.stringify({ + vi.stubEnv( + 'DATASOURCE_CREDENTIALS', + JSON.stringify({ 'ds-luid': [{ luid: 'test-luid', u: 'test-user', p: 'test-pass' }], }), - }); + ); expect(getDatasourceCredentials('ds-luid')).toEqual([ { @@ -55,19 +52,18 @@ describe('getDatasourceCredentials', () => { }); it('should return undefined for a non-existent datasource LUID', () => { - mocks.mockGetConfig.mockReturnValue({ - datasourceCredentials: JSON.stringify({ + vi.stubEnv( + 'DATASOURCE_CREDENTIALS', + JSON.stringify({ 'ds-luid': [{ luid: 'test-luid', u: 'test-user', p: 'test-pass' }], }), - }); + ); expect(getDatasourceCredentials('other-luid')).toBeUndefined(); }); it('should throw error when DATASOURCE_CREDENTIALS is invalid JSON', () => { - mocks.mockGetConfig.mockReturnValue({ - datasourceCredentials: 'invalid-json', - }); + vi.stubEnv('DATASOURCE_CREDENTIALS', 'invalid-json'); expect(() => getDatasourceCredentials('test-luid')).toThrow( 'Invalid datasource credentials format. Could not parse JSON string: invalid-json', @@ -75,11 +71,12 @@ describe('getDatasourceCredentials', () => { }); it('should throw error when credential schema is invalid', () => { - mocks.mockGetConfig.mockReturnValue({ - datasourceCredentials: JSON.stringify({ + vi.stubEnv( + 'DATASOURCE_CREDENTIALS', + JSON.stringify({ 'ds-luid': [{ luid: 'test-luid', x: 'test-user', y: 'test-pass' }], }), - }); + ); expect(() => getDatasourceCredentials('ds-luid')).toThrow(); }); diff --git a/src/tools/queryDatasource/queryDatasource.test.ts b/src/tools/queryDatasource/queryDatasource.test.ts index f66f0edd..cbc4520f 100644 --- a/src/tools/queryDatasource/queryDatasource.test.ts +++ b/src/tools/queryDatasource/queryDatasource.test.ts @@ -4,6 +4,7 @@ import { Err, Ok } from 'ts-results-es'; import { queryOutputSchema } from '../../sdks/tableau/apis/vizqlDataServiceApi.js'; import { Server } from '../../server.js'; +import { stubDefaultEnvVars } from '../../testShared.js'; import invariant from '../../utils/invariant.js'; import { Provider } from '../../utils/provider.js'; import { getVizqlDataServiceDisabledError } from '../getVizqlDataServiceDisabledError.js'; @@ -45,7 +46,6 @@ const mockVdsResponses = vi.hoisted(() => ({ const mocks = vi.hoisted(() => ({ mockQueryDatasource: vi.fn(), - mockGetConfig: vi.fn(), })); vi.mock('../../restApiInstance.js', () => ({ @@ -60,29 +60,17 @@ vi.mock('../../restApiInstance.js', () => ({ ), })); -vi.mock('../../config.js', () => ({ - getConfig: mocks.mockGetConfig, -})); - describe('queryDatasourceTool', () => { beforeEach(() => { vi.clearAllMocks(); + vi.unstubAllEnvs(); + stubDefaultEnvVars(); resetDatasourceCredentials(); resetResourceAccessCheckerSingleton(); - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - datasourceCredentials: undefined, - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - getMaxResultLimit: vi.fn().mockReturnValue(null), - }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); }); it('should create a tool instance with correct properties', () => { @@ -185,21 +173,14 @@ describe('queryDatasourceTool', () => { it('should add datasource credentials to the request when provided', async () => { mocks.mockQueryDatasource.mockResolvedValue(new Ok(mockVdsResponses.success)); - mocks.mockGetConfig.mockReturnValue({ - server: 'https://10ax.online.tableau.com', - datasourceCredentials: JSON.stringify({ + vi.stubEnv( + 'DATASOURCE_CREDENTIALS', + JSON.stringify({ '71db762b-6201-466b-93da-57cc0aec8ed9': [ { luid: 'test-luid', u: 'test-user', p: 'test-pass' }, ], }), - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - getMaxResultLimit: vi.fn().mockReturnValue(null), - }); + ); const result = await getToolResult(); expect(result.isError).toBe(false); @@ -552,16 +533,7 @@ describe('queryDatasourceTool', () => { }); it('should return data source not allowed error when datasource is not allowed', async () => { - mocks.mockGetConfig.mockReturnValue({ - datasourceCredentials: undefined, - boundedContext: { - projectIds: null, - datasourceIds: new Set(['some-other-datasource-luid']), - workbookIds: null, - tags: null, - }, - getMaxResultLimit: vi.fn().mockReturnValue(null), - }); + vi.stubEnv('INCLUDE_DATASOURCE_IDS', 'some-other-datasource-luid'); const result = await getToolResult(); expect(result.isError).toBe(true); diff --git a/src/tools/views/getViewData.test.ts b/src/tools/views/getViewData.test.ts index 81f8db95..2b2350fa 100644 --- a/src/tools/views/getViewData.test.ts +++ b/src/tools/views/getViewData.test.ts @@ -1,6 +1,7 @@ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { Server } from '../../server.js'; +import { stubDefaultEnvVars } from '../../testShared.js'; import invariant from '../../utils/invariant.js'; import { Provider } from '../../utils/provider.js'; import { exportedForTesting as resourceAccessCheckerExportedForTesting } from '../resourceAccessChecker.js'; @@ -15,7 +16,6 @@ const mockViewData = const mocks = vi.hoisted(() => ({ mockGetView: vi.fn(), mockQueryViewData: vi.fn(), - mockGetConfig: vi.fn(), })); vi.mock('../../restApiInstance.js', () => ({ @@ -30,26 +30,16 @@ vi.mock('../../restApiInstance.js', () => ({ ), })); -vi.mock('../../config.js', () => ({ - getConfig: mocks.mockGetConfig, -})); - describe('getViewDataTool', () => { beforeEach(() => { vi.clearAllMocks(); + vi.unstubAllEnvs(); + stubDefaultEnvVars(); resetResourceAccessCheckerSingleton(); - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); }); it('should create a tool instance with correct properties', () => { @@ -86,14 +76,7 @@ describe('getViewDataTool', () => { }); it('should return view not allowed error when view is not allowed', async () => { - mocks.mockGetConfig.mockReturnValue({ - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: new Set(['some-other-workbook-id']), - tags: null, - }, - }); + vi.stubEnv('INCLUDE_WORKBOOK_IDS', 'some-other-workbook-id'); mocks.mockGetView.mockResolvedValue(mockView); const result = await getToolResult({ viewId: mockView.id }); diff --git a/src/tools/views/getViewImage.test.ts b/src/tools/views/getViewImage.test.ts index 0ddacb76..0060e487 100644 --- a/src/tools/views/getViewImage.test.ts +++ b/src/tools/views/getViewImage.test.ts @@ -1,6 +1,7 @@ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { Server } from '../../server.js'; +import { stubDefaultEnvVars } from '../../testShared.js'; import invariant from '../../utils/invariant.js'; import { Provider } from '../../utils/provider.js'; import { exportedForTesting as resourceAccessCheckerExportedForTesting } from '../resourceAccessChecker.js'; @@ -18,7 +19,6 @@ const base64PngData = Buffer.from(mockPngData).toString('base64'); const mocks = vi.hoisted(() => ({ mockGetView: vi.fn(), mockQueryViewImage: vi.fn(), - mockGetConfig: vi.fn(), })); vi.mock('../../restApiInstance.js', () => ({ @@ -33,26 +33,16 @@ vi.mock('../../restApiInstance.js', () => ({ ), })); -vi.mock('../../config.js', () => ({ - getConfig: mocks.mockGetConfig, -})); - describe('getViewImageTool', () => { beforeEach(() => { vi.clearAllMocks(); + vi.unstubAllEnvs(); + stubDefaultEnvVars(); resetResourceAccessCheckerSingleton(); - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); }); it('should create a tool instance with correct properties', () => { @@ -93,15 +83,7 @@ describe('getViewImageTool', () => { }); it('should return view not allowed error when view is not allowed', async () => { - mocks.mockGetConfig.mockReturnValue({ - datasourceCredentials: undefined, - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: new Set(['some-other-workbook-id']), - tags: null, - }, - }); + vi.stubEnv('INCLUDE_WORKBOOK_IDS', 'some-other-workbook-id'); mocks.mockGetView.mockResolvedValue(mockView); const result = await getToolResult({ viewId: mockView.id }); diff --git a/src/tools/workbooks/getWorkbook.test.ts b/src/tools/workbooks/getWorkbook.test.ts index 20b2e921..5e28e180 100644 --- a/src/tools/workbooks/getWorkbook.test.ts +++ b/src/tools/workbooks/getWorkbook.test.ts @@ -1,6 +1,7 @@ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { Server } from '../../server.js'; +import { stubDefaultEnvVars } from '../../testShared.js'; import invariant from '../../utils/invariant.js'; import { Provider } from '../../utils/provider.js'; import { exportedForTesting as resourceAccessCheckerExportedForTesting } from '../resourceAccessChecker.js'; @@ -13,7 +14,6 @@ const { resetResourceAccessCheckerSingleton } = resourceAccessCheckerExportedFor const mocks = vi.hoisted(() => ({ mockGetWorkbook: vi.fn(), mockQueryViewsForWorkbook: vi.fn(), - mockGetConfig: vi.fn(), })); vi.mock('../../restApiInstance.js', () => ({ @@ -30,26 +30,16 @@ vi.mock('../../restApiInstance.js', () => ({ ), })); -vi.mock('../../config.js', () => ({ - getConfig: mocks.mockGetConfig, -})); - describe('getWorkbookTool', () => { beforeEach(() => { vi.clearAllMocks(); + vi.unstubAllEnvs(); + stubDefaultEnvVars(); resetResourceAccessCheckerSingleton(); - mocks.mockGetConfig.mockReturnValue({ - productTelemetryEndpoint: 'https://test.telemetry.example.com', - productTelemetryEnabled: true, - siteName: 'test-site', - server: 'https://test-server.example.com', - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: null, - tags: null, - }, - }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); }); it('should create a tool instance with correct properties', () => { @@ -84,14 +74,7 @@ describe('getWorkbookTool', () => { }); it('should return workbook not allowed error when workbook is not allowed', async () => { - mocks.mockGetConfig.mockReturnValue({ - boundedContext: { - projectIds: null, - datasourceIds: null, - workbookIds: new Set(['some-other-workbook-id']), - tags: null, - }, - }); + vi.stubEnv('INCLUDE_WORKBOOK_IDS', 'some-other-workbook-id'); mocks.mockGetWorkbook.mockResolvedValue(mockWorkbook); const result = await getToolResult({ workbookId: mockWorkbook.id });