mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-28 23:11:19 -07:00
163 lines
4.3 KiB
TypeScript
163 lines
4.3 KiB
TypeScript
|
|
/**
|
||
|
|
* @license
|
||
|
|
* Copyright 2025 Google LLC
|
||
|
|
* SPDX-License-Identifier: Apache-2.0
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { describe, it, expect, vi } from 'vitest';
|
||
|
|
import {
|
||
|
|
createConversationOffered,
|
||
|
|
formatProtoJsonDuration,
|
||
|
|
} from './telemetry.js';
|
||
|
|
import { ActionStatus, type StreamingLatency } from './types.js';
|
||
|
|
import { FinishReason, GenerateContentResponse } from '@google/genai';
|
||
|
|
|
||
|
|
function createMockResponse(
|
||
|
|
candidates: GenerateContentResponse['candidates'] = [],
|
||
|
|
ok = true,
|
||
|
|
) {
|
||
|
|
const response = new GenerateContentResponse();
|
||
|
|
response.candidates = candidates;
|
||
|
|
response.sdkHttpResponse = {
|
||
|
|
responseInternal: {
|
||
|
|
ok,
|
||
|
|
} as unknown as Response,
|
||
|
|
json: async () => ({}),
|
||
|
|
};
|
||
|
|
return response;
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('telemetry', () => {
|
||
|
|
describe('createConversationOffered', () => {
|
||
|
|
it('should create a ConversationOffered object with correct values', () => {
|
||
|
|
const response = createMockResponse([
|
||
|
|
{
|
||
|
|
index: 0,
|
||
|
|
content: {
|
||
|
|
role: 'model',
|
||
|
|
parts: [{ text: 'response with ```code```' }],
|
||
|
|
},
|
||
|
|
citationMetadata: {
|
||
|
|
citations: [
|
||
|
|
{ uri: 'https://example.com', startIndex: 0, endIndex: 10 },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
finishReason: FinishReason.STOP,
|
||
|
|
},
|
||
|
|
]);
|
||
|
|
const traceId = 'test-trace-id';
|
||
|
|
const streamingLatency: StreamingLatency = { totalLatency: '1s' };
|
||
|
|
|
||
|
|
const result = createConversationOffered(
|
||
|
|
response,
|
||
|
|
traceId,
|
||
|
|
undefined,
|
||
|
|
streamingLatency,
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(result).toEqual({
|
||
|
|
citationCount: '1',
|
||
|
|
includedCode: true,
|
||
|
|
status: ActionStatus.ACTION_STATUS_NO_ERROR,
|
||
|
|
traceId,
|
||
|
|
streamingLatency,
|
||
|
|
isAgentic: true,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should set status to CANCELLED if signal is aborted', () => {
|
||
|
|
const response = createMockResponse();
|
||
|
|
const signal = new AbortController().signal;
|
||
|
|
vi.spyOn(signal, 'aborted', 'get').mockReturnValue(true);
|
||
|
|
|
||
|
|
const result = createConversationOffered(
|
||
|
|
response,
|
||
|
|
'trace-id',
|
||
|
|
signal,
|
||
|
|
{},
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(result.status).toBe(ActionStatus.ACTION_STATUS_CANCELLED);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should set status to ERROR_UNKNOWN if response has error (non-OK SDK response)', () => {
|
||
|
|
const response = createMockResponse([], false);
|
||
|
|
|
||
|
|
const result = createConversationOffered(
|
||
|
|
response,
|
||
|
|
'trace-id',
|
||
|
|
undefined,
|
||
|
|
{},
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(result.status).toBe(ActionStatus.ACTION_STATUS_ERROR_UNKNOWN);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should set status to ERROR_UNKNOWN if finishReason is not STOP or MAX_TOKENS', () => {
|
||
|
|
const response = createMockResponse([
|
||
|
|
{
|
||
|
|
index: 0,
|
||
|
|
finishReason: FinishReason.SAFETY,
|
||
|
|
},
|
||
|
|
]);
|
||
|
|
|
||
|
|
const result = createConversationOffered(
|
||
|
|
response,
|
||
|
|
'trace-id',
|
||
|
|
undefined,
|
||
|
|
{},
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(result.status).toBe(ActionStatus.ACTION_STATUS_ERROR_UNKNOWN);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should set status to EMPTY if candidates is empty', () => {
|
||
|
|
const response = createMockResponse();
|
||
|
|
|
||
|
|
const result = createConversationOffered(
|
||
|
|
response,
|
||
|
|
'trace-id',
|
||
|
|
undefined,
|
||
|
|
{},
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(result.status).toBe(ActionStatus.ACTION_STATUS_EMPTY);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should detect code in response', () => {
|
||
|
|
const response = createMockResponse([
|
||
|
|
{
|
||
|
|
index: 0,
|
||
|
|
content: {
|
||
|
|
parts: [
|
||
|
|
{ text: 'Here is some code:\n```js\nconsole.log("hi")\n```' },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
},
|
||
|
|
]);
|
||
|
|
const result = createConversationOffered(response, 'id', undefined, {});
|
||
|
|
expect(result.includedCode).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not detect code if no backticks', () => {
|
||
|
|
const response = createMockResponse([
|
||
|
|
{
|
||
|
|
index: 0,
|
||
|
|
content: {
|
||
|
|
parts: [{ text: 'Here is some text.' }],
|
||
|
|
},
|
||
|
|
},
|
||
|
|
]);
|
||
|
|
const result = createConversationOffered(response, 'id', undefined, {});
|
||
|
|
expect(result.includedCode).toBe(false);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('formatProtoJsonDuration', () => {
|
||
|
|
it('should format milliseconds to seconds string', () => {
|
||
|
|
expect(formatProtoJsonDuration(1500)).toBe('1.5s');
|
||
|
|
expect(formatProtoJsonDuration(100)).toBe('0.1s');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|