TDD course with AdonisJs - 7. Moderators
Tags: tdd, testing, node, javascript
Published: 2019-10-06 05:54:31

> Originally posted at [michaelzanggl.com](https://michaelzanggl.com/articles/tdd-with-adonisjs-7). Subscribe to [my newsletter](https://michaelzanggl.com/) to never miss out on new content.

Let's build an option for moderators to delete/update any thread.

You can find all the changes in the following commit: https://github.com/MZanggl/tdd-adonisjs/commit/1618a0c17e80ac2f75c148f4bacb054757d1eaee

```javascript
test('moderator can delete threads', async ({ assert, client }) => {
  const moderator = await Factory.model('App/Models/User').create({ type: 1 })
  const thread = await Factory.model('App/Models/Thread').create()
  const response = await client.delete(thread.url()).send().loginVia(moderator).end()
  response.assertStatus(204)
  assert.equal(await Thread.getCount(), 0)
})
```

As you can see we simply create a user through our factory. But this time, we pass an object. This is to override the factory settings.
To make the override work, let's head over to factory.js, where we receive the passed data as the third argument in our user factory.

```javascript
Factory.blueprint('App/Models/User', (faker, i, data) => {
  return {
    username: faker.username(),
    email: faker.email(),
    password: '123456',
    ...data,
  }
})
```

Run the tests and we get the error `SQLITE_ERROR: table users has no column named type`.

So let's add the "type" field to our user migrations. We will simply add it to the existing migration file that ends with `_user.js.` in the "database/migrations" folder. (Tip: in vscode just search for "migration user" and the fuzzy search will find it)

```javascript
table.integer('type').defaultTo(0)
```

The way the "type" field works for now is 0 = normal user and 1 = moderator.

Running the test again returns
```javascript
expected 403 to equal 204
  403 => 204
```

This makes sense, moderators currently receive a 403 (forbidden), since we haven't made the change in our middleware yet. For that let's first break down from the feature test into a unit test in `modify-thread-policy.spec.js`

Add the following test

```javascript
test('moderator can modify threads', async ({ client }) => {
  const moderator = await Factory.model('App/Models/User').create({ type: 1 })
  const thread = await Factory.model('App/Models/Thread').create()
  let response = await client.post(`test/modify-thread-policy/${thread.id}`).loginVia(moderator).send().end()
  response.assertStatus(200)
})
```

Now this test will also return a 403, so let's change the code in `ModifyThreadPolicy.js`. 

```javascript
class ModifyThreadPolicy {
  async handle ({ params, auth, response }, next) {
    const thread = await Thread.findOrFail(params.id)
    if (auth.user.type !== 1 && thread.user_id !== auth.user.id) {
      return response.forbidden()
    }

    await next()
  }
}
```

Alright, that makes the tests pass. Now of course we have to refactor this! But now we have the tests to let us change the code with confidence.

---

First thing we want to refactor is `auth.user.type !== 1`. I don't like passing around these hardcoded values, so let's change it up like this 

```javascript
if (!auth.user.isModerator() // ...
```

If we run the tests, we will have broken most of them because the `isModerator` method does not exist yet. To create it, let's again first break down to a unit test that checks this one thing specifically.

Run the following command to create a new test "adonis make:test user" and choose "Unit test".

Replace the file with the following code testing if the user is a moderator.

```javascript
'use strict'

const { test, trait } = use('Test/Suite')('User')

const Factory = use('Factory')

trait('DatabaseTransactions')

test('can check if user is moderator', async ({ assert }) => {
  const user = await Factory.model('App/Models/User').make({ type: 1 })
  assert.isTrue(user.isModerator())
})

```

The difference between Factory.model(...).make and .create is that "make" does not store the user in the database, therefore making it a little faster.

And run the test in isolation
```bash
npm t -- -f "user.spec.js"
```

This will return the same error as before `TypeError: user.isModerator is not a function`.

Now let's add the actual code in `app/Models/User.js`

```javascript
isModerator() {
    return this.type === 1
}
```

And the test becomes green!

Let's add another test testing if the code also works for users that are not moderators.

```javascript
test('can check if user is not a moderator', async ({ assert }) => {
  const user = await Factory.model('App/Models/User').make()
  assert.isFalse(user.isModerator())
})
```

And now when we run the entire test suite again, all tests are green!


Let's head back to the policy again. Personally I find our condition to be hard to read, it can certainly be simplified:

```javascript
async handle ({ params, auth, response }, next) {
    const thread = await Thread.findOrFail(params.id)

    if (auth.user.isModerator()) {
      return next()
    }

    if (thread.user_id === auth.user.id) {
      return next()
    }

    return response.forbidden()  
  }
```

Finally let's add the missing test that moderators can update threads

```javascript
test('moderator can update title and body of threads', async ({ assert, client }) => {
  const thread = await Factory.model('App/Models/Thread').create()
  const moderator = await Factory.model('App/Models/User').create({ type: 1})
  const attributes = { title: 'new title', body: 'new body' }

  const response = await client.put(thread.url()).loginVia(moderator).send(attributes).end()
  response.assertStatus(200)
})
```