Unit Testing with Jest for Strapi v4

Viewed 480

I am trying to perform unit tests with Jest for the new version of Strapi, v4 which was just released a couple of weeks ago. In accordance with their documentation, the old guide for unit testing no longer runs as expected. I have, however, modified the code to work to a certain extent. Currently I have the following:

./test/helpers/strapi.js:

const Strapi = require("@strapi/strapi");

let instance;

async function setupStrapi() {
  if (!instance) {
    /** the following code in copied from `./node_modules/strapi/lib/Strapi.js` */
    await Strapi().load();
    instance = strapi; // strapi is global now
    await instance.server
      .use(instance.server.router.routes()) // populate KOA routes
      .use(instance.server.router.allowedMethods()); // populate KOA methods

    await instance.server.mount();
  }
  return instance;
}

module.exports = {
  setupStrapi
};

./tests/app.test.js:

const fs = require("fs");
const { setupStrapi } = require("./helpers/strapi");

beforeAll(async () => {
  await setupStrapi();
});

afterAll(async () => {
  const dbSettings = strapi.config.get("database.connection.connection");

  //close server to release the db-file
  await strapi.server.destroy();

  //DATABASE_FILENAME=.tmp/test.db

  //delete test database after all tests
  if (dbSettings && dbSettings.filename) {
    const tmpDbFile = `${dbSettings.filename}`;

    if (fs.existsSync(tmpDbFile)) {
      fs.unlinkSync(tmpDbFile);
    }
  }
});

it("should return hello world", async () => {
  await request(strapi.server.httpServer).get("/api/hello").expect(200); // Expect response http code 200
});

./config/env/test/database.js

const path = require("path");

module.exports = ({ env }) => ({
  connection: {
    client: "sqlite",
    connection: {
      filename: path.join(
        __dirname,
        "../../../",
        env("DATABASE_FILENAME", ".tmp/test.db")
      ),
    },
    useNullAsDefault: true,
  },
});

The route /api/hello is a custom API endpoint. This works perfectly when running strapi develop, and all permissions are set correctly.

The tests run, but every endpoint that is not / or /admin returns 403 Forbidden, meaning there is a problem with the permissions. It would seem that the database file .tmp/data.db (used in development) is not replicated correctly in .tmp/test.db. In other words, this is close to working, but the permissions for API endpoints are not set correctly.

I have been searching through StackOverflow and the Stapi Forums over the past few days but to no avail. I would greatly appreciate some pointers as to how to fix this :)

2 Answers

It seems you need to grant the right privileges to your routes on your test DB.

For that you can create a function, lets call it grantPriviledge, and call it in your test in the function beforeAll.

// Here I want to grant the route update in my organization collection

beforeAll(async () => {
  await grantPrivilege(1, 'permissions.application.controllers.organization.update');
});

And here is the function grantPriviledge:

// roleID is 1 for authenticated and 2 for public

const grantPrivilege = async (roleID = 1, value, enabled = true, policy = '') => {
  const updateObj = value
    .split('.')
    .reduceRight((obj, next) => ({ [next]: obj }), { enabled, policy });

  const roleName = roleID === 1 ? 'Authenticated' : 'Public';
  const roleIdInDB = await strapi
    .query('role', 'users-permissions')
    .findOne({ name: roleName });
  return strapi.plugins['users-permissions'].services.userspermissions.updateRole(
    roleIdInDB,
    updateObj,
  );
};

Let me know if that helps

So in order for that to work in v4, this is how I did.

This was based in this and this but Stf's posting in this saying "inject them in your database during the bootstrap phase like it is made in the templates" was what really set me in the right track.

So if you look here you will see this function:

async function setPublicPermissions(newPermissions) {
  // Find the ID of the public role
  const publicRole = await strapi
    .query("plugin::users-permissions.role")
    .findOne({
      where: {
        type: "public",
      },
    });

  // Create the new permissions and link them to the public role
  const allPermissionsToCreate = [];
  Object.keys(newPermissions).map((controller) => {
    const actions = newPermissions[controller];
    const permissionsToCreate = actions.map((action) => {
      return strapi.query("plugin::users-permissions.permission").create({
        data: {
          action: `api::${controller}.${controller}.${action}`,
          role: publicRole.id,
        },
      });
    });
    allPermissionsToCreate.push(...permissionsToCreate);
  });
  await Promise.all(allPermissionsToCreate);
}

Later on the code, this function is called like this:

  await setPublicPermissions({
    article: ["find", "findOne"],
    category: ["find", "findOne"],
    author: ["find", "findOne"],
    global: ["find", "findOne"],
    about: ["find", "findOne"],
  });

So in my case I modified this function a bit to accept between authenticated (1) and public (2) roles inspired by Sidney C answer above.

This is how I did it:

const grantPrivilege = async (roleID = 1, newPermissions) => {
  const roleName = roleID === 1 ? "authenticated" : "public";
  // Find the ID of the public role
  const roleEntry = await strapi
    .query("plugin::users-permissions.role")
    .findOne({
      where: {
        type: roleName,
      },
    });

  // Create the new permissions and link them to the public role
  const allPermissionsToCreate = [];
  Object.keys(newPermissions).map((controller) => {
    const actions = newPermissions[controller];
    const permissionsToCreate = actions.map((action) => {
      return strapi.query("plugin::users-permissions.permission").create({
        data: {
          action: `api::${controller}.${controller}.${action}`,
          role: roleEntry.id,
        },
      });
    });
    allPermissionsToCreate.push(...permissionsToCreate);
  });
  await Promise.all(allPermissionsToCreate);
};

And then in my beforeAll block I call it like this:

    await grantPrivilege(1, {
      "my-custom-collection": ["create", "update"],
      category: ["find", "findOne"],
      author: ["find", "findOne"],
    });
Related