-
Notifications
You must be signed in to change notification settings - Fork 10.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(API): Add user management endpoints to the Projects Public API #12329
base: master
Are you sure you want to change the base?
Changes from 19 commits
5a5ce6e
6dcbb7a
d471ed1
0cd58cb
73a4b9b
cdfc40d
e9d96f5
79d347f
d976cb6
e5ece7f
9f4727f
da0fbfe
7dafb2a
70c6792
741791e
a7af9dd
307ede1
f3c99b9
4835ad2
0a030ed
fc38d4b
8404464
c9052b3
a042310
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import { AddUsersToProjectDto } from '../add-users-to-project.dto'; | ||
|
||
describe('AddUsersToProjectDto', () => { | ||
describe('Valid requests', () => { | ||
test.each([ | ||
{ | ||
name: 'with single user', | ||
request: { | ||
relations: [ | ||
{ | ||
userId: 'user-123', | ||
role: 'project:admin', | ||
}, | ||
], | ||
}, | ||
}, | ||
{ | ||
name: 'with multiple relations', | ||
request: { | ||
relations: [ | ||
{ | ||
userId: 'user-123', | ||
role: 'project:admin', | ||
}, | ||
{ | ||
userId: 'user-456', | ||
role: 'project:editor', | ||
}, | ||
{ | ||
userId: 'user-789', | ||
role: 'project:viewer', | ||
}, | ||
], | ||
}, | ||
}, | ||
{ | ||
name: 'with all possible roles', | ||
request: { | ||
relations: [ | ||
{ userId: 'user-1', role: 'project:personalOwner' }, | ||
{ userId: 'user-2', role: 'project:admin' }, | ||
{ userId: 'user-3', role: 'project:editor' }, | ||
{ userId: 'user-4', role: 'project:viewer' }, | ||
], | ||
}, | ||
}, | ||
])('should validate $name', ({ request }) => { | ||
const result = AddUsersToProjectDto.safeParse(request); | ||
expect(result.success).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('Invalid requests', () => { | ||
test.each([ | ||
{ | ||
name: 'missing relations array', | ||
request: {}, | ||
expectedErrorPath: ['relations'], | ||
}, | ||
{ | ||
name: 'empty relations array', | ||
request: { | ||
relations: [], | ||
}, | ||
expectedErrorPath: ['relations'], | ||
}, | ||
{ | ||
name: 'invalid userId type', | ||
request: { | ||
relations: [ | ||
{ | ||
userId: 123, | ||
role: 'project:admin', | ||
}, | ||
], | ||
}, | ||
expectedErrorPath: ['relations', 0, 'userId'], | ||
}, | ||
{ | ||
name: 'empty userId', | ||
request: { | ||
relations: [ | ||
{ | ||
userId: '', | ||
role: 'project:admin', | ||
}, | ||
], | ||
}, | ||
expectedErrorPath: ['relations', 0, 'userId'], | ||
}, | ||
{ | ||
name: 'invalid role', | ||
request: { | ||
relations: [ | ||
{ | ||
userId: 'user-123', | ||
role: 'invalid-role', | ||
}, | ||
], | ||
}, | ||
expectedErrorPath: ['relations', 0, 'role'], | ||
}, | ||
{ | ||
name: 'missing role', | ||
request: { | ||
relations: [ | ||
{ | ||
userId: 'user-123', | ||
}, | ||
], | ||
}, | ||
expectedErrorPath: ['relations', 0, 'role'], | ||
}, | ||
{ | ||
name: 'invalid relations array type', | ||
request: { | ||
relations: 'not-an-array', | ||
}, | ||
expectedErrorPath: ['relations'], | ||
}, | ||
{ | ||
name: 'invalid user object in array', | ||
request: { | ||
relations: ['not-an-object'], | ||
}, | ||
expectedErrorPath: ['relations', 0], | ||
}, | ||
])('should fail validation for $name', ({ request, expectedErrorPath }) => { | ||
const result = AddUsersToProjectDto.safeParse(request); | ||
|
||
expect(result.success).toBe(false); | ||
|
||
if (expectedErrorPath) { | ||
expect(result.error?.issues[0].path).toEqual(expectedErrorPath); | ||
} | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { ChangeUserRoleInProject } from '../change-user-role-in-project.dto'; | ||
|
||
describe('ChangeUserRoleInProject', () => { | ||
describe('Allow valid roles', () => { | ||
test.each(['project:admin', 'project:editor', 'project:viewer'])('should allow %s', (role) => { | ||
const result = ChangeUserRoleInProject.safeParse({ role }); | ||
expect(result.success).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('Reject invalid roles', () => { | ||
test.each([ | ||
{ | ||
name: 'missing role', | ||
request: {}, | ||
expectedErrorPath: ['role'], | ||
}, | ||
{ | ||
name: 'empty role', | ||
request: { | ||
role: '', | ||
}, | ||
expectedErrorPath: ['role'], | ||
}, | ||
{ | ||
name: 'invalid role type', | ||
request: { | ||
role: 123, | ||
}, | ||
expectedErrorPath: ['role'], | ||
}, | ||
{ | ||
name: 'invalid role value', | ||
request: { | ||
role: 'invalid-role', | ||
}, | ||
expectedErrorPath: ['role'], | ||
}, | ||
{ | ||
name: 'personal owner role', | ||
request: { role: 'project:personalOwner' }, | ||
expectedErrorPath: ['role'], | ||
}, | ||
])('should reject $name', ({ request, expectedErrorPath }) => { | ||
const result = ChangeUserRoleInProject.safeParse(request); | ||
|
||
expect(result.success).toBe(false); | ||
|
||
if (expectedErrorPath) { | ||
expect(result.error?.issues[0].path).toEqual(expectedErrorPath); | ||
} | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { z } from 'zod'; | ||
import { Z } from 'zod-class'; | ||
|
||
import { projectRelationSchema } from '../../schemas/project.schema'; | ||
|
||
export class AddUsersToProjectDto extends Z.class({ | ||
relations: z.array(projectRelationSchema).min(1), | ||
}) {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { Z } from 'zod-class'; | ||
|
||
import { projectRoleSchema } from '../../schemas/project.schema'; | ||
|
||
export class ChangeUserRoleInProject extends Z.class({ | ||
role: projectRoleSchema.exclude(['project:personalOwner']), | ||
}) {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ export const projectRoleSchema = z.enum([ | |
export type ProjectRole = z.infer<typeof projectRoleSchema>; | ||
|
||
export const projectRelationSchema = z.object({ | ||
userId: z.string(), | ||
role: projectRoleSchema, | ||
userId: z.string().min(1), | ||
netroy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
role: projectRoleSchema.exclude(['project:personalOwner']), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've excluded the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've temporarily reverted this change, to make the build pass again. |
||
}); | ||
export type ProjectRelation = z.infer<typeof projectRelationSchema>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if the public API DTOs should be in it's own folder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And if the public API should define their own DTOs. We don't want to give the public API more power by accident when we add more power to the internal API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should definitely keep all public API DTOs in the public-api folder, since they don't need to be shared with editor-ui. but we can do that later in another PR.