import { describe, it, expect, beforeEach, vi } from 'vitest'; import { setActivePinia, createPinia } from 'pinia'; vi.mock('../../resources/js/api/autopodbor'); import * as api from '../../resources/js/api/autopodbor'; import { useAutopodborStore } from '../../resources/js/stores/autopodborStore'; describe('autopodborStore', () => { beforeEach(() => { setActivePinia(createPinia()); vi.clearAllMocks(); }); it('loadState заполняет enabled/prices/runs', async () => { (api.fetchState as ReturnType).mockResolvedValue({ enabled: true, prices: { search: '500', study: '300' }, runs: [{ id: 1, kind: 'search', status: 'done' }], }); const s = useAutopodborStore(); await s.loadState(); expect(s.enabled).toBe(true); expect(s.prices.search).toBe('500'); expect(s.runs.length).toBe(1); }); it('search кладёт currentRun', async () => { (api.startSearch as ReturnType).mockResolvedValue({ id: 9, kind: 'search', status: 'queued' }); const s = useAutopodborStore(); await s.search({ region_code: 16, examples: ['okna.ru'], about_self: [], include_federal: true }); expect(api.startSearch).toHaveBeenCalled(); expect(s.currentRun?.id).toBe(9); }); it('loadCompetitor кладёт competitor и sources', async () => { (api.fetchCompetitor as ReturnType).mockResolvedValue({ competitor: { id: 3, name: 'Окна' }, sources: [{ id: 1, signal_type: 'site' }], }); const s = useAutopodborStore(); await s.loadCompetitor(3); expect(s.competitor?.id).toBe(3); expect(s.sources.length).toBe(1); }); it('pollRun опрашивает до терминального статуса', async () => { vi.useFakeTimers(); (api.fetchRun as ReturnType) .mockResolvedValueOnce({ id: 5, kind: 'search', status: 'running' }) .mockResolvedValueOnce({ id: 5, kind: 'search', status: 'done' }); const s = useAutopodborStore(); const p = s.pollRun(5); // прокрутить таймеры и микрозадачи await vi.runAllTimersAsync(); const final = await p; expect(final.status).toBe('done'); expect(s.currentRun?.status).toBe('done'); vi.useRealTimers(); }); it('makeProjects возвращает созданные проекты', async () => { (api.createProjects as ReturnType).mockResolvedValue([{ id: 1, name: 'Окна Комфорт' }]); const s = useAutopodborStore(); const res = await s.makeProjects({ source_ids: [1], regions: [16], daily_limit_target: 20, delivery_days_mask: 127, launch: false, }); expect(res).toHaveLength(1); }); // ——— «Конкурентное поле»: рабочее место (два ящика) ——— it('loadField кладёт конкурентов поля', async () => { (api.fetchField as ReturnType).mockResolvedValue([ { id: 7, name: 'Окна', box: 'field', counters: { sources: 2, projects_created: 1, projects_in_work: 1 }, sources: [] }, ]); const s = useAutopodborStore(); await s.loadField(); expect(s.field).toHaveLength(1); expect(s.field[0].counters.projects_in_work).toBe(1); }); it('moveCompetitorToBox в proposal убирает конкурента из поля', async () => { (api.fetchField as ReturnType).mockResolvedValue([ { id: 7, name: 'Окна', box: 'field', counters: { sources: 0, projects_created: 0, projects_in_work: 0 }, sources: [] }, ]); (api.setCompetitorBox as ReturnType).mockResolvedValue({ id: 7, box: 'proposal' }); const s = useAutopodborStore(); await s.loadField(); await s.moveCompetitorToBox(7, 'proposal'); expect(api.setCompetitorBox).toHaveBeenCalledWith(7, 'proposal'); expect(s.field.find((c) => c.id === 7)).toBeUndefined(); }); it('removeCompetitor убирает конкурента из поля', async () => { (api.fetchField as ReturnType).mockResolvedValue([ { id: 7, name: 'Окна', box: 'field', counters: { sources: 0, projects_created: 0, projects_in_work: 0 }, sources: [] }, ]); (api.deleteCompetitor as ReturnType).mockResolvedValue(undefined); const s = useAutopodborStore(); await s.loadField(); await s.removeCompetitor(7); expect(api.deleteCompetitor).toHaveBeenCalledWith(7); expect(s.field).toHaveLength(0); }); it('editCompetitor обновляет поля конкурента на месте', async () => { (api.fetchField as ReturnType).mockResolvedValue([ { id: 7, name: 'Старое', box: 'field', counters: { sources: 0, projects_created: 0, projects_in_work: 0 }, sources: [] }, ]); (api.updateCompetitor as ReturnType).mockResolvedValue({ id: 7, name: 'Новое', relevance_pct: 88 }); const s = useAutopodborStore(); await s.loadField(); await s.editCompetitor(7, { name: 'Новое', relevance_pct: 88 }); expect(s.field[0].name).toBe('Новое'); expect(s.field[0].relevance_pct).toBe(88); }); it('addFieldCompetitor добавляет нового конкурента в поле', async () => { (api.createManualCompetitor as ReturnType).mockResolvedValue({ id: 99, name: 'Ромашка', box: 'field', origin: 'manual', }); const s = useAutopodborStore(); const c = await s.addFieldCompetitor({ name: 'Ромашка' }); expect(c.id).toBe(99); expect(s.field.find((x) => x.id === 99)?.name).toBe('Ромашка'); }); it('changeProjectSource зовёт ручку проектов и возвращает сообщение', async () => { (api.changeProjectSource as ReturnType).mockResolvedValue({ source_change_message: 'Лиды по старому источнику придут до 30.06, дальше — по новому.', }); const s = useAutopodborStore(); const res = await s.changeProjectSource(100, 'new.ru'); expect(api.changeProjectSource).toHaveBeenCalledWith(100, 'new.ru'); expect(res.source_change_message).toContain('по новому'); }); it('removeSource убирает источник из карточки конкурента в поле', async () => { (api.fetchField as ReturnType).mockResolvedValue([ { id: 7, name: 'Окна', box: 'field', counters: { sources: 1, projects_created: 0, projects_in_work: 0 }, sources: [{ id: 50, competitor_id: 7, signal_type: 'site', identifier: 'a.ru', box: 'field', project: null }], }, ]); (api.deleteSource as ReturnType).mockResolvedValue(undefined); const s = useAutopodborStore(); await s.loadField(); await s.removeSource(7, 50); expect(api.deleteSource).toHaveBeenCalledWith(50); expect(s.field[0].sources).toHaveLength(0); }); });