test(settings): settingsGuard unit tests — tab gating scenarios
Extract guard logic to settingsGuard.ts for testability. Router beforeEach keeps async config.load() wrapper, delegates to sync guard. 14 test cases cover system/fine-tune/developer gates across cloud/self-hosted/tier/GPU profile combos.
This commit is contained in:
parent
55051818ef
commit
fe4091c7ba
3 changed files with 168 additions and 12 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useAppConfigStore } from '../stores/appConfig'
|
||||
import { settingsGuard } from './settingsGuard'
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
|
|
@ -39,16 +40,5 @@ router.beforeEach(async (to, _from, next) => {
|
|||
if (!to.path.startsWith('/settings/')) return next()
|
||||
const config = useAppConfigStore()
|
||||
if (!config.loaded) await config.load()
|
||||
const tab = to.path.replace('/settings/', '')
|
||||
const devOverride = config.devTierOverride
|
||||
const gpuProfiles = ['single-gpu', 'dual-gpu']
|
||||
|
||||
if (tab === 'system' && config.isCloud) return next('/settings/my-profile')
|
||||
if (tab === 'fine-tune') {
|
||||
const cloudBlocked = config.isCloud && config.tier !== 'premium'
|
||||
const selfHostedBlocked = !config.isCloud && !gpuProfiles.includes(config.inferenceProfile)
|
||||
if (cloudBlocked || selfHostedBlocked) return next('/settings/my-profile')
|
||||
}
|
||||
if (tab === 'developer' && !config.isDevMode && !devOverride) return next('/settings/my-profile')
|
||||
next()
|
||||
settingsGuard(to, _from, next)
|
||||
})
|
||||
|
|
|
|||
135
web/src/router/settings.guard.test.ts
Normal file
135
web/src/router/settings.guard.test.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { useAppConfigStore } from '../stores/appConfig'
|
||||
import { settingsGuard } from './settingsGuard'
|
||||
|
||||
vi.mock('../composables/useApi', () => ({ useApiFetch: vi.fn() }))
|
||||
|
||||
describe('settingsGuard', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
localStorage.clear()
|
||||
})
|
||||
afterEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
it('passes through non-settings routes immediately', () => {
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/review' }, {}, next)
|
||||
// Guard only handles /settings/* — for non-settings routes the router
|
||||
// calls next() before reaching settingsGuard, but the guard itself
|
||||
// will still call next() with no redirect since no tab matches
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('redirects /settings/system in cloud mode', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = true
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/system' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith('/settings/my-profile')
|
||||
})
|
||||
|
||||
it('allows /settings/system in self-hosted mode', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = false
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/system' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('redirects /settings/fine-tune for non-GPU self-hosted', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = false
|
||||
store.inferenceProfile = 'cpu'
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/fine-tune' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith('/settings/my-profile')
|
||||
})
|
||||
|
||||
it('allows /settings/fine-tune for single-gpu self-hosted', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = false
|
||||
store.inferenceProfile = 'single-gpu'
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/fine-tune' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('allows /settings/fine-tune for dual-gpu self-hosted', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = false
|
||||
store.inferenceProfile = 'dual-gpu'
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/fine-tune' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('redirects /settings/fine-tune on cloud when tier is not premium', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = true
|
||||
store.tier = 'paid'
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/fine-tune' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith('/settings/my-profile')
|
||||
})
|
||||
|
||||
it('allows /settings/fine-tune on cloud when tier is premium', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = true
|
||||
store.tier = 'premium'
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/fine-tune' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('redirects /settings/developer when not dev mode and no override', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isDevMode = false
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/developer' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith('/settings/my-profile')
|
||||
})
|
||||
|
||||
it('allows /settings/developer when isDevMode is true', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isDevMode = true
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/developer' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('allows /settings/developer when dev_tier_override set in localStorage', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isDevMode = false
|
||||
localStorage.setItem('dev_tier_override', 'premium')
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/developer' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('allows /settings/privacy in cloud mode', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = true
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/privacy' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('allows /settings/privacy in self-hosted mode', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = false
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/privacy' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
|
||||
it('allows /settings/license in both modes', () => {
|
||||
const store = useAppConfigStore()
|
||||
store.isCloud = true
|
||||
const next = vi.fn()
|
||||
settingsGuard({ path: '/settings/license' }, {}, next)
|
||||
expect(next).toHaveBeenCalledWith()
|
||||
})
|
||||
})
|
||||
31
web/src/router/settingsGuard.ts
Normal file
31
web/src/router/settingsGuard.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { useAppConfigStore } from '../stores/appConfig'
|
||||
|
||||
const GPU_PROFILES = ['single-gpu', 'dual-gpu']
|
||||
|
||||
/**
|
||||
* Synchronous tab-gating logic for /settings/* routes.
|
||||
* Called by the async router.beforeEach after config.load() has resolved.
|
||||
* Reading devTierOverride from localStorage here (not only the store ref) ensures
|
||||
* the guard reflects overrides set externally before the store hydrates.
|
||||
*/
|
||||
export function settingsGuard(
|
||||
to: { path: string },
|
||||
_from: unknown,
|
||||
next: (to?: string) => void,
|
||||
): void {
|
||||
const config = useAppConfigStore()
|
||||
const tab = to.path.replace('/settings/', '')
|
||||
const devOverride = config.devTierOverride || localStorage.getItem('dev_tier_override')
|
||||
|
||||
if (tab === 'system' && config.isCloud) return next('/settings/my-profile')
|
||||
|
||||
if (tab === 'fine-tune') {
|
||||
const cloudBlocked = config.isCloud && config.tier !== 'premium'
|
||||
const selfHostedBlocked = !config.isCloud && !GPU_PROFILES.includes(config.inferenceProfile)
|
||||
if (cloudBlocked || selfHostedBlocked) return next('/settings/my-profile')
|
||||
}
|
||||
|
||||
if (tab === 'developer' && !config.isDevMode && !devOverride) return next('/settings/my-profile')
|
||||
|
||||
next()
|
||||
}
|
||||
Loading…
Reference in a new issue