-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
pr05 Typescript Migration #14: Migrate User Controller #3681
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
base: develop
Are you sure you want to change the base?
pr05 Typescript Migration #14: Migrate User Controller #3681
Conversation
…e, and resolve type errors
…tePreferences, no-verify
…eferences to /userPreferences & add response and request types
…teCookiePreferences & move to /userPreferences
…e to /authmanagement and add request and response types
Ready for review! I know this is a big one as I have also migrated the user controller methods into somewhat of a barrel structure for organisation I have annotated the places where the original code content has changed, but other than adding I will also make a PR into this branch to share for a proposal on improving the logic in All controller methods have been unit tested before migrating to maximum safety! |
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.
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.
helper to create user
with a few more properties than the mock file
i wasn't sure how sinon
worked, so I just mocked a simple object with the relevant properties, and devs can override per test basis
/* @jest-environment node */ | ||
|
||
import { last } from 'lodash'; | ||
import { Request, Response } from 'jest-express'; |
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.
this was highlighted as not being an export when the file was changed to ts
I did some digging and found the actual place where these are exported which fixed it
User.findById = jest.fn().mockResolvedValue(null); | ||
|
||
await createApiKey(request, response); | ||
await createApiKey(request, response, next); |
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.
Express RequestHandler
require a next
function, so added one here
this doesn't seem to be an issue where it's used in the actual non-test code though (routes/user)
const lastKey = last(user.apiKeys); | ||
|
||
expect(lastKey.label).toBe('my key'); | ||
expect(typeof lastKey.hashedKey).toBe('string'); | ||
expect(lastKey?.label).toBe('my key'); | ||
expect(typeof lastKey?.hashedKey).toBe('string'); | ||
|
||
const responseData = response.json.mock.calls[0][0]; | ||
expect(responseData.apiKeys.length).toBe(1); | ||
expect(responseData.apiKeys[0]).toMatchObject({ | ||
label: 'my key', | ||
token: lastKey.hashedKey | ||
token: lastKey?.hashedKey |
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.
?
was to resolve a ts error where it said lastKey might not exist
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 the logic on this controller method is a bit wonky, I will propose some changes on another PR that I'll tag you guys on
Included some comments in the test below re thoughts on the method
// NOTE: This should not pass, but it currently does!! | ||
describe('and when there is no new password on the request', () => { | ||
beforeEach(async () => { | ||
requestBody = { | ||
...minimumValidRequest, | ||
newPassword: undefined, | ||
currentPassword: OLD_PASSWORD | ||
}; | ||
|
||
// simulate check for password match passing | ||
startingUser.comparePassword = jest.fn().mockResolvedValue(true); | ||
|
||
request.setBody(requestBody); | ||
await updateSettings(request, response, next); | ||
}); | ||
it('calls saveUser with the new empty password', () => { | ||
expect(saveUser).toHaveBeenCalledWith(response, { | ||
...startingUser, | ||
password: undefined | ||
}); | ||
}); | ||
}); |
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.
highlighting this
// currently frontend doesn't seem to call password-change related things the below | ||
// not sure if we should update the logic to be cleaner? |
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.
highlighting this comment -- I couldn't find where we change password on the UI, so not sure if this is being used?
if not maybe we could remove this
I will otherwise propose how to make the logic cleaner on the other PR
describe('createUser', () => { | ||
it('should return 422 if email already exists', async () => { | ||
User.findByEmailAndUsername = jest.fn().mockResolvedValue({ | ||
email: '[email protected]', | ||
username: 'anyusername' | ||
}); | ||
|
||
request.setBody({ | ||
username: 'testuser', | ||
email: '[email protected]', | ||
password: 'password' | ||
}); | ||
|
||
await createUser(request, response, next); | ||
|
||
expect(User.findByEmailAndUsername).toHaveBeenCalledWith( | ||
'[email protected]', | ||
'testuser' | ||
); | ||
expect(response.status).toHaveBeenCalledWith(422); | ||
expect(response.send).toHaveBeenCalledWith({ error: 'Email is in use' }); | ||
}); | ||
it('should return 422 if username already exists', async () => { | ||
User.findByEmailAndUsername = jest.fn().mockResolvedValue({ | ||
email: '[email protected]', | ||
username: 'testuser' | ||
}); | ||
|
||
request.setBody({ | ||
username: 'testuser', | ||
email: '[email protected]', | ||
password: 'password' | ||
}); | ||
|
||
await createUser(request, response, next); | ||
|
||
expect(User.findByEmailAndUsername).toHaveBeenCalledWith( | ||
'[email protected]', | ||
'testuser' | ||
); | ||
expect(response.status).toHaveBeenCalledWith(422); | ||
expect(response.send).toHaveBeenCalledWith({ | ||
error: 'Username is in use' | ||
}); | ||
}); | ||
}); |
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.
this is the only controller method where not all the logic was tested
I couldn't work out how to control what new User({..})
does in the test environment, but everything is otherwise 100% tested
* Description: | ||
* - Create API key | ||
*/ | ||
export const createApiKey: RequestHandler< |
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.
res.status(404).json({ error: 'User not found' }); | ||
return; | ||
} | ||
user.username = req.body.username; |
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.
user.username = req.body.username; | |
user.username = req.body.username ?? user.username; |
Or error early if req.body doesn't have username or email
res.status(401).json({ error: 'Current password is invalid.' }); | ||
return; | ||
} | ||
user.password = req.body.newPassword!; |
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 had to add !
to req.body.newPassword
to resolve the typeError where newpassword might not exist, without changing the code logic, but I would propose to move this block within if (req.body.newPassword)
/** | ||
* - Method: `DELETE` | ||
* - Endpoint: `/auth/github` | ||
* - Authenticated: `false` -- TODO: update to true? |
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.
this is currently not a route that goes after the isAuthenticated
middleware, should we put it behind isAuthenticated
and remove the login checks?
On the UI I think I can only see if I'm logged in
/** | ||
* - Method: `DELETE` | ||
* - Endpoint: `/auth/google` | ||
* - Authenticated: `false` -- TODO: update to true? |
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.
same as above
domain: `${protocol}://${req.headers.host}`, | ||
link: `${protocol}://${req.headers.host}/verify?t=${token}` | ||
}, | ||
to: req.user!.email |
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.
added to resolve type error for user
potentially not existing
safe to do so since this is after middleware
> = async (req, res) => { | ||
try { | ||
const token = await generateToken(); | ||
const user = await User.findById(req.user!.id).exec(); |
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.
added to resolve type error for user
potentially not existing
this isn't a route thats hidden past the middleware, but I think it's called after the user has signed up, so their new user is already attached to the request/passport right?
UpdatePreferencesRequestBody | ||
> = async (req, res) => { | ||
try { | ||
const user = await User.findById(req.user!.id).exec(); |
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.
added to resolve type error for user
potentially not existing
safe to do so since this is after middleware
UpdateCookieConsentRequestBody | ||
> = async (req, res) => { | ||
try { | ||
const user = await User.findById(req.user!.id).exec(); |
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.
added to resolve type error for user
potentially not existing
safe to do so since this is after middleware
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.
this file was originally in server/utils but it's a middleware so I thought it was more appropriate here
added test before moving
"lib": ["ES2022"], | ||
"types": ["node", "jest"] | ||
"types": ["node", "jest", "express"], | ||
"typeRoots": ["./types", "../node_modules/@types"] |
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.
updated so that we can extend the express
namespace user
type to be our user
type in each request
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.
Added so that we can extend the express namespace user
type to be our user
type in each request.user
899c10c
to
1426142
Compare
Migration of the
userController
to tsChanges:
userController
methods, which can act as examples of how to test for other contributors to do migration workuser
routes into "subdomains" (related topics of routes)userController
methods into barrel structure based on these "subdomains" -- NO LOGIC CHANGESuserController
methods as ExpressRequestHandler
& extract the types for each route --> added to relevant/server/types
file:params
responseBody
requestBody
query
User
type in theExpress
namespace so that eachRequest.user
is our customUser
typeNotes:
I have some suggestions for logic improvements on the methods, but I will make those on a PR into this branch that I will link below when completed so that it's easier to view the diff
I have verified that this pull request:
npm run lint
)npm run test
)develop
branch.Fixes #123