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:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user