You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
7.9 KiB

import { shallowMount, createLocalVue } from '@vue/test-utils'
import ElementUI from 'element-ui'
import News from '@/views/companyNews/index.vue'
import { getAutoSettings, saveAutoSettings, newsCategories, saveCategorySettings, listNews, companies, latestBatch, startBatch, reviewNews, showNews, saveNews } from '@/api/companyNews'
jest.mock('@/api/companyNews', () => ({
getAutoSettings: jest.fn(), saveAutoSettings: jest.fn(), newsCategories: jest.fn(), saveCategorySettings: jest.fn(), listNews: jest.fn(), companies: jest.fn(), showNews: jest.fn(), saveNews: jest.fn(), reviewNews: jest.fn(),
newsLogs: jest.fn(), startBatch: jest.fn(), latestBatch: jest.fn(), listBatches: jest.fn(), listTasks: jest.fn()
}))
const localVue = createLocalVue()
localVue.use(ElementUI)
const flush = async() => { await Promise.resolve(); await Promise.resolve(); await Promise.resolve() }
let wrapper
const create = () => shallowMount(News, {
localVue, stubs: ['lx-header', 'my-tinymce'],
mocks: {
$message: { success: jest.fn(), warning: jest.fn() },
$confirm: jest.fn(() => Promise.resolve()),
$prompt: jest.fn(() => Promise.resolve({ value: '不相关' }))
}
})
describe('Enterprise news management', () => {
beforeEach(() => {
jest.useFakeTimers()
newsCategories.mockResolvedValue({ categories: [{ code: '12014', name: '产品信息', count: 317 }], retained_categories: ['12014'] })
saveCategorySettings.mockResolvedValue({})
getAutoSettings.mockResolvedValue({ enabled: false, start_time: '22:00', group_size: 10, timezone: 'Asia/Shanghai' })
saveAutoSettings.mockResolvedValue({})
listNews.mockResolvedValue({ data: [], total: 0 })
companies.mockResolvedValue({ data: [], total: 0, current_page: 1, last_page: 1 })
latestBatch.mockResolvedValue({ batch: null })
})
afterEach(() => {
if (wrapper) wrapper.destroy()
jest.clearAllMocks(); jest.clearAllTimers(); jest.useRealTimers()
})
it('defaults to retained and submits multiple categories and retention settings', async() => {
wrapper = create(); await flush()
expect(listNews).toHaveBeenCalledWith(expect.objectContaining({ review_status: 'retained' }))
wrapper.vm.query.category_codes = ['12014', '00000']
wrapper.vm.search(); await flush()
expect(listNews).toHaveBeenLastCalledWith(expect.objectContaining({ category_codes: ['12014', '00000'] }))
await wrapper.vm.openSettings()
expect(wrapper.vm.retainedCategories).toEqual(['12014'])
wrapper.setData({ retainedCategories: [] })
await wrapper.vm.saveSettings()
expect(saveCategorySettings).toHaveBeenCalledWith({ retained_categories: [] })
wrapper.vm.reset()
expect(wrapper.vm.query.category_codes).toEqual([])
expect(wrapper.vm.query.review_status).toBe('retained')
})
it('loads and saves the daily schedule without starting a collection directly', async() => {
wrapper = create(); await flush()
await wrapper.vm.openAutoSettings()
expect(wrapper.vm.autoForm).toEqual({ enabled: false, start_time: '22:00', group_size: 10 })
wrapper.setData({ autoForm: { enabled: true, start_time: '22:30', group_size: 5 }})
await wrapper.vm.saveAutomation()
expect(saveAutoSettings).toHaveBeenCalledWith({ enabled: true, start_time: '22:30', group_size: 5 })
expect(startBatch).not.toHaveBeenCalled()
expect(wrapper.vm.autoVisible).toBe(false)
wrapper.setData({ autoForm: { enabled: true, start_time: '22:30', group_size: 0 }})
await wrapper.vm.saveAutomation()
expect(saveAutoSettings).toHaveBeenCalledTimes(1)
})
it('restores running progress and prevents starting a duplicate batch', async() => {
latestBatch.mockResolvedValue({ batch: { id: 3, status: 'running', total: 100, processed: 40, success: 38, failed: 2, inserted: 8, progress: 40 }})
wrapper = create(); await flush()
expect(wrapper.vm.running).toBe(true)
expect(wrapper.text()).toContain('100')
await wrapper.vm.start()
expect(startBatch).not.toHaveBeenCalled()
expect(wrapper.find('el-progress-stub').attributes('percentage')).toBe('40')
})
it('sends company and inclusive publication date filters', async() => {
wrapper = create(); await flush()
wrapper.vm.query.company_id = 41
wrapper.vm.dates = ['2026-10-01', '2026-10-09']
wrapper.vm.search(); await flush()
expect(listNews).toHaveBeenLastCalledWith(expect.objectContaining({ company_id: 41, date_from: '2026-10-01', date_to: '2026-10-09', page: 1 }))
})
it('starts all-eligible-company batch without leaking list filters', async() => {
wrapper = create(); await flush()
wrapper.vm.query.company_id = 41
startBatch.mockResolvedValue({ id: 9, total: 100, status: 'running', progress: 0 })
await wrapper.vm.start()
expect(startBatch).toHaveBeenCalledWith({})
expect(wrapper.vm.batch.id).toBe(9)
})
it('retains news without ever sending is_visible and preserves version checks', async() => {
wrapper = create(); await flush()
reviewNews.mockResolvedValue({})
await wrapper.vm.review({ id: 8, version: 3 }, 'retained')
expect(reviewNews).toHaveBeenCalledWith({ id: 8, version: 3, review_status: 'retained', review_note: '' })
})
it('edits only editorial fields and blocks unsafe source URLs', async() => {
wrapper = create(); await flush()
showNews.mockResolvedValue({ id: 8, version: 3, title: '标题', source_content: '接口原始', content_html: '<p>人工</p>', raw_payload: {}})
await wrapper.vm.edit({ id: 8 })
saveNews.mockResolvedValue({ id: 8, version: 4, title: '新标题', content_html: '<p>人工</p>' })
wrapper.vm.$set(wrapper.vm.form, 'title', '新标题')
await wrapper.vm.save()
expect(saveNews).toHaveBeenCalledWith({ id: 8, version: 3, title: '新标题', summary: undefined, content_html: '<p>人工</p>' })
expect(wrapper.vm.safeUrl('javascript:alert(1)')).toBe(false)
expect(wrapper.vm.safeUrl('https://example.com/news')).toBe(true)
})
it('appends companies on dropdown scroll and prevents duplicate page loads', async() => {
companies.mockResolvedValueOnce({ data: [{ id: 1, company_name: '候选企业' }], total: 2, current_page: 1, last_page: 2 })
wrapper = create(); await flush()
let resolvePage
companies.mockImplementationOnce(() => new Promise(resolve => { resolvePage = resolve }))
wrapper.vm.onCompanyScroll({ target: { scrollHeight: 500, scrollTop: 300, clientHeight: 200 }})
wrapper.vm.onCompanyScroll({ target: { scrollHeight: 500, scrollTop: 300, clientHeight: 200 }})
expect(companies).toHaveBeenCalledTimes(2)
expect(companies).toHaveBeenLastCalledWith({ keyword: '', page: 2 })
resolvePage({ data: [{ id: 2, company_name: '已占额企业' }], total: 2, current_page: 2, last_page: 2 })
await flush()
expect(wrapper.vm.companyOptions.map(c => c.id)).toEqual([1, 2])
expect(wrapper.vm.companyHasMore).toBe(false)
})
it('discards stale company responses after changing search keyword', async() => {
wrapper = create(); await flush()
let resolveOld
companies.mockImplementationOnce(() => new Promise(resolve => { resolveOld = resolve }))
const old = wrapper.vm.searchCompanies('旧')
companies.mockResolvedValueOnce({ data: [{ id: 9, company_name: '新企业' }], total: 1, current_page: 1, last_page: 1 })
await wrapper.vm.searchCompanies('新')
resolveOld({ data: [{ id: 8, company_name: '旧企业' }], total: 1, current_page: 1, last_page: 1 })
await old
expect(wrapper.vm.companyOptions.map(c => c.id)).toEqual([9])
expect(wrapper.vm.companyKeyword).toBe('新')
})
it('shows missing configuration and prevents creating doomed batches', async() => {
latestBatch.mockResolvedValue({ batch: null, configuration_error: '缺少 YUANHE_BASE_URL' })
wrapper = create(); await flush()
expect(wrapper.vm.configurationError).toBe('缺少 YUANHE_BASE_URL')
await wrapper.vm.start()
expect(startBatch).not.toHaveBeenCalled()
expect(wrapper.findAll('el-alert-stub').at(0).attributes('title')).toBe('缺少 YUANHE_BASE_URL')
})
})