feat: add admin password reset
This commit is contained in:
@@ -14,7 +14,8 @@ vi.mock('../composables/useAuth', () => ({
|
||||
|
||||
describe('AdminPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
authedFetch.mockReset()
|
||||
logout.mockReset()
|
||||
authedFetch.mockResolvedValue([
|
||||
{ id: 'u1', username: 'teacher', role: 'user', createdAt: '2026-01-01T00:00:00.000Z' },
|
||||
])
|
||||
@@ -33,8 +34,42 @@ describe('AdminPage', () => {
|
||||
expect.arrayContaining(['ui-button', 'ui-button--primary']),
|
||||
)
|
||||
expect(wrapper.get('table').classes()).toContain('ui-table')
|
||||
expect(wrapper.get('button[data-testid="reset-password-u1"]').classes()).toContain('ui-button')
|
||||
expect(wrapper.get('button[data-testid="delete-user-u1"]').classes()).toEqual(
|
||||
expect.arrayContaining(['ui-button', 'ui-button--danger']),
|
||||
)
|
||||
})
|
||||
|
||||
it('resets a user password after confirmation', async () => {
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(true)
|
||||
authedFetch.mockResolvedValueOnce([
|
||||
{ id: 'u1', username: 'teacher', role: 'user', createdAt: '2026-01-01T00:00:00.000Z' },
|
||||
])
|
||||
authedFetch.mockResolvedValueOnce({ ok: true })
|
||||
|
||||
const wrapper = mount(AdminPage)
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.get('button[data-testid="reset-password-u1"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(window.confirm).toHaveBeenCalledWith('确定要将该用户密码重置为 123456 吗?')
|
||||
expect(authedFetch).toHaveBeenCalledWith('/api/admin/users/u1/reset-password', { method: 'POST' })
|
||||
expect(wrapper.text()).toContain('已将密码重置为 123456。')
|
||||
})
|
||||
|
||||
it('does not reset a password when confirmation is declined', async () => {
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(false)
|
||||
authedFetch.mockResolvedValueOnce([
|
||||
{ id: 'u1', username: 'teacher', role: 'user', createdAt: '2026-01-01T00:00:00.000Z' },
|
||||
])
|
||||
|
||||
const wrapper = mount(AdminPage)
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.get('button[data-testid="reset-password-u1"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(authedFetch).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ const newUsername = ref('')
|
||||
const newPassword = ref('')
|
||||
const newRole = ref<'user' | 'admin'>('user')
|
||||
const error = ref('')
|
||||
const success = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
async function loadUsers(): Promise<void> {
|
||||
@@ -24,6 +25,7 @@ async function loadUsers(): Promise<void> {
|
||||
async function createUser(): Promise<void> {
|
||||
if (!newUsername.value.trim() || !newPassword.value) return
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
loading.value = true
|
||||
try {
|
||||
await authedFetch('/api/admin/users', {
|
||||
@@ -55,6 +57,18 @@ async function removeUser(id: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function resetPassword(id: string): Promise<void> {
|
||||
if (!confirm('确定要将该用户密码重置为 123456 吗?')) return
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
try {
|
||||
await authedFetch(`/api/admin/users/${id}/reset-password`, { method: 'POST' })
|
||||
success.value = '已将密码重置为 123456。'
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : '重置失败'
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout(): Promise<void> {
|
||||
await logout()
|
||||
}
|
||||
@@ -96,6 +110,7 @@ onMounted(loadUsers)
|
||||
</button>
|
||||
</form>
|
||||
<p v-if="error" class="ui-error">{{ error }}</p>
|
||||
<p v-if="success" class="ui-success">{{ success }}</p>
|
||||
</section>
|
||||
|
||||
<section class="user-list">
|
||||
@@ -115,6 +130,14 @@ onMounted(loadUsers)
|
||||
<td>{{ u.role === 'admin' ? '管理员' : '普通用户' }}</td>
|
||||
<td>{{ new Date(u.createdAt).toLocaleDateString('zh-CN') }}</td>
|
||||
<td>
|
||||
<button
|
||||
class="ui-button"
|
||||
type="button"
|
||||
:data-testid="`reset-password-${u.id}`"
|
||||
@click="resetPassword(u.id)"
|
||||
>
|
||||
重置密码
|
||||
</button>
|
||||
<button
|
||||
class="ui-button ui-button--danger"
|
||||
type="button"
|
||||
|
||||
@@ -162,6 +162,12 @@ input {
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
|
||||
.ui-success {
|
||||
color: var(--green-700);
|
||||
font-size: 14px;
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
.workspace-toolbar {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user