import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import type { ThoughtSpotRestApi } from '@thoughtspot/rest-api-sdk';
import {
getRelevantQuestions,
getAnswerForQuestion,
fetchTMLAndCreateLiveboard,
createLiveboard,
getDataSources,
getSessionInfo,
getDataSourceSuggestions,
ThoughtSpotService,
} from '../../src/thoughtspot/thoughtspot-service';
// Mock the ThoughtSpot REST API client
const mockClient = {
queryGetDecomposedQuery: vi.fn(),
singleAnswer: vi.fn(),
exportAnswerReport: vi.fn(),
exportUnsavedAnswerTML: vi.fn(),
importMetadataTML: vi.fn(),
searchMetadata: vi.fn(),
getSessionInfo: vi.fn(),
getAnswerSession: vi.fn(),
instanceUrl: 'https://test.thoughtspot.com'
} as any;
describe('thoughtspot-service', () => {
beforeEach(() => {
vi.clearAllMocks();
console.error = vi.fn(); // Mock console.error to avoid noise in tests
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('getRelevantQuestions', () => {
it('should return relevant questions successfully', async () => {
const mockResponse = {
decomposedQueryResponse: {
decomposedQueries: [
{ query: 'What is the total revenue?', worksheetId: 'ws1' },
{ query: 'Show me sales by region', worksheetId: 'ws2' }
]
}
};
mockClient.queryGetDecomposedQuery = vi.fn().mockResolvedValue(mockResponse);
const result = await getRelevantQuestions(
'Show me revenue data',
['ws1', 'ws2'],
'Additional context',
mockClient
);
expect(mockClient.queryGetDecomposedQuery).toHaveBeenCalledWith({
nlsRequest: {
query: 'Show me revenue data',
},
content: ['Additional context'],
worksheetIds: ['ws1', 'ws2'],
maxDecomposedQueries: 5,
});
expect(result).toEqual({
questions: [
{ question: 'What is the total revenue?', datasourceId: 'ws1' },
{ question: 'Show me sales by region', datasourceId: 'ws2' }
],
error: null,
});
});
it('should handle empty additional context', async () => {
const mockResponse = {
decomposedQueryResponse: {
decomposedQueries: []
}
};
mockClient.queryGetDecomposedQuery = vi.fn().mockResolvedValue(mockResponse);
const result = await getRelevantQuestions(
'Test query',
['ws1'],
'',
mockClient
);
expect(mockClient.queryGetDecomposedQuery).toHaveBeenCalledWith({
nlsRequest: {
query: 'Test query',
},
content: [''],
worksheetIds: ['ws1'],
maxDecomposedQueries: 5,
});
expect(result).toEqual({
questions: [],
error: null,
});
});
it('should handle null additional context', async () => {
const mockResponse = {
decomposedQueryResponse: {
decomposedQueries: []
}
};
mockClient.queryGetDecomposedQuery = vi.fn().mockResolvedValue(mockResponse);
const result = await getRelevantQuestions(
'Test query',
['ws1'],
null as any,
mockClient
);
expect(mockClient.queryGetDecomposedQuery).toHaveBeenCalledWith({
nlsRequest: {
query: 'Test query',
},
content: [''],
worksheetIds: ['ws1'],
maxDecomposedQueries: 5,
});
expect(result).toEqual({
questions: [],
error: null,
});
});
it('should handle missing decomposedQueryResponse', async () => {
const mockResponse = {};
mockClient.queryGetDecomposedQuery = vi.fn().mockResolvedValue(mockResponse);
const result = await getRelevantQuestions(
'Test query',
['ws1'],
'context',
mockClient
);
expect(result).toEqual({
questions: [],
error: null,
});
});
it('should handle API errors', async () => {
const error = new Error('API Error');
mockClient.queryGetDecomposedQuery = vi.fn().mockRejectedValue(error);
const result = await getRelevantQuestions(
'Test query',
['ws1'],
'context',
mockClient
);
expect(result).toEqual({
questions: [],
error: error,
});
});
});
describe('getAnswerForQuestion', () => {
it('should return answer data successfully without TML', async () => {
const mockAnswerResponse = {
session_identifier: 'session123',
generation_number: 1
};
const mockDataResponse = {
text: vi.fn().mockResolvedValue('col1,col2\nval1,val2\nval3,val4')
};
const mockSessionResponse = {
sessionId: 'session123',
genNo: 1,
acSession: {
sessionId: 'acSession123',
genNo: 1
}
};
mockClient.singleAnswer = vi.fn().mockResolvedValue(mockAnswerResponse);
mockClient.exportAnswerReport = vi.fn().mockResolvedValue(mockDataResponse);
mockClient.getAnswerSession = vi.fn().mockResolvedValue(mockSessionResponse);
const result = await getAnswerForQuestion(
'What is the revenue?',
'ws1',
false,
mockClient
);
expect(mockClient.singleAnswer).toHaveBeenCalledWith({
query: 'What is the revenue?',
metadata_identifier: 'ws1',
});
expect(mockClient.exportAnswerReport).toHaveBeenCalledWith({
session_identifier: 'session123',
generation_number: 1,
file_format: 'CSV',
});
expect(mockClient.getAnswerSession).toHaveBeenCalledWith({
session_identifier: 'session123',
generation_number: 1,
});
expect(result).toEqual({
question: 'What is the revenue?',
...mockAnswerResponse,
data: 'col1,col2\nval1,val2\nval3,val4',
tml: null,
frame_url: 'https://test.thoughtspot.com/#/embed/conv-assist-answer?sessionId=session123&genNo=1&acSessionId=acSession123&acGenNo=1',
error: null,
});
});
it('should return answer data with TML when requested', async () => {
const mockAnswerResponse = {
session_identifier: 'session123',
generation_number: 1
};
const mockDataResponse = {
text: vi.fn().mockResolvedValue('col1,col2\nval1,val2')
};
const mockTMLResponse = { answer: { name: 'Test Answer' } };
const mockSessionResponse = {
sessionId: 'session123',
genNo: 1,
acSession: {
sessionId: 'acSession123',
genNo: 1
}
};
mockClient.singleAnswer = vi.fn().mockResolvedValue(mockAnswerResponse);
mockClient.exportAnswerReport = vi.fn().mockResolvedValue(mockDataResponse);
mockClient.exportUnsavedAnswerTML = vi.fn().mockResolvedValue(mockTMLResponse);
mockClient.getAnswerSession = vi.fn().mockResolvedValue(mockSessionResponse);
const result = await getAnswerForQuestion(
'What is the revenue?',
'ws1',
true,
mockClient
);
expect(mockClient.exportUnsavedAnswerTML).toHaveBeenCalledWith({
session_identifier: 'session123',
generation_number: 1,
});
expect(result).toEqual({
question: 'What is the revenue?',
...mockAnswerResponse,
data: 'col1,col2\nval1,val2',
tml: mockTMLResponse,
frame_url: 'https://test.thoughtspot.com/#/embed/conv-assist-answer?sessionId=session123&genNo=1&acSessionId=acSession123&acGenNo=1',
error: null,
});
});
it('should limit CSV data to 100 lines', async () => {
const mockAnswerResponse = {
session_identifier: 'session123',
generation_number: 1
};
// Create CSV with more than 100 lines
const longCSV = Array.from({ length: 150 }, (_, i) => `col1,col2\nval${i},val${i}`).join('\n');
const mockDataResponse = {
text: vi.fn().mockResolvedValue(longCSV)
};
const mockSessionResponse = {
sessionId: 'session123',
genNo: 1,
acSession: {
sessionId: 'acSession123',
genNo: 1
}
};
mockClient.singleAnswer = vi.fn().mockResolvedValue(mockAnswerResponse);
mockClient.exportAnswerReport = vi.fn().mockResolvedValue(mockDataResponse);
mockClient.getAnswerSession = vi.fn().mockResolvedValue(mockSessionResponse);
const result = await getAnswerForQuestion(
'What is the revenue?',
'ws1',
false,
mockClient
);
// Type guard to check if result has data property
if ('data' in result && result.data) {
const lines = result.data.split('\n');
expect(lines.length).toBeLessThanOrEqual(100);
} else {
expect.fail('Expected result to have data property');
}
});
it('should handle TML export errors gracefully', async () => {
const mockAnswerResponse = {
session_identifier: 'session123',
generation_number: 1
};
const mockDataResponse = {
text: vi.fn().mockResolvedValue('col1,col2\nval1,val2')
};
const mockSessionResponse = {
sessionId: 'session123',
genNo: 1,
acSession: {
sessionId: 'acSession123',
genNo: 1
}
};
mockClient.singleAnswer = vi.fn().mockResolvedValue(mockAnswerResponse);
mockClient.exportAnswerReport = vi.fn().mockResolvedValue(mockDataResponse);
mockClient.exportUnsavedAnswerTML = vi.fn().mockRejectedValue(new Error('TML Error'));
mockClient.getAnswerSession = vi.fn().mockResolvedValue(mockSessionResponse);
const result = await getAnswerForQuestion(
'What is the revenue?',
'ws1',
true,
mockClient
);
expect(result).toEqual({
question: 'What is the revenue?',
...mockAnswerResponse,
data: 'col1,col2\nval1,val2',
tml: null,
frame_url: 'https://test.thoughtspot.com/#/embed/conv-assist-answer?sessionId=session123&genNo=1&acSessionId=acSession123&acGenNo=1',
error: null,
});
});
it('should handle API errors', async () => {
const error = new Error('API Error');
mockClient.singleAnswer = vi.fn().mockRejectedValue(error);
const result = await getAnswerForQuestion(
'What is the revenue?',
'ws1',
false,
mockClient
);
expect(result).toEqual({
error: error,
});
});
});
describe('fetchTMLAndCreateLiveboard', () => {
it('should fetch TML and create liveboard successfully', async () => {
const answers = [
{
question: 'Question 1',
session_identifier: 'session1',
generation_number: 1
},
{
question: 'Question 2',
session_identifier: 'session2',
generation_number: 1
}
];
const mockTML1 = { answer: { name: 'Answer 1' } };
const mockTML2 = { answer: { name: 'Answer 2' } };
(mockClient as any).exportUnsavedAnswerTML = vi.fn()
.mockResolvedValueOnce(mockTML1)
.mockResolvedValueOnce(mockTML2);
mockClient.importMetadataTML = vi.fn().mockResolvedValue([
{ response: { header: { id_guid: 'liveboard123' } } }
]);
const result = await fetchTMLAndCreateLiveboard('Test Liveboard', answers, 'Test summary', mockClient);
expect((mockClient as any).exportUnsavedAnswerTML).toHaveBeenCalledTimes(2);
expect(mockClient.importMetadataTML).toHaveBeenCalledWith({
metadata_tmls: [expect.any(String)],
import_policy: 'ALL_OR_NONE',
});
expect(result).toEqual({
url: 'https://test.thoughtspot.com/#/pinboard/liveboard123',
error: null,
});
});
it('should handle TML fetch errors', async () => {
const answers = [
{
question: 'Question 1',
session_identifier: 'session1',
generation_number: 1
}
];
(mockClient as any).exportUnsavedAnswerTML = vi.fn().mockRejectedValue(new Error('TML Error'));
const result = await fetchTMLAndCreateLiveboard('Test Liveboard', answers, 'Test summary', mockClient);
expect(result).toEqual({
error: expect.any(Error),
});
});
});
describe('createLiveboard', () => {
it('should create liveboard with valid TML data', async () => {
const answers = [
{
question: 'Question 1',
tml: { answer: { name: 'Answer 1', content: 'test' } }
},
{
question: 'Question 2',
tml: { answer: { name: 'Answer 2', content: 'test' } }
}
];
mockClient.importMetadataTML = vi.fn().mockResolvedValue([
{ response: { header: { id_guid: 'liveboard123' } } }
]);
const result = await createLiveboard('Test Liveboard', answers, mockClient);
expect(mockClient.importMetadataTML).toHaveBeenCalledWith({
metadata_tmls: [expect.stringContaining('"liveboard"')],
import_policy: 'ALL_OR_NONE',
});
expect(result).toBe('https://test.thoughtspot.com/#/pinboard/liveboard123');
});
it('should filter out answers without TML', async () => {
const answers = [
{
id: 'Viz_1',
question: 'Question 1',
answer: { name: 'Answer 1' }
},
{
id: 'Viz_2',
question: 'Question 2',
answer: { name: 'Answer 2' }
}
];
mockClient.importMetadataTML = vi.fn().mockResolvedValue([
{ response: { header: { id_guid: 'liveboard123' } } }
]);
await createLiveboard('Test Liveboard', answers, mockClient);
const tmlCall = (mockClient.importMetadataTML as any).mock.calls[0][0];
const tmlData = JSON.parse(tmlCall.metadata_tmls[0]);
expect(tmlData.liveboard.visualizations).toHaveLength(2);
expect(tmlData.liveboard.layout.tiles).toHaveLength(2);
});
it('should handle import errors', async () => {
const answers = [
{
question: 'Question 1',
tml: { answer: { name: 'Answer 1' } }
}
];
mockClient.importMetadataTML = vi.fn().mockRejectedValue(new Error('Import Error'));
await expect(createLiveboard('Test Liveboard', answers, mockClient))
.rejects.toThrow('Import Error');
});
});
describe('getDataSources', () => {
it('should return data sources successfully', async () => {
const mockResponse = [
{
metadata_header: {
type: 'WORKSHEET',
name: 'Sales Data',
id: 'ws1',
description: 'Sales information',
aiAnswerGenerationDisabled: false
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Revenue Data',
id: 'ws2',
description: 'Revenue information',
aiAnswerGenerationDisabled: false
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Revenue Data aiAnswerGenerationDisabled',
id: 'ws3',
description: 'Revenue information',
aiAnswerGenerationDisabled: true
}
},
{
metadata_header: {
type: 'LOGICAL_TABLE', // This should be filtered out due to aiAnswerGenerationDisabled: true
name: 'Other Data',
id: 'lt1',
description: 'Other information',
aiAnswerGenerationDisabled: true
}
},
{
metadata_header: {
type: 'LOGICAL_TABLE', // This should be filtered out due to aiAnswerGenerationDisabled: true
name: 'Other Data 2',
id: 'lt2',
description: 'Other information',
aiAnswerGenerationDisabled: false
}
}
];
mockClient.searchMetadata = vi.fn().mockResolvedValue(mockResponse);
const result = await getDataSources(mockClient);
expect(mockClient.searchMetadata).toHaveBeenCalledWith({
metadata: [{ type: 'LOGICAL_TABLE' }],
record_size: 2000,
sort_options: {
field_name: 'LAST_ACCESSED',
order: 'DESC',
}
});
expect(result).toEqual([
{
name: 'Sales Data',
id: 'ws1',
description: 'Sales information'
},
{
name: 'Revenue Data',
id: 'ws2',
description: 'Revenue information'
},
{
name: 'Other Data 2',
id: 'lt2',
description: 'Other information'
}
]);
});
it('should handle empty response', async () => {
mockClient.searchMetadata = vi.fn().mockResolvedValue([]);
const result = await getDataSources(mockClient);
expect(result).toEqual([]);
});
it('should handle API errors', async () => {
const error = new Error('API Error');
mockClient.searchMetadata = vi.fn().mockRejectedValue(error);
await expect(getDataSources(mockClient)).rejects.toThrow('API Error');
});
});
describe('getSessionInfo', () => {
it('should return session info with production mixpanel token', async () => {
const mockResponse = {
configInfo: {
mixpanelConfig: {
production: true,
devSdkKey: 'dev-key',
prodSdkKey: 'prod-key'
},
selfClusterName: 'test-cluster',
selfClusterId: 'cluster-123'
},
userGUID: 'user-123',
userName: 'testuser',
releaseVersion: '8.0.0',
currentOrgId: 'org-123',
privileges: ['READ', 'WRITE']
};
(mockClient as any).getSessionInfo = vi.fn().mockResolvedValue(mockResponse);
const result = await getSessionInfo(mockClient);
expect((mockClient as any).getSessionInfo).toHaveBeenCalled();
expect(result).toEqual({
mixpanelToken: 'prod-key',
userGUID: 'user-123',
userName: 'testuser',
clusterName: 'test-cluster',
clusterId: 'cluster-123',
releaseVersion: '8.0.0',
currentOrgId: 'org-123',
privileges: ['READ', 'WRITE']
});
});
it('should return session info with development mixpanel token', async () => {
const mockResponse = {
configInfo: {
mixpanelConfig: {
production: false,
devSdkKey: 'dev-key',
prodSdkKey: 'prod-key'
},
selfClusterName: 'test-cluster',
selfClusterId: 'cluster-123'
},
userGUID: 'user-123',
userName: 'testuser',
releaseVersion: '8.0.0',
currentOrgId: 'org-123',
privileges: ['READ']
};
(mockClient as any).getSessionInfo = vi.fn().mockResolvedValue(mockResponse);
const result = await getSessionInfo(mockClient);
expect(result.mixpanelToken).toBe('dev-key');
});
it('should handle API errors', async () => {
const error = new Error('API Error');
(mockClient as any).getSessionInfo = vi.fn().mockRejectedValue(error);
await expect(getSessionInfo(mockClient)).rejects.toThrow('API Error');
});
});
describe('getDataSourceSuggestions', () => {
it('should return single data source when only one suggestion is available', async () => {
const mockResponse = {
dataSources: [
{
confidence: 0.85,
header: {
description: 'Sales data for the current year',
displayName: 'Sales Data',
guid: 'ds-123'
},
llmReasoning: 'This data source contains sales information relevant to your query'
}
]
};
(mockClient as any).queryGetDataSourceSuggestions = vi.fn().mockResolvedValue(mockResponse);
const result = await getDataSourceSuggestions('show me sales data', mockClient);
expect((mockClient as any).queryGetDataSourceSuggestions).toHaveBeenCalledWith('show me sales data');
expect(result).toEqual([{
confidence: 0.85,
header: {
description: 'Sales data for the current year',
displayName: 'Sales Data',
guid: 'ds-123'
},
llmReasoning: 'This data source contains sales information relevant to your query'
}]);
});
it('should return both data sources when multiple suggestions are available', async () => {
const mockResponse = {
dataSources: [
{
confidence: 0.70,
header: {
description: 'Sales data for the current year',
displayName: 'Sales Data',
guid: 'ds-123'
},
llmReasoning: 'This data source contains sales information'
},
{
confidence: 0.65,
header: {
description: 'Revenue analysis data',
displayName: 'Revenue Data',
guid: 'ds-456'
},
llmReasoning: 'This data source contains revenue information'
}
]
};
(mockClient as any).queryGetDataSourceSuggestions = vi.fn().mockResolvedValue(mockResponse);
const result = await getDataSourceSuggestions('revenue analysis', mockClient);
expect(result).toHaveLength(2);
expect(result![0].confidence).toBe(0.70);
expect(result![1].confidence).toBe(0.65);
});
it('should return both data sources regardless of confidence difference', async () => {
const mockResponse = {
dataSources: [
{
confidence: 0.90,
header: {
description: 'Sales data for the current year',
displayName: 'Sales Data',
guid: 'ds-123'
},
llmReasoning: 'High confidence match for sales data'
},
{
confidence: 0.55,
header: {
description: 'Customer data',
displayName: 'Customer Data',
guid: 'ds-456'
},
llmReasoning: 'Lower confidence match'
}
]
};
(mockClient as any).queryGetDataSourceSuggestions = vi.fn().mockResolvedValue(mockResponse);
const result = await getDataSourceSuggestions('sales performance', mockClient);
// Always return both data sources when there are multiple suggestions
expect(result).toHaveLength(2);
expect(result![0].confidence).toBe(0.90);
expect(result![0].header.guid).toBe('ds-123');
expect(result![1].confidence).toBe(0.55);
expect(result![1].header.guid).toBe('ds-456');
});
it('should sort data sources by confidence in descending order', async () => {
const mockResponse = {
dataSources: [
{
confidence: 0.80,
header: {
description: 'Higher confidence data source',
displayName: 'Data Source A',
guid: 'ds-123'
},
llmReasoning: 'Higher confidence reasoning'
},
{
confidence: 0.60,
header: {
description: 'Lower confidence data source',
displayName: 'Data Source B',
guid: 'ds-456'
},
llmReasoning: 'Lower confidence reasoning'
}
]
};
(mockClient as any).queryGetDataSourceSuggestions = vi.fn().mockResolvedValue(mockResponse);
const result = await getDataSourceSuggestions('test query', mockClient);
expect(result![0].confidence).toBe(0.80);
expect(result![0].header.guid).toBe('ds-123');
expect(result![1].confidence).toBe(0.60);
expect(result![1].header.guid).toBe('ds-456');
});
it('should return null when no data sources are available', async () => {
const mockResponse = {
dataSources: []
};
(mockClient as any).queryGetDataSourceSuggestions = vi.fn().mockResolvedValue(mockResponse);
const result = await getDataSourceSuggestions('unknown query', mockClient);
expect(result).toBeNull();
});
it('should return null when response.dataSources is null or undefined', async () => {
const mockResponse = {
dataSources: null
};
(mockClient as any).queryGetDataSourceSuggestions = vi.fn().mockResolvedValue(mockResponse);
const result = await getDataSourceSuggestions('test query', mockClient);
expect(result).toBeNull();
});
it('should handle API errors by throwing the error', async () => {
const error = new Error('GraphQL API Error');
(mockClient as any).queryGetDataSourceSuggestions = vi.fn().mockRejectedValue(error);
await expect(getDataSourceSuggestions('test query', mockClient))
.rejects.toThrow('GraphQL API Error');
});
it('should handle multiple data sources with same confidence', async () => {
const mockResponse = {
dataSources: [
{
confidence: 0.75,
header: {
description: 'First data source',
displayName: 'Data Source 1',
guid: 'ds-123'
},
llmReasoning: 'First reasoning'
},
{
confidence: 0.75,
header: {
description: 'Second data source',
displayName: 'Data Source 2',
guid: 'ds-456'
},
llmReasoning: 'Second reasoning'
}
]
};
(mockClient as any).queryGetDataSourceSuggestions = vi.fn().mockResolvedValue(mockResponse);
const result = await getDataSourceSuggestions('test query', mockClient);
expect(result).toHaveLength(2);
expect(result![0].confidence).toBe(0.75);
expect(result![1].confidence).toBe(0.75);
});
it('should use ThoughtSpotService class method correctly', async () => {
const mockResponse = {
dataSources: [
{
confidence: 0.85,
header: {
description: 'Test data source',
displayName: 'Test Data',
guid: 'ds-123'
},
llmReasoning: 'Test reasoning'
}
]
};
(mockClient as any).queryGetDataSourceSuggestions = vi.fn().mockResolvedValue(mockResponse);
const service = new ThoughtSpotService(mockClient);
const result = await service.getDataSourceSuggestions('test query');
expect((mockClient as any).queryGetDataSourceSuggestions).toHaveBeenCalledWith('test query');
expect(result).toEqual(mockResponse.dataSources);
});
});
describe('searchWorksheets', () => {
it('should search and return matching worksheets successfully', async () => {
const mockResponse = [
{
metadata_header: {
type: 'WORKSHEET',
name: 'Sales Data Analytics',
id: 'ws1',
description: 'Sales analytics worksheet'
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Revenue Analysis',
id: 'ws2',
description: 'Revenue analysis worksheet'
}
},
{
metadata_header: {
type: 'LOGICAL_TABLE', // This should be filtered out
name: 'Sales Data Table',
id: 'lt1',
description: 'Sales data table'
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Customer Data',
id: 'ws3',
description: 'Customer information worksheet'
}
}
];
mockClient.searchMetadata = vi.fn().mockResolvedValue(mockResponse);
const service = new ThoughtSpotService(mockClient);
const result = await service.searchWorksheets('sales');
expect(mockClient.searchMetadata).toHaveBeenCalledWith({
metadata: [{ type: 'LOGICAL_TABLE' }],
record_size: 100,
sort_options: {
field_name: 'NAME',
order: 'ASC',
}
});
expect(result).toEqual([
{
name: 'Sales Data Analytics',
id: 'ws1',
description: 'Sales analytics worksheet'
}
]);
});
it('should perform case-insensitive search', async () => {
const mockResponse = [
{
metadata_header: {
type: 'WORKSHEET',
name: 'Sales Data Analytics',
id: 'ws1',
description: 'Sales analytics worksheet'
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'REVENUE Analysis',
id: 'ws2',
description: 'Revenue analysis worksheet'
}
}
];
mockClient.searchMetadata = vi.fn().mockResolvedValue(mockResponse);
const service = new ThoughtSpotService(mockClient);
const result = await service.searchWorksheets('SALES');
expect(result).toEqual([
{
name: 'Sales Data Analytics',
id: 'ws1',
description: 'Sales analytics worksheet'
}
]);
});
it('should return empty array when no worksheets match search term', async () => {
const mockResponse = [
{
metadata_header: {
type: 'WORKSHEET',
name: 'Customer Data',
id: 'ws1',
description: 'Customer information worksheet'
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Product Catalog',
id: 'ws2',
description: 'Product catalog worksheet'
}
}
];
mockClient.searchMetadata = vi.fn().mockResolvedValue(mockResponse);
const service = new ThoughtSpotService(mockClient);
const result = await service.searchWorksheets('nonexistent');
expect(result).toEqual([]);
});
it('should handle empty search term', async () => {
const mockResponse = [
{
metadata_header: {
type: 'WORKSHEET',
name: 'Sales Data',
id: 'ws1',
description: 'Sales worksheet'
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Revenue Data',
id: 'ws2',
description: 'Revenue worksheet'
}
}
];
mockClient.searchMetadata = vi.fn().mockResolvedValue(mockResponse);
const service = new ThoughtSpotService(mockClient);
const result = await service.searchWorksheets('');
// Empty string should match all worksheets
expect(result).toEqual([
{
name: 'Sales Data',
id: 'ws1',
description: 'Sales worksheet'
},
{
name: 'Revenue Data',
id: 'ws2',
description: 'Revenue worksheet'
}
]);
});
it('should filter out non-worksheet metadata types', async () => {
const mockResponse = [
{
metadata_header: {
type: 'LOGICAL_TABLE',
name: 'Sales Table',
id: 'lt1',
description: 'Sales table'
}
},
{
metadata_header: {
type: 'LIVEBOARD',
name: 'Sales Dashboard',
id: 'lb1',
description: 'Sales dashboard'
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Sales Data',
id: 'ws1',
description: 'Sales worksheet'
}
}
];
mockClient.searchMetadata = vi.fn().mockResolvedValue(mockResponse);
const service = new ThoughtSpotService(mockClient);
const result = await service.searchWorksheets('sales');
expect(result).toEqual([
{
name: 'Sales Data',
id: 'ws1',
description: 'Sales worksheet'
}
]);
});
it('should handle API errors', async () => {
const error = new Error('Search API Error');
mockClient.searchMetadata = vi.fn().mockRejectedValue(error);
const service = new ThoughtSpotService(mockClient);
await expect(service.searchWorksheets('sales')).rejects.toThrow('Search API Error');
});
it('should handle empty API response', async () => {
mockClient.searchMetadata = vi.fn().mockResolvedValue([]);
const service = new ThoughtSpotService(mockClient);
const result = await service.searchWorksheets('sales');
expect(result).toEqual([]);
});
it('should handle partial matches in worksheet names', async () => {
const mockResponse = [
{
metadata_header: {
type: 'WORKSHEET',
name: 'Q1 Sales Performance',
id: 'ws1',
description: 'Q1 sales performance worksheet'
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Annual Sales Report',
id: 'ws2',
description: 'Annual sales report worksheet'
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Customer Data',
id: 'ws3',
description: 'Customer information worksheet'
}
}
];
mockClient.searchMetadata = vi.fn().mockResolvedValue(mockResponse);
const service = new ThoughtSpotService(mockClient);
const result = await service.searchWorksheets('sales');
expect(result).toEqual([
{
name: 'Q1 Sales Performance',
id: 'ws1',
description: 'Q1 sales performance worksheet'
},
{
name: 'Annual Sales Report',
id: 'ws2',
description: 'Annual sales report worksheet'
}
]);
});
it('should handle search with special characters', async () => {
const mockResponse = [
{
metadata_header: {
type: 'WORKSHEET',
name: 'Sales & Marketing Data',
id: 'ws1',
description: 'Sales and marketing worksheet'
}
},
{
metadata_header: {
type: 'WORKSHEET',
name: 'Revenue Analysis',
id: 'ws2',
description: 'Revenue analysis worksheet'
}
}
];
mockClient.searchMetadata = vi.fn().mockResolvedValue(mockResponse);
const service = new ThoughtSpotService(mockClient);
const result = await service.searchWorksheets('&');
expect(result).toEqual([
{
name: 'Sales & Marketing Data',
id: 'ws1',
description: 'Sales and marketing worksheet'
}
]);
});
});
});