Email a set-password link when an admin creates a user

This commit is contained in:
NeilClack
2026-09-10 13:42:33 -05:00
parent 8a3c1587b4
commit 5ec3018f8b
5 changed files with 180 additions and 1 deletions
@@ -0,0 +1,106 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createLocals, createFormRequest, mockUser } from '$lib/test-utils';
vi.mock('$lib/db', () => ({
prisma: {
user: {
findUnique: vi.fn(),
findMany: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn()
},
userRole: { findMany: vi.fn() }
}
}));
const { requestPasswordReset } = vi.hoisted(() => ({
requestPasswordReset: vi.fn()
}));
vi.mock('$lib/auth', () => ({
auth: { api: { requestPasswordReset } }
}));
import { prisma } from '$lib/db';
import { actions } from './+page.server';
const userFindUnique = prisma.user.findUnique as ReturnType<typeof vi.fn>;
const userCreate = prisma.user.create as ReturnType<typeof vi.fn>;
const userUpdate = prisma.user.update as ReturnType<typeof vi.fn>;
const invokeUpsert = (request: Request, locals: App.Locals) =>
actions.upsert({ request, locals } as any);
function upsertRequest(overrides: Record<string, string> = {}) {
return createFormRequest({
name: 'Jane Doe',
email: 'jane@example.com',
roleId: '4',
...overrides
});
}
describe('users admin page server upsert', () => {
beforeEach(() => {
vi.clearAllMocks();
userFindUnique.mockResolvedValue({ roleId: 1 });
userCreate.mockResolvedValue({ id: 'new-user' });
userUpdate.mockResolvedValue({ id: 'user-1' });
requestPasswordReset.mockResolvedValue(undefined);
});
it('marks a created user verified and emails a set-password link', async () => {
const locals = createLocals(mockUser() as any);
const result = await invokeUpsert(upsertRequest(), locals);
expect(result).toEqual({ success: true, action: 'upsert' });
expect(userCreate).toHaveBeenCalledWith({
data: expect.objectContaining({
email: 'jane@example.com',
roleId: 4,
emailVerified: true
})
});
expect(requestPasswordReset).toHaveBeenCalledWith({
body: {
email: 'jane@example.com',
redirectTo: '/reset-password?invite=1'
}
});
});
it('does not email a registration link when updating an existing user', async () => {
const locals = createLocals(mockUser() as any);
const result = await invokeUpsert(upsertRequest({ id: 'user-9' }), locals);
expect(result).toEqual({ success: true, action: 'upsert' });
expect(userUpdate).toHaveBeenCalled();
expect(userCreate).not.toHaveBeenCalled();
expect(requestPasswordReset).not.toHaveBeenCalled();
});
it('still succeeds when the registration email fails to send', async () => {
requestPasswordReset.mockRejectedValue(new Error('resend down'));
const locals = createLocals(mockUser() as any);
const result = await invokeUpsert(upsertRequest(), locals);
expect(result).toEqual({ success: true, action: 'upsert' });
expect(userCreate).toHaveBeenCalled();
});
it('blocks assigning a role with more privileges than the actor', async () => {
userFindUnique.mockResolvedValue({ roleId: 2 });
const locals = createLocals(mockUser() as any);
const result = await invokeUpsert(upsertRequest({ roleId: '1' }), locals);
expect(result).toEqual({
success: false,
action: 'upsert',
error: 'You cannot assign a role with more privileges than your own.'
});
expect(userCreate).not.toHaveBeenCalled();
});
});