156 lines
5.3 KiB
TypeScript
156 lines
5.3 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 AionUi (aionui.com)
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
isOfficialHost,
|
|
isGoogleApisHost,
|
|
isOpenAIHost,
|
|
isAnthropicHost,
|
|
API_HOST_CONFIG,
|
|
GOOGLE_API_HOSTS,
|
|
} from '@/common/utils/urlValidation';
|
|
|
|
describe('urlValidation', () => {
|
|
describe('isOfficialHost', () => {
|
|
const allowedHosts = ['api.example.com', 'api.test.com'];
|
|
|
|
it('returns true for allowed host', () => {
|
|
expect(isOfficialHost('https://api.example.com/v1', allowedHosts)).toBe(true);
|
|
expect(isOfficialHost('https://api.test.com/resource', allowedHosts)).toBe(true);
|
|
});
|
|
|
|
it('returns false for host not in allowed list', () => {
|
|
expect(isOfficialHost('https://evil.com/v1', allowedHosts)).toBe(false);
|
|
expect(isOfficialHost('https://api.other.com/v1', allowedHosts)).toBe(false);
|
|
});
|
|
|
|
it('returns false for subdomain attack', () => {
|
|
expect(isOfficialHost('https://api.example.com.evil.com/v1', allowedHosts)).toBe(false);
|
|
});
|
|
|
|
it('returns false for path-based attack', () => {
|
|
expect(isOfficialHost('https://evil.com/api.example.com', allowedHosts)).toBe(false);
|
|
});
|
|
|
|
it('returns false for invalid URL', () => {
|
|
expect(isOfficialHost('not a url', allowedHosts)).toBe(false);
|
|
expect(isOfficialHost('', allowedHosts)).toBe(false);
|
|
});
|
|
|
|
it('handles URL with port', () => {
|
|
expect(isOfficialHost('https://api.example.com:8080/v1', allowedHosts)).toBe(true);
|
|
});
|
|
|
|
it('handles URL with query parameters', () => {
|
|
expect(isOfficialHost('https://api.example.com/v1?key=value', allowedHosts)).toBe(true);
|
|
});
|
|
|
|
it('handles URL with fragments', () => {
|
|
expect(isOfficialHost('https://api.example.com/v1#section', allowedHosts)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('isGoogleApisHost', () => {
|
|
it('returns true for Gemini API host', () => {
|
|
expect(isGoogleApisHost('https://generativelanguage.googleapis.com/v1')).toBe(true);
|
|
expect(isGoogleApisHost('https://generativelanguage.googleapis.com/v1/models')).toBe(true);
|
|
});
|
|
|
|
it('returns true for Vertex AI host', () => {
|
|
expect(isGoogleApisHost('https://aiplatform.googleapis.com/v1')).toBe(true);
|
|
});
|
|
|
|
it('returns false for non-Google host', () => {
|
|
expect(isGoogleApisHost('https://api.openai.com/v1')).toBe(false);
|
|
expect(isGoogleApisHost('https://evil.com')).toBe(false);
|
|
});
|
|
|
|
it('returns false for subdomain attack on Google host', () => {
|
|
expect(isGoogleApisHost('https://generativelanguage.googleapis.com.evil.com/v1')).toBe(false);
|
|
});
|
|
|
|
it('returns false for path-based attack', () => {
|
|
expect(isGoogleApisHost('https://evil.com/generativelanguage.googleapis.com')).toBe(false);
|
|
});
|
|
|
|
it('returns false for invalid URL', () => {
|
|
expect(isGoogleApisHost('invalid')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isOpenAIHost', () => {
|
|
it('returns true for OpenAI API host', () => {
|
|
expect(isOpenAIHost('https://api.openai.com/v1')).toBe(true);
|
|
expect(isOpenAIHost('https://api.openai.com/v1/chat/completions')).toBe(true);
|
|
});
|
|
|
|
it('returns false for non-OpenAI host', () => {
|
|
expect(isOpenAIHost('https://generativelanguage.googleapis.com/v1')).toBe(false);
|
|
expect(isOpenAIHost('https://evil.com')).toBe(false);
|
|
});
|
|
|
|
it('returns false for subdomain attack', () => {
|
|
expect(isOpenAIHost('https://api.openai.com.evil.com/v1')).toBe(false);
|
|
});
|
|
|
|
it('returns false for path-based attack', () => {
|
|
expect(isOpenAIHost('https://evil.com/api.openai.com')).toBe(false);
|
|
});
|
|
|
|
it('returns false for invalid URL', () => {
|
|
expect(isOpenAIHost('not a url')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isAnthropicHost', () => {
|
|
it('returns true for Anthropic API host', () => {
|
|
expect(isAnthropicHost('https://api.anthropic.com/v1')).toBe(true);
|
|
expect(isAnthropicHost('https://api.anthropic.com/v1/messages')).toBe(true);
|
|
});
|
|
|
|
it('returns false for non-Anthropic host', () => {
|
|
expect(isAnthropicHost('https://api.openai.com/v1')).toBe(false);
|
|
expect(isAnthropicHost('https://evil.com')).toBe(false);
|
|
});
|
|
|
|
it('returns false for subdomain attack', () => {
|
|
expect(isAnthropicHost('https://api.anthropic.com.evil.com/v1')).toBe(false);
|
|
});
|
|
|
|
it('returns false for path-based attack', () => {
|
|
expect(isAnthropicHost('https://evil.com/api.anthropic.com')).toBe(false);
|
|
});
|
|
|
|
it('returns false for invalid URL', () => {
|
|
expect(isAnthropicHost('invalid')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('API_HOST_CONFIG', () => {
|
|
it('exports expected Google hosts', () => {
|
|
expect(API_HOST_CONFIG.google.gemini).toBe('generativelanguage.googleapis.com');
|
|
expect(API_HOST_CONFIG.google.vertexAi).toBe('aiplatform.googleapis.com');
|
|
});
|
|
|
|
it('exports expected OpenAI host', () => {
|
|
expect(API_HOST_CONFIG.openai.api).toBe('api.openai.com');
|
|
});
|
|
|
|
it('exports expected Anthropic host', () => {
|
|
expect(API_HOST_CONFIG.anthropic.api).toBe('api.anthropic.com');
|
|
});
|
|
});
|
|
|
|
describe('GOOGLE_API_HOSTS', () => {
|
|
it('contains all Google API hosts', () => {
|
|
expect(GOOGLE_API_HOSTS).toContain('generativelanguage.googleapis.com');
|
|
expect(GOOGLE_API_HOSTS).toContain('aiplatform.googleapis.com');
|
|
expect(GOOGLE_API_HOSTS).toHaveLength(2);
|
|
});
|
|
});
|
|
});
|