Restarting Strapi in Unit Tests

System Information
  • Strapi Version: 3.2.5
  • Operating System: ubuntu
  • Database: sqlite
  • Node Version: 12
  • NPM Version: ?
  • Yarn Version: ?

Hi peeps, hope you’re all doing all right!

I’ve encountered an issue where I’ve setup some bootstrapped roles and permissions in the bootstrap.js file (below), but when I run with a sqlite db - it fails to create new permissions.

Somehow, the second run it works fine - so my question here is:

How can I restart strapi in the Unit tests?

Here are the things I’ve tried in app.test.js

Attempt 1 - reload()

beforeAll(async done => {
const strapiServer = await setupStrapi() 
strapiServer.reload()
done()
})

This does not seam to have an effect

Attempt 2 - stop then setup again

beforeAll(async done => {
const strapiServer = await setupStrapi() 
strapiServer.stop()
await setupStrapi() 
done()
})

This exits node and doesn’t run the tests.

PS: This is how I'm creating roles at bootstrap
'use strict'
const _ = require("lodash")

module.exports = async () => {
  const userPermissionsService = await strapi.plugins["users-permissions"].services.userspermissions

  const MY_ROLES = [{ name: 'MyRoleA' }, { name: 'MyRoleB' }]
  await createAllRoles(MY_ROLES, userPermissionsService)

  const myRole1 = await getRole("myrolea", userPermissionsService)
  setPermission(myRole1, "application", "user", "create", true)
  await userPermissionsService.updateRole(myRole1.id, myRole1)

  return
}

const createAllRoles = async (ALL_ROLES, userPermissionsService) => {
  try {
    const createRoleIfNotExists = async (role) => {
      const tentativeRole = await getRole(role.name.toLowerCase(), userPermissionsService)
      if (tentativeRole) {
        strapi.log.info(`role ${role.name} already exists`)
        return
      } else {
        strapi.log.info(`creating role ${role.name}`)
        return userPermissionsService.createRole(role)
      }
    }

    for (const role of ALL_ROLES) {
      await createRoleIfNotExists(role)
    }

  } catch (e) {
    const msg = 'Failed to create roles'
    throw new Error(msg, e)
  }
}


/**
 * @param {PluginPermissionKey} roleType
 * @param {UserPermissionsService} userPermissionsService 
 */
const getRole = async (roleType, userPermissionsService) => {
  const plugins = await userPermissionsService.getPlugins("en")
  const allRoles = await userPermissionsService.getRoles()

  const tentativeRole = _.find(allRoles, x => x.type === roleType)
  if (!tentativeRole) {
    return undefined
  }
  const { id } = tentativeRole
  return userPermissionsService.getRole(id, plugins)
}

/**
 * @param {Role} role
 * @param {PluginPermissionKey} type
 * @param {string} controller
 * @param {string} action
 * @param {boolean} enabled
 */
const setPermission = (role, type, controller, action, enabled) => {
  try {
    role.permissions[type].controllers[controller][action].enabled = enabled
  }
  catch (e) {
    strapi.log.error(`Couldn't set permission ${role.name} ${type}:${controller}:${action}:${enabled}`, e)
  }
}
Error explanation

while the printed error is
error Couldn't set permission MyRoleA application:user:create:true {}, the reason for it seems to be that the permissions of the role object are empty, and therefore the actual error is:

null: TypeError: Cannot read property 'controllers' of undefined
    at setPermission (/home/joey/humoov/humoov-backend/config/functions/bootstrap.js:66:28)
    at module.exports (/home/joey/humoov/humoov-backend/config/functions/bootstrap.js:11:3)
    at async Strapi.runBootstrapFunctions (/home/joey/humoov/humoov-backend/node_modules/strapi/lib/Strapi.js:407:5)
    at async Strapi.load (/home/joey/humoov/humoov-backend/node_modules/strapi/lib/Strapi.js:336:5)
    at async Strapi.start (/home/joey/humoov/humoov-backend/node_modules/strapi/lib/Strapi.js:190:9) {stack: 'TypeError: Cannot read property 'controllers'…kend/node_modules/strapi/lib/Strapi.js:190:9)', message: 'Cannot read property 'controllers' of undefined'}
message: 'Cannot read property 'controllers' of undefined'
stack: 'TypeError: Cannot read property 'controllers' of undefined\n    at setPermission (/home/joey/humoov/humoov-backend/config/functions/bootstrap.js:66:28)\n    at module.exports (/home/joey/humoov/humoov-backend/config/functions/bootstrap.js:11:3)\n    at async Strapi.runBootstrapFunctions (/home/joey/humoov/humoov-backend/node_modules/strapi/lib/Strapi.js:407:5)\n    at async Strapi.load (/home/joey/humoov/humoov-backend/node_modules/strapi/lib/Strapi.js:336:5)\n    at async Strapi.start (/home/joey/humoov/humoov-backend/node_modules/strapi/lib/Strapi.js:190:9)'
__proto__: Error

which makes sense since the role object looks like this:

null: {id: 3, name: 'MyRoleA', description: null, type: 'myrolea', created_by: null, …}
created_by: null
description: null
id: 3
name: 'MyRoleA'
permissions: {}
type: 'myrolea'
updated_by: null
__proto__: Object

Any advice here would be appreciated!

To clarify - this is only a problem when using sqlite (testing), as with our postgres db it is not an issue.

Update -

I’m restarting the server in case a new role was added - pay attention to the createAllRoles function below.

This does not solve the problem in testing, though - still get the same errors.

'use strict'
const _ = require("lodash")

module.exports = async () => {
  const userPermissionsService = await strapi.plugins["users-permissions"].services.userspermissions

  const MY_ROLES = [{ name: 'MyRoleA' }, { name: 'MyRoleB' }]
  await createAllRoles(MY_ROLES, userPermissionsService)

  const myRole1 = await getRole("myrolea", userPermissionsService)
  setPermission(myRole1, "application", "user", "create", true)
  await userPermissionsService.updateRole(myRole1.id, myRole1)

  return
}

const createAllRoles = async (ALL_ROLES, userPermissionsService) => {
  let newRoleWasCreated = false
  try {
    const createRoleIfNotExists = async (role) => {
      const tentativeRole = await getRole(role.name.toLowerCase(), userPermissionsService)
      if (tentativeRole) {
        strapi.log.info(`role ${role.name} already exists`)
        return
      } else {
        strapi.log.info(`creating role ${role.name}`)
        newRoleWasCreated = true
        return userPermissionsService.createRole(role)
      }
    }

    for (const role of ALL_ROLES) {
      await createRoleIfNotExists(role)
    }
    if (newRoleWasCreated) {
      strapi.log.info('at least one new role was created, restarting server to populate its permissions')
      strapi.reload()
    }
  } catch (e) {
    const msg = 'Failed to create roles'
    throw new Error(msg, e)
  }
}


/**
 * @param {PluginPermissionKey} roleType
 * @param {UserPermissionsService} userPermissionsService 
 */
const getRole = async (roleType, userPermissionsService) => {
  const plugins = await userPermissionsService.getPlugins("en")
  const allRoles = await userPermissionsService.getRoles()

  const tentativeRole = _.find(allRoles, x => x.type === roleType)
  if (!tentativeRole) {
    return undefined
  }
  const { id } = tentativeRole
  return userPermissionsService.getRole(id, plugins)
}

/**
 * @param {Role} role
 * @param {PluginPermissionKey} type
 * @param {string} controller
 * @param {string} action
 * @param {boolean} enabled
 */
const setPermission = (role, type, controller, action, enabled) => {
  try {
    role.permissions[type].controllers[controller][action].enabled = enabled
  }
  catch (e) {
    strapi.log.error(`Couldn't set permission ${role.name} ${type}:${controller}:${action}:${enabled}`, e)
  }
}

Can you share your unit test files? So I could run them locally with your bootstrap code.

If you have the bootstrap.js setup like above, as well as the unit testing setup like in the docs:

Add the following test case to tests/user/index.js:

it('should allow myRoleA to call the POST /users endpoint as defined in bootstrap.js', async done => {
  const userspermissions = strapi.plugins['users-permissions'].services.userspermissions
  const allRoles = await userspermissions.getRoles()
  const myRoleA = allRoles.filter(role => role.type === 'myrolea') //underscore because of how role.name is converted to role.type

  // create a user with myRoleA
  const user = await strapi.plugins['users-permissions'].services.user.add({
    ...mockUserData,
    username: 'myrolea1',
    email: 'myrolea1@strapi.com',
    role: myRoleA
  })

  // get jwt for that user
  const jwt = strapi.plugins['users-permissions'].services.jwt.issue({
    id: user.id
  })


  // call endpoint that should be accessible, but is not 
  await request(strapi.server)
    .post('/users') //permission to this is set to true for myRoleA in bootstrap.js, but fails to be set
    .set('accept', 'application/json')
    .set('Content-Type', 'application/json')
    .set('Authorization', 'Bearer ' + jwt)
    .expect('Content-Type', /json/)
    .expect(200) // ERROR: gets 403: Forbidden


  done()
})

I suggest you look at the printout of your server loading, as it may contain something that looks like this:

error Couldn’t set permission MyRoleA users-permissions:user:create:true:
TypeError: Cannot read property ‘controllers’ of undefined

The above error is not present when using postgres, and I belive that it’s because

strapi.reload()

does not have an effect when using sqlite but does when using postgres.

calling that reload somehow sanitizes the roles you create, s.t. you’re able to add permissions to them.