import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' // Mock api/client.js vi.mock('../../../api/client.js', () => ({ adminListUsers: vi.fn(), adminGetUserQuota: vi.fn(), adminUpdateQuota: vi.fn(), })) import AdminQuotasTab from '../../../views/admin/AdminQuotasView.vue' import * as api from '../../../api/client.js' const MB = 1048576 function makeUser(id, email) { return { id, email, handle: email.split('@')[0], role: 'user', is_active: true } } function makeQuota(userId, usedMB, limitMB) { return { user_id: userId, used_bytes: usedMB * MB, limit_bytes: limitMB * MB, } } beforeEach(() => { vi.clearAllMocks() }) // ── onMounted: loads users + quotas ────────────────────────────────────────── describe('AdminQuotasTab — onMounted', () => { it('calls adminListUsers() on mount', async () => { api.adminListUsers.mockResolvedValue({ items: [] }) mount(AdminQuotasTab) await flushPromises() expect(api.adminListUsers).toHaveBeenCalledTimes(1) }) it('shows "No users yet" empty state when API returns empty list', async () => { api.adminListUsers.mockResolvedValue({ items: [] }) const w = mount(AdminQuotasTab) await flushPromises() expect(w.text()).toContain('No users yet') }) it('calls adminGetUserQuota for each user', async () => { api.adminListUsers.mockResolvedValue({ items: [makeUser('u1', 'alice@example.com'), makeUser('u2', 'bob@example.com')], }) api.adminGetUserQuota .mockResolvedValueOnce(makeQuota('u1', 10, 100)) .mockResolvedValueOnce(makeQuota('u2', 50, 200)) mount(AdminQuotasTab) await flushPromises() expect(api.adminGetUserQuota).toHaveBeenCalledWith('u1') expect(api.adminGetUserQuota).toHaveBeenCalledWith('u2') }) it('displays quota data in the table', async () => { api.adminListUsers.mockResolvedValue({ items: [makeUser('u1', 'alice@example.com')], }) api.adminGetUserQuota.mockResolvedValue(makeQuota('u1', 10, 100)) const w = mount(AdminQuotasTab) await flushPromises() expect(w.text()).toContain('alice@example.com') // 10 MB used, 100 MB limit expect(w.text()).toContain('10 MB') expect(w.text()).toContain('100 MB') }) }) // ── saveQuota: calls adminUpdateQuota(id, bytes) ────────────────────────────── describe('AdminQuotasTab — saveQuota', () => { it('calls adminUpdateQuota with user id and new limit in bytes on save', async () => { api.adminListUsers.mockResolvedValue({ items: [makeUser('u1', 'alice@example.com')], }) api.adminGetUserQuota.mockResolvedValue(makeQuota('u1', 10, 100)) api.adminUpdateQuota.mockResolvedValue({ user_id: 'u1', used_bytes: 10 * MB, limit_bytes: 200 * MB, warning: false, }) const w = mount(AdminQuotasTab) await flushPromises() // Click "Edit" button const editBtn = w.find('button') expect(editBtn.text()).toContain('Edit') await editBtn.trigger('click') await w.vm.$nextTick() // Change the limit input to 200 MB const input = w.find('input[type="number"]') await input.setValue(200) // Click "Save" const saveBtn = w.findAll('button').find(b => b.text().includes('Save')) await saveBtn.trigger('click') await flushPromises() expect(api.adminUpdateQuota).toHaveBeenCalledWith('u1', 200 * MB) }) it('shows warning text when API response has warning: true', async () => { api.adminListUsers.mockResolvedValue({ items: [makeUser('u1', 'alice@example.com')], }) api.adminGetUserQuota.mockResolvedValue(makeQuota('u1', 90, 100)) api.adminUpdateQuota.mockResolvedValue({ user_id: 'u1', used_bytes: 90 * MB, limit_bytes: 50 * MB, // below current usage warning: true, }) const w = mount(AdminQuotasTab) await flushPromises() // Enter edit mode await w.find('button').trigger('click') await w.vm.$nextTick() // Set limit below current usage const input = w.find('input[type="number"]') await input.setValue(50) // Save const saveBtn = w.findAll('button').find(b => b.text().includes('Save')) await saveBtn.trigger('click') await flushPromises() // Warning text must appear expect(w.text()).toMatch(/below current usage|uploads will be blocked/i) }) })