Example #1
0
test.group('Context - Share', (group) => {
  group.after(() => {
    delete process.env.ENV_SILENT
  })

  group.beforeEach(() => {
    ioc.restore()
    RouteStore.clear()
  })

  test('share request with view', async (assert) => {
    const Route = use('Route')

    Route.get('/', function ({ view }) {
      return view._locals.request.url()
    })

    const { text } = await supertest(appUrl).get('/').expect(200)
    assert.equal(text, '/')
  })

  test('call {is} method from view locals', async (assert) => {
    const Route = use('Route')

    Route.get('/', function ({ view }) {
      return view._locals.is('/')
    })

    const { text } = await supertest(appUrl).get('/').expect(200)
    assert.equal(text, 'true')
  })
})
test.group('Sanitizations | To Int', () => {
  test('should convert string representation of integer to integer', function (assert) {
    const sanitized = sanitizations.toInt('20')
    assert.equal(sanitized, 20)
  })

  test('should convert string representation of float to integer', function (assert) {
    const sanitized = sanitizations.toInt('20.40')
    assert.equal(sanitized, 20)
  })

  test('should convert boolean to NaN', function (assert) {
    const sanitized = sanitizations.toInt(null)
    assert.deepEqual(sanitized, NaN)
  })

  test('should convert string to NaN', function (assert) {
    const sanitized = sanitizations.toInt('hello')
    assert.deepEqual(sanitized, NaN)
  })

  test('use different radix', function (assert) {
    const sanitized = sanitizations.toInt('10', [5])
    assert.deepEqual(sanitized, 5)
  })
})
test.group('Sanitizations | trim', () => {
  test('remove whitespace from both sides of a given string.', (assert) => {
    assert.equal(sanitizations.trim(' joe '), 'joe')
  })

  test('return same value when it\'s not a string', (assert) => {
    assert.equal(sanitizations.trim(22), 22)
  })
})
test.group('Sanitizations | stripLinks', () => {
  test('remove anchor tags from a string', (assert) => {
    assert.equal(sanitizations.stripLinks('<a href=""> google.com </a>'), 'google.com')
  })

  test('return same value when it\'s not a string', (assert) => {
    assert.equal(sanitizations.stripLinks(22), 22)
  })
})
test.group('Sanitizations | escape', () => {
  test('escape HTML string', (assert) => {
    assert.equal(sanitizations.escape('<div> hello </div>'), '&lt;div&gt; hello &lt;/div&gt;')
  })

  test('return same value when it\'s not a string', (assert) => {
    assert.equal(sanitizations.escape(22), 22)
  })
})
test.group('Sanitizations | Singular', () => {
  test('should pluralize a given string', function (assert) {
    const sanitized = sanitizations.singular('people')
    assert.equal(sanitized, 'person')
  })

  test('return exact value when not a string', function (assert) {
    const sanitized = sanitizations.singular(null)
    assert.isNull(sanitized)
  })
})
Example #7
0
test.group('View', (group) => {
  group.before(async () => {
    await pify(fs.ensureDir)(path.join(__dirname, 'resources/views'))
  })

  group.after(async () => {
    await pify(fs.remove)(path.join(__dirname, 'resources'))
  })

  group.beforeEach(() => {
    this.helpers = new Helpers(path.join(__dirname, './'))
  })

  test('configure edge', (assert) => {
    const view = new View(this.helpers)
    assert.equal(view.engine._loader._viewsPath, path.join(__dirname, './resources/views'))
    assert.equal(view.engine._loader._presentersPath, path.join(__dirname, './resources/presenters'))
    assert.equal(view.engine._options.cache, false)
  })

  test('configure edge with cache enabled', (assert) => {
    const view = new View(this.helpers, true)
    assert.equal(view.engine._options.cache, true)
  })

  test('call edge methods directly on the engine', (assert) => {
    const view = new View(this.helpers, true)
    assert.equal(view.engine.renderString('{{ 2 + 2 }}').trim(), '4')
  })

  test('define globals', (assert) => {
    const view = new View(this.helpers, true)
    const fn = function () {}
    view.global('myGlobal', fn)
    assert.equal(view.engine._globals.myGlobal, fn)
  })

  test('render string', (assert) => {
    const view = new View(this.helpers, true)
    assert.equal(view.renderString('{{ 2 + 2 }}').trim(), '4')
  })

  test('share locals', (assert) => {
    const view = new View(this.helpers, true)
    assert.equal(view.share({age: 22}).renderString('{{ age }}').trim(), '22')
  })

  test('render a view', async (assert) => {
    await pify(fs.writeFile)(path.join(__dirname, 'resources/views/hello.edge'), 'Hello {{ name }}')
    const view = new View(this.helpers, true)
    assert.equal(view.render('hello', { name: 'virk' }).trim(), 'Hello virk')
  })

  test('get access to the base presenter', async (assert) => {
    const view = new View(this.helpers, true)
    assert.isDefined(view.BasePresenter)
  })
})
Example #8
0
test.group('Views tags', () => {
  test('convert inline svg to svg source', (assert) => {
    const view = new View(this.helpers)

    Tags(view, this.helpers)

    const fooSvg = path.join(__dirname, './public', 'logo.svg')
    const template = `@inlineSvg('${fooSvg}')`
    assert.equal(view.renderString(template).trim(), '<svg id="logo"></svg>')
  })

  test('throw exception with correct lineno when file is not found', (assert) => {
    assert.plan(3)

    const view = new View(this.helpers)
    Tags(view, this.helpers)

    const fooSvg = path.join(__dirname, './public', 'foo.svg')
    const template = `
    <h2> Hello </h2>
    @inlineSvg('${fooSvg}')
    `

    try {
      view.renderString(template).trim()
    } catch ({ code, lineno, charno }) {
      assert.equal(code, 'ENOENT')
      assert.equal(lineno, 2)
      assert.equal(charno, 1)
    }
  })

  test('load file when path is not absolute', (assert) => {
    const view = new View(this.helpers)
    Tags(view, this.helpers)

    const template = `@inlineSvg('logo.svg')`
    assert.equal(view.renderString(template).trim(), '<svg id="logo"></svg>')
  })

  test('prefix svg extension if missing', (assert) => {
    const view = new View(this.helpers)
    Tags(view, this.helpers)

    const template = `@inlineSvg('logo')`
    assert.equal(view.renderString(template).trim(), '<svg id="logo"></svg>')
  })
})
test.group('Sanitizations | To Date', () => {
  test('should convert a date to date object', function (assert) {
    const sanitized = sanitizations.toDate('2015-10-20')
    assert.deepEqual(sanitized, new Date('2015-10-20'))
  })

  test('do not convert existing date objects', function (assert) {
    const date = new Date()
    const sanitized = sanitizations.toDate(date)
    assert.deepEqual(sanitized, date)
  })

  test('should return null when is not a valid date', function (assert) {
    const sanitized = sanitizations.toDate('hello')
    assert.equal(sanitized, null)
  })
})
Example #10
0
test.group('Sanitizations | Normalize email', () => {
  test('normalize email by lowercasing the domain', (assert) => {
    assert.equal(sanitizations.normalizeEmail('*****@*****.**'), '*****@*****.**')
  })

  test('remove dots from gmail email address', (assert) => {
    assert.equal(sanitizations.normalizeEmail('*****@*****.**'), '*****@*****.**')
  })

  test('should return original value email is incorrect', function (assert) {
    assert.equal(sanitizations.normalizeEmail(1111), 1111)
  })

  test('return email as it is when invalid', function (assert) {
    assert.equal(sanitizations.normalizeEmail('*****@*****.**'), '*****@*****.**')
  })
})
test.group('Relations Parser', () => {
  test('parse individual relationship string', (assert) => {
    const parsed = RelationsParser.parseRelation('posts')
    assert.deepEqual(parsed, { name: 'posts', nested: null, callback: null })
  })

  test('parse relationship string with callback', (assert) => {
    const fn = function () {}
    const parsed = RelationsParser.parseRelation('posts', fn)
    assert.deepEqual(parsed, { name: 'posts', nested: null, callback: fn })
  })

  test('parse nested relationship string', (assert) => {
    const parsed = RelationsParser.parseRelation('posts.comments')
    assert.deepEqual(parsed, { name: 'posts', nested: { comments: null }, callback: null })
  })

  test('parse nested relationship with callback', (assert) => {
    const fn = function () {}
    const parsed = RelationsParser.parseRelation('posts.comments', fn)
    assert.deepEqual(parsed, { name: 'posts', nested: { comments: fn }, callback: null })
  })

  test('parse deeply nested relationship', (assert) => {
    const parsed = RelationsParser.parseRelation('posts.comments.votes')
    assert.deepEqual(parsed, { name: 'posts', nested: { 'comments.votes': null }, callback: null })
  })

  test('parse deeply nested relationship with callback', (assert) => {
    const fn = function () {}
    const parsed = RelationsParser.parseRelation('posts.comments.votes', fn)
    assert.deepEqual(parsed, { name: 'posts', nested: { 'comments.votes': fn }, callback: null })
  })

  test('parse multiple nested relations', (assert) => {
    const parsed = RelationsParser.parseRelations({ 'posts.comments': null, 'posts.likes': null })
    assert.deepEqual(parsed, { posts: { name: 'posts', callback: null, nested: { comments: null, likes: null } } })
  })

  test('parse multiple nested relations when first relation doesn\t have nesting', (assert) => {
    const parsed = RelationsParser.parseRelations({ 'posts': null, 'posts.comments': null, 'posts.likes': null })
    assert.deepEqual(parsed, { posts: { name: 'posts', callback: null, nested: { comments: null, likes: null } } })
  })

  test('parse multiple nested relations with callback', (assert) => {
    const fn = function () {}
    const parsed = RelationsParser.parseRelations({ 'posts.comments': null, 'posts.likes': null, posts: fn })
    assert.deepEqual(parsed, { posts: { name: 'posts', callback: fn, nested: { comments: null, likes: null } } })
  })
})
Example #12
0
test.group('Route | Group', () => {
  test('group routes', (assert) => {
    const route = new Route('/', function () {})
    const group = new RouteGroup([route])
    assert.deepEqual(group._routes, [route])
  })

  test('add middleware to route via group', (assert) => {
    const route = new Route('/', function () {})
    const group = new RouteGroup([route])
    group.middleware(['foo'])
    assert.deepEqual(route._middleware, ['foo'])
  })

  test('add formats to route via group', (assert) => {
    const route = new Route('/', function () {})
    const userRoute = new Route('/user', function () {})
    const group = new RouteGroup([route, userRoute])
    group.formats(['json'], true)
    assert.ok(route._regexp.test('/.json'))
    assert.ok(userRoute._regexp.test('/user.json'))
  })

  test('prefix route via group', (assert) => {
    const route = new Route('/', function () {})
    const userRoute = new Route('/user', function () {})
    const group = new RouteGroup([route, userRoute])
    group.prefix('api')
    assert.ok(route._regexp.test('/api'))
    assert.ok(userRoute._regexp.test('/api/user'))
  })

  test('define domain via group', (assert) => {
    const route = new Route('/', function () {})
    const postsRoute = new Route('/posts', function () {})
    const group = new RouteGroup([route, postsRoute])
    group.domain('blog.adonisjs.com')
    assert.ok(route._domain.test('blog.adonisjs.com'))
    assert.ok(postsRoute._domain.test('blog.adonisjs.com'))
  })
})
Example #13
0
test.group('Sanitizations | Strip tags', () => {
  test('should remove tags from a given string', function (assert) {
    const para = `Click <a href="http://google.com">here</a> to search and visit <a href="http://adonisjs.com">AdonisJs</a>`

    const sanitized = sanitizations.stripTags(para, [])
    assert.equal(sanitized, 'Click here to search and visit AdonisJs')
  })

  test('whitelist tags', function (assert) {
    const para = `Click <a href="http://google.com">here</a> to search and visit <a href="http://adonisjs.com">AdonisJs</a>`

    const sanitized = sanitizations.stripTags(para, ['a'])
    assert.equal(sanitized, 'Click <a href="http://google.com">here</a> to search and visit <a href="http://adonisjs.com">AdonisJs</a>')
  })

  test('should actual value when value is not a string', function (assert) {
    const para = 11
    const sanitized = sanitizations.stripTags(para)
    assert.equal(sanitized, para)
  })
})
Example #14
0
test.group('Sanitizations | toNull', () => {
  test('convert empty string to null', function (assert) {
    assert.isNull(sanitizations.toNull(''))
  })

  test('convert empty string with multiple spaces to null', function (assert) {
    assert.isNull(sanitizations.toNull('    '))
  })

  test('convert undefined to null', function (assert) {
    assert.isNull(sanitizations.toNull())
  })

  test('convert null to null', function (assert) {
    assert.isNull(sanitizations.toNull(null))
  })

  test('return original value when actual value is empty array', function (assert) {
    assert.isArray(sanitizations.toNull([]))
  })
})
Example #15
0
test.group('Sanitizations | Slug', () => {
  test('should convert a value to a slug', function (assert) {
    const sanitized = sanitizations.slug('learn jquery in 30minutes')
    assert.equal(sanitized, 'learn-jquery-in-30-minutes')
  })

  test('should convert a weired value to a slug', function (assert) {
    const sanitized = sanitizations.slug('weird[case')
    assert.equal(sanitized, 'weird-case')
  })

  test('should convert a dot seperate value to a slug', function (assert) {
    const sanitized = sanitizations.slug('dot.case')
    assert.equal(sanitized, 'dot-case')
  })

  test('should convert a weird character to a slug', function (assert) {
    const sanitized = sanitizations.slug('tôi tên là đức tạ')
    assert.equal(sanitized, 'toi-ten-la-duc-ta')
  })

  test('return exact value when its a number', function (assert) {
    const sanitized = sanitizations.slug(12)
    assert.equal(sanitized, 12)
  })

  test('return exact value when value is null', function (assert) {
    const sanitized = sanitizations.slug(null)
    assert.isNull(sanitized)
  })
})
Example #16
0
test.group('Sanitizations | To Boolean', () => {
  test('should convert string false to boolean false', function (assert) {
    const sanitized = sanitizations.toBoolean('false')
    assert.equal(sanitized, false)
  })

  test('should convert string 0 to boolean false', function (assert) {
    const sanitized = sanitizations.toBoolean('0')
    assert.equal(sanitized, false)
  })

  test('should convert boolean false to boolean false', function (assert) {
    const sanitized = sanitizations.toBoolean(false)
    assert.equal(sanitized, false)
  })

  test('should convert integer 0 to boolean false', function (assert) {
    const sanitized = sanitizations.toBoolean(0)
    assert.equal(sanitized, false)
  })

  test('should convert empty string to boolean false', function (assert) {
    const sanitized = sanitizations.toBoolean('')
    assert.equal(sanitized, false)
  })

  test('should convert any string to boolean true', function (assert) {
    const sanitized = sanitizations.toBoolean('hello')
    assert.equal(sanitized, true)
  })
})
test.group('Middleware | Guest', (group) => {
  groupSetup.databaseHook(group)
  groupSetup.hashHook(group)

  group.before(async () => {
    await setup()
  })

  group.beforeEach(() => {
    this.server = http.createServer()
  })

  test('throw exception when trying to reach a guest route and user is logged in', async (assert) => {
    await ioc.use('App/Models/User').create({ email: '*****@*****.**', password: '******' })

    this.server.on('request', (req, res) => {
      const Context = ioc.use('Adonis/Src/HttpContext')

      const ctx = new Context()
      ctx.request = helpers.getRequest(req)
      ctx.response = helpers.getResponse(req, res)
      ctx.session = helpers.getSession(req, res)

      const guestMiddleware = ioc.use('Adonis/Middleware/AllowGuestOnly')

      guestMiddleware
        .handle(ctx, function () {})
        .then((status) => {
          res.writeHead(200)
          res.write('skipped')
          res.end()
        })
        .catch(({ status, message }) => {
          res.writeHead(status || 500)
          res.write(message)
          res.end()
        })
    })

    const { text } = await supertest(this.server).get('/').set('Cookie', 'adonis-auth=1').expect(403)
    assert.equal(text, `E_GUEST_ONLY: Only guest user can access the route GET /`)
  })
})
Example #18
0
test.group('Route | Group', () => {
  test('group routes', (assert) => {
    const route = new Route('/', function () {})
    const group = new RouteGroup([route])
    assert.deepEqual(group._routes, [route])
  })

  test('add middleware to route via group', (assert) => {
    const route = new Route('/', function () {})
    const group = new RouteGroup([route])
    group.middleware(['foo'])
    assert.deepEqual(route.middlewareList, ['foo'])
  })

  test('add formats to route via group', (assert) => {
    const route = new Route('/', function () {})
    const userRoute = new Route('/user', function () {})
    const group = new RouteGroup([route, userRoute])
    group.formats(['json'], true)
    assert.ok(route._regexp.test('/.json'))
    assert.ok(userRoute._regexp.test('/user.json'))
  })

  test('prefix route via group', (assert) => {
    const route = new Route('/', function () {})
    const userRoute = new Route('/user', function () {})
    const group = new RouteGroup([route, userRoute])
    group.prefix('api')
    assert.ok(route._regexp.test('/api'))
    assert.ok(userRoute._regexp.test('/api/user'))
  })

  test('namespace route via group', (assert) => {
    const route = new Route('/', 'IndexController')
    const userRoute = new Route('/user', 'UserController')
    const group = new RouteGroup([route, userRoute])
    group.namespace('Admin')
    assert.equal(route.handler, 'Admin/IndexController')
    assert.equal(userRoute.handler, 'Admin/UserController')
  })

  test('define domain via group', (assert) => {
    const route = new Route('/', function () {})
    const postsRoute = new Route('/posts', function () {})
    const group = new RouteGroup([route, postsRoute])
    group.domain('blog.adonisjs.com')
    assert.ok(route.forDomain.test('blog.adonisjs.com'))
    assert.ok(postsRoute.forDomain.test('blog.adonisjs.com'))
  })
})
test.group('Response', (group) => {
  group.beforeEach(() => {
    RouteStore.clear()
  })

  test('send raw string as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.send('hello world')
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /plain/).expect(200)
    assert.equal(res.text, 'hello world')
  })

  test('send json object as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.send({name: 'virk'})
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /json/).expect(200)
    assert.deepEqual(res.body, {name: 'virk'})
  })

  test('send number as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.send(22)
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /plain/).expect(200)
    assert.equal(res.text, 22)
  })

  test('send boolean as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.send(true)
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /plain/).expect(200)
    assert.equal(res.text, 'true')
  })

  test('send html as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.send('<h2> Hello world </h2>')
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /html/).expect(200)
    assert.equal(res.text, '<h2> Hello world </h2>')
  })

  test('set http response status', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.status(304).send('hello')
      response.end()
    })
    await supertest(server).get('/').expect(304)
  })

  test('set http request header', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.header('Link', ['<http://localhost/>', '<http://localhost:3000/>'])
      response.end()
    })
    await supertest(server).get('/').expect('link', '<http://localhost/>, <http://localhost:3000/>')
  })

  test('only set the header when does not exists', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.safeHeader('Content-Type', 'application/json')
      response.safeHeader('Content-Type', 'text/plain')
      response.send('')
      response.end()
    })
    await supertest(server).get('/').expect('Content-Type', /json/)
  })

  test('remove response header', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.header('Link', ['<http://localhost/>', '<http://localhost:3000/>'])
      response.removeHeader('link')
      response.end()
    })
    const res = await supertest(server).get('/')
    assert.notProperty(res.headers, 'link')
  })

  test('get value for existing header', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.header('Link', ['<http://localhost/>', '<http://localhost:3000/>'])
      response.send(response.getHeader('link'))
      response.end()
    })
    const res = await supertest(server).get('/').expect(200)
    assert.deepEqual(res.body, ['<http://localhost/>', '<http://localhost:3000/>'])
  })

  test('download file', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.download(path.join(__dirname, '../../package.json'))
    })
    const res = await supertest(server).get('/').expect(200)
    assert.isObject(res.body)
    assert.equal(res.body.name, '@adonisjs/framework')
  })

  test('send 404 when file does not exists', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.download(path.join(__dirname, '../../logo.svg'))
    })
    await supertest(server).get('/').expect(404)
  })

  test('force download the file by setting content-disposition', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.attachment(path.join(__dirname, '../../package.json'))
    })

    const res = await supertest(server).get('/').expect('Content-Disposition', /filename="package.json"/)
    assert.isObject(res.body)
    assert.equal(res.body.name, '@adonisjs/framework')
  })

  test('force download the file with different file name', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.attachment(path.join(__dirname, '../../package.json'), 'adonis.json')
    })

    const res = await supertest(server).get('/').expect('Content-Disposition', /filename="adonis.json"/)
    assert.isObject(res.body)
    assert.equal(res.body.name, '@adonisjs/framework')
  })

  test('set the location http header', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.location('http://adonisjs.com')
      response.end()
    })

    await supertest(server).get('/').expect('Location', 'http://adonisjs.com')
  })

  test('redirect the request', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.redirect('http://adonisjs.com')
      response.end()
    })

    await supertest(server).get('/').expect('Location', 'http://adonisjs.com').expect(302)
  })

  test('set content-type based on type', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.type('html').send({username: '******'})
      response.end()
    })

    await supertest(server).get('/').expect('Content-Type', /html/).expect(200)
  })

  test('send content as json with content-type explicitly set to text/javascript', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.jsonp({username: '******'})
      response.end()
    })

    await supertest(server).get('/').expect('Content-Type', /javascript/).expect(200)
  })

  test('use the request query param callback for jsonp response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.jsonp({username: '******'})
      response.end()
    })

    const res = await supertest(server).get('/?callback=exec').expect(200)
    assert.equal(res.text, `/**/ typeof exec === 'function' && exec({"username":"******"});`)
  })

  test('use the explicit callbackFn over request query param', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.jsonp({username: '******'}, 'eval')
      response.end()
    })

    const res = await supertest(server).get('/?callback=exec').expect(200)
    assert.equal(res.text, `/**/ typeof eval === 'function' && eval({"username":"******"});`)
  })

  test('set 401 as the status via unauthorized method', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.unauthorized('Login First')
      response.end()
    })

    const res = await supertest(server).get('/').expect(401)
    assert.equal(res.text, 'Login First')
  })

  test('save cookie to the browser', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.cookie('cart_total', '20')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    assert.equal(headers['set-cookie'][0], 'cart_total=20')
  })

  test('encrypt cookie when secret is defined', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      config.set('app.appKey', SECRET)
      const response = new Response(new Request(req, res), config)
      response.cookie('cart_total', '20')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    const encrypter = simpleEncryptor({
      key: SECRET,
      hmac: false
    })

    assert.strictEqual(
      sig.unsign(
        encrypter.decrypt(querystring.unescape(headers['set-cookie'][0].replace('cart_total=', ''))).replace('s:', ''),
        SECRET
      ),
      '20'
    )
  })

  test('send plain cookie even if secret is defined', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      config.set('app.appKey', SECRET)
      const response = new Response(new Request(req, res), config)
      response.plainCookie('cart_total', '20')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    assert.equal(headers['set-cookie'][0], 'cart_total=20')
  })

  test('send vary header', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      const response = new Response(new Request(req, res), config)
      response.vary('Origin')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    assert.equal(headers.vary, 'Origin')
  })

  test('clear existing cookie by setting expiry in past', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      const response = new Response(new Request(req, res), config)
      response.clearCookie('cart')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    assert.equal(headers['set-cookie'][0], 'cart=; Expires=Thu, 01 Jan 1970 00:00:00 GMT')
  })

  test('redirect to a registered route', async (assert) => {
    RouteManager.get('users', function () {}).as('listUsers')

    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.route('listUsers')
      response.end()
    })

    await supertest(server).get('/').expect('Location', '/users').expect(302)
  })

  test('redirect to a registered route by passing controller method', async (assert) => {
    RouteManager.get('users', 'UserController.index')

    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.route('UserController.index')
      response.end()
    })

    await supertest(server).get('/').expect('Location', '/users').expect(302)
  })

  test('redirect to the string when unable to resolve route', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.route('UserController.index')
      response.end()
    })
    await supertest(server).get('/').expect('Location', 'UserController.index').expect(302)
  })

  test('redirect to dynamic route', async (assert) => {
    RouteManager.get('users/:id', 'UserController.show')

    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.route('UserController.show', { id: 2 })
      response.end()
    })

    await supertest(server).get('/').expect('Location', '/users/2').expect(302)
  })

  test('redirect with params', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.redirect('users', true)
      response.end()
    })

    await supertest(server).get('/?name=virk&age=22').expect('Location', 'users?name=virk&age=22').expect(302)
  })

  test('throw abort exception when abortIf expression is truthy', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      try {
        response.abortIf(true, 500, 'Aborted')
      } catch (error) {
        response.status(error.status)
        response.send(error.body)
      }
      response.end()
    })

    const response = await supertest(server)
      .get('/?name=virk&age=22')
      .expect(500)

    assert.equal(response.error.text, 'Aborted')
  })

  test('throw abort exception with default body and status', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      try {
        response.abortIf(true)
      } catch (error) {
        response.status(error.status)
        response.send(error.body)
      }
      response.end()
    })

    const response = await supertest(server)
      .get('/?name=virk&age=22')
      .expect(400)

    assert.equal(response.error.text, 'Request aborted')
  })

  test('evaluate expression when expression is a function', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      try {
        response.abortIf(function () { return true })
      } catch (error) {
        response.status(error.status)
        response.send(error.body)
      }
      response.end()
    })

    const response = await supertest(server)
      .get('/?name=virk&age=22')
      .expect(400)

    assert.equal(response.error.text, 'Request aborted')
  })

  test('do not throw exception when expression is not truthy', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      try {
        response.abortIf(function () { return false })
      } catch (error) {
        response.status(error.status)
        response.send(error.body)
      }
      response.end()
    })

    await supertest(server)
      .get('/?name=virk&age=22')
      .expect(204)
  })

  test('throw abort exception when abortUnless expression is falsy', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      try {
        response.abortUnless(false, 500, 'Aborted')
      } catch (error) {
        response.status(error.status)
        response.send(error.body)
      }
      response.end()
    })

    const response = await supertest(server)
      .get('/?name=virk&age=22')
      .expect(500)

    assert.equal(response.error.text, 'Aborted')
  })

  test('throw abort exception with default body and status', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      try {
        response.abortUnless(false)
      } catch (error) {
        response.status(error.status)
        response.send(error.body)
      }
      response.end()
    })

    const response = await supertest(server)
      .get('/?name=virk&age=22')
      .expect(400)

    assert.equal(response.error.text, 'Request aborted')
  })

  test('evaluate expression when expression is a function', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      try {
        response.abortUnless(function () { return false })
      } catch (error) {
        response.status(error.status)
        response.send(error.body)
      }
      response.end()
    })

    const response = await supertest(server)
      .get('/?name=virk&age=22')
      .expect(400)

    assert.equal(response.error.text, 'Request aborted')
  })

  test('do not throw exception when expression is not falsy', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      try {
        response.abortUnless(function () { return true })
      } catch (error) {
        response.status(error.status)
        response.send(error.body)
      }
      response.end()
    })

    await supertest(server)
      .get('/?name=virk&age=22')
      .expect(204)
  })

  test('do not set etag when set as false', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.send('hello world', false)
      response.end()
    })

    const res = await supertest(server).get('/').expect(200)
    assert.notProperty(res.headers, 'etag')
  })

  test('pull etag from config file', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      config.set('app.http.etag', false)
      const response = new Response(new Request(req, res), config)
      response.send('hello world')
      response.end()
    })

    const res = await supertest(server).get('/').expect(200)
    assert.notProperty(res.headers, 'etag')
  })

  test('fallback etag to true', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      config.set('app.http.etag', true)
      const response = new Response(new Request(req, res), config)
      response.send('hello world')
      response.end()
    })

    const res = await supertest(server).get('/').expect(200)
    assert.property(res.headers, 'etag')
  })

  test('do not set etag when set as false in explicit mode', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.implicitEnd = false
      response.send('hello world', false)
    })

    const res = await supertest(server).get('/').expect(200)
    assert.notProperty(res.headers, 'etag')
  })

  test('pull etag from config file in explicit mode', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      config.set('app.http.etag', false)
      const response = new Response(new Request(req, res), config)
      response.implicitEnd = false
      response.send('hello world')
    })

    const res = await supertest(server).get('/').expect(200)
    assert.notProperty(res.headers, 'etag')
  })

  test('fallback etag to true in explicit mode', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      config.set('app.http.etag', true)
      const response = new Response(new Request(req, res), config)
      response.implicitEnd = false
      response.send('hello world')
    })

    const res = await supertest(server).get('/').expect(200)
    assert.property(res.headers, 'etag')
  })

  test('send 204 when response body is null', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.send(null)
      response.end()
    })

    await supertest(server).get('/').expect(204)
  })

  test('send empty response for HEAD request', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.send({ username: '******' })
      response.end()
    })

    const { body } = await supertest(server).head('/')
    assert.deepEqual(body, {})
  })

  test('set response body empty when request is fresh', async (assert) => {
    const server = http.createServer((req, res) => {
      const resourceEtag = etag('hello world')
      req.headers['if-none-match'] = resourceEtag

      const config = new Config()
      config.set('app.http.etag', true)

      const response = new Response(new Request(req, res), config)
      response.send('hello world')
      response.end()
    })

    await supertest(server).get('/').expect(304)
  })

  test('return exact response when etag is disabled', async (assert) => {
    const server = http.createServer((req, res) => {
      const resourceEtag = etag('hello world')
      req.headers['if-none-match'] = resourceEtag

      const response = new Response(new Request(req, res), new Config())
      response.send('hello world')
      response.end()
    })

    await supertest(server).get('/').expect(200)
  })

  test('do not generate etag for intentional 400 status code', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      config.set('app.http.etag', true)

      const response = new Response(new Request(req, res), config)
      response.notFound('Not found')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(404)
    assert.notProperty(headers, 'etag')
  })

  test('generate etag when using description methods', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.ok('Received', true)
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    assert.property(headers, 'etag')
  })

  test('do not generate etag for POST request', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.ok('Received', true)
      response.end()
    })

    const { headers } = await supertest(server).post('/').expect(200)
    assert.notProperty(headers, 'etag')
  })

  test('do not generate etag for PUT request', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.ok('Received', true)
      response.end()
    })

    const { headers } = await supertest(server).put('/').expect(200)
    assert.notProperty(headers, 'etag')
  })

  test('generate etag using json method', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(new Request(req, res), new Config())
      response.json({ username: '******' }, true)
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect('content-type', 'application/json; charset=utf-8').expect(200)
    assert.property(headers, 'etag')
  })
})
test.group('Scheduler', (group) => {
  /**
   * @param {String} name
   * @return {Helpers}
   */
  function getHelpers (name) {
    ioc.fake('Adonis/Src/Helpers', () => new Helpers(path.join(__dirname, `./projects/${name}`)))
    return ioc.use('Adonis/Src/Helpers')
  }

  group.before(() => {
    ioc.fake('Task', () => require('./../src/Scheduler/Task'))
    ioc.fake('Adonis/Src/Logger', () => new Logger())
  })

  test('Should instantiate correctly', (assert) => {
    const Helpers = getHelpers('good')
    const scheduler = new Scheduler(Helpers)
    assert.equal(scheduler.tasksPath, path.join(Helpers.appRoot(), 'app', 'Tasks'))
    assert.deepEqual(scheduler.registeredTasks, [])
  })

  test('Should run with good tasks', async (assert) => {
    const Helpers = getHelpers('good')
    const scheduler = new Scheduler(Helpers)
    await scheduler.run()
    assert.isArray(scheduler.registeredTasks)
    assert.equal(scheduler.registeredTasks.length, 1)
  })

  test('Should ignore invalid task file types', async (assert) => {
    const Helpers = getHelpers('badFile')
    const scheduler = new Scheduler(Helpers)

    try {
      await scheduler.run()
      assert.isTrue(true)
    } catch (e) {
      assert.isTrue(false)
    }
  })

  test('Should fail to run gracefully if task has no schedule property', async (assert) => {
    const Helpers = getHelpers('noSchedule')
    const scheduler = new Scheduler(Helpers)

    try {
      await scheduler.run()
      assert.isTrue(false)
    } catch (e) {
      assert.isTrue(true)
    }
  })

  test('Should fail to run gracefully if task has no handler', async (assert) => {
    const Helpers = getHelpers('noHandler')
    const scheduler = new Scheduler(Helpers)

    try {
      await scheduler.run()
      assert.isTrue(false)
    } catch (e) {
      assert.isTrue(true)
    }
  })

  test('Should fail to run gracefully if no task dir exists', async (assert) => {
    const Helpers = getHelpers('noTasksDir')
    const scheduler = new Scheduler(Helpers)

    try {
      await scheduler.run()
      assert.isTrue(false)
    } catch (e) {
      assert.isTrue(true)
    }
  })

  test('Should fail to run gracefully if task dir is empty', async (assert) => {
    const Helpers = getHelpers('noTasks')
    const scheduler = new Scheduler(Helpers)

    try {
      await scheduler.run()
      assert.isTrue(false)
    } catch (e) {
      assert.isTrue(true)
    }
  })
})
test.group('Seed Database', (group) => {
  group.before(async () => {
    ioc.bind('Adonis/Src/Config', () => {
      const config = new Config()
      config.set('database', {
        connection: 'testing',
        testing: helpers.getConfig()
      })
      return config
    })

    ioc.bind('Adonis/Src/Helpers', () => {
      return new Helpers(path.join(__dirname))
    })

    await fs.ensureDir(path.join(__dirname, 'database/seeds'))

    await registrar
      .providers([
        path.join(__dirname, '../../providers/LucidProvider'),
        path.join(__dirname, '../../providers/MigrationsProvider')
      ]).registerAndBoot()

    await fs.ensureDir(path.join(__dirname, '../unit/tmp'))
    await helpers.createTables(ioc.use('Database'))
    setupResolver()
  })

  group.afterEach(async () => {
    ace.commands = {}
  })

  group.after(async () => {
    await helpers.dropTables(ioc.use('Database'))
    ioc.use('Database').close()

    try {
      await fs.remove(path.join(__dirname, '../unit/tmp'))
      await fs.remove(path.join(__dirname, 'database'))
    } catch (error) {
      if (process.platform !== 'win32' || error.code !== 'EBUSY') {
        throw error
      }
    }
  }).timeout(0)

  test('skip when there are no seed files', async (assert) => {
    ace.addCommand(Seed)
    const result = await ace.call('seed')
    assert.equal(result, 'Nothing to seed')
  })

  test('run seeds in sequence', async (assert) => {
    ace.addCommand(Seed)
    const g = global || GLOBAL
    g.stack = []

    await fs.outputFile(path.join(__dirname, 'database/seeds/bar.js'), `
      class Seed {
        run () {
          return new Promise((resolve) => {
            setTimeout(() => {
              (global || GLOBAL).stack.push('bar')
              resolve()
            }, 10)
          })
        }
      }
      module.exports = Seed
    `)

    await fs.outputFile(path.join(__dirname, 'database/seeds/baz.js'), `
      class Seed {
        run () {
          (global || GLOBAL).stack.push('baz')
        }
      }
      module.exports = Seed
    `)

    await ace.call('seed')
    assert.deepEqual(global.stack, ['bar', 'baz'])
  })

  test('run only selected files', async (assert) => {
    ace.addCommand(Seed)
    const g = global || GLOBAL
    g.stack = []

    await fs.outputFile(path.join(__dirname, 'database/seeds/bar.js'), `
      class Seed {
        run () {
          return new Promise((resolve) => {
            setTimeout(() => {
              (global || GLOBAL).stack.push('bar')
              resolve()
            }, 10)
          })
        }
      }
      module.exports = Seed
    `)

    await fs.outputFile(path.join(__dirname, 'database/seeds/foo.js'), `
      class Seed {
        run () {
          (global || GLOBAL).stack.push('foo')
        }
      }
      module.exports = Seed
    `)

    await ace.call('seed', {}, { files: 'foo.js' })
    assert.deepEqual(global.stack, ['foo'])
  })
})
test.group('Relations | Has Many Through - Belongs To Many', (group) => {
  group.before(async () => {
    ioc.singleton('Adonis/Src/Database', function () {
      const config = new Config()
      config.set('database', {
        connection: 'testing',
        testing: helpers.getConfig()
      })
      return new DatabaseManager(config)
    })
    ioc.alias('Adonis/Src/Database', 'Database')

    await fs.ensureDir(path.join(__dirname, './tmp'))
    await helpers.createTables(ioc.use('Adonis/Src/Database'))
  })

  group.afterEach(async () => {
    await ioc.use('Adonis/Src/Database').table('categories').truncate()
    await ioc.use('Adonis/Src/Database').table('sections').truncate()
    await ioc.use('Adonis/Src/Database').table('posts').truncate()
    await ioc.use('Adonis/Src/Database').table('post_section').truncate()
  })

  group.after(async () => {
    await helpers.dropTables(ioc.use('Adonis/Src/Database'))
    ioc.use('Database').close()
    try {
      await fs.remove(path.join(__dirname, './tmp'))
    } catch (error) {
      if (process.platform !== 'win32' || error.code !== 'EBUSY') {
        throw error
      }
    }
  }).timeout(0)

  test('create correct query', (assert) => {
    class Post extends Model {
    }

    class Section extends Model {
      posts () {
        return this.belongsToMany(Post)
      }
    }

    class Category extends Model {
      posts () {
        return this.manyThrough(Section, 'posts')
      }
    }

    Category._bootIfNotBooted()
    Section._bootIfNotBooted()
    Post._bootIfNotBooted()

    const category = new Category()
    category.id = 1
    category.$persisted = true

    const query = category.posts().toSQL()
    assert.equal(query.sql, helpers.formatQuery('select "posts".* from "posts" inner join "post_section" on "posts"."id" = "post_section"."post_id" inner join "sections" on "sections"."id" = "post_section"."section_id" where "sections"."category_id" = ?'))
  })

  test('selected related rows', async (assert) => {
    class Post extends Model {
    }

    class Section extends Model {
      posts () {
        return this.belongsToMany(Post)
      }
    }

    class Category extends Model {
      posts () {
        return this.manyThrough(Section, 'posts')
      }
    }

    Category._bootIfNotBooted()
    Section._bootIfNotBooted()
    Post._bootIfNotBooted()

    await ioc.use('Database').table('categories').insert([{ name: 'Sql' }, { name: 'Javascript' }])
    await ioc.use('Database').table('sections').insert([
      { name: 'Loops', category_id: 2 },
      { name: 'Conditionals', category_id: 2 }
    ])
    await ioc.use('Database').table('posts').insert({ title: 'For each loop' })
    await ioc.use('Database').table('post_section').insert({ post_id: 1, section_id: 1 })

    const js = await Category.find(2)
    const posts = await js.posts().fetch()
    assert.equal(posts.size(), 1)
    assert.equal(posts.rows[0].title, 'For each loop')
  })

  test('eagerload related rows', async (assert) => {
    class Post extends Model {
    }

    class Section extends Model {
      posts () {
        return this.belongsToMany(Post)
      }
    }

    class Category extends Model {
      posts () {
        return this.manyThrough(Section, 'posts')
      }
    }

    Category._bootIfNotBooted()
    Section._bootIfNotBooted()
    Post._bootIfNotBooted()

    await ioc.use('Database').table('categories').insert([{ name: 'Sql' }, { name: 'Javascript' }])

    await ioc.use('Database').table('sections').insert([
      { name: 'Loops', category_id: 2 },
      { name: 'Conditionals', category_id: 2 },
      { name: 'Transactions', category_id: 1 }
    ])

    await ioc.use('Database').table('posts').insert([{ title: 'For each loop' }, { title: 'Transactions 101' }])

    await ioc.use('Database').table('post_section').insert([
      { post_id: 1, section_id: 1 },
      { post_id: 2, section_id: 3 }
    ])

    const categories = await Category.query().with('posts').orderBy('id', 'asc').fetch()
    assert.instanceOf(categories, VanillaSerializer)
    assert.equal(categories.size(), 2)
    assert.equal(categories.last().getRelated('posts').size(), 1)
    assert.equal(categories.last().getRelated('posts').toJSON()[0].title, 'For each loop')
    assert.equal(categories.first().getRelated('posts').size(), 1)
    assert.equal(categories.first().getRelated('posts').toJSON()[0].title, 'Transactions 101')
  })

  test('add constraints when eager loading', async (assert) => {
    class Post extends Model {
    }

    class Section extends Model {
      posts () {
        return this.belongsToMany(Post)
      }
    }

    class Category extends Model {
      posts () {
        return this.manyThrough(Section, 'posts')
      }
    }

    Category._bootIfNotBooted()
    Section._bootIfNotBooted()
    Post._bootIfNotBooted()

    let postsQuery = null
    Post.onQuery((query) => (postsQuery = query))

    await ioc.use('Database').table('categories').insert([{ name: 'Sql' }, { name: 'Javascript' }])

    await ioc.use('Database').table('sections').insert([
      { name: 'Loops', category_id: 2, is_active: true },
      { name: 'Conditionals', category_id: 2, is_active: true },
      { name: 'Transactions', category_id: 1 }
    ])

    await ioc.use('Database').table('posts').insert([{ title: 'For each loop' }, { title: 'Transactions 101' }])

    await ioc.use('Database').table('post_section').insert([
      { post_id: 1, section_id: 1 },
      { post_id: 2, section_id: 3 }
    ])

    const categories = await Category.query().with('posts', (builder) => {
      builder.where('sections.is_active', true)
    }).orderBy('id', 'asc').fetch()

    assert.equal(postsQuery.sql, helpers.formatQuery('select "posts".*, "sections"."category_id" as "through_category_id" from "posts" inner join "post_section" on "posts"."id" = "post_section"."post_id" inner join "sections" on "sections"."id" = "post_section"."section_id" where "sections"."is_active" = ? and "sections"."category_id" in (?, ?)'))
    assert.instanceOf(categories, VanillaSerializer)
    assert.equal(categories.size(), 2)
    assert.equal(categories.last().getRelated('posts').size(), 1)
    assert.equal(categories.last().getRelated('posts').toJSON()[0].title, 'For each loop')
    assert.equal(categories.first().getRelated('posts').size(), 0)
  })

  test('fetch related row via IoC container binding', async (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany('App/Models/Post')
      }
    }

    ioc.fake('App/Models/Post', () => Post)
    ioc.fake('App/Models/User', () => User)

    class Country extends Model {
      posts () {
        return this.manyThrough('App/Models/User', 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    await ioc.use('Database').table('countries').insert({ name: 'India', id: 2 })
    await ioc.use('Database').table('users').insert({ country_id: 2, id: 20, username: '******' })
    await ioc.use('Database').table('posts').insert({ user_id: 20, title: 'Adonis 101' })

    const country = await Country.find(2)
    const posts = await country.posts().fetch()
    assert.instanceOf(posts, VanillaSerializer)
    assert.equal(posts.size(), 1)
    assert.deepEqual(posts.toJSON(), [
      {
        id: 1,
        user_id: 20,
        title: 'Adonis 101',
        created_at: null,
        updated_at: null,
        deleted_at: null
      }
    ])
  })
})
test.group('Migration Rollback', (group) => {
  group.before(async () => {
    ioc.bind('Adonis/Src/Config', () => {
      const config = new Config()
      config.set('database', {
        connection: 'testing',
        testing: helpers.getConfig()
      })
      return config
    })

    ioc.bind('Adonis/Src/Helpers', () => {
      return new Helpers(path.join(__dirname))
    })

    await fs.ensureDir(path.join(__dirname, 'database/migrations'))

    await registrar
      .providers([
        path.join(__dirname, '../../providers/LucidProvider'),
        path.join(__dirname, '../../providers/MigrationsProvider')
      ]).registerAndBoot()

    await fs.ensureDir(path.join(__dirname, '../unit/tmp'))
    await helpers.createTables(ioc.use('Database'))
    setupResolver()
  })

  group.afterEach(async () => {
    ace.commands = {}
    await ioc.use('Database').table('adonis_schema').truncate()
    await ioc.use('Database').schema.dropTableIfExists('schema_users')
  })

  group.after(async () => {
    await helpers.dropTables(ioc.use('Database'))
    await ioc.use('Database').schema.dropTableIfExists('adonis_schema')
    ioc.use('Database').close()

    try {
      await fs.remove(path.join(__dirname, '../unit/tmp'))
      await fs.remove(path.join(__dirname, 'database'))
    } catch (error) {
      if (process.platform !== 'win32' || error.code !== 'EBUSY') {
        throw error
      }
    }
  }).timeout(0)

  test('skip when there is nothing to rollback', async (assert) => {
    ace.addCommand(MigrationRollback)
    const result = await ace.call('migration:rollback')
    assert.deepEqual(result, { migrated: [], status: 'skipped', queries: undefined })
  })

  test('rollback migrations by requiring all schema files', async (assert) => {
    ace.addCommand(MigrationRun)
    ace.addCommand(MigrationRollback)

    await fs.writeFile(path.join(__dirname, 'database/migrations/User.js'), `
      const Schema = use('Schema')
      class User extends Schema {
        up () {
          this.createTable('schema_users', (table) => {
            table.increments()
            table.string('username')
          })
        }

        down () {
          this.drop('schema_users')
        }
      }

      module.exports = User
    `)

    await ace.call('migration:run')
    const result = await ace.call('migration:rollback')
    assert.deepEqual(result, { migrated: ['User'], status: 'completed', queries: undefined })
    const migrations = await ioc.use('Database').table('adonis_schema')
    assert.lengthOf(migrations, 0)
  })

  test('log queries when asked to log', async (assert) => {
    ace.addCommand(MigrationRun)
    ace.addCommand(MigrationRollback)

    await fs.writeFile(path.join(__dirname, 'database/migrations/User.js'), `
      const Schema = use('Schema')
      class User extends Schema {
        up () {
          this.createTable('schema_users', (table) => {
            table.increments()
            table.string('username')
          })
        }

        down () {
          this.drop('schema_users')
        }
      }

      module.exports = User
    `)

    await ace.call('migration:run')
    const result = await ace.call('migration:rollback', {}, { log: true })
    assert.isArray(result.queries)
  })
})
Example #24
0
test.group('Indicative', () => {
  test('should be able to call validate with inbuilt rules', async (assert) => {
    assert.plan(1)

    const data = {}
    const rules = {
      username: '******'
    }

    try {
      await indicative.validate(data, rules)
    } catch (errors) {
      assert.deepEqual(errors, [
        {
          validation: 'required',
          field: 'username',
          message: 'required validation failed on username'
        }
      ])
    }
  })

  test('should be able to call validateAll with inbuilt rules', async (assert) => {
    assert.plan(1)

    const data = {}
    const rules = {
      username: '******',
      email: 'required'
    }

    try {
      await indicative.validateAll(data, rules)
    } catch (errors) {
      assert.deepEqual(errors, [
        {
          validation: 'required',
          field: 'username',
          message: 'required validation failed on username'
        },
        {
          validation: 'required',
          field: 'email',
          message: 'required validation failed on email'
        }
      ])
    }
  })

  test('should be able to call raw validation methods', async (assert) => {
    assert.isTrue(indicative.is.existy('hello'))
  })

  test('should be able to use sanitize method', async (assert) => {
    const data = {
      email: '*****@*****.**'
    }

    const rules = {
      email: 'normalize_email'
    }

    assert.deepEqual(indicative.sanitize(data, rules), {
      email: '*****@*****.**'
    })
  })

  test('should be able to call raw sanitization methods', async (assert) => {
    assert.equal(indicative.sanitizor.normalizeEmail('*****@*****.**'), '*****@*****.**')
  })

  test('should be able to call rule method', async (assert) => {
    assert.deepEqual(indicative.rule('foo', 1), {
      name: 'foo',
      args: [1]
    })
  })

  test('should be able to access formatters', async (assert) => {
    assert.property(indicative.formatters, 'Vanilla')
  })

  test('export configure method and update defaults', async (assert) => {
    assert.plan(1)
    indicative.configure({
      EXISTY_STRICT: true
    })

    const data = {
      email: null
    }

    const rules = {
      username: '******'
    }

    try {
      await indicative.validate(data, rules)
    } catch (errors) {
      assert.deepEqual(errors, [
        {
          validation: 'required',
          field: 'username',
          message: 'required validation failed on username'
        }
      ])
    }

    indicative.configure({
      EXISTY_STRICT: indicative.configure.DEFAULTS.EXISTY_STRICT
    })
  })

  test('add new validation rules', async (assert) => {
    assert.plan(1)
    indicative.validations.time = function () {
      return new Promise((resolve, reject) => {
        resolve('validation passed')
      })
    }

    const data = {
      call: '10:20'
    }

    const rules = {
      call: 'time'
    }

    const result = await indicative.validate(data, rules)
    assert.deepEqual(result, data)
  })

  test('add new sanitization rules', async (assert) => {
    assert.plan(1)
    indicative.sanitizor.stringToNull = function () {
      return null
    }

    const data = {
      username: ''
    }

    const rules = {
      username: '******'
    }

    const result = indicative.sanitize(data, rules)
    assert.deepEqual(result, {
      username: null
    })
  })

  test('use jsonapi formatter', async (assert) => {
    assert.plan(1)

    const data = {}
    const rules = {
      username: '******'
    }

    try {
      await indicative.validate(data, rules, null, indicative.formatters.JsonApi)
    } catch (errors) {
      assert.deepEqual(errors, {
        errors: [
          {
            title: 'required',
            source: { pointer: 'username' },
            detail: 'required validation failed on username'
          }
        ]
      })
    }
  })

  test('validate array expressions', async (assert) => {
    assert.plan(1)

    const data = {
      names: [22]
    }

    const rules = {
      'names.*': 'string'
    }

    try {
      await indicative.validate(data, rules, null)
    } catch (errors) {
      assert.deepEqual(errors, [
        {
          validation: 'string',
          field: 'names.0',
          message: 'string validation failed on names.0'
        }
      ])
    }
  })
})
test.group('Encryption', () => {
  test('throw exception app key is missing', (assert) => {
    const fn = () => new Encryption(new Config())
    assert.throw(fn, 'E_MISSING_APP_KEY: Make sure to define appKey inside config/app.js file before using Encryption provider')
  })

  test('throw exception when key is smaller than 16 digits', (assert) => {
    const config = new Config()
    config.set('app.appKey', 'foo')
    const fn = () => new Encryption(config)
    assert.throw(fn, 'key must be at least 16 characters long')
  })

  test('encrypt string using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    assert.isDefined(encryption.encrypt('hello world'))
  })

  test('encrypt object using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    assert.isDefined(encryption.encrypt({name: 'virk'}))
  })

  test('encrypt boolean using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    assert.isDefined(encryption.encrypt(true))
  })

  test('encrypt number using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    assert.isDefined(encryption.encrypt(20))
  })

  test('encrypt date using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    assert.isDefined(encryption.encrypt(new Date()))
  })

  test('decrypt string using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    const encryptedValue = encryption.encrypt('hello world')
    assert.equal(encryption.decrypt(encryptedValue), 'hello world')
  })

  test('decrypt object using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    const encryptedValue = encryption.encrypt({name: 'virk'})
    assert.deepEqual(encryption.decrypt(encryptedValue), {name: 'virk'})
  })

  test('decrypt boolean using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    const encryptedValue = encryption.encrypt(true)
    assert.equal(encryption.decrypt(encryptedValue), true)
  })

  test('decrypt number using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    const encryptedValue = encryption.encrypt(20)
    assert.equal(encryption.decrypt(encryptedValue), 20)
  })

  test('decrypt date using valid key', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    const date = new Date().toString()
    const encryptedValue = encryption.encrypt(date)
    assert.deepEqual(encryption.decrypt(encryptedValue), date)
  })

  test('base64 encode a string', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    const encodedValue = encryption.base64Encode('hello world')
    assert.isDefined(encodedValue)
  })

  test('base64 decode a string', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    const encodedValue = encryption.base64Encode('hello world')
    assert.equal(encryption.base64Decode(encodedValue), 'hello world')
  })

  test('base64 decode a buffer', (assert) => {
    const config = new Config()
    config.set('app.appKey', getAppKey())
    const encryption = new Encryption(config)
    const buff = Buffer.from('hello world')
    assert.equal(encryption.base64Decode(buff), 'hello world')
  })
})
test.group('Response', () => {
  test('send raw string as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.send('hello world')
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /plain/).expect(200)
    assert.equal(res.text, 'hello world')
  })

  test('send json object as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.send({name: 'virk'})
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /json/).expect(200)
    assert.deepEqual(res.body, {name: 'virk'})
  })

  test('send number as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.send(22)
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /plain/).expect(200)
    assert.equal(res.text, 22)
  })

  test('send boolean as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.send(true)
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /plain/).expect(200)
    assert.equal(res.text, 'true')
  })

  test('send html as response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.send('<h2> Hello world </h2>')
      response.end()
    })

    const res = await supertest(server).get('/').expect('Content-Type', /html/).expect(200)
    assert.equal(res.text, '<h2> Hello world </h2>')
  })

  test('set http response status', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.status(304).send('hello')
      response.end()
    })
    await supertest(server).get('/').expect(304)
  })

  test('set http request header', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.header('Link', ['<http://localhost/>', '<http://localhost:3000/>'])
      response.end()
    })
    await supertest(server).get('/').expect('link', '<http://localhost/>, <http://localhost:3000/>')
  })

  test('only set the header when does not exists', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.safeHeader('Content-Type', 'application/json')
      response.safeHeader('Content-Type', 'text/plain')
      response.send('')
      response.end()
    })
    await supertest(server).get('/').expect('Content-Type', /json/)
  })

  test('remove response header', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.header('Link', ['<http://localhost/>', '<http://localhost:3000/>'])
      response.removeHeader('link')
      response.end()
    })
    const res = await supertest(server).get('/')
    assert.notProperty(res.headers, 'link')
  })

  test('get value for existing header', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.header('Link', ['<http://localhost/>', '<http://localhost:3000/>'])
      response.send(response.getHeader('link'))
      response.end()
    })
    const res = await supertest(server).get('/').expect(200)
    assert.deepEqual(res.body, ['<http://localhost/>', '<http://localhost:3000/>'])
  })

  test('download file', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.download(path.join(__dirname, '../../package.json'))
    })
    const res = await supertest(server).get('/').expect(200)
    assert.isObject(res.body)
    assert.equal(res.body.name, '@adonisjs/framework')
  })

  test('send 404 when file does not exists', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.download(path.join(__dirname, '../../logo.svg'))
    })
    await supertest(server).get('/').expect(404)
  })

  test('force download the file by setting content-disposition', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.attachment(path.join(__dirname, '../../package.json'))
    })

    const res = await supertest(server).get('/').expect('Content-Disposition', /filename="package.json"/)
    assert.isObject(res.body)
    assert.equal(res.body.name, '@adonisjs/framework')
  })

  test('force download the file with different file name', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.attachment(path.join(__dirname, '../../package.json'), 'adonis.json')
    })

    const res = await supertest(server).get('/').expect('Content-Disposition', /filename="adonis.json"/)
    assert.isObject(res.body)
    assert.equal(res.body.name, '@adonisjs/framework')
  })

  test('set the location http header', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.location('http://adonisjs.com')
      response.end()
    })

    await supertest(server).get('/').expect('Location', 'http://adonisjs.com')
  })

  test('redirect the request', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.redirect('http://adonisjs.com')
      response.end()
    })

    await supertest(server).get('/').expect('Location', 'http://adonisjs.com').expect(302)
  })

  test('set content-type based on type', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.type('html').send({username: '******'})
      response.end()
    })

    await supertest(server).get('/').expect('Content-Type', /html/).expect(200)
  })

  test('send content as json with content-type explicitly set to text/javascript', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.jsonp({username: '******'})
      response.end()
    })

    await supertest(server).get('/').expect('Content-Type', /javascript/).expect(200)
  })

  test('use the request query param callback for jsonp response', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.jsonp({username: '******'})
      response.end()
    })

    const res = await supertest(server).get('/?callback=exec').expect(200)
    assert.equal(res.text, `/**/ typeof exec === 'function' && exec({"username":"******"});`)
  })

  test('use the explicit callbackFn over request query param', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.jsonp({username: '******'}, 'eval')
      response.end()
    })

    const res = await supertest(server).get('/?callback=exec').expect(200)
    assert.equal(res.text, `/**/ typeof eval === 'function' && eval({"username":"******"});`)
  })

  test('set 401 as the status via unauthorized method', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.unauthorized('Login First')
      response.end()
    })

    const res = await supertest(server).get('/').expect(401)
    assert.equal(res.text, 'Login First')
  })

  test('save cookie to the browser', async (assert) => {
    const server = http.createServer((req, res) => {
      const response = new Response(req, res, new Config())
      response.cookie('cart_total', '20')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    assert.equal(headers['set-cookie'][0], 'cart_total=20')
  })

  test('encrypt cookie when secret is defined', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      config.set('app.secret', SECRET)
      const response = new Response(req, res, config)
      response.cookie('cart_total', '20')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    const encrypter = simpleEncryptor({
      key: SECRET,
      hmac: false
    })

    assert.strictEqual(
      sig.unsign(
        encrypter.decrypt(querystring.unescape(headers['set-cookie'][0].replace('cart_total=', ''))).replace('s:', ''),
        SECRET
      ),
      '20'
    )
  })

  test('send plain cookie even if secret is defined', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      config.set('app.secret', SECRET)
      const response = new Response(req, res, config)
      response.plainCookie('cart_total', '20')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    assert.equal(headers['set-cookie'][0], 'cart_total=20')
  })

  test('send vary header', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      const response = new Response(req, res, config)
      response.vary('Origin')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    assert.equal(headers.vary, 'Origin')
  })

  test('clear existing cookie by setting expiry in past', async (assert) => {
    const server = http.createServer((req, res) => {
      const config = new Config()
      const response = new Response(req, res, config)
      response.clearCookie('cart')
      response.send('')
      response.end()
    })

    const { headers } = await supertest(server).get('/').expect(200)
    assert.equal(headers['set-cookie'][0], 'cart=; Expires=Thu, 01 Jan 1970 00:00:00 GMT')
  })
})
Example #27
0
test.group('Schemes - Jwt', (group) => {
  setup.databaseHook(group)
  setup.hashHook(group)

  test('throw exception when unable to validate credentials', async (assert) => {
    assert.plan(1)

    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      scheme: 'jwt'
    }

    const lucid = new LucidSerializer()
    lucid.setConfig(config)

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    try {
      await jwt.validate('*****@*****.**', 'secret')
    } catch ({ message }) {
      assert.equal(message, 'E_USER_NOT_FOUND: Cannot find user with email as foo@bar.com')
    }
  })

  test('throw exception when password mismatches', async (assert) => {
    assert.plan(1)

    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      scheme: 'jwt'
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    try {
      await jwt.validate('*****@*****.**', 'supersecret')
    } catch ({ message }) {
      assert.equal(message, 'E_PASSWORD_MISMATCH: Cannot verify user password')
    }
  })

  test('return true when able to validate credentials', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******'
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    const validated = await jwt.validate('*****@*****.**', 'secret')
    assert.isTrue(validated)
  })

  test('generate token for user', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    const tokenPayload = await jwt.generate(user)
    const payload = await verifyToken(tokenPayload.token)

    assert.property(tokenPayload, 'token')
    assert.isNull(tokenPayload.refreshToken)
    assert.equal(tokenPayload.type, 'bearer')
    assert.equal(payload.uid, 1)
  })

  test('generate token for user and attach user to it', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    const { token } = await jwt.generate(user, true)
    const payload = await verifyToken(token)

    assert.equal(payload.uid, 1)
    assert.equal(payload.data.id, 1)
    assert.equal(payload.data.email, '*****@*****.**')
    assert.isUndefined(payload.data.password)
  })

  test('generate token for user and attach custom data to it', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    const { token } = await jwt.generate(user, { isAdmin: true })
    const payload = await verifyToken(token)

    assert.equal(payload.uid, 1)
    assert.equal(payload.data.isAdmin, true)
  })

  test('set jwt options', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET,
        issuer: 'adonisjs'
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    const { token } = await jwt.generate(user)
    const payload = await verifyToken(token)

    assert.equal(payload.iss, 'adonisjs')
  })

  test('generate jwt token with refresh token', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET,
        issuer: 'adonisjs'
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    const { refreshToken } = await jwt.withRefreshToken().generate(user)
    assert.isDefined(refreshToken)
    const userTokens = await user.tokens().fetch()
    assert.equal(userTokens.size(), 1)
    assert.equal(`e${userTokens.first().token}`, refreshToken)
    assert.equal(userTokens.first().is_revoked, 0)
    assert.equal(userTokens.first().type, 'jwt_refresh_token')
  })

  test('verify user token from header', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })
    const token = await generateToken({ uid: 1 })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header (key) {
          return `Bearer ${token}`
        }
      }
    })

    const isLoggedIn = await jwt.check()
    assert.isTrue(isLoggedIn)
    assert.instanceOf(jwt.user, User)
    assert.equal(jwt.jwtPayload.uid, jwt.user.id)
  })

  test('throw exception when jwt token is invalid', async (assert) => {
    assert.plan(2)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header (key) {
          return 'Bearer 20'
        }
      }
    })

    try {
      await jwt.check()
    } catch ({ name, message }) {
      assert.equal(message, 'E_INVALID_JWT_TOKEN: jwt malformed')
      assert.equal(name, 'InvalidJwtToken')
    }
  })

  test('throw exception when user doesn\'t exist', async (assert) => {
    assert.plan(2)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)
    const token = await generateToken({ uid: 1 })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header (key) {
          return `Bearer ${token}`
        }
      }
    })

    try {
      await jwt.check()
    } catch ({ name, message }) {
      assert.equal(message, 'E_INVALID_JWT_TOKEN: The Jwt token is invalid')
      assert.equal(name, 'InvalidJwtToken')
    }
  })

  test('throw exception when options mismatches', async (assert) => {
    assert.plan(2)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET,
        issuer: 'adonisjs'
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })
    const token = await generateToken({ uid: 1 })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header (key) {
          return `Bearer ${token}`
        }
      }
    })

    try {
      await jwt.check()
    } catch ({ name, message }) {
      assert.equal(message, 'E_INVALID_JWT_TOKEN: jwt issuer invalid. expected: adonisjs')
      assert.equal(name, 'InvalidJwtToken')
    }
  })

  test('throw exception when token has been expired', async (assert) => {
    assert.plan(2)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })
    const token = await generateToken({ uid: 1 }, { expiresIn: 1 })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header (key) {
          return `Bearer ${token}`
        }
      }
    })

    await helpers.sleep(1000)
    try {
      await jwt.check()
    } catch ({ name, message }) {
      assert.equal(message, 'E_JWT_TOKEN_EXPIRED: The jwt token has been expired. Generate a new one to continue')
      assert.equal(name, 'ExpiredJwtToken')
    }
  }).timeout(0)

  test('return user when token is correct', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })
    const token = await generateToken({ uid: 1 })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header (key) {
          return `Bearer ${token}`
        }
      }
    })

    const user = await jwt.getUser()
    assert.instanceOf(user, User)
  })

  test('find user for a refresh token', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })
    await user.tokens().create({ token: '20', is_revoked: false, type: 'jwt_refresh_token' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    const { token } = await jwt.generateForRefreshToken('20', false, { expiresIn: '1m' })
    assert.isDefined(token)

    const payload = await verifyToken(token)
    assert.equal(payload.uid, 1)
    assert.isDefined(payload.exp)
    const firstToken = await user.tokens().where('token', '20').first()
    assert.equal(firstToken.is_revoked, 0)
  })

  test('on generating token with refreshToken remove the old one', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })
    await user.tokens().create({ token: '20', is_revoked: false, type: 'jwt_refresh_token' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    const { token, refreshToken } = await jwt.newRefreshToken().generateForRefreshToken('20')
    assert.isDefined(token)

    const payload = await verifyToken(token)
    assert.equal(payload.uid, 1)
    assert.notEqual(refreshToken, '20')
    const firstToken = await user.tokens().where('token', '20').first()
    assert.isNull(firstToken)
  })

  test('generate token using credentials', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    const { token, refreshToken } = await jwt.attempt('*****@*****.**', 'secret')
    assert.isDefined(token)

    const payload = await verifyToken(token)
    assert.equal(payload.uid, 1)
    assert.notEqual(refreshToken, '20')
  })

  test('throw exception when user is not found for a refreshToken', async (assert) => {
    assert.plan(2)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    try {
      await jwt.generateForRefreshToken('20')
    } catch ({ name, message }) {
      assert.equal(name, 'InvalidRefreshToken')
      assert.equal(message, 'E_INVALID_JWT_REFRESH_TOKEN: Invalid refresh token 20')
    }
  })

  test('throw exception when jwtSecret is missing', async (assert) => {
    assert.plan(2)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {}
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    try {
      await jwt.generate({})
    } catch ({ name, message }) {
      assert.equal(name, 'RuntimeException')
      assert.match(message, /^E_INCOMPLETE_CONFIG: Make sure to define secret on jwt inside config\/auth.js/)
    }
  })

  test('throw exception when user doesn\'t have an id', async (assert) => {
    assert.plan(2)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: 'SECRET'
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    try {
      await jwt.generate({})
    } catch ({ name, message }) {
      assert.equal(name, 'RuntimeException')
      assert.match(message, /^E_RUNTIME_ERROR: Primary key value is missing for user/)
    }
  })

  test('calling check twice should return true', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })
    const token = await generateToken({ uid: 1 })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header (key) {
          return `Bearer ${token}`
        }
      }
    })

    await jwt.check()
    jwt._verifyToken = function () {
      throw new Error('Unexpected call')
    }
    await jwt.check()
  })

  test('read token from request input', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })
    const token = await generateToken({ uid: 1 }, { expiresIn: 1 })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header () {
          return null
        },
        input () {
          return token
        }
      }
    })

    const isLogged = await jwt.check()
    assert.isTrue(isLogged)
  })

  test('parse token only when token is bearer type', async (assert) => {
    assert.plan(1)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })
    const token = await generateToken({ uid: 1 }, { expiresIn: 1 })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header () {
          return token
        }
      }
    })

    try {
      await jwt.check()
    } catch ({ message }) {
      assert.equal(message, 'E_INVALID_JWT_TOKEN: jwt must be provided')
    }
  })

  test('pass runtime options to generate token', async (assert) => {
    assert.plan(1)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    const { token } = await jwt.generate(user, {}, { issuer: 'adonisjs' })
    const { iss } = await verifyToken(token)
    assert.equal(iss, 'adonisjs')
  })

  test('throw exception when calling login()', async (assert) => {
    assert.plan(1)
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      scheme: 'jwt',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    try {
      jwt.login(user)
    } catch ({ message }) {
      assert.match(message, /^E_INVALID_METHOD: login method is not implemented by jwt scheme/)
    }
  })

  test('list refresh tokens', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    const payload = await jwt.withRefreshToken().generate(user)
    const tokensList = await jwt.listTokensForUser(user)
    assert.equal(tokensList.length, 1)
    assert.equal(tokensList[0].token, payload.refreshToken)
  })

  test('return empty array when tokens doesn\'t exists', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    const tokensList = await jwt.listTokens(user)
    assert.lengthOf(tokensList, 0)
  })

  test('return empty array when user has not been defined', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    await jwt.withRefreshToken().generate(user)

    const tokensList = await jwt.listTokens()
    assert.lengthOf(tokensList, 0)
  })

  test('return encrypted tokens via database serializer', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      primaryKey: 'id',
      table: 'users',
      tokensTable: 'tokens',
      uid: 'email',
      foreignKey: 'user_id',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const database = new DatabaseSerializer(ioc.use('Hash'))
    database.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, database)
    const payload = await jwt.withRefreshToken().generate(user)

    const tokensList = await jwt.listTokensForUser({ id: 1 }, 'jwt_refresh_token')
    assert.deepEqual(tokensList[0].token, payload.refreshToken)
  })

  test('login as client', async (assert) => {
    assert.plan(2)

    const config = {
      primaryKey: 'id',
      table: 'users',
      tokensTable: 'tokens',
      uid: 'email',
      foreignKey: 'user_id',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const database = new DatabaseSerializer(ioc.use('Hash'))
    database.setConfig(config)

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, database)

    const headerFn = function (key, value) {
      assert.equal(key, 'authorization')
      assert.include(value, 'Bearer')
    }

    await jwt.clientLogin(headerFn, null, { id: 1 })
  })

  test('revoke tokens for a given user', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })
    await user.tokens().create({ type: 'jwt_refresh_token', token: '22', is_revoked: false })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    await jwt.revokeTokensForUser(user, ['22'])

    const token = await user.tokens().first()
    assert.equal(token.is_revoked, true)
    assert.equal(token.token, '22')
  })

  test('revoke all tokens for the current user', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    const payload = await jwt.withRefreshToken().generate(user)

    jwt.setCtx({
      request: {
        header (key) {
          return `Bearer ${payload.token}`
        }
      }
    })

    await jwt.check()
    await jwt.revokeTokens()

    const token = await user.tokens().first()
    assert.equal(token.is_revoked, true)
    assert.equal(`e${token.token}`, payload.refreshToken)
  })

  test('delete tokens instead of revoking it', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    const user = await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)

    const payload = await jwt.generate(user)

    jwt.setCtx({
      request: {
        header (key) {
          return `Bearer ${payload.token}`
        }
      }
    })

    await jwt.check()
    await jwt.revokeTokens(null, true)

    const token = await user.tokens().first()
    assert.isNull(token)
  })

  test('set user property when token exists', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })
    const token = await generateToken({ uid: 1 }, { expiresIn: 1 })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header () {
          return `Bearer ${token}`
        }
      }
    })

    const isLogged = await jwt.loginIfCan()
    assert.isTrue(isLogged)
    assert.instanceOf(jwt.user, User)
  })

  test('silently ignore when token header is missing', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header () {
          return null
        },
        input () {
          return null
        }
      }
    })

    const isLogged = await jwt.loginIfCan()
    assert.isFalse(isLogged)
    assert.isNull(jwt.user)
  })

  test('silently ignore when token is invalid', async (assert) => {
    const User = helpers.getUserModel()

    const config = {
      model: User,
      uid: 'email',
      password: '******',
      options: {
        secret: SECRET
      }
    }

    const lucid = new LucidSerializer(ioc.use('Hash'))
    lucid.setConfig(config)

    await User.create({ email: '*****@*****.**', password: '******' })

    const jwt = new Jwt(Encryption)
    jwt.setOptions(config, lucid)
    jwt.setCtx({
      request: {
        header () {
          return '30'
        }
      }
    })

    const isLogged = await jwt.loginIfCan()
    assert.isFalse(isLogged)
    assert.isNull(jwt.user)
  })
})
test.group('Relations | Has Many Through - Has Many ', (group) => {
  group.before(async () => {
    ioc.singleton('Adonis/Src/Database', function () {
      const config = new Config()
      config.set('database', {
        connection: 'testing',
        testing: helpers.getConfig()
      })
      return new DatabaseManager(config)
    })
    ioc.alias('Adonis/Src/Database', 'Database')

    await fs.ensureDir(path.join(__dirname, './tmp'))
    await helpers.createTables(ioc.use('Adonis/Src/Database'))
  })

  group.afterEach(async () => {
    ioc.restore()
    await ioc.use('Adonis/Src/Database').table('countries').truncate()
    await ioc.use('Adonis/Src/Database').table('users').truncate()
    await ioc.use('Adonis/Src/Database').table('posts').truncate()
  })

  group.after(async () => {
    await helpers.dropTables(ioc.use('Adonis/Src/Database'))
    ioc.use('Database').close()
    try {
      await fs.remove(path.join(__dirname, './tmp'))
    } catch (error) {
      if (process.platform !== 'win32' || error.code !== 'EBUSY') {
        throw error
      }
    }
  }).timeout(0)

  test('create correct query', (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany(Post)
      }
    }

    class Country extends Model {
      posts () {
        return this.manyThrough(User, 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    const country = new Country()
    country.id = 1
    country.$persisted = true

    const query = country.posts().toSQL()
    assert.equal(query.sql, helpers.formatQuery('select "posts".* from "posts" inner join "users" on "users"."id" = "posts"."user_id" where "users"."country_id" = ?'))
  })

  test('define through fields', (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany(Post)
      }
    }

    class Country extends Model {
      posts () {
        return this.manyThrough(User, 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    const country = new Country()
    country.id = 1
    country.$persisted = true

    const query = country.posts().selectThrough('username').toSQL()
    assert.equal(query.sql, helpers.formatQuery('select "posts".*, "users"."username" as "through_username" from "posts" inner join "users" on "users"."id" = "posts"."user_id" where "users"."country_id" = ?'))
  })

  test('define related fields', (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany(Post)
      }
    }

    class Country extends Model {
      posts () {
        return this.manyThrough(User, 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    const country = new Country()
    country.id = 1
    country.$persisted = true

    const query = country.posts().selectRelated('title').select('name').toSQL()
    assert.equal(query.sql, helpers.formatQuery('select "countries"."name", "posts"."title" from "posts" inner join "users" on "users"."id" = "posts"."user_id" where "users"."country_id" = ?'))
  })

  test('fetch related row', async (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany(Post)
      }
    }

    class Country extends Model {
      posts () {
        return this.manyThrough(User, 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    await ioc.use('Database').table('countries').insert({ name: 'India', id: 2 })
    await ioc.use('Database').table('users').insert({ country_id: 2, id: 20, username: '******' })
    await ioc.use('Database').table('posts').insert({ user_id: 20, title: 'Adonis 101' })

    const country = await Country.find(2)
    const posts = await country.posts().fetch()
    assert.instanceOf(posts, VanillaSerializer)
    assert.equal(posts.size(), 1)
    assert.deepEqual(posts.toJSON(), [
      {
        id: 1,
        user_id: 20,
        title: 'Adonis 101',
        created_at: null,
        updated_at: null,
        deleted_at: null
      }
    ])
  })

  test('eagerload related rows', async (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany(Post)
      }
    }

    class Country extends Model {
      posts () {
        return this.manyThrough(User, 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    await ioc.use('Database').table('countries').insert({ name: 'India', id: 2 })
    await ioc.use('Database').table('users').insert({ country_id: 2, id: 20, username: '******' })
    await ioc.use('Database').table('posts').insert({ user_id: 20, title: 'Adonis 101' })

    const countries = await Country.query().with('posts', (builder) => {
      builder.selectThrough('id')
    }).fetch()

    assert.equal(countries.size(), 1)
    const country = countries.first()

    assert.instanceOf(country.getRelated('posts'), VanillaSerializer)
    assert.equal(country.getRelated('posts').size(), 1)
    assert.equal(country.getRelated('posts').toJSON()[0].__meta__.through_country_id, country.id)
  })

  test('limit parent rows based upon child rows', async (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany(Post)
      }
    }

    class Country extends Model {
      posts () {
        return this.manyThrough(User, 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    let countryQuery = null
    Country.onQuery((query) => (countryQuery = query))

    await ioc.use('Database').table('countries').insert({ name: 'India', id: 2 })

    await Country.query().has('posts').fetch()
    assert.equal(countryQuery.sql, helpers.formatQuery('select * from "countries" where exists (select * from "posts" inner join "users" on "users"."id" = "posts"."user_id" where countries.id = users.country_id)'))
  })

  test('limit parent rows based upon child rows count', async (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany(Post)
      }
    }

    class Country extends Model {
      posts () {
        return this.manyThrough(User, 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    let countryQuery = null
    Country.onQuery((query) => (countryQuery = query))

    await ioc.use('Database').table('countries').insert({ name: 'India', id: 2 })

    await Country.query().has('posts', '>', 1).fetch()
    assert.equal(countryQuery.sql, helpers.formatQuery('select * from "countries" where (select count(*) from "posts" inner join "users" on "users"."id" = "posts"."user_id" where countries.id = users.country_id) > ?'))
  })

  test('fetch only filtered parent rows', async (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany(Post)
      }
    }

    class Country extends Model {
      posts () {
        return this.manyThrough(User, 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    let countryQuery = null
    Country.onQuery((query) => (countryQuery = query))

    await ioc.use('Database').table('countries').insert([{ name: 'India', id: 2 }, { name: 'UK', id: 3 }])
    await ioc.use('Database').table('users').insert([{ country_id: 2, id: 20, username: '******' }, { country_id: 3, username: '******' }])
    await ioc.use('Database').table('posts').insert({ user_id: 20, title: 'Adonis 101' })

    const countries = await Country.query().has('posts').fetch()
    assert.equal(countries.size(), 1)
    assert.equal(countries.first().name, 'India')
    assert.equal(countryQuery.sql, helpers.formatQuery('select * from "countries" where exists (select * from "posts" inner join "users" on "users"."id" = "posts"."user_id" where countries.id = users.country_id)'))
  })

  test('paginate only filtered parent rows', async (assert) => {
    class Post extends Model {
    }

    class User extends Model {
      posts () {
        return this.hasMany(Post)
      }
    }

    class Country extends Model {
      posts () {
        return this.manyThrough(User, 'posts')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Post._bootIfNotBooted()

    let countryQuery = null
    Country.onQuery((query) => (countryQuery = query))

    await ioc.use('Database').table('countries').insert([{ name: 'India', id: 2 }, { name: 'UK', id: 3 }])
    await ioc.use('Database').table('users').insert([{ country_id: 2, id: 20, username: '******' }, { country_id: 3, username: '******' }])
    await ioc.use('Database').table('posts').insert({ user_id: 20, title: 'Adonis 101' })

    const countries = await Country.query().has('posts').paginate()
    assert.equal(countries.size(), 1)
    assert.equal(countries.first().name, 'India')
    assert.equal(countryQuery.sql, helpers.formatQuery('select * from "countries" where exists (select * from "posts" inner join "users" on "users"."id" = "posts"."user_id" where countries.id = users.country_id) limit ?'))
  })
})
test.group('Relations | Has Many', (group) => {
  group.before(async () => {
    ioc.singleton('Adonis/Src/Database', function () {
      const config = new Config()
      config.set('database', {
        connection: 'testing',
        testing: helpers.getConfig()
      })
      return new DatabaseManager(config)
    })
    ioc.alias('Adonis/Src/Database', 'Database')

    await fs.ensureDir(path.join(__dirname, './tmp'))
    await helpers.createTables(ioc.use('Adonis/Src/Database'))
  })

  group.afterEach(async () => {
    ioc.restore()
    await ioc.use('Adonis/Src/Database').table('users').truncate()
    await ioc.use('Adonis/Src/Database').table('cars').truncate()
    await ioc.use('Adonis/Src/Database').table('parts').truncate()
  })

  group.after(async () => {
    await helpers.dropTables(ioc.use('Adonis/Src/Database'))
    ioc.use('Database').close()
    try {
      await fs.remove(path.join(__dirname, './tmp'))
    } catch (error) {
      if (process.platform !== 'win32' || error.code !== 'EBUSY') {
        throw error
      }
    }
  }).timeout(0)

  test('get instance of has many when calling to relation method', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    Car.onQuery((query) => (carQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'merc', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])

    const user = await User.find(1)
    const cars = await user.cars().fetch()
    assert.instanceOf(cars, VanillaSerializer)
    assert.equal(cars.size(), 2)
    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "user_id" = ?'))
    assert.deepEqual(carQuery.bindings, helpers.formatBindings([1]))
  })

  test('get first instance of related model', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    Car.onQuery((query) => (carQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'merc', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])

    const user = await User.find(1)
    const car = await user.cars().first()
    assert.instanceOf(car, Car)
    assert.equal(car.name, 'merc')
    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "user_id" = ? limit ?'))
    assert.deepEqual(carQuery.bindings, helpers.formatBindings([1, 1]))
  })

  test('eagerload relation', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    Car.onQuery((query) => (carQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'merc', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])

    const user = await User.query().with('cars').first()
    assert.instanceOf(user.getRelated('cars'), VanillaSerializer)
    assert.equal(user.getRelated('cars').size(), 2)
    assert.deepEqual(user.getRelated('cars').rows.map((car) => car.$parent), ['User', 'User'])
    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "user_id" = ?'))
    assert.deepEqual(carQuery.bindings, helpers.formatBindings([1]))
  })

  test('add constraints when eagerloading', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    Car.onQuery((query) => (carQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'merc', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])

    const users = await User.query().with('cars', (builder) => {
      builder.where('model', '>', '2000')
    }).fetch()
    const user = users.first()
    assert.equal(user.getRelated('cars').size(), 1)
    assert.equal(user.getRelated('cars').rows[0].name, 'audi')
    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "model" > ? and "user_id" in (?)'))
    assert.deepEqual(carQuery.bindings, helpers.formatBindings(['2000', 1]))
  })

  test('return serailizer instance when nothing exists', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    Car.onQuery((query) => (carQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    const users = await User.query().with('cars').fetch()
    const user = users.first()
    assert.equal(user.getRelated('cars').size(), 0)
    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "user_id" in (?)'))
    assert.deepEqual(carQuery.bindings, helpers.formatBindings([1]))
  })

  test('calling toJSON should build right json structure', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'merc', model: '1990' },
      { user_id: 2, name: 'audi', model: '2001' }
    ])

    const users = await User.query().with('cars').fetch()
    const json = users.toJSON()
    assert.equal(json[0].cars[0].name, 'merc')
    assert.equal(json[1].cars[0].name, 'audi')
  })

  test('calling toJSON should build right json structure', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'merc', model: '1990' },
      { user_id: 2, name: 'audi', model: '2001' }
    ])

    const users = await User.query().with('cars').fetch()
    const json = users.toJSON()
    assert.equal(json[0].cars[0].name, 'merc')
    assert.equal(json[1].cars[0].name, 'audi')
  })

  test('calling toJSON should build right json structure', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'merc', model: '1990' },
      { user_id: 2, name: 'audi', model: '2001' }
    ])

    const users = await User.query().with('cars').fetch()
    const json = users.toJSON()
    assert.equal(json[0].cars[0].name, 'merc')
    assert.equal(json[1].cars[0].name, 'audi')
  })

  test('should work with nested relations', async (assert) => {
    class Part extends Model {
    }

    class Car extends Model {
      parts () {
        return this.hasMany(Part)
      }
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Part._bootIfNotBooted()
    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    let partQuery = null
    Car.onQuery((query) => (carQuery = query))
    Part.onQuery((query) => (partQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])
    await ioc.use('Database').table('parts').insert([
      { car_id: 1, part_name: 'wheels' },
      { car_id: 1, part_name: 'engine' },
      { car_id: 2, part_name: 'wheels' },
      { car_id: 2, part_name: 'engine' }
    ])

    const user = await User.query().with('cars.parts').first()
    assert.equal(user.getRelated('cars').size(), 2)
    assert.equal(user.getRelated('cars').first().getRelated('parts').size(), 2)
    assert.equal(user.getRelated('cars').last().getRelated('parts').size(), 2)
    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "user_id" = ?'))
    assert.equal(partQuery.sql, helpers.formatQuery('select * from "parts" where "car_id" in (?, ?)'))
  })

  test('add query constraint to nested query', async (assert) => {
    class Part extends Model {
    }

    class Car extends Model {
      parts () {
        return this.hasMany(Part)
      }
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Part._bootIfNotBooted()
    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    let partQuery = null
    Car.onQuery((query) => (carQuery = query))
    Part.onQuery((query) => (partQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])
    await ioc.use('Database').table('parts').insert([
      { car_id: 1, part_name: 'wheels' },
      { car_id: 1, part_name: 'engine' },
      { car_id: 2, part_name: 'wheels' },
      { car_id: 2, part_name: 'engine' }
    ])

    const user = await User.query().with('cars.parts', (builder) => builder.where('part_name', 'engine')).first()
    assert.equal(user.getRelated('cars').size(), 2)
    assert.equal(user.getRelated('cars').first().getRelated('parts').size(), 1)
    assert.equal(user.getRelated('cars').last().getRelated('parts').size(), 1)
    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "user_id" = ?'))
    assert.equal(partQuery.sql, helpers.formatQuery('select * from "parts" where "part_name" = ? and "car_id" in (?, ?)'))
  })

  test('add query constraint to child and grand child query', async (assert) => {
    class Part extends Model {
    }

    class Car extends Model {
      parts () {
        return this.hasMany(Part)
      }
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Part._bootIfNotBooted()
    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    let partQuery = null
    Car.onQuery((query) => (carQuery = query))
    Part.onQuery((query) => (partQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])
    await ioc.use('Database').table('parts').insert([
      { car_id: 1, part_name: 'wheels' },
      { car_id: 1, part_name: 'engine' },
      { car_id: 2, part_name: 'wheels' },
      { car_id: 2, part_name: 'engine' }
    ])

    const user = await User.query().with('cars', (builder) => {
      builder.where('name', 'audi').with('parts', (builder) => builder.where('part_name', 'engine'))
    }).first()

    assert.equal(user.getRelated('cars').size(), 1)
    assert.equal(user.getRelated('cars').first().getRelated('parts').size(), 1)
    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "name" = ? and "user_id" = ?'))
    assert.equal(partQuery.sql, helpers.formatQuery('select * from "parts" where "part_name" = ? and "car_id" in (?)'))
  })

  test('get relation count', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let userQuery = null
    User.onQuery((query) => (userQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])

    const user = await User.query().withCount('cars').first()
    assert.deepEqual(user.$sideLoaded, { cars_count: helpers.formatNumber(2) })
    assert.equal(userQuery.sql, helpers.formatQuery('select *, (select count(*) from "cars" where users.id = cars.user_id) as "cars_count" from "users" limit ?'))
  })

  test('filter parent based upon child', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let userQuery = null
    User.onQuery((query) => (userQuery = query))

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])

    const users = await User.query().has('cars').fetch()
    assert.equal(users.size(), 1)
    assert.equal(userQuery.sql, helpers.formatQuery('select * from "users" where exists (select * from "cars" where users.id = cars.user_id)'))
  })

  test('define minimum count via has', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let userQuery = null
    User.onQuery((query) => (userQuery = query))

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' },
      { user_id: 2, name: 'audi', model: '2001' }
    ])

    const users = await User.query().has('cars', '>=', 2).fetch()
    assert.equal(users.size(), 1)
    assert.equal(userQuery.sql, helpers.formatQuery('select * from "users" where (select count(*) from "cars" where users.id = cars.user_id) >= ?'))
  })

  test('add additional constraints via where has', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let userQuery = null
    User.onQuery((query) => (userQuery = query))

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' },
      { user_id: 2, name: 'audi', model: '2001' }
    ])

    const users = await User.query().whereHas('cars', (builder) => {
      return builder.where('name', 'audi')
    }).fetch()
    assert.equal(users.size(), 2)
    assert.equal(userQuery.sql, helpers.formatQuery('select * from "users" where exists (select * from "cars" where "name" = ? and users.id = cars.user_id)'))
  })

  test('add additional constraints and count constraints at same time', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let userQuery = null
    User.onQuery((query) => (userQuery = query))

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' },
      { user_id: 2, name: 'audi', model: '2001' }
    ])

    const users = await User.query().whereHas('cars', (builder) => {
      return builder.where('name', 'audi')
    }, '>', 1).fetch()
    assert.equal(users.size(), 0)
    assert.equal(userQuery.sql, helpers.formatQuery('select * from "users" where (select count(*) from "cars" where "name" = ? and users.id = cars.user_id) > ?'))
  })

  test('add orWhereHas clause', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let userQuery = null
    User.onQuery((query) => (userQuery = query))

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' },
      { user_id: 2, name: 'audi', model: '2001' }
    ])

    const users = await User.query().whereHas('cars', (builder) => {
      return builder.where('name', 'audi')
    }, '>', 1).orWhereHas('cars', (builder) => builder.where('name', 'mercedes')).fetch()
    assert.equal(users.size(), 1)
    assert.equal(userQuery.sql, helpers.formatQuery('select * from "users" where (select count(*) from "cars" where "name" = ? and users.id = cars.user_id) > ? or exists (select * from "cars" where "name" = ? and users.id = cars.user_id)'))
  })

  test('paginate records', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' },
      { user_id: 2, name: 'audi', model: '2001' }
    ])

    const users = await User.query().with('cars').paginate()
    assert.equal(users.size(), 2)
    assert.deepEqual(users.pages, { total: helpers.formatNumber(2), perPage: 20, page: 1, lastPage: 1 })
  })

  test('convert paginated records to json', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    await ioc.use('Database').table('users').insert([{ username: '******' }, { username: '******' }])
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'mercedes', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' },
      { user_id: 2, name: 'audi', model: '2001' }
    ])

    const users = await User.query().with('cars').paginate()
    const json = users.toJSON()
    assert.deepEqual(json.total, helpers.formatNumber(2))
    assert.deepEqual(json.perPage, 20)
    assert.deepEqual(json.page, 1)
    assert.deepEqual(json.lastPage, 1)
    assert.isArray(json.data)
    assert.isArray(json.data[0].cars)
    assert.isArray(json.data[1].cars)
  })

  test('save related model instance', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    const user = new User()
    user.username = '******'
    await user.save()

    const mercedes = new Car()
    mercedes.name = 'mercedes'
    mercedes.model = '1992'

    await user.cars().save(mercedes)
    assert.equal(mercedes.user_id, user.id)
    assert.isTrue(mercedes.$persisted)
    assert.isFalse(mercedes.isNew)
  })

  test('create related model instance', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    const user = new User()
    user.username = '******'
    await user.save()

    const mercedes = await user.cars().create({ name: 'mercedes', model: '1992' })
    assert.equal(mercedes.user_id, 1)
    assert.equal(mercedes.user_id, user.id)
    assert.isTrue(mercedes.$persisted)
    assert.isFalse(mercedes.isNew)
  })

  test('persist parent model when isNew', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    const user = new User()
    user.username = '******'

    const mercedes = await user.cars().create({ name: 'mercedes', model: '1992' })
    assert.equal(mercedes.user_id, 1)
    assert.equal(mercedes.user_id, user.id)
    assert.isTrue(mercedes.$persisted)
    assert.isFalse(mercedes.isNew)
    assert.isTrue(user.$persisted)
    assert.isFalse(user.isNew)
  })

  test('persist parent model when isNew while calling save', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    const user = new User()
    user.username = '******'

    const mercedes = new Car()
    mercedes.name = 'mercedes'
    mercedes.model = '1992'

    await user.cars().save(mercedes)
    assert.equal(mercedes.user_id, 1)
    assert.equal(mercedes.user_id, user.id)
    assert.isTrue(mercedes.$persisted)
    assert.isFalse(mercedes.isNew)
    assert.isTrue(user.$persisted)
    assert.isFalse(user.isNew)
  })

  test('saveMany of related instances', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    const user = new User()
    user.username = '******'

    const mercedes = new Car()
    mercedes.name = 'mercedes'
    mercedes.model = '1992'

    const ferrari = new Car()
    ferrari.name = 'ferrari'
    ferrari.model = '2002'

    await user.cars().saveMany([mercedes, ferrari])
    assert.equal(mercedes.user_id, 1)
    assert.equal(mercedes.user_id, user.id)
    assert.equal(ferrari.user_id, user.id)
    assert.isTrue(mercedes.$persisted)
    assert.isFalse(ferrari.isNew)
    assert.isTrue(ferrari.$persisted)
    assert.isFalse(mercedes.isNew)
    assert.isTrue(user.$persisted)
    assert.isFalse(user.isNew)
  })

  test('createMany of related instances', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    const user = new User()
    user.username = '******'

    const [mercedes, ferrari] = await user.cars().createMany([{ name: 'mercedes', model: '1992' }, { name: 'ferrari', model: '2002' }])

    assert.equal(mercedes.user_id, 1)
    assert.equal(mercedes.user_id, user.id)
    assert.equal(ferrari.user_id, user.id)
    assert.isTrue(mercedes.$persisted)
    assert.isFalse(ferrari.isNew)
    assert.isTrue(ferrari.$persisted)
    assert.isFalse(mercedes.isNew)
    assert.isTrue(user.$persisted)
    assert.isFalse(user.isNew)
  })

  test('delete related rows', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()
    let carQuery = null
    Car.onQuery((query) => (carQuery = query))

    const user = new User()
    user.username = '******'

    await user.cars().createMany([{ name: 'mercedes', model: '1992' }, { name: 'ferrari', model: '2002' }])
    await user.cars().delete()
    const cars = await ioc.use('Database').table('cars')
    assert.lengthOf(cars, 0)
    assert.equal(carQuery.sql, helpers.formatQuery('delete from "cars" where "user_id" = ?'))
  })

  test('add constraints to delete query', async (assert) => {
    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()
    let carQuery = null
    Car.onQuery((query) => (carQuery = query))

    const user = new User()
    user.username = '******'

    await user.cars().createMany([{ name: 'mercedes', model: '1992' }, { name: 'ferrari', model: '2002' }])
    await user.cars().where('name', 'mercedes').delete()
    const cars = await ioc.use('Database').table('cars')
    assert.lengthOf(cars, 1)
    assert.equal(cars[0].name, 'ferrari')
    assert.equal(carQuery.sql, helpers.formatQuery('delete from "cars" where "name" = ? and "user_id" = ?'))
  })

  test('throw exception when createMany doesn\'t receives an array', async (assert) => {
    assert.plan(1)

    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    const user = new User()
    user.username = '******'

    try {
      await user.cars().createMany({ name: 'mercedes', model: '1992' })
    } catch ({ message }) {
      assert.equal(message, 'E_INVALID_PARAMETER: hasMany.createMany expects an array of values instead received object')
    }
  })

  test('throw exception when saveMany doesn\'t receives an array', async (assert) => {
    assert.plan(1)

    class Car extends Model {
    }

    class User extends Model {
      cars () {
        return this.hasMany(Car)
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    const user = new User()
    user.username = '******'

    try {
      await user.cars().saveMany(new Car())
    } catch ({ message }) {
      assert.equal(message, 'E_INVALID_PARAMETER: hasMany.saveMany expects an array of related model instances instead received object')
    }
  })

  test('get first instance of related model via IoC container', async (assert) => {
    class Car extends Model {
    }

    ioc.fake('App/Models/Car', () => Car)

    class User extends Model {
      cars () {
        return this.hasMany('App/Models/Car')
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    Car.onQuery((query) => (carQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })
    await ioc.use('Database').table('cars').insert([
      { user_id: 1, name: 'merc', model: '1990' },
      { user_id: 1, name: 'audi', model: '2001' }
    ])

    const user = await User.find(1)
    const car = await user.cars().first()
    assert.instanceOf(car, Car)
    assert.equal(car.name, 'merc')
    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "user_id" = ? limit ?'))
    assert.deepEqual(carQuery.bindings, helpers.formatBindings([1, 1]))
  })

  test('bind custom callback for eagerload query', async (assert) => {
    class Car extends Model {
    }

    ioc.fake('App/Models/Car', () => Car)

    class User extends Model {
      cars () {
        return this.hasMany('App/Models/Car')
      }
    }

    Car._bootIfNotBooted()
    User._bootIfNotBooted()

    let carQuery = null
    Car.onQuery((query) => (carQuery = query))

    await ioc.use('Database').table('users').insert({ username: '******' })

    await User.query().with('cars', (builder) => {
      builder.eagerLoadQuery((query, fk, values) => {
        query.whereIn(fk, values).where('model', 'BMW')
      })
    }).fetch()

    assert.equal(carQuery.sql, helpers.formatQuery('select * from "cars" where "user_id" in (?) and "model" = ?'))
    assert.deepEqual(carQuery.bindings, helpers.formatBindings([1, 'BMW']))
  })
})
test.group('Relations | Has Many Through - Belongs To', (group) => {
  group.before(async () => {
    ioc.singleton('Adonis/Src/Database', function () {
      const config = new Config()
      config.set('database', {
        connection: 'testing',
        testing: helpers.getConfig()
      })
      return new DatabaseManager(config)
    })
    ioc.alias('Adonis/Src/Database', 'Database')

    await fs.ensureDir(path.join(__dirname, './tmp'))
    await helpers.createTables(ioc.use('Adonis/Src/Database'))
  })

  group.afterEach(async () => {
    await ioc.use('Adonis/Src/Database').table('countries').truncate()
    await ioc.use('Adonis/Src/Database').table('users').truncate()
    await ioc.use('Adonis/Src/Database').table('profiles').truncate()
  })

  group.after(async () => {
    await helpers.dropTables(ioc.use('Adonis/Src/Database'))
    try {
      await fs.remove(path.join(__dirname, './tmp'))
    } catch (error) {
      if (process.platform !== 'win32' || error.code !== 'EBUSY') {
        throw error
      }
    }
  }).timeout(0)

  test('create correct query', (assert) => {
    class User extends Model {
    }

    class Profile extends Model {
      user () {
        return this.belongsTo(User)
      }
    }

    class Country extends Model {
      users () {
        return this.manyThrough(Profile, 'user')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Profile._bootIfNotBooted()

    const country = new Country()
    country.id = 1
    country.$persisted = true

    const query = country.users().toSQL()
    assert.equal(query.sql, helpers.formatQuery('select "users".* from "users" inner join "profiles" on "profiles"."user_id" = "users"."id" where "profiles"."country_id" = ?'))
  })

  test('select related rows', async (assert) => {
    class User extends Model {
    }

    class Profile extends Model {
      user () {
        return this.belongsTo(User)
      }
    }

    class Country extends Model {
      users () {
        return this.manyThrough(Profile, 'user')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Profile._bootIfNotBooted()

    await ioc.use('Database').table('countries').insert([{ name: 'India', id: 2 }, { name: 'Uk', id: 3 }])

    await ioc.use('Database').table('users').insert([
      { username: '******' },
      { username: '******' }
    ])

    await ioc.use('Database').table('profiles').insert([
      { user_id: 1, profile_name: 'Virk', country_id: 2 },
      { user_id: 1, profile_name: 'Virk', country_id: 3 },
      { user_id: 2, profile_name: 'Nikk', country_id: 2 }
    ])

    const india = await Country.find(2)
    const indianUsers = await india.users().fetch()
    assert.equal(indianUsers.size(), 2)
    assert.deepEqual(indianUsers.rows.map((user) => user.username), ['virk', 'nikk'])

    const uk = await Country.find(3)
    const ukUsers = await uk.users().fetch()
    assert.equal(ukUsers.size(), 1)
    assert.deepEqual(ukUsers.rows.map((user) => user.username), ['virk'])
  })

  test('eagerload related rows', async (assert) => {
    class User extends Model {
    }

    class Profile extends Model {
      user () {
        return this.belongsTo(User)
      }
    }

    class Country extends Model {
      users () {
        return this.manyThrough(Profile, 'user')
      }
    }

    User._bootIfNotBooted()
    Country._bootIfNotBooted()
    Profile._bootIfNotBooted()

    await ioc.use('Database').table('countries').insert([{ name: 'India', id: 2 }, { name: 'Uk', id: 3 }])

    await ioc.use('Database').table('users').insert([
      { username: '******' },
      { username: '******' }
    ])

    await ioc.use('Database').table('profiles').insert([
      { user_id: 1, profile_name: 'Virk', country_id: 2 },
      { user_id: 1, profile_name: 'Virk', country_id: 3 },
      { user_id: 2, profile_name: 'Nikk', country_id: 2 }
    ])

    const countries = await Country.query().with('users').orderBy('id', 'asc').fetch()
    assert.equal(countries.size(), 2)
    assert.equal(countries.first().getRelated('users').size(), 2)
    assert.equal(countries.last().getRelated('users').size(), 1)
  })
})