mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-19 19:11:13 +00:00
refactor(core): Update invitation endpoints to use DTOs (#12377)
This commit is contained in:
committed by
GitHub
parent
371a09de96
commit
7b2630d1a0
@@ -5,6 +5,9 @@ export { AiApplySuggestionRequestDto } from './ai/ai-apply-suggestion-request.dt
|
||||
export { LoginRequestDto } from './auth/login-request.dto';
|
||||
export { ResolveSignupTokenQueryDto } from './auth/resolve-signup-token-query.dto';
|
||||
|
||||
export { InviteUsersRequestDto } from './invitation/invite-users-request.dto';
|
||||
export { AcceptInvitationRequestDto } from './invitation/accept-invitation-request.dto';
|
||||
|
||||
export { OwnerSetupRequestDto } from './owner/owner-setup-request.dto';
|
||||
export { DismissBannerRequestDto } from './owner/dismiss-banner-request.dto';
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { AcceptInvitationRequestDto } from '../accept-invitation-request.dto';
|
||||
|
||||
describe('AcceptInvitationRequestDto', () => {
|
||||
const validUuid = '123e4567-e89b-12d3-a456-426614174000';
|
||||
|
||||
describe('Valid requests', () => {
|
||||
test.each([
|
||||
{
|
||||
name: 'complete valid invitation acceptance',
|
||||
request: {
|
||||
inviterId: validUuid,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
password: 'SecurePassword123',
|
||||
},
|
||||
},
|
||||
])('should validate $name', ({ request }) => {
|
||||
const result = AcceptInvitationRequestDto.safeParse(request);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid requests', () => {
|
||||
test.each([
|
||||
{
|
||||
name: 'missing inviterId',
|
||||
request: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
password: 'SecurePassword123',
|
||||
},
|
||||
expectedErrorPath: ['inviterId'],
|
||||
},
|
||||
{
|
||||
name: 'invalid inviterId',
|
||||
request: {
|
||||
inviterId: 'not-a-valid-uuid',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
password: 'SecurePassword123',
|
||||
},
|
||||
expectedErrorPath: ['inviterId'],
|
||||
},
|
||||
{
|
||||
name: 'missing first name',
|
||||
request: {
|
||||
inviterId: validUuid,
|
||||
firstName: '',
|
||||
lastName: 'Doe',
|
||||
password: 'SecurePassword123',
|
||||
},
|
||||
expectedErrorPath: ['firstName'],
|
||||
},
|
||||
{
|
||||
name: 'missing last name',
|
||||
request: {
|
||||
inviterId: validUuid,
|
||||
firstName: 'John',
|
||||
lastName: '',
|
||||
password: 'SecurePassword123',
|
||||
},
|
||||
expectedErrorPath: ['lastName'],
|
||||
},
|
||||
{
|
||||
name: 'password too short',
|
||||
request: {
|
||||
inviterId: validUuid,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
password: 'short',
|
||||
},
|
||||
expectedErrorPath: ['password'],
|
||||
},
|
||||
{
|
||||
name: 'password without number',
|
||||
request: {
|
||||
inviterId: validUuid,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
password: 'NoNumberPassword',
|
||||
},
|
||||
expectedErrorPath: ['password'],
|
||||
},
|
||||
])('should fail validation for $name', ({ request, expectedErrorPath }) => {
|
||||
const result = AcceptInvitationRequestDto.safeParse(request);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
|
||||
if (expectedErrorPath) {
|
||||
expect(result.error?.issues[0].path).toEqual(expectedErrorPath);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { InviteUsersRequestDto } from '../invite-users-request.dto';
|
||||
|
||||
describe('InviteUsersRequestDto', () => {
|
||||
describe('Valid requests', () => {
|
||||
test.each([
|
||||
{
|
||||
name: 'empty array',
|
||||
request: [],
|
||||
},
|
||||
{
|
||||
name: 'single user invitation with default role',
|
||||
request: [{ email: 'user@example.com' }],
|
||||
},
|
||||
{
|
||||
name: 'multiple user invitations with different roles',
|
||||
request: [
|
||||
{ email: 'user1@example.com', role: 'global:member' },
|
||||
{ email: 'user2@example.com', role: 'global:admin' },
|
||||
],
|
||||
},
|
||||
])('should validate $name', ({ request }) => {
|
||||
const result = InviteUsersRequestDto.safeParse(request);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should default role to global:member', () => {
|
||||
const result = InviteUsersRequestDto.safeParse([{ email: 'user@example.com' }]);
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data?.[0].role).toBe('global:member');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid requests', () => {
|
||||
test.each([
|
||||
{
|
||||
name: 'invalid email',
|
||||
request: [{ email: 'invalid-email' }],
|
||||
expectedErrorPath: [0, 'email'],
|
||||
},
|
||||
{
|
||||
name: 'invalid role',
|
||||
request: [
|
||||
{
|
||||
email: 'user@example.com',
|
||||
role: 'invalid-role',
|
||||
},
|
||||
],
|
||||
expectedErrorPath: [0, 'role'],
|
||||
},
|
||||
])('should fail validation for $name', ({ request, expectedErrorPath }) => {
|
||||
const result = InviteUsersRequestDto.safeParse(request);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
|
||||
if (expectedErrorPath) {
|
||||
expect(result.error?.issues[0].path).toEqual(expectedErrorPath);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
import { Z } from 'zod-class';
|
||||
|
||||
import { passwordSchema } from '../../schemas/password.schema';
|
||||
|
||||
export class AcceptInvitationRequestDto extends Z.class({
|
||||
inviterId: z.string().uuid(),
|
||||
firstName: z.string().min(1, 'First name is required'),
|
||||
lastName: z.string().min(1, 'Last name is required'),
|
||||
password: passwordSchema,
|
||||
}) {}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const roleSchema = z.enum(['global:member', 'global:admin']);
|
||||
|
||||
const invitedUserSchema = z.object({
|
||||
email: z.string().email(),
|
||||
role: roleSchema.default('global:member'),
|
||||
});
|
||||
|
||||
const invitationsSchema = z.array(invitedUserSchema);
|
||||
|
||||
export class InviteUsersRequestDto extends Array<z.infer<typeof invitedUserSchema>> {
|
||||
static safeParse(data: unknown) {
|
||||
return invitationsSchema.safeParse(data);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ export type * from './scaling';
|
||||
export type * from './frontend-settings';
|
||||
export type * from './user';
|
||||
|
||||
export type { BannerName } from './schemas/bannerName.schema';
|
||||
export type { Collaborator } from './push/collaboration';
|
||||
export type { SendWorkerStatusMessage } from './push/worker';
|
||||
|
||||
export type { BannerName } from './schemas/bannerName.schema';
|
||||
export { passwordSchema } from './schemas/password.schema';
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { passwordSchema } from '../password.schema';
|
||||
|
||||
describe('passwordSchema', () => {
|
||||
test('should throw on empty password', () => {
|
||||
const check = () => passwordSchema.parse('');
|
||||
|
||||
expect(check).toThrowError('Password must be 8 to 64 characters long');
|
||||
});
|
||||
|
||||
test('should return same password if valid', () => {
|
||||
const validPassword = 'abcd1234X';
|
||||
|
||||
const validated = passwordSchema.parse(validPassword);
|
||||
|
||||
expect(validated).toBe(validPassword);
|
||||
});
|
||||
|
||||
test('should require at least one uppercase letter', () => {
|
||||
const invalidPassword = 'abcd1234';
|
||||
|
||||
const failingCheck = () => passwordSchema.parse(invalidPassword);
|
||||
|
||||
expect(failingCheck).toThrowError('Password must contain at least 1 uppercase letter.');
|
||||
});
|
||||
|
||||
test('should require at least one number', () => {
|
||||
const validPassword = 'abcd1234X';
|
||||
const invalidPassword = 'abcdEFGH';
|
||||
|
||||
const validated = passwordSchema.parse(validPassword);
|
||||
|
||||
expect(validated).toBe(validPassword);
|
||||
|
||||
const check = () => passwordSchema.parse(invalidPassword);
|
||||
|
||||
expect(check).toThrowError('Password must contain at least 1 number.');
|
||||
});
|
||||
|
||||
test('should require a minimum length of 8 characters', () => {
|
||||
const invalidPassword = 'a'.repeat(7);
|
||||
|
||||
const check = () => passwordSchema.parse(invalidPassword);
|
||||
|
||||
expect(check).toThrowError('Password must be 8 to 64 characters long.');
|
||||
});
|
||||
|
||||
test('should require a maximum length of 64 characters', () => {
|
||||
const invalidPassword = 'a'.repeat(65);
|
||||
|
||||
const check = () => passwordSchema.parse(invalidPassword);
|
||||
|
||||
expect(check).toThrowError('Password must be 8 to 64 characters long.');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user