learning_ai_invt_trdg/web/src/tabs/TabHelpers.test.ts

137 lines
5.4 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import {
filterHistoryByProfileAndDate,
paginateHistory,
parseDateEnd,
parseDateStart,
sortHistoryByTimestamp,
toTimestampMs
} from './HistoryTab';
import { buildClonedEntryPayload, filterEntriesByTab } from './EntriesTab';
import { buildConfigUpsertPayload, cloneConfigItems, hasConfigChanges } from './ConfigTab';
import { mapProfileToFormData, parseNumericSettings } from './SettingsTab';
vi.mock('../lib/supabaseClient', () => ({
supabase: {
from: vi.fn(() => ({
select: vi.fn(),
order: vi.fn(),
eq: vi.fn(),
insert: vi.fn(),
update: vi.fn(),
upsert: vi.fn(),
delete: vi.fn()
}))
}
}));
vi.mock('../components/AuthContext', () => ({
useAuth: () => ({
user: null,
profile: { role: 'admin' },
refreshProfile: vi.fn()
})
}));
describe('tab helper coverage', () => {
it('parses and normalizes history timestamps and date bounds', () => {
expect(toTimestampMs(1700000000000)).toBe(1700000000000);
expect(toTimestampMs('2026-02-15T10:00:00.000Z')).toBeGreaterThan(0);
expect(toTimestampMs(undefined, '2026-02-15T10:00:00.000Z')).toBeGreaterThan(0);
expect(toTimestampMs('invalid')).toBe(0);
expect(parseDateStart('')).toBeNull();
expect(parseDateEnd('')).toBeNull();
expect(parseDateStart('2026-02-15')).toBeGreaterThan(0);
expect(parseDateEnd('2026-02-15')).toBeGreaterThan(0);
});
it('filters, sorts and paginates history rows deterministically', () => {
const records = [
{ id: 'b', profile_id: 'p1', timestamp: 20, created_at: '', symbol: 'BTC', side: 'BUY', entry_price: 1, exit_price: 2, pnl: 1, pnl_percent: 1, reason: 'r' },
{ id: 'a', profile_id: 'p1', timestamp: 20, created_at: '', symbol: 'BTC', side: 'BUY', entry_price: 1, exit_price: 2, pnl: 1, pnl_percent: 1, reason: 'r' },
{ id: 'c', profile_id: 'p2', timestamp: 10, created_at: '', symbol: 'ETH', side: 'SELL', entry_price: 2, exit_price: 1, pnl: -1, pnl_percent: -1, reason: 'r' }
] as any;
const filtered = filterHistoryByProfileAndDate(records, 'p1', { from: 0, to: 25 });
expect(filtered).toHaveLength(2);
const desc = sortHistoryByTimestamp(filtered, 'desc');
const asc = sortHistoryByTimestamp(filtered, 'asc');
expect(desc[0].id).toBe('b');
expect(asc[0].id).toBe('a');
const paged = paginateHistory(desc, 1, 1);
expect(paged).toHaveLength(1);
expect(paged[0].id).toBe('b');
});
it('filters entries by tab and builds clone payload safely', () => {
const entries = [
{ stock_instance_id: '1', is_real_trade: false, active: true, status: 'active' },
{ stock_instance_id: '2', is_real_trade: false, active: false, status: 'sellCompleted' },
{ stock_instance_id: '3', is_real_trade: true, active: true, status: 'active' }
] as any;
expect(filterEntriesByTab(entries, 'paperActive')).toHaveLength(1);
expect(filterEntriesByTab(entries, 'paperCompleted')).toHaveLength(1);
expect(filterEntriesByTab(entries, 'realActive')).toHaveLength(1);
expect(filterEntriesByTab(entries, 'unknown')).toHaveLength(0);
const cloned = buildClonedEntryPayload({
stock_instance_id: 'old',
id: 'legacy',
created_at: 't1',
updated_at: 't2',
symbol: 'BTC/USDT',
active: true,
user_id: 'u1',
status: 'active',
is_crypto: true,
is_real_trade: false
} as any, 'new-id');
expect(cloned.stock_instance_id).toBe('new-id');
expect(cloned.active).toBe(false);
expect(cloned.status).toBe('active');
expect((cloned as any).id).toBeUndefined();
});
it('clones, diffs and maps config upsert payloads', () => {
const configs = [{ key: 'A', value: '1', description: 'd' }];
const cloned = cloneConfigItems(configs as any);
cloned[0].value = '2';
expect(configs[0].value).toBe('1');
expect(hasConfigChanges(configs as any, cloned as any)).toBe(true);
expect(hasConfigChanges(configs as any, configs as any)).toBe(false);
const payload = buildConfigUpsertPayload(configs as any, '2026-02-16T00:00:00.000Z');
expect(payload[0]).toEqual({ key: 'A', value: '1', updated_at: '2026-02-16T00:00:00.000Z' });
});
it('maps settings form data and validates numeric fields', () => {
const mapped = mapProfileToFormData({
first_name: 'Sam',
last_name: 'Trader',
trade_enable: true,
drop_threshold_for_buy: 0.05,
gain_threshold_for_sell: 0.02,
market_poll_interval_in_seconds: 30000
});
expect(mapped.first_name).toBe('Sam');
expect(mapped.trade_enable).toBe(true);
expect(mapped.drop_threshold_for_buy).toBe('0.05');
const parsed = parseNumericSettings(mapped);
expect(parsed.buyThreshold).toBe(0.05);
expect(parsed.sellThreshold).toBe(0.02);
expect(parsed.interval).toBe(30000);
expect(() => parseNumericSettings({
...mapped,
drop_threshold_for_buy: 'x'
})).toThrow('Please enter valid numbers for thresholds and interval.');
});
});