denali-js/core

View on GitHub
test/unit/render/json-api-test.ts

Summary

Maintainability
A
3 hrs
Test Coverage
/* tslint:disable:completed-docs no-empty no-invalid-this member-access */
import { isArray } from 'lodash';
import {
  setupUnitTest,
  JSONAPISerializer,
  Model,
  attr,
  hasMany,
  Errors,
  hasOne } from '@denali-js/core';
import { ResourceObject, RelationshipsWithData } from 'jsonapi-typescript';

class Post extends Model {
  static schema = {
    title: attr('string'),
    content: attr('string'),
    publishedAt: attr('date'),
    author: hasOne('user'),
    comments: hasMany('comment')
  };
  addComment: (comment: Comment) => Promise<void>;
}

class User extends Model {}

class Comment extends Model {
  static schema = {
    text: attr('string'),
    publishedAt: attr('string')
  };
}

const test = setupUnitTest('serializer:json-api', {
  'app:router': true,
  'orm-adapter:application': true,
  'serializer:application': JSONAPISerializer,
  'model:post': Post,
  'model:comment': Comment,
  'model:user': User
});

test('renders models as JSON-API resource objects', async (t) => {
  let model = await Post.create({ title: 'foo' });
  class TestSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {};
  }
  let serializer = new TestSerializer();

  let result = await serializer.serialize(model, <any>{}, {});
  t.is((<ResourceObject>result.data).attributes.title, 'foo');
});

test('renders errors according to spec', async (t) => {
  class TestSerializer extends JSONAPISerializer {
    attributes: string[] = [];
    relationships = {};
  }
  let serializer = new TestSerializer();

  let result = await serializer.serialize(new Errors.InternalServerError('foo'), <any>{}, {});
  t.is(result.errors[0].status, '500');
  t.is(result.errors[0].code, 'InternalServerError');
  t.is(result.errors[0].detail, 'foo');
});

test('renders validation errors with additional details', async (t) => {
  class TestSerializer extends JSONAPISerializer {
    attributes: string[] = [];
    relationships = {};
  }
  let serializer = new TestSerializer();
  let error = new Errors.UnprocessableEntity('Email cannot be blank');
  error.title = 'presence';
  error.source = { pointer: '/data/attributes/email' };

  let result = await serializer.serialize(error, <any>{}, {});
  t.is(result.errors[0].status, '422');
  t.is(result.errors[0].code, 'UnprocessableEntityError');
  t.is(result.errors[0].title, 'presence');
  t.is(result.errors[0].source.pointer, '/data/attributes/email');
  t.is(result.errors[0].detail, 'Email cannot be blank');
});

test('sideloads related records', async (t) => {
  let { inject } = t.context;
  class PostSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {
      comments: {
        strategy: 'embed'
      }
    };
  }
  inject('serializer:comment', class CommentSerializer extends JSONAPISerializer {
    attributes = [ 'text' ];
    relationships = {};
  });
  inject('model:post', class Post extends Model {
    static schema = {
      title: attr('string'),
      comments: hasMany('comment')
    };
  });
  inject('model:comment', class Comment extends Model {
    static schema = {
      text: attr('string')
    };
  });
  let serializer = new PostSerializer();

  let post = await Post.create({ title: 'foo' });
  let comment = await Comment.create({ text: 'bar' });
  await post.addComment(comment);

  let result = await serializer.serialize(post, <any>{}, {});
  t.true(isArray(result.included));
  t.is(result.included[0].attributes.text, 'bar');
});

test.todo('dedupes sideloaded related records');

test('embeds related records as resource linkage objects', async (t) => {
  let { inject } = t.context;
  class PostSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {
      comments: {
        strategy: 'id'
      }
    };
  }
  inject('serializer:comment', class CommentSerializer extends JSONAPISerializer {
    attributes = [ 'text' ];
    relationships = {};
  });
  let serializer = new PostSerializer();
  let post = await Post.create({ title: 'foo' });
  let comment = await Comment.create({ text: 'bar' });
  await post.addComment(comment);

  let result = await serializer.serialize(post, <any>{}, {});
  t.falsy(result.included);
  let comments = (<any>result).data.relationships.comments;
  t.truthy(comments);
  t.true(Array.isArray(comments.data));
  t.truthy(comments.data[0].id);
});

test('renders document meta', async (t) => {
  class TestSerializer extends JSONAPISerializer {
    attributes: string[] = [];
    relationships = {};
  }
  let serializer = new TestSerializer();
  let model = await Post.create({});

  let result = await serializer.serialize(model, <any>{}, { meta: { foo: true }});
  t.true(result.meta.foo);
});

test('renders document links', async (t) => {
  class TestSerializer extends JSONAPISerializer {
    attributes: string[] = [];
    relationships = {};
  }
  let serializer = new TestSerializer();
  let model = await Post.create({});

  let result = await serializer.serialize(model, <any>{}, { links: { foo: true }});
  t.true(result.links.foo);
});

test('renders jsonapi version', async (t) => {
  class TestSerializer extends JSONAPISerializer {
    attributes: string[] = [];
    relationships = {};
  }
  let serializer = new TestSerializer();
  let model = await Post.create({});

  let result = await serializer.serialize(model, <any>{}, {});
  t.is(result.jsonapi.version, '1.0');
});

test('renders an array of models as an array under `data`', async (t) => {
  class TestSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {};
  }
  let serializer = new TestSerializer();
  let postOne = await Post.create({ title: 'foo' });
  let postTwo = await Post.create({ title: 'bar' });

  let result = await serializer.serialize([ postOne, postTwo ], <any>{}, {});
  let data = <ResourceObject[]>result.data;
  t.true(isArray(data));
  t.is(data[0].id, postOne.id);
  t.is(data[1].id, postTwo.id);
});

test('only renders whitelisted attributes', async (t) => {
  class PostSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {};
  }
  let serializer = new PostSerializer();
  let post = await Post.create({ title: 'foo', content: 'bar' });

  let result = await serializer.serialize(post, <any>{}, {});
  let data = <ResourceObject>result.data;
  t.is(data.attributes.title, 'foo');
  t.falsy(data.attributes.content);
});

test('allows render options to override class-level attributes whitelist', async (t) => {
  class PostSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {};
  }
  let serializer = new PostSerializer();
  let post = await Post.create({ title: 'foo', content: 'bar' });

  let result = await serializer.serialize(post, <any>{}, { attributes: [ 'content' ] });
  let data = <ResourceObject>result.data;
  t.is(data.attributes.content, 'bar');
  t.falsy(data.attributes.title);
});

test('only renders whitelisted relationships', async (t) => {
  let { inject } = t.context;
  class PostSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {
      comments: {
        strategy: 'id'
      }
    };
  }
  inject('serializer:comment', class CommentSerializer extends JSONAPISerializer {
    attributes = [ 'text' ];
    relationships = {};
  });
  let serializer = new PostSerializer();
  let post = await Post.create({ title: 'foo' });

  let result = await serializer.serialize(post, <any>{}, {});
  let data = <ResourceObject>result.data;
  t.falsy(data.relationships.author);
  let commentsRelationship = <RelationshipsWithData>data.relationships.comments;
  t.true(isArray(commentsRelationship.data));
});

test('allows render options to override class-level relationships whitelist', async (t) => {
  let { inject } = t.context;
  class PostSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {
      author: {
        strategy: 'id'
      }
    };
  }
  inject('serializer:comments', class CommentSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {};
  });
  let serializer = new PostSerializer();
  let post = await Post.create({ title: 'foo' });

  let result = await serializer.serialize(post, <any>{}, {
    relationships: {
      comments: {
        strategy: 'id'
      }
    }
  });
  let data = <ResourceObject>result.data;
  t.falsy(data.relationships.author);
  let commentsRelationship = <RelationshipsWithData>data.relationships.comments;
  t.true(isArray(commentsRelationship.data));
});

test('uses related serializers to render related records', async (t) => {
  let { inject } = t.context;
  class PostSerializer extends JSONAPISerializer {
    attributes = [ 'title' ];
    relationships = {
      comments: {
        strategy: 'embed'
      }
    };
  }
  inject('serializer:comment', class CommentSerializer extends JSONAPISerializer {
    attributes = [ 'text' ];
    relationships = {};
  });
  inject('model:comment', class Comment extends Model {
    static schema = {
      text: attr('string'),
      publishedAt: attr('string')
    };
  });
  let serializer = new PostSerializer();
  let post = await Post.create({ title: 'foo' });
  let comment = await Comment.create({ text: 'bar', publishedAt: 'fizz' });
  await post.addComment(comment);

  let result = await serializer.serialize(post, <any>{}, {});
  t.true(isArray(result.included));
  t.is(result.included[0].attributes.text, 'bar');
  t.falsy(result.included[0].attributes.publishedAt);
});

test.todo('renders resource object meta');

test.todo('renders resource object links');

test('dasherizes field names', async (t) => {
  class TestSerializer extends JSONAPISerializer {
    attributes = [ 'publishedAt' ];
    relationships = {};
  }
  let serializer = new TestSerializer();
  let post = await Post.create({ publishedAt: 'foo' });

  let result = await serializer.serialize(post, <any>{}, {});
  let data = <ResourceObject>result.data;
  t.is(data.attributes['published-at'], 'foo');
});

test.todo('renders relationship meta');

test.todo('renders relationship links');