TDD course with AdonisJs - 4. Using the auth middleware
Tags: tdd, testing, node, javascript
Published: 2019-09-14 00:12:54

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

Our routes can currently be accessed by users who are not authenticated, so let's write a new test to confirm this!

As always you can find all the changes we made here in the following commit on GitHub: https://github.com/MZanggl/tdd-adonisjs/commit/6f50e5f277674dfe460b692cedc28d5a67d1cc55

```javascript
// test/functional/thread.spec.js

test('unauthenticated user cannot create threads', async ({ client }) => {
  const response = await client.post('/threads').send({
    title: 'test title',
    body: 'body',
  }).end()

  response.assertStatus(401)
})
```

The test fails since the response code is still 200. So let's add the integrated auth middleware to our routes.

```javascript
// start/routes.js

Route.resource('threads', 'ThreadController').only(['store', 'destroy']).middleware('auth')
```

This makes the test pass, but at the same time, we broke our other tests since they now return a status code 401 as well (unauthenticated).
In order to make them pass again, we need to be able to authenticate with a user in the tests.

First, let's create a model factory for users, the same way we did with threads.

Head back into `database/factory.js` and add the following blueprint for users.

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

Let's try this out in our functional thread.spec.js test! We can "login" using the `loginVia` method.

```javascript
test('can create threads', async ({ client }) => {
  const user = await Factory.model('App/Models/User').create()
  const response = await client.post('/threads').loginVia(user).send({
    title: 'test title',
    body: 'body',
  }).end()

  response.assertStatus(200)

  const thread = await Thread.firstOrFail()
  response.assertJSON({ thread: thread.toJSON() })
})
```

However, this fails with the error `...loginVia is not a function`. Like previously, a trait can help us resolve this issue, so let's add `trait('Auth/Client')` to the top of the file and run the test again.

Sweet! Let's apply the same fix for our existing failing delete test.

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

  assert.equal(await Thread.getCount(), 0)
})
```

Sure it's not optimal that any user can delete any thread, but we are getting there...


I think it's about time we rename the tests cases to something more meaningful.

> test('can create threads') => test('authorized user can create threads')

> test('can delete threads') => test('authorized user can delete threads')

---

With that being done it makes sense to add the user_id column to the threads table.

For this we first have to refactor our test case 'authorized user can create threads'. We are currently not actually testing if the title and body are being inserted correctly, we just assert that the response matches the first thread found in the database. So let's add that part as well

```javascript
test('authorized user can create threads', async ({ client }) => {
  const user = await Factory.model('App/Models/User').create()
  const attributes = {
    title: 'test title',
    body: 'body',
  }

  const response = await client.post('/threads').loginVia(user).send(attributes).end()
  response.assertStatus(200)

  const thread = await Thread.firstOrFail()
  response.assertJSON({ thread: thread.toJSON() })
  response.assertJSONSubset({ thread: attributes })
})
```

The test should still pass, but let's go ahead and add the user_id to the assertion we added

```javascript
response.assertJSONSubset({ thread: {...attributes, user_id: user.id} })
```

We now receive the error
```
expected { Object (thread) } to contain subset { Object (thread) }
  {
    thread: {
    - created_at: "2019-09-08 08:57:59"
    - id: 1
    - updated_at: "2019-09-08 08:57:59"
    + user_id: 1
    }
```

So let's head over to the ThreadController and swap out the "store" method with this

```javascript
async store({ request, auth, response }) {
    const attributes = { ...request.only(['title', 'body']), user_id: auth.user.id }
    const thread = await Thread.create(attributes)
    return response.json({ thread })
    }
```

Don't worry, we will refactor this after the tests are green.

The tests will now fail at the assertion `response.assertStatus(200)` with a 500 error code, so let's add `console.log(response.error)` in the previous line. It will reveal that our table is missing the column `user_id`.

Head over to the threads migration file and after body, add the user_id column like this

```javascript
table.integer('user_id').unsigned().notNullable()
```

Let's also register the new column with a foreign key. I like to keep foreign keys after all the column declarations.

```javascript
// ... column declarations

table.foreign('user_id').references('id').inTable('users')
```

Great, this test is passing again!

But it turns out we broke two other tests!

Our unit tests "can access url" and the functional test "authorized user can delete threads" are now failing because of `SQLITE_CONSTRAINT: NOT NULL constraint failed: threads.user_id`.

Both tests are making use of our model factory for threads, and of course we haven't yet updated it with the user id. So let's head over to `database/factory.js` and add the user_id to the thread factory like this:

```javascript
return {
    title: faker.word(),
    body: faker.paragraph(),
    user_id: (await Factory.model('App/Models/User').create()).id
  }
```

Be sure to turn the function into an `async` function since we have to use await here.

If we run our test suite again we should get green!

# Refactoring

Let's head over to the ThreadController and think of a more object oriented approach for this part:

```javascript
const attributes = { ...request.only(['title', 'body']), user_id: auth.user.id }
const thread = await Thread.create(attributes)
```

Would be nice if we wouldn't have to define the relationship by ourselves.
We can swap out these two lines with this

```javascript
const thread = await auth.user.threads().create(request.only(['title', 'body']))
```

Since we haven't defined the relationship yet, we will get the error `TypeError: auth.user.threads is not a function`.

So all we have to do is go to "App/Models/User.js" and add the relationship

```javascript
threads() {
    return this.hasMany('App/Models/Thread')
}
```

And that's it, a solid refactor!

Let's add another test real quick to make sure that unauthenticated users can not delete threads

```javascript
test('unauthenticated user can not delete threads', async ({ assert, client }) => {
  const thread = await Factory.model('App/Models/Thread').create()
  const response = await client.delete(thread.url()).send().end()
  response.assertStatus(401)
})
```

Of course we have to add more tests here, not every user should be able to simply delete any thread. Next time, let's test and create a policy that takes care of this for us!