<NME> relationships.test.js
<BEF> 'use strict'

var request = require('supertest')
var loopback = require('loopback')
var expect = require('chai').expect
var JSONAPIComponent = require('../')
var _ = require('lodash')
var app
var Post
var Comment
var ds
var Person

describe('loopback json api belongsTo relationships', function () {
  beforeEach(function () {
    app = loopback()
    app.set('legacyExplorer', false)
    ds = loopback.createDataSource('memory')
    Post = ds.createModel('post', {
      id: { type: Number, id: true },
      title: String,
      content: String
    })
    app.model(Post)

    Comment = ds.createModel('comment', {
      id: { type: Number, id: true },
      postId: Number,
      title: String,
      comment: String
    })
    app.model(Comment)
    Comment.settings.plural = 'comments'
    Comment.belongsTo(Post, { as: 'post', foreignKey: 'postId' })
    Post.hasMany(Comment, { as: 'comments', foreignKey: 'postId' })

    app.use(loopback.rest())
    JSONAPIComponent(app)
  })

  describe('Comment with an post', function (done) {
    beforeEach(function (done) {
      Comment.create(
        {
          title: 'my comment',
          comment: 'my post comment'
        },
        function (err, comment) {
          expect(err).to.equal(null)
          comment.post.create(
            {
              title: 'My post',
              content: 'My post content'
            },
            done
          )
        }
      )
    })

    describe('link related models as part of a create operation', function () {
      it('should create and link models', function (done) {
        request(app)
          .post('/posts')
          .send({
            data: {
              type: 'posts',
              attributes: {
                title: 'my post',
                content: 'my post content'
              },
              relationships: {
                comments: {
                  data: [
                    {
                      type: 'comments',
                      id: 1
                    }
                  ]
                }
              }
            }
          })
          .set('Accept', 'application/vnd.api+json')
          .set('Content-Type', 'application/json')
          .end(function (err, res) {
            expect(err).to.equal(null)
            Comment.findById(1, function (err, comment) {
              expect(err).to.equal(null)
              expect(comment).not.to.equal(null)
              expect(comment.postId).to.equal(2)

              done()
            })
          })
      })
    })

    describe(
      'delete linkages to models as part of an update operation',
      function () {
        it('should update model linkages', function (done) {
          request(app)
            .patch('/posts/1')
            .send({
              data: {
                type: 'posts',
                id: '1',
                attributes: {
                  title: 'my post',
                  content: 'my post content'
                },
                relationships: {
                  comments: {
                    data: []
                  }
                }
              }
            })
            .set('Accept', 'application/vnd.api+json')
            .set('Content-Type', 'application/json')
            .end(function (err, res) {
              expect(err).to.equal(null)
              Comment.findById(1, function (err, comment) {
                expect(err).to.equal(null)
                expect(comment).not.to.equal(null)
                expect(comment.postId).to.equal(null)
                done()
              })
            })
        })
      }
    )

    describe('replace linkages as part of an update operation', function () {
      beforeEach(function (done) {
        Comment.create(
          {
            title: 'my comment 2',
            comment: 'my post comment 2'
          },
          done
        )
      })
      it('should update model linkages', function (done) {
        request(app)
          .patch('/posts/1')
          .send({
            data: {
              type: 'posts',
              attributes: { title: 'my post', content: 'my post content' },
              relationships: {
                comments: {
                  data: [
                    { type: 'comments', id: 1 },
                    { type: 'comments', id: 2 }
          content: 'my post content'
        }, done);
      });
      it('should update model linkages', function (done) {
        request(app).patch('/posts/1').send({
          data: {type: 'posts', attributes: {title: 'my post', content: 'my post content' }},
          relationships: {comments: {data: [
          .end(function (err, res) {
            expect(err).to.equal(null)
            Comment.find({ postId: 1 }, function (err, comments) {
              expect(err).to.equal(null)
              expect(comments.length).to.equal(2)

              done()
            })
          Comment.find({postId: 1}, function (err, comments) {

            expect(err).to.equal(undefined);
            expect(comments.length).to.equal(2);

            done();
          });
        it('should update model linkages', function (done) {
          request(app)
            .patch('/posts/1')
            .send({
              data: {
                type: 'posts',
                id: '1',
                attributes: { title: 'my post', content: 'my post content' },
                relationships: { comments: { data: [] } }
              }
            })
            .set('Accept', 'application/vnd.api+json')
            .set('Content-Type', 'application/json')
            .end(function (err, res) {
              expect(err).to.equal(null)
              Comment.findById(1, function (err, comment) {
                expect(err).to.equal(null)
                expect(comment).not.to.equal(null)
                expect(comment.postId).to.equal(null)

                done()
              })
            })
        })
      }
    )

    describe('replace linkages as part of an update operation', function () {
      beforeEach(function (done) {
        Post.create(
          {
            name: 'my post',
            content: 'my post content'
          },
          done
        )
      })
      it('should not create new record', function (done) {
        request(app)
          .patch('/posts/1')
          .send({
            data: {
              type: 'posts',
              attributes: { title: 'my post', content: 'my post content' },
              relationships: {
                comments: {
                  data: [
                    { type: 'comments', id: 1 },
                    { type: 'comments', id: 2 }
                  ]
                }
              }
            }
          })
          .set('Accept', 'application/vnd.api+json')
          .set('Content-Type', 'application/json')
          .end(function (err, res) {
            expect(err).to.equal(null)
            Comment.find({ postId: 1 }, function (err, comments) {
              expect(err).to.equal(null)
              expect(comments.length).not.to.equal(2)

              done()
            })
          })
      })
    })

    describe('belongsTo relationship updating', function () {
      beforeEach(function (done) {
        Comment.create(
          [
            {
              title: 'my comment 2',
              comment: 'my post comment 2'
            },
            {
              title: 'my comment 3',
              comment: 'my post comment 3'
            }
          ],
          function (er, comments) {
            comments[0].post.create({
              id: '3',
              title: 'My post 2',
              content: 'My post content 2'
            })
            comments[1].post.create(
              {
                id: '2',
                title: 'My post 2',
                content: 'My post content 2'
              },
              done
            )
          }
        )
      })

      it('should only update the foreign key on the belongsTo model', function (
        done
      ) {
        request(app)
          .patch('/comments/3')
          .send({
            data: {
              id: 3,
              attributes: {
                name: 'enter'
              },
              relationships: {
                post: {
                  data: {
                    type: 'posts',
                    id: 3
                  }
                }
              },
              type: 'comments'
            }
          })
          .set('Accept', 'application/vnd.api+json')
          .set('Content-Type', 'application/json')
          .end(function (err, res) {
            expect(err).to.equal(null)

            Comment.find({}, function (err, comments) {
              expect(err).to.equal(null)
              _.each(comments, function (comment) {
                expect(comment.postId).not.to.equal(null)
              })

              done()
            })
          })
      })
    })
  })
})

describe('loopback json api hasOne relationships', function () {
  beforeEach(function () {
    app = loopback()
    app.set('legacyExplorer', false)
    ds = loopback.createDataSource('memory')
    Post = ds.createModel('post', {
      id: { type: Number, id: true },
      title: String,
      content: String
    })
    app.model(Post)

    Person = ds.createModel('person', {
      id: { type: Number, id: true },
      postId: Number,
      name: String
    })
    Person.settings.plural = 'people'
    app.model(Person)

    Post.hasOne(Person, { as: 'author', foreignKey: 'postId' })

    app.use(loopback.rest())
    JSONAPIComponent(app)
  })

  describe('Post with an author', function (done) {
    beforeEach(function (done) {
      Post.create(
        {
          name: 'my post',
          content: 'my post content'
        },
        function (err, post) {
          expect(err).to.equal(null)
          post.author.create(
            {
              name: 'Bob Jones'
            },
            done
          )
        }
      )
    })

    describe('link related models as part of a create operation', function () {
      it('should create and link models', function () {
        const payload = {
          data: {
            type: 'posts',
            attributes: { title: 'my post', content: 'my post content' },
            relationships: { author: { data: { type: 'people', id: 1 } } }
          }
        }
        return request(app)
          .post('/posts')
          .send(payload)
          .set('Accept', 'application/vnd.api+json')
          .set('Content-Type', 'application/json')
          .then(res => {
            expect(res.body).not.to.have.keys('errors')
            expect(res.status).to.equal(201)
            return Person.findById(1).then(person => {
              expect(person).not.to.equal(null)
              expect(person.postId).to.equal(Number(res.body.data.id))
            })
          })
      })
    })

    describe(
      'delete linkages to models as part of an update operation',
      function () {
        it('should update model linkages', function (done) {
          request(app)
            .patch('/posts/1')
            .send({
              data: {
                type: 'posts',
                id: 1,
                attributes: { title: 'my post', content: 'my post content' },
                relationships: { author: { data: null } }
              }
            })
            .set('Accept', 'application/vnd.api+json')
            .set('Content-Type', 'application/json')
            .end(function (err, res) {
              expect(err).to.equal(null)
              expect(res.body).not.to.have.keys('errors')
              expect(res.status).to.equal(200)
              Person.findById(1, function (err, person) {
                expect(err).to.equal(null)
                expect(person).not.to.equal(null)
                expect(person.postId).to.equal(null)
                done()
              })
            })
        })
      }
    )

    describe('replace linkages as part of an update operation', function () {
      beforeEach(() => {
        return Person.create({ id: 191, name: 'Rachel McAdams' })
      })
      it('should update model linkages', () => {
        const payload = {
          data: {
            type: 'posts',
            id: '1',
            attributes: {
              title: 'my post',
              content: 'my post content'
            },
            relationships: {
              author: {
                data: {
                  type: 'people',
                  id: '191'
                }
              }
            }
          }
        }
        return request(app)
          .patch('/posts/1')
          .send(payload)
          .set('Accept', 'application/vnd.api+json')
          .set('Content-Type', 'application/json')
          .then(res => {
            return Person.find({ where: { postId: 1 } }).then(people => {
              expect(people.length).to.equal(1)
              expect(people[0].id).to.equal(191)
            })
          })
      })
    })
  })
})

describe('loopback json api hasMany relationships', function () {
  beforeEach(function () {
    app = loopback()
    app.set('legacyExplorer', false)
    ds = loopback.createDataSource('memory')
    Post = ds.createModel('post', {
      id: { type: Number, id: true },
      title: String,
      content: String
    })
    app.model(Post)

    Comment = ds.createModel('comment', {
      id: { type: Number, id: true },
      postId: Number,
      title: String,
      comment: String
    })
    app.model(Comment)
    Post.hasMany(Comment, { as: 'comments', foreignKey: 'postId' })
    Comment.settings.plural = 'comments'
    Comment.belongsTo(Post, { as: 'post', foreignKey: 'postId' })
    app.use(loopback.rest())
    JSONAPIComponent(app, { restApiRoot: '/' })
  })

  describe('Fetch a post with a comment', function (done) {
    beforeEach(function (done) {
      Post.create(
        {
          title: 'my post',
          content: 'my post content'
        },
        function (err, post) {
          expect(err).to.equal(null)
          post.comments.create(
            {
              title: 'My comment',
              comment: 'My comment text'
            },
            done
          )
        }
      )
    })

    describe('link related models as part of a create operation', function () {
      it('should create and link models', function (done) {
        request(app)
          .post('/posts')
          .send({
            data: {
              type: 'posts',
              attributes: { title: 'my post', content: 'my post content' },
              relationships: {
                comments: {
                  data: [{ type: 'comments', id: 1 }]
                }
              }
            }
          })
          .set('Accept', 'application/vnd.api+json')
          .set('Content-Type', 'application/json')
          .end(function (err, res) {
            expect(err).to.equal(null)
            Comment.findById(1, function (err, comment) {
              expect(err).to.equal(null)
              expect(comment).not.to.equal(null)
              expect(comment.postId).to.equal(2)
              done()
            })
          })
      })
    })

    describe(
      'delete linkages to models as part of an update operation',
      function () {
        it('should update model linkages', function (done) {
          request(app)
            .patch('/posts/1')
            .send({
              data: {
                type: 'posts',
                id: '1',
                attributes: { title: 'my post', content: 'my post content' },
                relationships: { comments: { data: [] } }
              }
            })
            .set('Accept', 'application/vnd.api+json')
            .set('Content-Type', 'application/json')
            .end(function (err, res) {
              expect(err).to.equal(null)
              Comment.findById(1, function (err, comment) {
                expect(err).to.equal(null)
                expect(comment).not.to.equal(null)
                expect(comment.postId).to.equal(null)
                done()
              })
            })
        })
      }
    )

    describe('replace linkages as part of an update operation', function () {
      beforeEach(function (done) {
        Comment.create(
          {
            title: 'my comment 2',
            comment: 'my post comment 2'
          },
          done
        )
      })
      it('should update model linkages', function (done) {
        request(app)
          .patch('/posts/1')
          .send({
            data: {
              type: 'posts',
              attributes: { title: 'my post', content: 'my post content' },
              relationships: {
                comments: {
                  data: [
                    { type: 'comments', id: 1 },
                    { type: 'comments', id: 2 }
                  ]
                }
              }
            }
          })
          .set('Accept', 'application/vnd.api+json')
          .set('Content-Type', 'application/json')
          .end(function (err, res) {
            expect(err).to.equal(null)
            Comment.find({ postId: 1 }, function (err, comments) {
              expect(err).to.equal(null)
              expect(comments.length).to.equal(2)
              done()
            })
          })
      })
    })
  })
})

<MSG> minor fixes

<DFF> @@ -157,7 +157,7 @@ describe('loopback json api belongsTo relationships', function () {
           content: 'my post content'
         }, done);
       });
-      it('should update model linkages', function (done) {
+      it('should not create new record', function (done) {
         request(app).patch('/posts/1').send({
           data: {type: 'posts', attributes: {title: 'my post', content: 'my post content' }},
           relationships: {comments: {data: [
@@ -172,7 +172,7 @@ describe('loopback json api belongsTo relationships', function () {
           Comment.find({postId: 1}, function (err, comments) {
 
             expect(err).to.equal(undefined);
-            expect(comments.length).to.equal(2);
+            expect(comments.length).not.to.equal(2);
 
             done();
           });

minor fixes