auto-claude: subtask-2-1 - Create isAPIProfileAuthenticated() function to val (#1745)

- Add isAPIProfileAuthenticated() function to profile-utils.ts
- Import APIProfile type from shared/types
- Export APIProfile from shared/types/index.ts for wider availability
- Add comprehensive unit tests for API profile authentication validation
- Validates both apiKey and baseUrl are present and non-empty
- Handles edge cases: whitespace, undefined, null values

Co-authored-by: Test User <test@example.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Andy
2026-02-09 10:27:01 +01:00
committed by GitHub
parent d09ebb8504
commit 57e38a692c
4 changed files with 136 additions and 1 deletions
@@ -0,0 +1,108 @@
/**
* Tests for profile-utils module
*/
import { describe, it, expect } from 'vitest';
import { isAPIProfileAuthenticated } from './profile-utils';
import type { APIProfile } from '../../shared/types';
describe('isAPIProfileAuthenticated', () => {
it('should return true when both apiKey and baseUrl are present and non-empty', () => {
const validProfile: APIProfile = {
id: 'test-1',
name: 'Test Profile',
baseUrl: 'https://api.anthropic.com',
apiKey: 'sk-ant-api03-test',
createdAt: Date.now(),
updatedAt: Date.now(),
};
expect(isAPIProfileAuthenticated(validProfile)).toBe(true);
});
it('should return false when apiKey is missing', () => {
const profileWithoutApiKey: APIProfile = {
id: 'test-2',
name: 'Test Profile',
baseUrl: 'https://api.anthropic.com',
apiKey: '',
createdAt: Date.now(),
updatedAt: Date.now(),
};
expect(isAPIProfileAuthenticated(profileWithoutApiKey)).toBe(false);
});
it('should return false when baseUrl is missing', () => {
const profileWithoutBaseUrl: APIProfile = {
id: 'test-3',
name: 'Test Profile',
baseUrl: '',
apiKey: 'sk-ant-api03-test',
createdAt: Date.now(),
updatedAt: Date.now(),
};
expect(isAPIProfileAuthenticated(profileWithoutBaseUrl)).toBe(false);
});
it('should return false when apiKey is only whitespace', () => {
const profileWithWhitespaceApiKey: APIProfile = {
id: 'test-4',
name: 'Test Profile',
baseUrl: 'https://api.anthropic.com',
apiKey: ' ',
createdAt: Date.now(),
updatedAt: Date.now(),
};
expect(isAPIProfileAuthenticated(profileWithWhitespaceApiKey)).toBe(false);
});
it('should return false when baseUrl is only whitespace', () => {
const profileWithWhitespaceBaseUrl: APIProfile = {
id: 'test-5',
name: 'Test Profile',
baseUrl: ' ',
apiKey: 'sk-ant-api03-test',
createdAt: Date.now(),
updatedAt: Date.now(),
};
expect(isAPIProfileAuthenticated(profileWithWhitespaceBaseUrl)).toBe(false);
});
it('should return false when both apiKey and baseUrl are missing', () => {
const profileWithoutCredentials: APIProfile = {
id: 'test-6',
name: 'Test Profile',
baseUrl: '',
apiKey: '',
createdAt: Date.now(),
updatedAt: Date.now(),
};
expect(isAPIProfileAuthenticated(profileWithoutCredentials)).toBe(false);
});
it('should return false when profile is undefined', () => {
expect(isAPIProfileAuthenticated(undefined as any)).toBe(false);
});
it('should return false when profile is null', () => {
expect(isAPIProfileAuthenticated(null as any)).toBe(false);
});
it('should handle profiles with apiKey and baseUrl containing leading/trailing whitespace', () => {
const profileWithWhitespace: APIProfile = {
id: 'test-7',
name: 'Test Profile',
baseUrl: ' https://api.anthropic.com ',
apiKey: ' sk-ant-api03-test ',
createdAt: Date.now(),
updatedAt: Date.now(),
};
expect(isAPIProfileAuthenticated(profileWithWhitespace)).toBe(true);
});
});
@@ -6,7 +6,7 @@
import { homedir } from 'os';
import { join } from 'path';
import { existsSync, readFileSync, readdirSync, mkdirSync } from 'fs';
import type { ClaudeProfile } from '../../shared/types';
import type { ClaudeProfile, APIProfile } from '../../shared/types';
import { getCredentialsFromKeychain } from './credential-utils';
/**
@@ -203,6 +203,26 @@ export function hasValidToken(profile: ClaudeProfile): boolean {
return true;
}
/**
* Check if an API profile has valid authentication credentials.
* Validates that both apiKey and baseUrl are present and non-empty.
*
* @param profile - The API profile to check
* @returns true if the profile has both apiKey and baseUrl, false otherwise
*/
export function isAPIProfileAuthenticated(profile: APIProfile): boolean {
// Check for presence of required fields
if (!profile?.apiKey || !profile?.baseUrl) {
return false;
}
// Validate that the fields are non-empty strings (after trimming whitespace)
const hasValidApiKey = typeof profile.apiKey === 'string' && profile.apiKey.trim().length > 0;
const hasValidBaseUrl = typeof profile.baseUrl === 'string' && profile.baseUrl.trim().length > 0;
return hasValidApiKey && hasValidBaseUrl;
}
/**
* Expand ~ in path to home directory
*/
+1
View File
@@ -11,6 +11,7 @@ export * from './task';
export * from './kanban';
export * from './terminal';
export * from './agent';
export * from './profile';
export * from './settings';
export * from './changelog';
export * from './insights';
@@ -1,3 +1,5 @@
import type { ClaudeUsageData, ClaudeRateLimitEvent } from './agent';
/**
* API Profile Management Types
*
@@ -27,6 +29,10 @@ export interface APIProfile {
};
createdAt: number; // Unix timestamp (ms)
updatedAt: number; // Unix timestamp (ms)
/** Current usage data from API */
usage?: ClaudeUsageData;
/** Recent rate limit events for this profile */
rateLimitEvents?: ClaudeRateLimitEvent[];
}
/**