fix linting

This commit is contained in:
Nate Kelley 2025-09-26 10:56:23 -06:00
parent 4ceb412e37
commit c2d093291b
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
7 changed files with 440 additions and 19 deletions

View File

@ -0,0 +1,195 @@
import { render, screen } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { HomePageController } from './HomePageController';
// Mock the dependencies
vi.mock('@/api/buster_rest/users/useGetUserInfo', () => ({
useGetUserBasicInfo: vi.fn(),
}));
vi.mock('./useNewChatWarning', () => ({
useNewChatWarning: vi.fn(),
}));
vi.mock('./NewChatWarning', () => ({
NewChatWarning: vi.fn(({ showWarning, hasDatasets, hasDatasources, isAdmin }) => (
<div data-testid="new-chat-warning">
Warning Component - showWarning: {showWarning.toString()}, hasDatasets:{' '}
{hasDatasets.toString()}, hasDatasources: {hasDatasources.toString()}, isAdmin:{' '}
{isAdmin?.toString() || 'undefined'}
</div>
)),
}));
vi.mock('./NewChatInput', () => ({
NewChatInput: vi.fn(({ initialValue, autoSubmit }) => (
<div data-testid="new-chat-input">
Chat Input - initialValue: {initialValue || 'none'}, autoSubmit:{' '}
{autoSubmit?.toString() || 'false'}
</div>
)),
}));
// Mock ClientOnly to render children directly in tests
vi.mock('@tanstack/react-router', () => ({
ClientOnly: vi.fn(({ children }) => <>{children}</>),
}));
import { useGetUserBasicInfo } from '@/api/buster_rest/users/useGetUserInfo';
import { useNewChatWarning } from './useNewChatWarning';
const mockUseGetUserBasicInfo = vi.mocked(useGetUserBasicInfo);
const mockUseNewChatWarning = vi.mocked(useNewChatWarning);
describe('HomePageController', () => {
beforeEach(() => {
// Default user info
mockUseGetUserBasicInfo.mockReturnValue({
id: 'user-1',
name: 'John Doe',
email: 'john.doe@example.com',
avatar_url: null,
created_at: '2023-01-01T00:00:00Z',
updated_at: '2023-01-01T00:00:00Z',
attributes: {
organization_id: 'org-1',
organization_role: 'querier',
user_email: 'john.doe@example.com',
user_id: 'user-1',
},
favorites: [],
});
// Mock time to ensure consistent greeting
vi.useFakeTimers();
vi.setSystemTime(new Date('2023-01-01 10:00:00')); // 10 AM - morning
});
afterEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
});
it('should render NewChatWarning when showWarning is true', () => {
// Mock the hook to return showWarning: true
mockUseNewChatWarning.mockReturnValue({
showWarning: true,
hasDatasets: false,
hasDatasources: false,
isFetched: true,
isAdmin: true,
userRole: 'workspace_admin',
});
render(<HomePageController initialValue="test" autoSubmit={true} />);
// Should show the warning component
expect(screen.getByTestId('new-chat-warning')).toBeInTheDocument();
expect(screen.getByText(/Warning Component.*showWarning: true/)).toBeInTheDocument();
// Should NOT show the main interface components
expect(screen.queryByTestId('new-chat-input')).not.toBeInTheDocument();
expect(screen.queryByText('Good morning, John Doe')).not.toBeInTheDocument();
expect(screen.queryByText('How can I help you today?')).not.toBeInTheDocument();
});
it('should render main interface when showWarning is false', () => {
// Mock the hook to return showWarning: false
mockUseNewChatWarning.mockReturnValue({
showWarning: false,
hasDatasets: true,
hasDatasources: true,
isFetched: true,
isAdmin: false,
userRole: 'querier',
});
render(<HomePageController initialValue="hello world" autoSubmit={false} />);
// Should show the main interface components
expect(screen.getByText('Good morning, John Doe')).toBeInTheDocument();
expect(screen.getByText('How can I help you today?')).toBeInTheDocument();
expect(screen.getByTestId('new-chat-input')).toBeInTheDocument();
expect(
screen.getByText(/Chat Input.*initialValue: hello world.*autoSubmit: false/)
).toBeInTheDocument();
// Should NOT show the warning component
expect(screen.queryByTestId('new-chat-warning')).not.toBeInTheDocument();
});
it('should pass correct props to NewChatInput', () => {
mockUseNewChatWarning.mockReturnValue({
showWarning: false,
hasDatasets: true,
hasDatasources: true,
isFetched: true,
isAdmin: false,
userRole: 'querier',
});
render(<HomePageController initialValue="custom input" autoSubmit={true} />);
expect(
screen.getByText(/Chat Input.*initialValue: custom input.*autoSubmit: true/)
).toBeInTheDocument();
});
it('should pass all newChatWarningProps to NewChatWarning', () => {
const warningProps = {
showWarning: true,
hasDatasets: false,
hasDatasources: true,
isFetched: true,
isAdmin: true,
userRole: 'workspace_admin' as const,
};
mockUseNewChatWarning.mockReturnValue(warningProps);
render(<HomePageController />);
expect(
screen.getByText(
/Warning Component.*showWarning: true.*hasDatasets: false.*hasDatasources: true.*isAdmin: true/
)
).toBeInTheDocument();
});
describe('greeting logic', () => {
beforeEach(() => {
mockUseNewChatWarning.mockReturnValue({
showWarning: false,
hasDatasets: true,
hasDatasources: true,
isFetched: true,
isAdmin: false,
userRole: 'querier',
});
});
it('should show morning greeting at 10 AM', () => {
vi.setSystemTime(new Date('2023-01-01 10:00:00'));
render(<HomePageController />);
expect(screen.getByText('Good morning, John Doe')).toBeInTheDocument();
});
it('should show afternoon greeting at 3 PM', () => {
vi.setSystemTime(new Date('2023-01-01 15:00:00'));
render(<HomePageController />);
expect(screen.getByText('Good afternoon, John Doe')).toBeInTheDocument();
});
it('should show evening greeting at 8 PM', () => {
vi.setSystemTime(new Date('2023-01-01 20:00:00'));
render(<HomePageController />);
expect(screen.getByText('Good evening, John Doe')).toBeInTheDocument();
});
it('should show night greeting at 2 AM', () => {
vi.setSystemTime(new Date('2023-01-01 02:00:00'));
render(<HomePageController />);
expect(screen.getByText('Good night, John Doe')).toBeInTheDocument();
});
});
});

View File

@ -0,0 +1,185 @@
import { renderHook } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useNewChatWarning } from './useNewChatWarning';
// Mock the API hooks
vi.mock('@/api/buster_rest/data_source', () => ({
useListDatasources: vi.fn(),
}));
vi.mock('@/api/buster_rest/datasets', () => ({
useGetDatasets: vi.fn(),
}));
vi.mock('@/api/buster_rest/users/useGetUserInfo', () => ({
useIsUserAdmin: vi.fn(),
useGetUserRole: vi.fn(),
}));
import { useListDatasources } from '@/api/buster_rest/data_source';
import { useGetDatasets } from '@/api/buster_rest/datasets';
import { useGetUserRole, useIsUserAdmin } from '@/api/buster_rest/users/useGetUserInfo';
const mockUseListDatasources = vi.mocked(useListDatasources);
const mockUseGetDatasets = vi.mocked(useGetDatasets);
const mockUseIsUserAdmin = vi.mocked(useIsUserAdmin);
const mockUseGetUserRole = vi.mocked(useGetUserRole);
describe('useNewChatWarning', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should show warning when datasets are empty and data is fetched', () => {
mockUseGetDatasets.mockReturnValue({
data: [],
isFetched: true,
} as any);
mockUseListDatasources.mockReturnValue({
data: [{ id: '1', name: 'Test Datasource' }],
isFetched: true,
} as any);
mockUseIsUserAdmin.mockReturnValue(true);
mockUseGetUserRole.mockReturnValue('workspace_admin');
const { result } = renderHook(() => useNewChatWarning());
expect(result.current.showWarning).toBe(true);
expect(result.current.hasDatasets).toBe(false);
expect(result.current.hasDatasources).toBe(true);
expect(result.current.isFetched).toBe(true);
});
it('should show warning when datasources are empty and data is fetched', () => {
mockUseGetDatasets.mockReturnValue({
data: [{ id: '1', name: 'Test Dataset' }],
isFetched: true,
} as any);
mockUseListDatasources.mockReturnValue({
data: [],
isFetched: true,
} as any);
mockUseIsUserAdmin.mockReturnValue(false);
mockUseGetUserRole.mockReturnValue('querier');
const { result } = renderHook(() => useNewChatWarning());
expect(result.current.showWarning).toBe(true);
expect(result.current.hasDatasets).toBe(true);
expect(result.current.hasDatasources).toBe(false);
expect(result.current.isFetched).toBe(true);
});
it('should show warning when both datasets and datasources are empty', () => {
mockUseGetDatasets.mockReturnValue({
data: [],
isFetched: true,
} as any);
mockUseListDatasources.mockReturnValue({
data: [],
isFetched: true,
} as any);
mockUseIsUserAdmin.mockReturnValue(true);
mockUseGetUserRole.mockReturnValue('workspace_admin');
const { result } = renderHook(() => useNewChatWarning());
expect(result.current.showWarning).toBe(true);
expect(result.current.hasDatasets).toBe(false);
expect(result.current.hasDatasources).toBe(false);
expect(result.current.isFetched).toBe(true);
});
it('should not show warning when both datasets and datasources have data', () => {
mockUseGetDatasets.mockReturnValue({
data: [{ id: '1', name: 'Test Dataset' }],
isFetched: true,
} as any);
mockUseListDatasources.mockReturnValue({
data: [{ id: '1', name: 'Test Datasource' }],
isFetched: true,
} as any);
mockUseIsUserAdmin.mockReturnValue(false);
mockUseGetUserRole.mockReturnValue('viewer');
const { result } = renderHook(() => useNewChatWarning());
expect(result.current.showWarning).toBe(false);
expect(result.current.hasDatasets).toBe(true);
expect(result.current.hasDatasources).toBe(true);
expect(result.current.isFetched).toBe(true);
});
it('should not show warning when datasets are not yet fetched', () => {
mockUseGetDatasets.mockReturnValue({
data: undefined,
isFetched: false,
} as any);
mockUseListDatasources.mockReturnValue({
data: [],
isFetched: true,
} as any);
mockUseIsUserAdmin.mockReturnValue(true);
mockUseGetUserRole.mockReturnValue('workspace_admin');
const { result } = renderHook(() => useNewChatWarning());
expect(result.current.showWarning).toBe(false);
expect(result.current.isFetched).toBe(false);
});
it('should not show warning when datasources are not yet fetched', () => {
mockUseGetDatasets.mockReturnValue({
data: [],
isFetched: true,
} as any);
mockUseListDatasources.mockReturnValue({
data: undefined,
isFetched: false,
} as any);
mockUseIsUserAdmin.mockReturnValue(false);
mockUseGetUserRole.mockReturnValue('querier');
const { result } = renderHook(() => useNewChatWarning());
expect(result.current.showWarning).toBe(false);
expect(result.current.isFetched).toBe(false);
});
it('should not show warning when neither datasets nor datasources are fetched', () => {
mockUseGetDatasets.mockReturnValue({
data: undefined,
isFetched: false,
} as any);
mockUseListDatasources.mockReturnValue({
data: undefined,
isFetched: false,
} as any);
mockUseIsUserAdmin.mockReturnValue(true);
mockUseGetUserRole.mockReturnValue('workspace_admin');
const { result } = renderHook(() => useNewChatWarning());
expect(result.current.showWarning).toBe(false);
expect(result.current.isFetched).toBe(false);
});
it('should correctly return admin status and user role', () => {
mockUseGetDatasets.mockReturnValue({
data: [{ id: '1', name: 'Test Dataset' }],
isFetched: true,
} as any);
mockUseListDatasources.mockReturnValue({
data: [{ id: '1', name: 'Test Datasource' }],
isFetched: true,
} as any);
mockUseIsUserAdmin.mockReturnValue(true);
mockUseGetUserRole.mockReturnValue('data_admin');
const { result } = renderHook(() => useNewChatWarning());
expect(result.current.isAdmin).toBe(true);
expect(result.current.userRole).toBe('data_admin');
expect(result.current.showWarning).toBe(false);
});
});

View File

@ -0,0 +1,27 @@
import { describe, expect, it } from 'vitest';
import { checkIfUserIsAdmin } from './user';
describe('checkIfUserIsAdmin', () => {
it('should return false when userOrganization is undefined', () => {
expect(checkIfUserIsAdmin(undefined)).toBe(false);
});
it('should return false when userOrganization is null', () => {
expect(checkIfUserIsAdmin(null)).toBe(false);
});
it('should return true when user role is data_admin', () => {
const userOrganization = { role: 'data_admin' as const };
expect(checkIfUserIsAdmin(userOrganization)).toBe(true);
});
it('should return true when user role is workspace_admin', () => {
const userOrganization = { role: 'workspace_admin' as const };
expect(checkIfUserIsAdmin(userOrganization)).toBe(true);
});
it('should return false when user role is neither data_admin nor workspace_admin', () => {
const userOrganization = { role: 'restricted_querier' as const };
expect(checkIfUserIsAdmin(userOrganization)).toBe(false);
});
});

View File

@ -98,10 +98,10 @@ export function createAnalystAgent(analystAgentOptions: AnalystAgentOptions) {
const docsSystemMessage = docsContent
? ({
role: 'system',
content: `<data_catalog_docs>\n${docsContent}\n</data_catalog_docs>`,
providerOptions: DEFAULT_ANTHROPIC_OPTIONS,
} as ModelMessage)
role: 'system',
content: `<data_catalog_docs>\n${docsContent}\n</data_catalog_docs>`,
providerOptions: DEFAULT_ANTHROPIC_OPTIONS,
} as ModelMessage)
: null;
async function stream({ messages }: AnalystStreamOptions) {
@ -134,19 +134,19 @@ export function createAnalystAgent(analystAgentOptions: AnalystAgentOptions) {
// Create analyst instructions system message with proper escaping
const analystInstructionsMessage = analystInstructions
? ({
role: 'system',
content: `<organization_instructions>\n${analystInstructions}\n</organization_instructions>`,
providerOptions: DEFAULT_ANTHROPIC_OPTIONS,
} as ModelMessage)
role: 'system',
content: `<organization_instructions>\n${analystInstructions}\n</organization_instructions>`,
providerOptions: DEFAULT_ANTHROPIC_OPTIONS,
} as ModelMessage)
: null;
// Create user personalization system message
const userPersonalizationSystemMessage = userPersonalizationMessageContent
? ({
role: 'system',
content: userPersonalizationMessageContent,
providerOptions: DEFAULT_ANTHROPIC_OPTIONS,
} as ModelMessage)
role: 'system',
content: userPersonalizationMessageContent,
providerOptions: DEFAULT_ANTHROPIC_OPTIONS,
} as ModelMessage)
: null;
return wrapTraced(

View File

@ -83,10 +83,14 @@ describe('Analyst Agent Instructions', () => {
expect(result).toContain('MANDATORY SQL NAMING CONVENTIONS');
// Ensure table references require full qualification
expect(result).toContain('All Table References: MUST be fully qualified: `DATABASE_NAME.SCHEMA_NAME.TABLE_NAME`');
expect(result).toContain(
'All Table References: MUST be fully qualified: `DATABASE_NAME.SCHEMA_NAME.TABLE_NAME`'
);
// Ensure column references use table aliases (not full qualifiers)
expect(result).toContain('All Column References: MUST be qualified with their table alias (e.g., `c.customerid`)');
expect(result).toContain(
'All Column References: MUST be qualified with their table alias (e.g., `c.customerid`)'
);
// Ensure examples show table alias usage without full qualification
expect(result).toContain('c.customerid');

View File

@ -151,16 +151,23 @@ describe('Think and Prep Agent Instructions', () => {
['investigation', 'investigation'],
])('SQL naming conventions in %s mode', (modeName, mode) => {
it(`should contain mandatory SQL naming conventions in ${modeName} mode`, () => {
const result = getThinkAndPrepAgentSystemPrompt('Test guidance', mode as 'standard' | 'investigation');
const result = getThinkAndPrepAgentSystemPrompt(
'Test guidance',
mode as 'standard' | 'investigation'
);
// Check for MANDATORY SQL NAMING CONVENTIONS section
expect(result).toContain('MANDATORY SQL NAMING CONVENTIONS');
// Ensure table references require full qualification
expect(result).toContain('All Table References: MUST be fully qualified: `DATABASE_NAME.SCHEMA_NAME.TABLE_NAME`');
expect(result).toContain(
'All Table References: MUST be fully qualified: `DATABASE_NAME.SCHEMA_NAME.TABLE_NAME`'
);
// Ensure column references use table aliases (not full qualifiers)
expect(result).toContain('All Column References: MUST be qualified with their table alias (e.g., `c.customerid`)');
expect(result).toContain(
'All Column References: MUST be qualified with their table alias (e.g., `c.customerid`)'
);
// Ensure examples show table alias usage without full qualification
expect(result).toContain('c.customerid');
@ -172,7 +179,10 @@ describe('Think and Prep Agent Instructions', () => {
});
it(`should use column names qualified with table aliases in ${modeName} mode`, () => {
const result = getThinkAndPrepAgentSystemPrompt('Test guidance', mode as 'standard' | 'investigation');
const result = getThinkAndPrepAgentSystemPrompt(
'Test guidance',
mode as 'standard' | 'investigation'
);
// Check for the updated description
expect(result).toContain('Use column names qualified with table aliases');

View File

@ -14,7 +14,7 @@ export const DEFAULT_ANTHROPIC_OPTIONS = {
additionalModelRequestFields: {
anthropic_beta: ['fine-grained-tool-streaming-2025-05-14'],
},
}
},
};
export const DEFAULT_OPENAI_OPTIONS = {