supertokens / docs

SuperTokens documentation
39 stars 64 forks source link

Improvements list #5

Open rishabhpoddar opened 3 years ago

rishabhpoddar commented 3 years ago
rishabhpoddar commented 6 months ago

About unit testing docs

The most common way to do this is to spin up a supertokens core in a docker image using the in memory db. During each test, you would create a new app in the core (https://supertokens.com/docs/multitenancy/new-app) which would essentially create a fresh db for you for that test, and then at the end of the test, you would delete this app. The app ID to use during each test would be the same. This feature is free to use with the in memory db.

If you do not want to spin up a core, then the other way is to override all the recipe functions for the recipes use. For example, below is a snippet for how to mock the updateEmailOrPassword function in the thirdpartyemailpassword recipe:

let userIdInfo: {
    [key: string]: {
        email: string,
        password: string
    }
} = {};

ThirdPartyEmailPassword.init({
    override: {
        // notice that we do not override the apis prop, and just the functions prop. 
        // this is done cause only the functions in the functions prop call the core.
        functions: (oI) => {
            return {
                ...oI,
                updateEmailOrPassword: async (input) => {
                    if (process.env.TEST) {
                        let existingUser = userIdInfo[input.userId];
                        if (existingUser === undefined) {
                            return {
                                status: "UNKNOWN_USER_ID_ERROR"
                            };
                        }
                        userIdInfo[input.userId] = {
                            password: input.password ?? existingUser.password,
                            email: input.email ?? existingUser.email
                        }
                        return {
                            status: "OK"
                        }
                    } else {
                        return oI.updateEmailOrPassword(input);
                    }
                }
            }
        }
    }
})

And then your API calls can just call the SDK functions as usual and it would work without querying the core. A few functions cannot be overriden, and those are present here: https://github.com/supertokens/supertokens-node/blob/13.4/lib/ts/index.ts.

As per the mocking the session object, you can override the session recipe as follows:

Session.init({
    override: {
        functions: (oI) => {
            return {
                ...oI,
                getSession: async (input) => {
                    if (process.env.TEST) {
                        const userId = input.req.getHeaderValue("access_token");
                        if (userId === undefined) {
                            return undefined;
                        }
                        return {
                            getAccessToken: () => "",
                            getAccessTokenPayload: () => null,
                            getExpiry: async () => -1,
                            getHandle: () => "",
                            getSessionData: async () => null,
                            getTimeCreated: async () => -1,
                            getUserId: () => userId,
                            revokeSession: async () => { },
                            updateSessionData: async () => { },
                            mergeIntoAccessTokenPayload: async () => { },
                            updateAccessTokenPayload: async () => { },
                            assertClaims: async () => { },
                            fetchAndSetClaim: async () => { },
                            getClaimValue: async () => undefined,
                            setClaimValue: async () => { },
                            removeClaim: async () => { },
                        }
                    } else {
                        return oI.getSession(input)
                    }
                },
                createNewSession: async (input) => {
                    if (process.env.TEST) {
                        // we set a mock access_token in the header which is the userId
                        input.res.setHeader("access_token", input.userId, false)
                        return {
                            getAccessToken: () => "",
                            getAccessTokenPayload: () => null,
                            getExpiry: async () => -1,
                            getHandle: () => "",
                            getSessionData: async () => null,
                            getTimeCreated: async () => -1,
                            getUserId: () => input.userId,
                            revokeSession: async () => { },
                            updateSessionData: async () => { },
                            mergeIntoAccessTokenPayload: async () => { },
                            updateAccessTokenPayload: async () => { },
                            assertClaims: async () => { },
                            fetchAndSetClaim: async () => { },
                            getClaimValue: async () => undefined,
                            setClaimValue: async () => { },
                            removeClaim: async () => { },
                        }
                    } else {
                        return oI.createNewSession(input)
                    }
                }
            }
        }
    }
})

Here we set the response object to have a header called access_token which is the userId when a new session is created. And then we override the getSession function to read this header and return the session object based on the userId. This is just an example, you can do whatever you want here. We do not override the refreshSession function cause I assume that during unit testing, you won't really run into that scenario.