Explorar o código

refactor: update authentication tests and configurations for consistency

- Change default password in tests from 'admin123' to '123456' for uniformity across test cases
- Refactor login logic in E2E tests to clear local storage and cookies before each test
- Update API mock handlers to reflect new password configuration
- Enhance test descriptions and structure for better clarity and maintainability
yb hai 3 semanas
pai
achega
f0098efd22

+ 64 - 123
tests/e2e/auth.spec.ts

@@ -1,142 +1,83 @@
 import { test, expect } from '@playwright/test'
 
-test.describe('Auth Module E2E Tests', () => {
-  test.describe('Login Page', () => {
-    test.beforeEach(async ({ page }) => {
-      await page.goto('/login')
-    })
-
-    test('should display login form correctly', async ({ page }) => {
-      await expect(page).toHaveTitle(/摄像头管理系统/)
-      await expect(page.getByPlaceholder('请输入用户名')).toBeVisible()
-      await expect(page.getByPlaceholder('请输入密码')).toBeVisible()
-      await expect(page.getByRole('button', { name: '登 录' })).toBeVisible()
-      await expect(page.getByText('记住我')).toBeVisible()
-    })
-
-    test('should show validation error when submitting empty form', async ({ page }) => {
-      await page.getByRole('button', { name: '登 录' }).click()
-      await expect(page.getByText('请输入用户名')).toBeVisible()
-      await expect(page.getByText('请输入密码')).toBeVisible()
-    })
-
-    test('should show error with invalid credentials', async ({ page }) => {
-      await page.getByPlaceholder('请输入用户名').fill('invalid_user')
-      await page.getByPlaceholder('请输入密码').fill('wrong_password')
-      await page.getByRole('button', { name: '登 录' }).click()
-      await expect(page.locator('.el-message--error').first()).toBeVisible({ timeout: 10000 })
-    })
-
-    test('should show help message when clicking forgot password', async ({ page }) => {
-      await page.getByText('忘记密码?').click()
-      await expect(page.locator('.el-message--info')).toBeVisible()
-      await expect(page.getByText('请联系管理员重置密码')).toBeVisible()
-    })
-
-    test('should have remember me checkbox checked by default', async ({ page }) => {
-      const rememberCheckbox = page.locator('.el-checkbox').filter({ hasText: '记住我' })
-      await expect(rememberCheckbox).toBeVisible()
-      await expect(rememberCheckbox).toHaveClass(/is-checked/)
+// 测试账号配置
+const TEST_USERNAME = process.env.TEST_USERNAME || 'admin'
+const TEST_PASSWORD = process.env.TEST_PASSWORD || '123456'
+
+test.describe('登录登出测试', () => {
+  test.beforeEach(async ({ page }) => {
+    // 清除登录状态
+    await page.goto('/login')
+    await page.evaluate(() => {
+      localStorage.clear()
+      document.cookie.split(';').forEach((c) => {
+        document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/')
+      })
     })
+    await page.reload()
   })
 
-  test.describe('Authenticated Features', () => {
-    // These tests require valid credentials
-    // Skip if no test credentials are configured
-    const username = process.env.TEST_USERNAME || 'admin'
-    const password = process.env.TEST_PASSWORD || 'admin123'
-
-    test.skip('should login and see user dropdown', async ({ page }) => {
-      await page.goto('/login')
-      await page.getByPlaceholder('请输入用户名').fill(username)
-      await page.getByPlaceholder('请输入密码').fill(password)
-      await page.getByRole('button', { name: '登 录' }).click()
-
-      // Wait for redirect and check user dropdown exists
-      await expect(page).not.toHaveURL(/\/login/, { timeout: 10000 })
-      await expect(page.locator('.user-info')).toBeVisible()
-    })
-
-    test.skip('should show change password dialog from dropdown', async ({ page }) => {
-      // Login first
-      await page.goto('/login')
-      await page.getByPlaceholder('请输入用户名').fill(username)
-      await page.getByPlaceholder('请输入密码').fill(password)
-      await page.getByRole('button', { name: '登 录' }).click()
-      await expect(page).not.toHaveURL(/\/login/, { timeout: 10000 })
-
-      // Open user dropdown
-      await page.locator('.user-info').click()
-      await expect(page.getByText('修改密码')).toBeVisible()
-
-      // Click change password
-      await page.getByText('修改密码').click()
+  test('登录页面正确显示', async ({ page }) => {
+    await expect(page.getByPlaceholder('请输入用户名')).toBeVisible()
+    await expect(page.getByPlaceholder('请输入密码')).toBeVisible()
+    await expect(page.getByRole('button', { name: '登 录' })).toBeVisible()
+    await expect(page.getByText('记住我')).toBeVisible()
+  })
 
-      // Verify dialog opens
-      await expect(page.getByRole('dialog')).toBeVisible()
-      await expect(page.getByText('修改密码').first()).toBeVisible()
-      await expect(page.getByPlaceholder('请输入原密码')).toBeVisible()
-      await expect(page.getByPlaceholder('请输入新密码')).toBeVisible()
-      await expect(page.getByPlaceholder('请再次输入新密码')).toBeVisible()
-    })
+  test('空表单提交显示验证错误', async ({ page }) => {
+    await page.getByRole('button', { name: '登 录' }).click()
+    await expect(page.getByText('请输入用户名')).toBeVisible()
+    await expect(page.getByText('请输入密码')).toBeVisible()
+  })
 
-    test.skip('should validate change password form', async ({ page }) => {
-      // Login first
-      await page.goto('/login')
-      await page.getByPlaceholder('请输入用户名').fill(username)
-      await page.getByPlaceholder('请输入密码').fill(password)
-      await page.getByRole('button', { name: '登 录' }).click()
-      await expect(page).not.toHaveURL(/\/login/, { timeout: 10000 })
+  test('登录成功并显示用户名 admin', async ({ page }) => {
+    // 输入登录信息
+    await page.getByPlaceholder('请输入用户名').fill(TEST_USERNAME)
+    await page.getByPlaceholder('请输入密码').fill(TEST_PASSWORD)
+    await page.getByRole('button', { name: '登 录' }).click()
 
-      // Open change password dialog
-      await page.locator('.user-info').click()
-      await page.getByText('修改密码').click()
+    // 等待跳转离开登录页
+    await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
 
-      // Try to submit empty form
-      await page.getByRole('dialog').getByRole('button', { name: '确定' }).click()
+    // 验证用户名显示为 admin
+    await expect(page.locator('.username')).toBeVisible({ timeout: 10000 })
+    await expect(page.locator('.username')).toContainText('admin')
+  })
 
-      // Check validation errors
-      await expect(page.getByText('请输入原密码')).toBeVisible()
-      await expect(page.getByText('请输入新密码')).toBeVisible()
-      await expect(page.getByText('请再次输入新密码')).toBeVisible()
-    })
+  test('登录后可以正常登出', async ({ page }) => {
+    // 先登录
+    await page.getByPlaceholder('请输入用户名').fill(TEST_USERNAME)
+    await page.getByPlaceholder('请输入密码').fill(TEST_PASSWORD)
+    await page.getByRole('button', { name: '登 录' }).click()
+    await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
 
-    test.skip('should validate password confirmation match', async ({ page }) => {
-      // Login first
-      await page.goto('/login')
-      await page.getByPlaceholder('请输入用户名').fill(username)
-      await page.getByPlaceholder('请输入密码').fill(password)
-      await page.getByRole('button', { name: '登 录' }).click()
-      await expect(page).not.toHaveURL(/\/login/, { timeout: 10000 })
+    // 点击用户下拉菜单
+    await page.locator('.user-info').click()
+    await page.waitForTimeout(500)
 
-      // Open change password dialog
-      await page.locator('.user-info').click()
-      await page.getByText('修改密码').click()
+    // 点击退出登录
+    await page.getByText('退出登录').click()
 
-      // Fill mismatched passwords
-      await page.getByPlaceholder('请输入原密码').fill('oldpass')
-      await page.getByPlaceholder('请输入新密码').fill('newpass123')
-      await page.getByPlaceholder('请再次输入新密码').fill('differentpass')
-      await page.getByPlaceholder('请再次输入新密码').blur()
+    // 验证跳转到登录页
+    await expect(page).toHaveURL(/\/login/, { timeout: 10000 })
+  })
 
-      // Check validation error
-      await expect(page.getByText('两次输入的密码不一致')).toBeVisible()
-    })
+  test('修改密码弹窗可以打开', async ({ page }) => {
+    // 先登录
+    await page.getByPlaceholder('请输入用户名').fill(TEST_USERNAME)
+    await page.getByPlaceholder('请输入密码').fill(TEST_PASSWORD)
+    await page.getByRole('button', { name: '登 录' }).click()
+    await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
 
-    test.skip('should logout successfully', async ({ page }) => {
-      // Login first
-      await page.goto('/login')
-      await page.getByPlaceholder('请输入用户名').fill(username)
-      await page.getByPlaceholder('请输入密码').fill(password)
-      await page.getByRole('button', { name: '登 录' }).click()
-      await expect(page).not.toHaveURL(/\/login/, { timeout: 10000 })
+    // 点击用户下拉菜单
+    await page.locator('.user-info').click()
+    await page.waitForTimeout(500)
 
-      // Open user dropdown and logout
-      await page.locator('.user-info').click()
-      await page.getByText('退出登录').click()
+    // 点击修改密码
+    await page.getByText('修改密码').click()
 
-      // Should redirect to login page
-      await expect(page).toHaveURL(/\/login/, { timeout: 10000 })
-    })
+    // 验证弹窗显示
+    await expect(page.getByRole('dialog')).toBeVisible()
+    await expect(page.locator('.el-dialog').getByText('修改密码')).toBeVisible()
   })
 })

+ 1 - 1
tests/e2e/camera.spec.ts

@@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'
 
 test.describe('Camera Management E2E Tests', () => {
   const username = process.env.TEST_USERNAME || 'admin'
-  const password = process.env.TEST_PASSWORD || 'admin123'
+  const password = process.env.TEST_PASSWORD || '123456'
 
   async function login(page: any) {
     await page.goto('/login')

+ 180 - 66
tests/e2e/machine.spec.ts

@@ -1,94 +1,208 @@
 import { test, expect } from '@playwright/test'
 
-test.describe('Machine Management E2E Tests', () => {
-  // Skip all tests that require login
-  const username = process.env.TEST_USERNAME || 'admin'
-  const password = process.env.TEST_PASSWORD || 'admin123'
+// 测试账号配置
+const TEST_USERNAME = process.env.TEST_USERNAME || 'admin'
+const TEST_PASSWORD = process.env.TEST_PASSWORD || '123456'
 
+// 生成唯一的测试机器ID
+const generateMachineId = () => `TEST_${Date.now()}`
+
+test.describe('机器管理 CRUD 测试', () => {
+  // 登录辅助函数
   async function login(page: any) {
     await page.goto('/login')
-    await page.getByPlaceholder('请输入用户名').fill(username)
-    await page.getByPlaceholder('请输入密码').fill(password)
+    await page.evaluate(() => {
+      localStorage.clear()
+      document.cookie.split(';').forEach((c) => {
+        document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/')
+      })
+    })
+    await page.reload()
+
+    await page.getByPlaceholder('请输入用户名').fill(TEST_USERNAME)
+    await page.getByPlaceholder('请输入密码').fill(TEST_PASSWORD)
     await page.getByRole('button', { name: '登 录' }).click()
-    await expect(page).not.toHaveURL(/\/login/, { timeout: 10000 })
+    await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
   }
 
-  test.describe('Machine List Page', () => {
-    test.skip('should display machine management page', async ({ page }) => {
-      await login(page)
-      await page.goto('/machine')
+  test('机器管理页面正确显示', async ({ page }) => {
+    await login(page)
+    await page.goto('/machine')
 
-      // Verify page elements
-      await expect(page.getByRole('button', { name: '新增机器' })).toBeVisible()
-      await expect(page.getByRole('button', { name: '刷新列表' })).toBeVisible()
-      await expect(page.locator('.el-table')).toBeVisible()
-    })
+    // 验证页面元素
+    await expect(page.getByRole('button', { name: '新增机器' })).toBeVisible()
+    await expect(page.getByRole('button', { name: '刷新列表' })).toBeVisible()
+    await expect(page.locator('.el-table')).toBeVisible()
 
-    test.skip('should have correct table columns', async ({ page }) => {
-      await login(page)
-      await page.goto('/machine')
+    // 验证表头
+    await expect(page.getByText('机器ID')).toBeVisible()
+    await expect(page.getByText('名称')).toBeVisible()
+    await expect(page.getByText('位置')).toBeVisible()
+  })
 
-      // Check table headers
-      await expect(page.getByText('机器ID')).toBeVisible()
-      await expect(page.getByText('名称')).toBeVisible()
-      await expect(page.getByText('位置')).toBeVisible()
-      await expect(page.getByText('摄像头数')).toBeVisible()
-      await expect(page.getByText('启用')).toBeVisible()
-    })
+  test('刷新列表功能', async ({ page }) => {
+    await login(page)
+    await page.goto('/machine')
+
+    // 等待表格加载
+    await expect(page.locator('.el-table')).toBeVisible()
+
+    // 点击刷新按钮
+    await page.getByRole('button', { name: '刷新列表' }).click()
+
+    // 验证加载状态(可能很快消失)
+    await page.waitForTimeout(500)
+
+    // 表格应该仍然可见
+    await expect(page.locator('.el-table')).toBeVisible()
   })
 
-  test.describe('Add Machine Dialog', () => {
-    test.skip('should open add dialog when clicking add button', async ({ page }) => {
-      await login(page)
-      await page.goto('/machine')
+  test('新增机器完整流程', async ({ page }) => {
+    await login(page)
+    await page.goto('/machine')
 
-      await page.getByRole('button', { name: '新增机器' }).click()
+    const machineId = generateMachineId()
+    const machineName = `测试机器_${Date.now()}`
 
-      // Verify dialog opens
-      await expect(page.getByRole('dialog')).toBeVisible()
-      await expect(page.getByText('新增机器')).toBeVisible()
-      await expect(page.getByPlaceholder('请输入机器ID')).toBeVisible()
-      await expect(page.getByPlaceholder('请输入名称')).toBeVisible()
-      await expect(page.getByPlaceholder('请输入位置')).toBeVisible()
-    })
+    // 点击新增按钮
+    await page.getByRole('button', { name: '新增机器' }).click()
 
-    test.skip('should validate required fields', async ({ page }) => {
-      await login(page)
-      await page.goto('/machine')
+    // 验证弹窗打开
+    await expect(page.getByRole('dialog')).toBeVisible()
+    await expect(page.locator('.el-dialog__title')).toContainText('新增机器')
 
-      await page.getByRole('button', { name: '新增机器' }).click()
-      await page.getByRole('dialog').getByRole('button', { name: '确定' }).click()
+    // 填写表单
+    await page.getByPlaceholder('请输入机器ID').fill(machineId)
+    await page.getByPlaceholder('请输入名称').fill(machineName)
+    await page.getByPlaceholder('请输入位置').fill('测试位置')
+    await page.getByPlaceholder('请输入描述').fill('E2E测试创建的机器')
 
-      // Check validation errors
-      await expect(page.getByText('请输入机器ID')).toBeVisible()
-      await expect(page.getByText('请输入名称')).toBeVisible()
-    })
+    // 提交表单
+    await page.getByRole('dialog').getByRole('button', { name: '确定' }).click()
 
-    test.skip('should close dialog when clicking cancel', async ({ page }) => {
-      await login(page)
-      await page.goto('/machine')
+    // 等待成功消息
+    await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 })
 
-      await page.getByRole('button', { name: '新增机器' }).click()
-      await expect(page.getByRole('dialog')).toBeVisible()
+    // 验证弹窗关闭
+    await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 })
 
-      await page.getByRole('dialog').getByRole('button', { name: '取消' }).click()
-      await expect(page.getByRole('dialog')).not.toBeVisible()
-    })
+    // 验证新机器出现在列表中
+    await expect(page.getByText(machineId)).toBeVisible({ timeout: 5000 })
+    await expect(page.getByText(machineName)).toBeVisible()
   })
 
-  test.describe('Navigation', () => {
-    test.skip('should have machine menu item in sidebar', async ({ page }) => {
-      await login(page)
+  test('编辑机器功能', async ({ page }) => {
+    await login(page)
+    await page.goto('/machine')
 
-      // Check sidebar menu
-      await expect(page.getByText('机器管理')).toBeVisible()
-    })
+    // 等待表格加载
+    await expect(page.locator('.el-table')).toBeVisible()
+    await page.waitForTimeout(1000)
 
-    test.skip('should navigate to machine page from sidebar', async ({ page }) => {
-      await login(page)
+    // 找到第一行的编辑按钮
+    const firstEditButton = page.locator('.el-table__body-wrapper').getByText('编辑').first()
 
-      await page.getByText('机器管理').click()
-      await expect(page).toHaveURL(/\/machine/)
-    })
+    // 检查是否有数据可编辑
+    const editButtonCount = await firstEditButton.count()
+    if (editButtonCount === 0) {
+      test.skip()
+      return
+    }
+
+    await firstEditButton.click()
+
+    // 验证弹窗打开
+    await expect(page.getByRole('dialog')).toBeVisible()
+    await expect(page.locator('.el-dialog__title')).toContainText('编辑机器')
+
+    // 修改名称
+    const nameInput = page.getByPlaceholder('请输入名称')
+    await nameInput.clear()
+    await nameInput.fill(`编辑测试_${Date.now()}`)
+
+    // 提交
+    await page.getByRole('dialog').getByRole('button', { name: '确定' }).click()
+
+    // 等待成功消息
+    await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 })
+
+    // 验证弹窗关闭
+    await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 })
+  })
+
+  test('删除机器功能', async ({ page }) => {
+    await login(page)
+    await page.goto('/machine')
+
+    // 先创建一个测试机器用于删除
+    const machineId = `DEL_${Date.now()}`
+    const machineName = `待删除机器_${Date.now()}`
+
+    // 新增机器
+    await page.getByRole('button', { name: '新增机器' }).click()
+    await page.getByPlaceholder('请输入机器ID').fill(machineId)
+    await page.getByPlaceholder('请输入名称').fill(machineName)
+    await page.getByRole('dialog').getByRole('button', { name: '确定' }).click()
+    await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 })
+    await page.waitForTimeout(1000)
+
+    // 找到刚创建的机器的删除按钮
+    const row = page.locator('.el-table__row').filter({ hasText: machineId })
+    await row.getByText('删除').click()
+
+    // 确认删除对话框
+    await expect(page.locator('.el-message-box')).toBeVisible()
+    await page.locator('.el-message-box').getByRole('button', { name: '确定' }).click()
+
+    // 等待成功消息
+    await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 10000 })
+
+    // 验证机器从列表中消失
+    await expect(page.getByText(machineId)).not.toBeVisible({ timeout: 5000 })
+  })
+
+  test('新增机器表单验证', async ({ page }) => {
+    await login(page)
+    await page.goto('/machine')
+
+    // 点击新增按钮
+    await page.getByRole('button', { name: '新增机器' }).click()
+    await expect(page.getByRole('dialog')).toBeVisible()
+
+    // 直接点击确定,不填写任何内容
+    await page.getByRole('dialog').getByRole('button', { name: '确定' }).click()
+
+    // 验证显示验证错误
+    await expect(page.getByText('请输入机器ID')).toBeVisible()
+    await expect(page.getByText('请输入名称')).toBeVisible()
+  })
+
+  test('取消新增机器', async ({ page }) => {
+    await login(page)
+    await page.goto('/machine')
+
+    // 点击新增按钮
+    await page.getByRole('button', { name: '新增机器' }).click()
+    await expect(page.getByRole('dialog')).toBeVisible()
+
+    // 填写部分内容
+    await page.getByPlaceholder('请输入机器ID').fill('TEST_CANCEL')
+
+    // 点击取消
+    await page.getByRole('dialog').getByRole('button', { name: '取消' }).click()
+
+    // 验证弹窗关闭
+    await expect(page.getByRole('dialog')).not.toBeVisible()
+  })
+
+  test('从侧边栏导航到机器管理', async ({ page }) => {
+    await login(page)
+
+    // 点击侧边栏机器管理菜单
+    await page.getByText('机器管理').click()
+
+    // 验证跳转到机器管理页面
+    await expect(page).toHaveURL(/\/machine/)
+    await expect(page.getByRole('button', { name: '新增机器' })).toBeVisible()
   })
 })

+ 9 - 2
tests/e2e/mocks/api-handlers.ts

@@ -1,5 +1,12 @@
 import type { Page } from '@playwright/test'
-import { mockLoginResponse, mockAdminInfo, mockMachines, mockCameras, mockDashboardStats, wrapResponse } from '../../fixtures'
+import {
+  mockLoginResponse,
+  mockAdminInfo,
+  mockMachines,
+  mockCameras,
+  mockDashboardStats,
+  wrapResponse
+} from '../../fixtures'
 
 /**
  * Mock login API responses
@@ -16,7 +23,7 @@ export async function mockLoginAPI(page: Page) {
 
     const body = request.postDataJSON()
 
-    if (body?.username === 'admin' && body?.password === 'admin123') {
+    if (body?.username === 'admin' && body?.password === '123456') {
       await route.fulfill({
         status: 200,
         contentType: 'application/json',

+ 1 - 1
tests/e2e/stats.spec.ts

@@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'
 
 test.describe('Stats/Dashboard E2E Tests', () => {
   const username = process.env.TEST_USERNAME || 'admin'
-  const password = process.env.TEST_PASSWORD || 'admin123'
+  const password = process.env.TEST_PASSWORD || '123456'
 
   async function login(page: any) {
     await page.goto('/login')

+ 4 - 4
tests/mocks/api.ts

@@ -33,7 +33,7 @@ export function mockLoginApi(request: MockedRequest) {
   // login
   request.post.mockImplementation((url: string, data?: any) => {
     if (url === '/admin/auth/login') {
-      if (data?.username === 'admin' && data?.password === 'admin123') {
+      if (data?.username === 'admin' && data?.password === '123456') {
         return Promise.resolve(fixtures.wrapResponse(fixtures.mockLoginResponse))
       }
       return Promise.resolve(fixtures.wrapErrorResponse('用户名或密码错误', 401))
@@ -42,7 +42,7 @@ export function mockLoginApi(request: MockedRequest) {
       return Promise.resolve(fixtures.wrapResponse(null))
     }
     if (url === '/admin/auth/password') {
-      if (data?.oldPassword === 'admin123') {
+      if (data?.oldPassword === '123456') {
         return Promise.resolve(fixtures.wrapResponse(null, 200, '密码修改成功'))
       }
       return Promise.resolve(fixtures.wrapErrorResponse('原密码错误', 400))
@@ -79,7 +79,7 @@ export function mockMachineApi(request: MockedRequest) {
 
   request.post.mockImplementation((url: string, data?: any, _config?: any) => {
     if (url === '/admin/machines/add') {
-      const newMachine: typeof fixtures.mockMachines[0] = {
+      const newMachine: (typeof fixtures.mockMachines)[0] = {
         id: fixtures.mockMachines.length + 1,
         machineId: data.machineId,
         name: data.name,
@@ -236,7 +236,7 @@ export function mockAllApis(request: MockedRequest) {
 
   request.post.mockImplementation((url: string, data?: any) => {
     if (url === '/admin/auth/login') {
-      if (data?.username === 'admin' && data?.password === 'admin123') {
+      if (data?.username === 'admin' && data?.password === '123456') {
         return Promise.resolve(fixtures.wrapResponse(fixtures.mockLoginResponse))
       }
       return Promise.resolve(fixtures.wrapErrorResponse('用户名或密码错误', 401))

+ 5 - 15
tests/unit/store/user.spec.ts

@@ -15,9 +15,7 @@ vi.mock('@/api/login', () => ({
 vi.mock('@/utils/auth', () => ({
   getToken: vi.fn(() => ''),
   setToken: vi.fn(),
-  removeToken: vi.fn(),
-  setRefreshToken: vi.fn(),
-  removeRefreshToken: vi.fn()
+  removeToken: vi.fn()
 }))
 
 describe('User Store', () => {
@@ -37,8 +35,8 @@ describe('User Store', () => {
       expect(result.code).toBe(200)
       expect(store.token).toBe(mockLoginResponse.token)
       expect(store.userInfo?.username).toBe(mockLoginResponse.admin.username)
-      expect(auth.setToken).toHaveBeenCalledWith(mockLoginResponse.token)
-      expect(auth.setRefreshToken).toHaveBeenCalledWith(mockLoginResponse.refreshToken)
+      // setToken now takes (token, expiresIn)
+      expect(auth.setToken).toHaveBeenCalledWith(mockLoginResponse.token, mockLoginResponse.expiresIn)
     })
 
     it('should not store token on login failure', async () => {
@@ -82,7 +80,6 @@ describe('User Store', () => {
       expect(store.token).toBe('')
       expect(store.userInfo).toBeNull()
       expect(auth.removeToken).toHaveBeenCalled()
-      expect(auth.removeRefreshToken).toHaveBeenCalled()
     })
 
     it('should clear state in finally block even if logout API fails', async () => {
@@ -92,19 +89,13 @@ describe('User Store', () => {
       store.token = mockLoginResponse.token
       store.userInfo = mockAdminInfo
 
-      // logoutAction uses try/finally without catch, so error will propagate
-      // but finally block still clears the state
-      try {
-        await store.logoutAction()
-      } catch {
-        // Expected to throw
-      }
+      // logoutAction now catches errors internally and still clears state
+      await store.logoutAction()
 
       // State should be cleared in finally block
       expect(store.token).toBe('')
       expect(store.userInfo).toBeNull()
       expect(auth.removeToken).toHaveBeenCalled()
-      expect(auth.removeRefreshToken).toHaveBeenCalled()
     })
   })
 
@@ -119,7 +110,6 @@ describe('User Store', () => {
       expect(store.token).toBe('')
       expect(store.userInfo).toBeNull()
       expect(auth.removeToken).toHaveBeenCalled()
-      expect(auth.removeRefreshToken).toHaveBeenCalled()
     })
   })
 })

+ 20 - 67
tests/unit/utils/auth.spec.ts

@@ -1,34 +1,33 @@
-import { describe, it, expect, beforeEach, afterEach } from 'vitest'
-import {
-  getToken,
-  setToken,
-  removeToken,
-  getRefreshToken,
-  setRefreshToken,
-  removeRefreshToken
-} from '@/utils/auth'
+import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
+import { getToken, setToken, removeToken } from '@/utils/auth'
 
 describe('Auth Utils', () => {
-  const TOKEN_KEY = 'Admin-Token'
-  const REFRESH_TOKEN_KEY = 'Admin-Refresh-Token'
   const mockToken = 'mock-jwt-token-12345'
-  const mockRefreshToken = 'mock-refresh-token-67890'
 
   beforeEach(() => {
-    // Clear localStorage before each test
-    localStorage.clear()
+    // Clear cookies before each test
+    document.cookie.split(';').forEach((c) => {
+      document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/')
+    })
   })
 
   afterEach(() => {
     // Clean up after each test
-    localStorage.clear()
+    document.cookie.split(';').forEach((c) => {
+      document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/')
+    })
   })
 
-  describe('Token Management', () => {
+  describe('Token Management (Cookie-based)', () => {
     it('should set and get token', () => {
       setToken(mockToken)
       expect(getToken()).toBe(mockToken)
-      expect(localStorage.getItem(TOKEN_KEY)).toBe(mockToken)
+    })
+
+    it('should set token with custom expiresIn', () => {
+      const customExpiry = 7200 // 2 hours
+      setToken(mockToken, customExpiry)
+      expect(getToken()).toBe(mockToken)
     })
 
     it('should return null when token is not set', () => {
@@ -41,7 +40,6 @@ describe('Auth Utils', () => {
 
       removeToken()
       expect(getToken()).toBeNull()
-      expect(localStorage.getItem(TOKEN_KEY)).toBeNull()
     })
 
     it('should overwrite existing token', () => {
@@ -50,56 +48,11 @@ describe('Auth Utils', () => {
       setToken(newToken)
       expect(getToken()).toBe(newToken)
     })
-  })
-
-  describe('Refresh Token Management', () => {
-    it('should set and get refresh token', () => {
-      setRefreshToken(mockRefreshToken)
-      expect(getRefreshToken()).toBe(mockRefreshToken)
-      expect(localStorage.getItem(REFRESH_TOKEN_KEY)).toBe(mockRefreshToken)
-    })
-
-    it('should return null when refresh token is not set', () => {
-      expect(getRefreshToken()).toBeNull()
-    })
-
-    it('should remove refresh token', () => {
-      setRefreshToken(mockRefreshToken)
-      expect(getRefreshToken()).toBe(mockRefreshToken)
-
-      removeRefreshToken()
-      expect(getRefreshToken()).toBeNull()
-      expect(localStorage.getItem(REFRESH_TOKEN_KEY)).toBeNull()
-    })
-
-    it('should overwrite existing refresh token', () => {
-      setRefreshToken(mockRefreshToken)
-      const newRefreshToken = 'new-refresh-token-11111'
-      setRefreshToken(newRefreshToken)
-      expect(getRefreshToken()).toBe(newRefreshToken)
-    })
-  })
-
-  describe('Token Isolation', () => {
-    it('should keep tokens independent', () => {
-      setToken(mockToken)
-      setRefreshToken(mockRefreshToken)
-
-      expect(getToken()).toBe(mockToken)
-      expect(getRefreshToken()).toBe(mockRefreshToken)
-
-      removeToken()
-      expect(getToken()).toBeNull()
-      expect(getRefreshToken()).toBe(mockRefreshToken) // Should still exist
-    })
-
-    it('should remove refresh token without affecting access token', () => {
-      setToken(mockToken)
-      setRefreshToken(mockRefreshToken)
 
-      removeRefreshToken()
-      expect(getRefreshToken()).toBeNull()
-      expect(getToken()).toBe(mockToken) // Should still exist
+    it('should handle special characters in token', () => {
+      const specialToken = 'token+with/special=chars'
+      setToken(specialToken)
+      expect(getToken()).toBe(specialToken)
     })
   })
 })

+ 457 - 110
tg-live-game.postman_collection.json

@@ -43,8 +43,14 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/auth/register",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "auth", "register"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "auth",
+                    "register"
+                  ]
                 }
               },
               "response": []
@@ -82,8 +88,14 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/auth/login",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "auth", "login"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "auth",
+                    "login"
+                  ]
                 }
               },
               "response": []
@@ -119,8 +131,14 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/auth/refresh",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "auth", "refresh"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "auth",
+                    "refresh"
+                  ]
                 }
               },
               "response": []
@@ -132,8 +150,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/auth/me",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "auth", "me"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "auth",
+                    "me"
+                  ]
                 }
               },
               "response": []
@@ -150,12 +174,18 @@
                 ],
                 "body": {
                   "mode": "raw",
-                  "raw": "{\n  \"oldPassword\": \"admin123\",\n  \"newPassword\": \"newpassword123\"\n}"
+                  "raw": "{\n  \"oldPassword\": \"123456\",\n  \"newPassword\": \"newpassword123\"\n}"
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/auth/change-password",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "auth", "change-password"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "auth",
+                    "change-password"
+                  ]
                 }
               },
               "response": []
@@ -167,8 +197,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/auth/logout",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "auth", "logout"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "auth",
+                    "logout"
+                  ]
                 }
               },
               "response": []
@@ -188,8 +224,15 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/users/:id/permissions",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "users", ":id", "permissions"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "users",
+                        ":id",
+                        "permissions"
+                      ],
                       "variable": [
                         {
                           "key": "id",
@@ -216,8 +259,15 @@
                     },
                     "url": {
                       "raw": "{{baseUrl}}/api/users/:id/permissions",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "users", ":id", "permissions"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "users",
+                        ":id",
+                        "permissions"
+                      ],
                       "variable": [
                         {
                           "key": "id",
@@ -235,8 +285,16 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/users/:id/permissions/:permissionId",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "users", ":id", "permissions", ":permissionId"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "users",
+                        ":id",
+                        "permissions",
+                        ":permissionId"
+                      ],
                       "variable": [
                         {
                           "key": "id",
@@ -270,8 +328,13 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/users?page=1&pageSize=20",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "users"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "users"
+                  ],
                   "query": [
                     {
                       "key": "page",
@@ -308,8 +371,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/users/:id",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "users", ":id"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "users",
+                    ":id"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -336,8 +405,13 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/users",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "users"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "users"
+                  ]
                 }
               },
               "response": []
@@ -358,8 +432,14 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/users/:id",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "users", ":id"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "users",
+                    ":id"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -377,8 +457,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/users/:id",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "users", ":id"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "users",
+                    ":id"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -401,8 +487,13 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/cameras?page=1&pageSize=20",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "cameras"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "cameras"
+                  ],
                   "query": [
                     {
                       "key": "page",
@@ -439,8 +530,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/cameras/:id",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "cameras", ":id"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "cameras",
+                    ":id"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -467,8 +564,13 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/cameras",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "cameras"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "cameras"
+                  ]
                 }
               },
               "response": []
@@ -489,8 +591,14 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/cameras/:id",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "cameras", ":id"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "cameras",
+                    ":id"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -508,8 +616,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/cameras/:id",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "cameras", ":id"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "cameras",
+                    ":id"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -527,8 +641,15 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/cameras/:id/sessions?page=1&pageSize=20",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "cameras", ":id", "sessions"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "cameras",
+                    ":id",
+                    "sessions"
+                  ],
                   "query": [
                     {
                       "key": "page",
@@ -566,8 +687,13 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/sessions?page=1&pageSize=20",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "sessions"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "sessions"
+                  ],
                   "query": [
                     {
                       "key": "page",
@@ -599,8 +725,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/sessions/live",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "sessions", "live"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "sessions",
+                    "live"
+                  ]
                 }
               },
               "response": []
@@ -612,8 +744,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/sessions/:id",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "sessions", ":id"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "sessions",
+                    ":id"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -640,8 +778,13 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/sessions",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "sessions"]
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "sessions"
+                  ]
                 }
               },
               "response": []
@@ -662,8 +805,15 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/sessions/:id/end",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "sessions", ":id", "end"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "sessions",
+                    ":id",
+                    "end"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -690,8 +840,15 @@
                 },
                 "url": {
                   "raw": "{{baseUrl}}/api/sessions/:id/viewers",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "sessions", ":id", "viewers"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "sessions",
+                    ":id",
+                    "viewers"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -709,8 +866,15 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/sessions/:id/stats",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "sessions", ":id", "stats"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "sessions",
+                    ":id",
+                    "stats"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -728,8 +892,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/sessions/:id",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "sessions", ":id"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "sessions",
+                    ":id"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -767,8 +937,15 @@
                     },
                     "url": {
                       "raw": "{{baseUrl}}/api/stats/view/start",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stats", "view", "start"]
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stats",
+                        "view",
+                        "start"
+                      ]
                     }
                   },
                   "response": []
@@ -792,8 +969,15 @@
                     },
                     "url": {
                       "raw": "{{baseUrl}}/api/stats/view/end",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stats", "view", "end"]
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stats",
+                        "view",
+                        "end"
+                      ]
                     }
                   },
                   "response": []
@@ -817,8 +1001,15 @@
                     },
                     "url": {
                       "raw": "{{baseUrl}}/api/stats/view/heartbeat",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stats", "view", "heartbeat"]
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stats",
+                        "view",
+                        "heartbeat"
+                      ]
                     }
                   },
                   "response": []
@@ -832,8 +1023,15 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/stats/video/:videoId",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "stats", "video", ":videoId"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "stats",
+                    "video",
+                    ":videoId"
+                  ],
                   "variable": [
                     {
                       "key": "videoId",
@@ -851,8 +1049,15 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/stats/session/:sessionId",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "stats", "session", ":sessionId"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "stats",
+                    "session",
+                    ":sessionId"
+                  ],
                   "variable": [
                     {
                       "key": "sessionId",
@@ -870,8 +1075,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/stats/overview?days=7",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "stats", "overview"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "stats",
+                    "overview"
+                  ],
                   "query": [
                     {
                       "key": "days",
@@ -889,8 +1100,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/stats/views?page=1&pageSize=50",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "stats", "views"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "stats",
+                    "views"
+                  ],
                   "query": [
                     {
                       "key": "page",
@@ -935,8 +1152,15 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/audit-logs/stats/summary?days=7",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "audit-logs", "stats", "summary"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "audit-logs",
+                        "stats",
+                        "summary"
+                      ],
                       "query": [
                         {
                           "key": "days",
@@ -956,8 +1180,13 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/audit-logs?page=1&pageSize=50",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "audit-logs"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "audit-logs"
+                  ],
                   "query": [
                     {
                       "key": "page",
@@ -1004,8 +1233,14 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/audit-logs/:id",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "audit-logs", ":id"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "audit-logs",
+                    ":id"
+                  ],
                   "variable": [
                     {
                       "key": "id",
@@ -1023,8 +1258,15 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/audit-logs/user/:userId?page=1&pageSize=20",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "audit-logs", "user", ":userId"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "audit-logs",
+                    "user",
+                    ":userId"
+                  ],
                   "query": [
                     {
                       "key": "page",
@@ -1052,8 +1294,16 @@
                 "header": [],
                 "url": {
                   "raw": "{{baseUrl}}/api/audit-logs/resource/:resource/:resourceId?page=1&pageSize=20",
-                  "host": ["{{baseUrl}}"],
-                  "path": ["api", "audit-logs", "resource", ":resource", ":resourceId"],
+                  "host": [
+                    "{{baseUrl}}"
+                  ],
+                  "path": [
+                    "api",
+                    "audit-logs",
+                    "resource",
+                    ":resource",
+                    ":resourceId"
+                  ],
                   "query": [
                     {
                       "key": "page",
@@ -1093,8 +1343,15 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/video/list",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "video", "list"]
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "video",
+                        "list"
+                      ]
                     }
                   },
                   "response": []
@@ -1106,8 +1363,15 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/video/:videoId",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "video", ":videoId"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "video",
+                        ":videoId"
+                      ],
                       "variable": [
                         {
                           "key": "videoId",
@@ -1125,8 +1389,15 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/video/:videoId",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "video", ":videoId"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "video",
+                        ":videoId"
+                      ],
                       "variable": [
                         {
                           "key": "videoId",
@@ -1153,8 +1424,15 @@
                     },
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/video/import",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "video", "import"]
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "video",
+                        "import"
+                      ]
                     }
                   },
                   "response": []
@@ -1175,8 +1453,15 @@
                     },
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/video/upload-url",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "video", "upload-url"]
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "video",
+                        "upload-url"
+                      ]
                     }
                   },
                   "response": []
@@ -1188,8 +1473,16 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/video/:videoId/playback",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "video", ":videoId", "playback"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "video",
+                        ":videoId",
+                        "playback"
+                      ],
                       "variable": [
                         {
                           "key": "videoId",
@@ -1212,8 +1505,15 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/live/list",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "live", "list"]
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "live",
+                        "list"
+                      ]
                     }
                   },
                   "response": []
@@ -1234,8 +1534,14 @@
                     },
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/live",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "live"]
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "live"
+                      ]
                     }
                   },
                   "response": []
@@ -1247,8 +1553,15 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/live/:liveInputId",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "live", ":liveInputId"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "live",
+                        ":liveInputId"
+                      ],
                       "variable": [
                         {
                           "key": "liveInputId",
@@ -1275,8 +1588,15 @@
                     },
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/live/:liveInputId",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "live", ":liveInputId"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "live",
+                        ":liveInputId"
+                      ],
                       "variable": [
                         {
                           "key": "liveInputId",
@@ -1294,8 +1614,15 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/live/:liveInputId",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "live", ":liveInputId"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "live",
+                        ":liveInputId"
+                      ],
                       "variable": [
                         {
                           "key": "liveInputId",
@@ -1313,8 +1640,16 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/live/:liveInputId/playback",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "live", ":liveInputId", "playback"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "live",
+                        ":liveInputId",
+                        "playback"
+                      ],
                       "variable": [
                         {
                           "key": "liveInputId",
@@ -1332,8 +1667,16 @@
                     "header": [],
                     "url": {
                       "raw": "{{baseUrl}}/api/stream/live/:liveInputId/recordings",
-                      "host": ["{{baseUrl}}"],
-                      "path": ["api", "stream", "live", ":liveInputId", "recordings"],
+                      "host": [
+                        "{{baseUrl}}"
+                      ],
+                      "path": [
+                        "api",
+                        "stream",
+                        "live",
+                        ":liveInputId",
+                        "recordings"
+                      ],
                       "variable": [
                         {
                           "key": "liveInputId",
@@ -1368,7 +1711,9 @@
         "type": "text/javascript",
         "packages": {},
         "requests": {},
-        "exec": [""]
+        "exec": [
+          ""
+        ]
       }
     },
     {
@@ -1377,7 +1722,9 @@
         "type": "text/javascript",
         "packages": {},
         "requests": {},
-        "exec": [""]
+        "exec": [
+          ""
+        ]
       }
     }
   ],
@@ -1395,4 +1742,4 @@
       "value": ""
     }
   ]
-}
+}