87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
import { beforeEach, expect, test } from 'bun:test';
|
|
|
|
import { prismaMock, sendMailMock, transactionMock } from '../support/prisma.mock';
|
|
|
|
const { ComplexMembersError, inviteComplexUser } = await import(
|
|
'@/modules/complex/services/complex-members.service'
|
|
);
|
|
|
|
beforeEach(() => {
|
|
prismaMock._reset();
|
|
sendMailMock.mockClear();
|
|
transactionMock.mockClear();
|
|
});
|
|
|
|
test('creates a pending invitation and sends the email', async () => {
|
|
prismaMock.complexUser.findUnique.mockResolvedValue({ role: 'ADMIN' } as never);
|
|
prismaMock.complex.findUnique.mockResolvedValue({ complexName: 'Playzer Norte' } as never);
|
|
prismaMock.user.findUnique.mockResolvedValue(null);
|
|
prismaMock.complexInvitation.findFirst.mockResolvedValue(null);
|
|
prismaMock.complexInvitation.create.mockResolvedValue({
|
|
id: 'inv-1',
|
|
complexId: 'complex-1',
|
|
email: 'new.member@example.com',
|
|
tokenHash: 'token-hash',
|
|
expiresAt: new Date('2027-01-01T00:00:00.000Z'),
|
|
acceptedAt: null,
|
|
revokedAt: null,
|
|
createdAt: new Date('2026-12-01T00:00:00.000Z'),
|
|
} as never);
|
|
|
|
const result = await inviteComplexUser('admin-1', 'complex-1', {
|
|
email: ' New.Member@example.com ',
|
|
});
|
|
|
|
expect(result.invitation.email).toBe('new.member@example.com');
|
|
expect(result.invitation.status).toBe('PENDING');
|
|
expect(prismaMock.complexInvitation.create).toHaveBeenCalledTimes(1);
|
|
|
|
const createArgs = prismaMock.complexInvitation.create.mock.calls[0]?.[0] as {
|
|
data: {
|
|
complexId: string;
|
|
email: string;
|
|
id: string;
|
|
tokenHash: string;
|
|
expiresAt: Date;
|
|
};
|
|
};
|
|
|
|
expect(createArgs.data.complexId).toBe('complex-1');
|
|
expect(createArgs.data.email).toBe('new.member@example.com');
|
|
expect(typeof createArgs.data.id).toBe('string');
|
|
expect(typeof createArgs.data.tokenHash).toBe('string');
|
|
expect(sendMailMock).toHaveBeenCalledTimes(1);
|
|
expect(transactionMock).toHaveBeenCalledTimes(1);
|
|
|
|
const mailArgs = sendMailMock.mock.calls[0][0] as {
|
|
to: string;
|
|
subject: string;
|
|
};
|
|
|
|
expect(mailArgs.to).toBe('new.member@example.com');
|
|
expect(mailArgs.subject).toContain('Playzer Norte');
|
|
});
|
|
|
|
test('rejects when the invite target already belongs to the complex', async () => {
|
|
prismaMock.complexUser.findUnique
|
|
.mockResolvedValueOnce({ role: 'ADMIN' } as never)
|
|
.mockResolvedValueOnce({ userId: 'member-1' } as never);
|
|
prismaMock.complex.findUnique.mockResolvedValue({ complexName: 'Playzer Norte' } as never);
|
|
prismaMock.user.findUnique.mockResolvedValue({ id: 'member-1' } as never);
|
|
|
|
let caught: unknown;
|
|
|
|
try {
|
|
await inviteComplexUser('admin-1', 'complex-1', {
|
|
email: 'member@example.com',
|
|
});
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
|
|
expect(caught).toBeInstanceOf(ComplexMembersError);
|
|
expect((caught as ComplexMembersError).status).toBe(409);
|
|
expect(transactionMock).not.toHaveBeenCalled();
|
|
expect(sendMailMock).not.toHaveBeenCalled();
|
|
});
|