material_manage_system/frontend/src/stores/organization.test.js
文睿 张 6cc3cd5dda feat: 新增公司切换与请求上下文
- 新增 company 请求上下文工具、organization store 与公司切换器
- axios 拦截器为组织级请求写入 X-Organization-ID 并校验上下文版本
- 切换公司用 AbortController 取消旧读请求,丢弃旧响应且不弹提示
- 路由与页面加公司初始化屏障,组织级页面按公司 ID + 版本挂载
2026-10-09 10:19:19 +08:00

242 lines
8.8 KiB
JavaScript

import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { authApi } from '../api/auth'
import { useOrganizationStore } from './organization'
import { resetOrganizationContext } from '../utils/organizationContext'
vi.mock('../api/auth', () => ({
authApi: {
listOrganizations: vi.fn(),
switchOrganizationContext: vi.fn()
}
}))
// 模拟某个账号已登录:公司选择按用户 id 分开存储,这里写入对应账号。
function loginAs(id) {
localStorage.setItem('wmms_user', JSON.stringify({ id, username: `user${id}` }))
}
function orgList(organizations, defaultOrganizationId) {
return { data: { organizations, default_organization_id: defaultOrganizationId } }
}
function switchResult(organizationId, warehouse = null) {
return { data: { organization_id: organizationId, organization: { id: organizationId }, warehouse } }
}
const ACTIVE_WAREHOUSE = {
id: 10,
name: '武汉仓',
status: 'active',
location: { id: 100, name: 'A库位', status: 'active' }
}
describe('organization store', () => {
beforeEach(() => {
localStorage.clear()
sessionStorage.clear()
resetOrganizationContext()
setActivePinia(createPinia())
authApi.listOrganizations.mockReset()
authApi.switchOrganizationContext.mockReset()
})
test('默认公司进入并自动选中唯一启用的仓库与库位', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1, name: '北京' }, { id: 2, name: '武汉' }], 2))
authApi.switchOrganizationContext.mockResolvedValue(switchResult(2, ACTIVE_WAREHOUSE))
const store = useOrganizationStore()
await store.initialize()
expect(store.organizations.map(o => o.id)).toEqual([1, 2])
expect(store.currentOrganizationId).toBe(2)
expect(store.currentWarehouseId).toBe(10)
expect(store.currentLocationId).toBe(100)
expect(store.initialized).toBe(true)
expect(authApi.switchOrganizationContext).toHaveBeenCalledWith(2, 0)
})
test('只展示接口返回的授权公司', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 3, name: '上海' }], 3))
authApi.switchOrganizationContext.mockResolvedValue(switchResult(3))
const store = useOrganizationStore()
await store.initialize()
expect(store.organizations.map(o => o.id)).toEqual([3])
expect(store.currentOrganizationId).toBe(3)
})
test('默认公司失效时保留列表并要求重新选择', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1, name: '北京' }], 99))
authApi.switchOrganizationContext.mockRejectedValue(new Error('公司不可访问'))
const store = useOrganizationStore()
await store.initialize()
expect(store.organizations.map(o => o.id)).toEqual([1])
expect(store.currentOrganizationId).toBeNull()
expect(store.initialized).toBe(true)
})
test('没有授权公司时进入空状态且不发起校验', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([], null))
const store = useOrganizationStore()
await store.initialize()
expect(store.organizations).toEqual([])
expect(store.currentOrganizationId).toBeNull()
expect(store.currentWarehouseId).toBeNull()
expect(store.currentLocationId).toBeNull()
expect(authApi.switchOrganizationContext).not.toHaveBeenCalled()
})
test('切换失败时保留旧公司', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1 }, { id: 2 }], 1))
authApi.switchOrganizationContext.mockResolvedValueOnce(
switchResult(1, { id: 9, name: '北京仓', status: 'active', location: { id: 90, name: '库位', status: 'active' } })
)
const store = useOrganizationStore()
await store.initialize()
expect(store.currentOrganizationId).toBe(1)
authApi.switchOrganizationContext.mockRejectedValueOnce(new Error('公司不可访问'))
await expect(store.switchOrganization(2)).rejects.toThrow()
expect(store.currentOrganizationId).toBe(1)
expect(store.currentWarehouseId).toBe(9)
expect(store.currentLocationId).toBe(90)
expect(store.switching).toBe(false)
})
test('切换成功后提交新公司并递增上下文版本', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1 }, { id: 2 }], 1))
authApi.switchOrganizationContext.mockResolvedValueOnce(switchResult(1))
const store = useOrganizationStore()
await store.initialize()
const versionBefore = store.contextVersion
authApi.switchOrganizationContext.mockResolvedValueOnce(switchResult(2, ACTIVE_WAREHOUSE))
await store.switchOrganization(2)
expect(store.currentOrganizationId).toBe(2)
expect(store.contextVersion).toBeGreaterThan(versionBefore)
expect(store.currentWarehouseId).toBe(10)
expect(store.currentLocationId).toBe(100)
expect(authApi.switchOrganizationContext).toHaveBeenLastCalledWith(2, 1)
})
test('切换到当前公司不重复发起校验', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1 }], 1))
authApi.switchOrganizationContext.mockResolvedValueOnce(switchResult(1))
const store = useOrganizationStore()
await store.initialize()
authApi.switchOrganizationContext.mockClear()
await store.switchOrganization(1)
expect(authApi.switchOrganizationContext).not.toHaveBeenCalled()
})
test('仓库停用时仓库与库位都为空', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1 }], 1))
authApi.switchOrganizationContext.mockResolvedValue(switchResult(1, {
id: 5, name: '停用仓', status: 'inactive', location: { id: 50, name: '库位', status: 'active' }
}))
const store = useOrganizationStore()
await store.initialize()
expect(store.currentWarehouseId).toBeNull()
expect(store.currentLocationId).toBeNull()
})
test('库位缺失或停用时仅库位为空', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1 }], 1))
authApi.switchOrganizationContext.mockResolvedValue(switchResult(1, {
id: 5, name: '启用仓', status: 'active', location: null
}))
const store = useOrganizationStore()
await store.initialize()
expect(store.currentWarehouseId).toBe(5)
expect(store.currentLocationId).toBeNull()
})
test('目标公司没有仓库时清空旧公司的仓库与库位', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1 }, { id: 2 }], 1))
authApi.switchOrganizationContext.mockResolvedValueOnce(switchResult(1, ACTIVE_WAREHOUSE))
const store = useOrganizationStore()
await store.initialize()
expect(store.currentWarehouseId).toBe(10)
authApi.switchOrganizationContext.mockResolvedValueOnce(switchResult(2, null))
await store.switchOrganization(2)
expect(store.currentOrganizationId).toBe(2)
expect(store.currentWarehouseId).toBeNull()
expect(store.currentLocationId).toBeNull()
})
test('不同账号不继承上一账号的公司选择', async () => {
// 账号 1 上次在本标签页选择了公司 2
loginAs(1)
sessionStorage.setItem('wmms_organization_id:1', '2')
// 换成账号 2 登录:读到的应是账号 2 的存储(为空),回落到默认公司 1
loginAs(2)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1 }, { id: 2 }], 1))
authApi.switchOrganizationContext.mockResolvedValue(switchResult(1))
const store = useOrganizationStore()
await store.initialize()
expect(store.currentOrganizationId).toBe(1)
expect(authApi.switchOrganizationContext).toHaveBeenCalledWith(1, 0)
})
test('切换进行中禁止再次切换', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1 }], 1))
authApi.switchOrganizationContext.mockResolvedValueOnce(switchResult(1))
const store = useOrganizationStore()
await store.initialize()
authApi.switchOrganizationContext.mockClear()
store.switching = true
await store.switchOrganization(2)
expect(authApi.switchOrganizationContext).not.toHaveBeenCalled()
expect(store.currentOrganizationId).toBe(1)
})
test('reset 清空公司状态与上下文', async () => {
loginAs(1)
authApi.listOrganizations.mockResolvedValue(orgList([{ id: 1 }], 1))
authApi.switchOrganizationContext.mockResolvedValue(switchResult(1, ACTIVE_WAREHOUSE))
const store = useOrganizationStore()
await store.initialize()
expect(store.currentOrganizationId).toBe(1)
store.reset()
expect(store.organizations).toEqual([])
expect(store.currentOrganizationId).toBeNull()
expect(store.currentWarehouseId).toBeNull()
expect(store.currentLocationId).toBeNull()
expect(store.initialized).toBe(false)
})
})