Skip to content

Commit

Permalink
[UXE-4954] test: add coverage for redirectToManager guard and update …
Browse files Browse the repository at this point in the history
…router guards (#1764)

Unit tests for the redirectToManager guard were added, and the router guards were refactored to accept routerHandler and dependencies as parameters, improving dependency injection across the guards.
  • Loading branch information
HerbertJulio authored Oct 1, 2024
1 parent 33f2e90 commit 1e7231a
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 31 deletions.
14 changes: 8 additions & 6 deletions src/router/hooks/beforeEachRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import * as guards from '@/router/hooks/guards'
import { useHelpCenterStore } from '@/stores/help-center'
import { useRouter } from 'vue-router'

/** @type {import('vue-router').NavigationGuardWithThis} */
export default async function beforeEachRoute(to, from, next) {
export default async function beforeEachRoute(routeHandler, guardDependency) {
const { to, next } = routeHandler
const { accountStore } = guardDependency

const helpCenterStore = useHelpCenterStore()
helpCenterStore.close()
const router = useRouter()
await guards.logoutGuard(to, next)
await guards.logoutGuard(to, next, accountStore)
guards.loadingGuard(to)
await guards.accountGuard(to, next)
guards.themeGuard()
guards.billingGuard(to, next)
await guards.accountGuard(to, next, accountStore)
guards.themeGuard(accountStore)
guards.billingGuard(to, next, accountStore)
guards.redirectGuard(to, next, router)

return next()
Expand Down
4 changes: 1 addition & 3 deletions src/router/hooks/guards/accountGuard.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { getAccountInfoService, getUserInfoService } from '@/services/account-services'
import { loadAccountJobRoleService } from '@/services/account-settings-services'
import { useAccountStore } from '@/stores/account'
import { setRedirectRoute } from '@/helpers'

/** @type {import('vue-router').NavigationGuardWithThis} */
export async function accountGuard(to, next) {
const accountStore = useAccountStore()
export async function accountGuard(to, next, accountStore) {
const isPrivateRoute = !to.meta.isPublic
const userNotIsLoggedIn = !accountStore.hasActiveUserId

Expand Down
4 changes: 1 addition & 3 deletions src/router/hooks/guards/billingGuard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useAccountStore } from '@/stores/account'
import { billingRoutes } from '@/router/routes/billing-routes'
import { getStaticUrlsByEnvironment } from '@/helpers'

Expand All @@ -10,8 +9,7 @@ const BILLING_REDIRECT_OPTIONS = {
const billingUrl = getStaticUrlsByEnvironment('billing')

/** @type {import('vue-router').NavigationGuardWithThis} */
export async function billingGuard(to, next) {
const accountStore = useAccountStore()
export async function billingGuard(to, next, accountStore) {
const {
hasActiveUserId,
redirectToExternalBillingNeeded,
Expand Down
4 changes: 1 addition & 3 deletions src/router/hooks/guards/logoutGuard.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { logoutService } from '@/services/auth-services'
import { useAccountStore } from '@/stores/account'

/** @type {import('vue-router').NavigationGuardWithThis} */
export async function logoutGuard(to, next) {
export async function logoutGuard(to, next, accountStore) {
if (to.path === '/logout') {
await logoutService()
const accountStore = useAccountStore()
accountStore.setAccountData({})
return next()
}
Expand Down
6 changes: 1 addition & 5 deletions src/router/hooks/guards/themeGuard.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { useAccountStore } from '@/stores/account'

export async function themeGuard() {
const accountStore = useAccountStore()

export async function themeGuard(accountStore) {
// TODO: remove the usage of localStorage when API returns the theme
const theme = localStorage.getItem('theme')
const fallbackTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
Expand Down
17 changes: 8 additions & 9 deletions src/router/hooks/redirectToManager.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import { getEnvironment, getStaticUrlsByEnvironment } from '@/helpers'
import { loadContractServicePlan } from '@/services/contract-services'
import { useAccountStore } from '@/stores/account'

/** @type {import('vue-router').NavigationGuardWithThis} */
export default async function redirectToManager(to, from, next) {
const accountStore = useAccountStore()
export default async function redirectToManager(routeHandler, guardDependency) {
const { to, from, next } = routeHandler
const { accountStore, loadContractServicePlan } = guardDependency
const isPrivateRoute = !to.meta.isPublic
const accountData = accountStore.accountData

try {
if (accountStore.hasActiveUserId && isPrivateRoute) {
if (accountStore.metricsOnlyAccessRestriction) {
return handleNavigationRestriction(to, from, next)
}

const isAzion = accountData.email.includes('@azion.com')
// Azion internal access to console.
if (isAzion || accountStore.hasAccessConsole) {
return next()
}

if (accountStore.metricsOnlyAccessRestriction) {
return handleNavigationRestriction(to, from, next)
}

const isNotClientKind = !accountData.client_id
// [Brand,Reseller,Group] are the kins without client id.
if (isNotClientKind && !isAzion) {
Expand Down
17 changes: 15 additions & 2 deletions src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import { createRouter, createWebHistory } from 'vue-router'
import afterEachRouteGuard from './hooks/afterEachRoute'
import beforeEachRoute from './hooks/beforeEachRoute'
import redirectToManager from './hooks/redirectToManager'
import { useAccountStore } from '@/stores/account'
import { loadContractServicePlan } from '@/services/contract-services'

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
Expand Down Expand Up @@ -85,8 +87,19 @@ const router = createRouter({
].concat(errorRoutes)
})

router.beforeEach(beforeEachRoute)
router.beforeEach(redirectToManager)
router.beforeEach((to, from, next) => {
const accountStore = useAccountStore()
const routeHandler = { to, from, next }

const guardDependency = {
accountStore,
loadContractServicePlan
}

beforeEachRoute(routeHandler, guardDependency)
redirectToManager(routeHandler, guardDependency)
})

router.afterEach(afterEachRouteGuard)

export default router
179 changes: 179 additions & 0 deletions src/tests/router/hooks/redirectToManager.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { describe, afterAll, it, expect, vi } from 'vitest'
import redirectToManager from '@/router/hooks/redirectToManager'

vi.stubGlobal('window', {
location: {
replace: vi.fn()
}
})

const makeSut = (scenario) => {
const {
hasActiveUserId = false,
isPrivateRoute,
hasAccessConsole,
metricsOnlyAccessRestriction,
email = '',
clientId,
isDeveloperSupportPlan,
fromName = ''
} = scenario

const accountStore = {
hasActiveUserId: hasActiveUserId,
hasAccessConsole: hasAccessConsole,
metricsOnlyAccessRestriction: metricsOnlyAccessRestriction,
accountData: {
email: email,
client_id: clientId
},
setAccountData: vi.fn()
}

const loadContractServicePlan = vi.fn().mockResolvedValue({ isDeveloperSupportPlan })

const guardDependency = {
accountStore,
loadContractServicePlan
}

const routeHandler = {
to: { meta: { isPublic: !isPrivateRoute }, name: 'some-route' },
from: { name: fromName },
next: vi.fn()
}

return { guardDependency, routeHandler }
}

afterAll(() => {
vi.unstubAllGlobals()
vi.unstubAllEnvs()
})

describe('redirectToManager', () => {
it('should call next if user has an active user id and is on a non-private route', async () => {
const windowLocationReplaceMock = vi.spyOn(window.location, 'replace')
const { routeHandler, guardDependency } = makeSut({
hasActiveUserId: true,
isPrivateRoute: false
})
await redirectToManager(routeHandler, guardDependency)

expect(windowLocationReplaceMock).not.toHaveBeenCalled()
expect(routeHandler.next).toHaveBeenCalled()
})

it('should allow full access for users with an @azion.com account', async () => {
const windowLocationReplaceMock = vi.spyOn(window.location, 'replace')

const { routeHandler, guardDependency } = makeSut({
hasActiveUserId: true,
isPrivateRoute: true,
email: '@azion.com'
})
await redirectToManager(routeHandler, guardDependency)

expect(routeHandler.next).toHaveBeenCalled()
expect(windowLocationReplaceMock).not.toHaveBeenCalled()
})

it('should allow access for accounts with Developer type', async () => {
const windowLocationReplaceMock = vi.spyOn(window.location, 'replace')

const { routeHandler, guardDependency } = makeSut({
hasActiveUserId: true,
isPrivateRoute: true,
email: '@developer.com',
clientId: '123',
isDeveloperSupportPlan: true
})
await redirectToManager(routeHandler, guardDependency)

expect(guardDependency.accountStore.setAccountData).toHaveBeenCalledWith({
isDeveloperSupportPlan: true
})
expect(routeHandler.next).toHaveBeenCalled()
expect(windowLocationReplaceMock).not.toHaveBeenCalled()
})
it('should redirect non-developer accounts to the RTM', async () => {
const windowLocationReplaceMock = vi.spyOn(window.location, 'replace')

const { routeHandler, guardDependency } = makeSut({
hasActiveUserId: true,
isPrivateRoute: true,
hasAccessConsole: false,
email: '@developer.com',
clientId: '123',
isDeveloperSupportPlan: false
})
await redirectToManager(routeHandler, guardDependency)

expect(guardDependency.accountStore.setAccountData).toHaveBeenCalledWith({
isDeveloperSupportPlan: false
})
expect(windowLocationReplaceMock).toHaveBeenCalled()
})

it('should redirect to the RTM when accountData does not have a client_id', async () => {
const windowLocationReplaceMock = vi.spyOn(window.location, 'replace')

const { routeHandler, guardDependency } = makeSut({
hasActiveUserId: true,
isPrivateRoute: true,
email: '@developer.com',
clientId: null
})

await redirectToManager(routeHandler, guardDependency)

expect(windowLocationReplaceMock).toHaveBeenCalled()
expect(routeHandler.next).toHaveBeenCalled()
})

it('should handle navigation restriction for metrics only access', async () => {
const windowLocationReplaceMock = vi.spyOn(window.location, 'replace')

const { routeHandler, guardDependency } = makeSut({
hasActiveUserId: true,
isPrivateRoute: true,
metricsOnlyAccessRestriction: true
})

await redirectToManager(routeHandler, guardDependency)

expect(windowLocationReplaceMock).not.toHaveBeenCalled()
expect(routeHandler.next).toHaveBeenCalled({ name: 'real-time-metrics' })
})

it('should handle navigation restriction for metrics only access and from.name is not metrics', async () => {
const windowLocationReplaceMock = vi.spyOn(window.location, 'replace')

const { routeHandler, guardDependency } = makeSut({
hasActiveUserId: true,
isPrivateRoute: true,
metricsOnlyAccessRestriction: true,
fromName: 'real-time-metrics'
})

await redirectToManager(routeHandler, guardDependency)

expect(windowLocationReplaceMock).not.toHaveBeenCalled()
expect(routeHandler.next).toHaveBeenCalledWith(false)
})

it('should allow access for accounts with console access', async () => {
const windowLocationReplaceMock = vi.spyOn(window.location, 'replace')

const { routeHandler, guardDependency } = makeSut({
hasActiveUserId: true,
isPrivateRoute: true,
hasAccessConsole: true
})

await redirectToManager(routeHandler, guardDependency)

expect(windowLocationReplaceMock).not.toHaveBeenCalled()
expect(routeHandler.next).toHaveBeenCalled()
})
})

0 comments on commit 1e7231a

Please sign in to comment.