mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-31 08:25:35 +00:00
feat: add usage IP geolocation lookup
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
const ipGeoMocks = vi.hoisted(() => ({
|
||||
getEntry: vi.fn(() => ({ status: 'idle' as const })),
|
||||
fetchOne: vi.fn(),
|
||||
fetchBatch: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/ipGeoLookup', () => ipGeoMocks)
|
||||
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
@@ -323,6 +331,104 @@ describe('admin UsageTable tooltip', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('admin UsageTable IP geolocation batch toolbar', () => {
|
||||
const DataTableStubWithIp = {
|
||||
props: ['data'],
|
||||
template: `
|
||||
<div>
|
||||
<div v-for="row in data" :key="row.request_id">
|
||||
<slot name="cell-ip_address" :row="row" />
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
ipGeoMocks.getEntry.mockReset()
|
||||
ipGeoMocks.fetchOne.mockReset()
|
||||
ipGeoMocks.fetchBatch.mockReset()
|
||||
ipGeoMocks.getEntry.mockReturnValue({ status: 'idle' })
|
||||
})
|
||||
|
||||
it('does not render the batch toolbar when the ip_address column is not visible', () => {
|
||||
const wrapper = mount(UsageTable, {
|
||||
props: {
|
||||
data: [{ request_id: 'r1', ip_address: '8.8.8.8' }],
|
||||
loading: false,
|
||||
columns: [],
|
||||
},
|
||||
global: { stubs: { DataTable: DataTableStubWithIp, EmptyState: true, Teleport: true } },
|
||||
})
|
||||
expect(wrapper.text()).not.toContain('usage.ipGeo.batchFetch')
|
||||
})
|
||||
|
||||
it('renders the batch toolbar with a pending count when the ip_address column is visible', () => {
|
||||
const wrapper = mount(UsageTable, {
|
||||
props: {
|
||||
data: [
|
||||
{ request_id: 'r1', ip_address: '8.8.8.8' },
|
||||
{ request_id: 'r2', ip_address: '8.8.8.8' },
|
||||
{ request_id: 'r3', ip_address: '1.1.1.1' },
|
||||
],
|
||||
loading: false,
|
||||
columns: [{ key: 'ip_address', label: 'IP' }],
|
||||
},
|
||||
global: { stubs: { DataTable: DataTableStubWithIp, EmptyState: true, Teleport: true } },
|
||||
})
|
||||
expect(wrapper.text()).toContain('usage.ipGeo.pending')
|
||||
const button = wrapper.find('button')
|
||||
expect(button.exists()).toBe(true)
|
||||
expect((button.element as HTMLButtonElement).disabled).toBe(false)
|
||||
})
|
||||
|
||||
it('fetches deduplicated IPs from the current page when the batch button is clicked', async () => {
|
||||
ipGeoMocks.fetchBatch.mockResolvedValue(true)
|
||||
const wrapper = mount(UsageTable, {
|
||||
props: {
|
||||
data: [
|
||||
{ request_id: 'r1', ip_address: '8.8.8.8' },
|
||||
{ request_id: 'r2', ip_address: '8.8.8.8' },
|
||||
{ request_id: 'r3', ip_address: '1.1.1.1' },
|
||||
],
|
||||
loading: false,
|
||||
columns: [{ key: 'ip_address', label: 'IP' }],
|
||||
},
|
||||
global: { stubs: { DataTable: DataTableStubWithIp, EmptyState: true, Teleport: true } },
|
||||
})
|
||||
await wrapper.find('button').trigger('click')
|
||||
expect(ipGeoMocks.fetchBatch).toHaveBeenCalledWith(['8.8.8.8', '1.1.1.1'])
|
||||
expect(wrapper.emitted('ipGeoBatchFailed')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('emits ipGeoBatchFailed when the batch request reports a network-level failure', async () => {
|
||||
ipGeoMocks.fetchBatch.mockResolvedValue(false)
|
||||
const wrapper = mount(UsageTable, {
|
||||
props: {
|
||||
data: [{ request_id: 'r1', ip_address: '8.8.8.8' }],
|
||||
loading: false,
|
||||
columns: [{ key: 'ip_address', label: 'IP' }],
|
||||
},
|
||||
global: { stubs: { DataTable: DataTableStubWithIp, EmptyState: true, Teleport: true } },
|
||||
})
|
||||
await wrapper.find('button').trigger('click')
|
||||
expect(wrapper.emitted('ipGeoBatchFailed')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('renders IpGeoCell content for ip_address cells', () => {
|
||||
ipGeoMocks.getEntry.mockReturnValue({ status: 'success', label: 'CN · Guangdong · Shenzhen', detail: {} })
|
||||
const wrapper = mount(UsageTable, {
|
||||
props: {
|
||||
data: [{ request_id: 'r1', ip_address: '121.35.47.43' }],
|
||||
loading: false,
|
||||
columns: [{ key: 'ip_address', label: 'IP' }],
|
||||
},
|
||||
global: { stubs: { DataTable: DataTableStubWithIp, EmptyState: true, Teleport: true } },
|
||||
})
|
||||
expect(wrapper.text()).toContain('121.35.47.43')
|
||||
expect(wrapper.text()).toContain('CN · Guangdong · Shenzhen')
|
||||
})
|
||||
})
|
||||
|
||||
// A DataTable stub that also renders cell-user, so the deleted badge can be asserted.
|
||||
const DataTableStubWithUser = {
|
||||
props: ['data'],
|
||||
|
||||
Reference in New Issue
Block a user