mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-31 00:11:11 -07:00
299 lines
9.9 KiB
TypeScript
299 lines
9.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
A2AAgentError,
|
|
AgentCardNotFoundError,
|
|
AgentCardAuthError,
|
|
AgentAuthConfigMissingError,
|
|
AgentConnectionError,
|
|
classifyAgentError,
|
|
} from './a2a-errors.js';
|
|
|
|
describe('A2A Error Types', () => {
|
|
describe('A2AAgentError', () => {
|
|
it('should set name, agentName, and userMessage', () => {
|
|
const error = new A2AAgentError('my-agent', 'internal msg', 'user msg');
|
|
expect(error.name).toBe('A2AAgentError');
|
|
expect(error.agentName).toBe('my-agent');
|
|
expect(error.message).toBe('internal msg');
|
|
expect(error.userMessage).toBe('user msg');
|
|
});
|
|
});
|
|
|
|
describe('AgentCardNotFoundError', () => {
|
|
it('should produce a user-friendly 404 message', () => {
|
|
const error = new AgentCardNotFoundError(
|
|
'my-agent',
|
|
'https://example.com/card',
|
|
);
|
|
expect(error.name).toBe('AgentCardNotFoundError');
|
|
expect(error.agentName).toBe('my-agent');
|
|
expect(error.userMessage).toContain('404');
|
|
expect(error.userMessage).toContain('https://example.com/card');
|
|
expect(error.userMessage).toContain('agent_card_url');
|
|
});
|
|
});
|
|
|
|
describe('AgentCardAuthError', () => {
|
|
it('should produce a user-friendly 401 message', () => {
|
|
const error = new AgentCardAuthError(
|
|
'secure-agent',
|
|
'https://example.com/card',
|
|
401,
|
|
);
|
|
expect(error.name).toBe('AgentCardAuthError');
|
|
expect(error.statusCode).toBe(401);
|
|
expect(error.userMessage).toContain('401');
|
|
expect(error.userMessage).toContain('Unauthorized');
|
|
expect(error.userMessage).toContain('"auth" configuration');
|
|
});
|
|
|
|
it('should produce a user-friendly 403 message', () => {
|
|
const error = new AgentCardAuthError(
|
|
'secure-agent',
|
|
'https://example.com/card',
|
|
403,
|
|
);
|
|
expect(error.statusCode).toBe(403);
|
|
expect(error.userMessage).toContain('403');
|
|
expect(error.userMessage).toContain('Forbidden');
|
|
});
|
|
});
|
|
|
|
describe('AgentAuthConfigMissingError', () => {
|
|
it('should list missing config fields', () => {
|
|
const error = new AgentAuthConfigMissingError(
|
|
'api-agent',
|
|
'API Key (x-api-key): Send x-api-key in header',
|
|
[
|
|
'Authentication is required but not configured',
|
|
"Scheme 'api_key' requires apiKey authentication",
|
|
],
|
|
);
|
|
expect(error.name).toBe('AgentAuthConfigMissingError');
|
|
expect(error.requiredAuth).toContain('API Key');
|
|
expect(error.missingFields).toHaveLength(2);
|
|
expect(error.userMessage).toContain('API Key');
|
|
expect(error.userMessage).toContain('no auth is configured');
|
|
expect(error.userMessage).toContain('Missing:');
|
|
});
|
|
});
|
|
|
|
describe('AgentConnectionError', () => {
|
|
it('should wrap the original error cause', () => {
|
|
const cause = new Error('ECONNREFUSED');
|
|
const error = new AgentConnectionError(
|
|
'my-agent',
|
|
'https://example.com/card',
|
|
cause,
|
|
);
|
|
expect(error.name).toBe('AgentConnectionError');
|
|
expect(error.userMessage).toContain('ECONNREFUSED');
|
|
expect(error.userMessage).toContain('https://example.com/card');
|
|
});
|
|
|
|
it('should handle non-Error causes', () => {
|
|
const error = new AgentConnectionError(
|
|
'my-agent',
|
|
'https://example.com/card',
|
|
'raw string error',
|
|
);
|
|
expect(error.userMessage).toContain('raw string error');
|
|
});
|
|
});
|
|
|
|
describe('classifyAgentError', () => {
|
|
it('should classify a 404 error message', () => {
|
|
const raw = new Error('HTTP 404: Not Found');
|
|
const result = classifyAgentError(
|
|
'agent-a',
|
|
'https://example.com/card',
|
|
raw,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardNotFoundError);
|
|
expect(result.agentName).toBe('agent-a');
|
|
});
|
|
|
|
it('should classify a "not found" error message (case-insensitive)', () => {
|
|
const raw = new Error('Agent card not found at the given URL');
|
|
const result = classifyAgentError(
|
|
'agent-a',
|
|
'https://example.com/card',
|
|
raw,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardNotFoundError);
|
|
});
|
|
|
|
it('should classify a 401 error message', () => {
|
|
const raw = new Error('Request failed with status 401');
|
|
const result = classifyAgentError(
|
|
'agent-b',
|
|
'https://example.com/card',
|
|
raw,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardAuthError);
|
|
expect((result as AgentCardAuthError).statusCode).toBe(401);
|
|
});
|
|
|
|
it('should classify an "unauthorized" error message', () => {
|
|
const raw = new Error('Unauthorized access to agent card');
|
|
const result = classifyAgentError(
|
|
'agent-b',
|
|
'https://example.com/card',
|
|
raw,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardAuthError);
|
|
});
|
|
|
|
it('should classify a 403 error message', () => {
|
|
const raw = new Error('HTTP 403 Forbidden');
|
|
const result = classifyAgentError(
|
|
'agent-c',
|
|
'https://example.com/card',
|
|
raw,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardAuthError);
|
|
expect((result as AgentCardAuthError).statusCode).toBe(403);
|
|
});
|
|
|
|
it('should fall back to AgentConnectionError for unknown errors', () => {
|
|
const raw = new Error('Something completely unexpected');
|
|
const result = classifyAgentError(
|
|
'agent-d',
|
|
'https://example.com/card',
|
|
raw,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentConnectionError);
|
|
});
|
|
|
|
it('should classify ECONNREFUSED as AgentConnectionError', () => {
|
|
const raw = new Error('ECONNREFUSED 127.0.0.1:8080');
|
|
const result = classifyAgentError(
|
|
'agent-d',
|
|
'https://example.com/card',
|
|
raw,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentConnectionError);
|
|
});
|
|
|
|
it('should handle non-Error values', () => {
|
|
const result = classifyAgentError(
|
|
'agent-e',
|
|
'https://example.com/card',
|
|
'some string error',
|
|
);
|
|
expect(result).toBeInstanceOf(AgentConnectionError);
|
|
});
|
|
|
|
describe('cause chain inspection', () => {
|
|
it('should detect 404 in a nested cause', () => {
|
|
const inner = new Error('HTTP 404 Not Found');
|
|
const outer = new Error('fetch failed', { cause: inner });
|
|
const result = classifyAgentError(
|
|
'agent-nested',
|
|
'https://example.com/card',
|
|
outer,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardNotFoundError);
|
|
});
|
|
|
|
it('should detect 401 in a deeply nested cause', () => {
|
|
const innermost = new Error('Server returned 401');
|
|
const middle = new Error('Request error', { cause: innermost });
|
|
const outer = new Error('fetch failed', { cause: middle });
|
|
const result = classifyAgentError(
|
|
'agent-deep',
|
|
'https://example.com/card',
|
|
outer,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardAuthError);
|
|
expect((result as AgentCardAuthError).statusCode).toBe(401);
|
|
});
|
|
|
|
it('should detect ECONNREFUSED error code in cause chain', () => {
|
|
const inner = Object.assign(new Error('connect failed'), {
|
|
code: 'ECONNREFUSED',
|
|
});
|
|
const outer = new Error('fetch failed', { cause: inner });
|
|
const result = classifyAgentError(
|
|
'agent-conn',
|
|
'https://example.com/card',
|
|
outer,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentConnectionError);
|
|
});
|
|
|
|
it('should detect status property on error objects in cause chain', () => {
|
|
const inner = Object.assign(new Error('Bad response'), {
|
|
status: 403,
|
|
});
|
|
const outer = new Error('agent card resolution failed', {
|
|
cause: inner,
|
|
});
|
|
const result = classifyAgentError(
|
|
'agent-status',
|
|
'https://example.com/card',
|
|
outer,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardAuthError);
|
|
expect((result as AgentCardAuthError).statusCode).toBe(403);
|
|
});
|
|
|
|
it('should detect status on a plain-object cause (non-Error)', () => {
|
|
const outer = new Error('fetch failed');
|
|
// Some HTTP libs set cause to a plain object, not an Error instance
|
|
(outer as unknown as { cause: unknown }).cause = {
|
|
message: 'Unauthorized',
|
|
status: 401,
|
|
};
|
|
const result = classifyAgentError(
|
|
'agent-plain-cause',
|
|
'https://example.com/card',
|
|
outer,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardAuthError);
|
|
expect((result as AgentCardAuthError).statusCode).toBe(401);
|
|
});
|
|
|
|
it('should detect statusCode on a plain-object cause (non-Error)', () => {
|
|
const outer = new Error('fetch failed');
|
|
(outer as unknown as { cause: unknown }).cause = {
|
|
message: 'Forbidden',
|
|
statusCode: 403,
|
|
};
|
|
const result = classifyAgentError(
|
|
'agent-plain-cause-403',
|
|
'https://example.com/card',
|
|
outer,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentCardAuthError);
|
|
expect((result as AgentCardAuthError).statusCode).toBe(403);
|
|
});
|
|
|
|
it('should classify ENOTFOUND as AgentConnectionError, not 404', () => {
|
|
// ENOTFOUND (DNS resolution failure) should NOT be misclassified
|
|
// as a 404 despite containing "NOTFOUND" in the error code.
|
|
const inner = Object.assign(
|
|
new Error('getaddrinfo ENOTFOUND example.invalid'),
|
|
{
|
|
code: 'ENOTFOUND',
|
|
},
|
|
);
|
|
const outer = new Error('fetch failed', { cause: inner });
|
|
const result = classifyAgentError(
|
|
'agent-dns',
|
|
'https://example.invalid/card',
|
|
outer,
|
|
);
|
|
expect(result).toBeInstanceOf(AgentConnectionError);
|
|
expect(result).not.toBeInstanceOf(AgentCardNotFoundError);
|
|
});
|
|
});
|
|
});
|
|
});
|