Step 4

Let's start refactoring the app so the tests pass.

First, add the following helper method to server/util/token.js:

const parseBearer = (bearer) => {
  const [_, token] = bearer.trim().split(" ");
  return token;
};

Make sure to update the export statement:

  module.exports = {
    createToken,
    verifyToken,
    decodeToken,
+   parseBearer
  };

Then, update the chekAdmin middleware in server/routes/users.js:

- const { verifyToken, decodeToken } = require("../util/token");
+ const { verifyToken, decodeToken, parseBearer } = require("../util/token");

  const checkAdmin = async (req, res, next) => {
    const { authorization } = req.headers;
-   const [_, token] = authorization.trim().split(" ");
+   const token = authorization ? parseBearer(authorization) : "";
    const valid = await verifyToken(token);
    const user = decodeToken(token);
    if (!valid || user.role !== "ADMIN") {
      return res.status(403).json({
        message:
          "You are not authorized to access this resource.",
      });
    }
    next();
  };

The updates above will ensure our server will not crash if an authorization token was not provided.

Let's make a few more updates! When I was writing the tests, I decided to slightly modify the behaviour of the API. For example, I decided to return an empty array when a client sends a HTTP GET request to api/users?username={{some-username}} when n user matches the {{some-username}} query. Therefore, we must update the readOne operation in server/data/UserDao.js as follows:

- // returns null if no user matches the search query
+ // returns empty array if no user matches the username
  async readOne(username) {
-   const user = await User.findOne({ username });
+   const user = await User.find({ username });
    return user;
  }

Moreover, I decided to return $404$ when a client sends a HTTP GET request to api/users/:id with an ID parameter that does not match any of the users. Therefore, we must update the read operation in server/data/UserDao.js as follows:

 // returns an empty array if there is no user with the given ID
  async read(id) {
    const user = await User.findById(id);
-   return user ? user : [];
+   if (user === null) {
+     return res.status(400).json({ message: "You must provide at least one user attribute!" });
+   }

+   return user;
  }

We should also update the /api/users/:id handler as follows:

  router.get("/api/users/:id", checkAdmin, async (req, res) => {
+   try {
      const { id } = req.params;
      const data = await users.read(id);
      res.json({ data: data ? data : [] });
+   } catch (err) {
+     res.status(err.status).json({ message: err.message });
+   }
  });

While at it, let's also update the /api/users/:id handler for PUT request to capture the edge case of a request without a payload:

  router.put("/api/users/:id", checkAdmin, async (req, res) => {
    try {
      const { id } = req.params;
      const { password, role } = req.body;
+     if (!password && !role) {
+       throw new ApiError(400, "You must provide at least one user attribute!");
+     }
      const data = await users.update(id, { password, role });
      res.json({ data });
    } catch (err) {
      res.status(err.status).json({ message: err.message });
    }
  });

Save all changes and run the tests. Except for a handful, all other tests must pass.