'use strict';

const _ = require('lodash');
const sanitizeEntity = require('../sanitize-entity');

describe('Sanitize Entity', () => {
  const input = {
    id: 1,
    email: 'foo@bar.com',
    firstname: 'foo',
    lastname: 'bar',
    password: 'qwerty',
  };

  const article = {
    name: 'foobar',
    content: 'lorem ipsum',
    secret: 'very-secret',
  };

  const inputWithRelation = {
    ...input,
    article,
  };

  const inputWithDz = {
    ...input,
    dz: [
      {
        __component: 'article',
        name: 'foo',
        content: 'bar',
        secret: 'secret-foobar',
      },
    ],
  };

  const userModel = {
    primaryKey: 'id',
    options: {
      timestamps: ['created_at', 'updated_at'],
    },
    privateAttributes: ['email'],
    attributes: {
      email: {
        type: 'text',
        private: true,
      },
      firstname: {
        type: 'text',
      },
      lastname: {
        type: 'text',
      },
      password: {
        type: 'password',
      },
    },
  };

  const userRelModel = {
    ...userModel,
    attributes: {
      ...userModel.attributes,
      article: {
        collection: 'article',
      },
    },
  };

  const userDzModel = {
    ...userModel,
    attributes: {
      ...userModel.attributes,
      dz: {
        type: 'dynamiczone',
        components: ['article'],
      },
    },
  };

  const articleModel = {
    primaryKey: 'id',
    options: {
      timestamps: ['created_at', 'updated_at'],
    },
    privateAttributes: ['secret'],
    attributes: {
      name: {
        type: 'text',
      },
      content: {
        type: 'text',
      },
      secret: {
        type: 'text',
        private: true,
      },
    },
  };

  const models = {
    user: userModel,
    article: articleModel,
    userRel: userRelModel,
    userDz: userDzModel,
  };

  beforeEach(() => {
    global.strapi = {
      getModel(name) {
        return models[name];
      },
      config: {
        get: jest.fn,
      },
    };
  });

  describe('Basic', () => {
    const tests = [
      [
        { withPrivate: false, isOutput: true, includeFields: null },
        _.pick(input, ['id', 'firstname', 'lastname']),
      ],
      [{ withPrivate: false, isOutput: false, includeFields: null }, input],
      [
        { withPrivate: false, isOutput: true, includeFields: ['firstname'] },
        _.pick(input, ['id', 'firstname']),
      ],
      [
        { withPrivate: false, isOutput: true, includeFields: ['email', 'firstname'] },
        _.pick(input, ['id', 'firstname']),
      ],
      [{ withPrivate: false, isOutput: true, includeFields: ['password'] }, _.pick(input, ['id'])],
      [
        { withPrivate: true, isOutput: true, includeFields: null },
        _.pick(input, ['id', 'email', 'firstname', 'lastname']),
      ],
      [{ withPrivate: true, isOutput: false, includeFields: null }, input],
      [
        { withPrivate: true, isOutput: true, includeFields: ['firstname'] },
        _.pick(input, ['id', 'firstname']),
      ],
      [
        { withPrivate: true, isOutput: true, includeFields: ['email', 'firstname'] },
        _.pick(input, ['id', 'email', 'firstname']),
      ],
      [{ withPrivate: true, isOutput: true, includeFields: ['password'] }, _.pick(input, ['id'])],
      [
        { withPrivate: true, isOutput: false, includeFields: ['firstname'] },
        _.pick(input, ['id', 'firstname']),
      ],
      [
        { withPrivate: true, isOutput: false, includeFields: ['email', 'firstname'] },
        _.pick(input, ['id', 'email', 'firstname']),
      ],
      [
        { withPrivate: true, isOutput: false, includeFields: ['password'] },
        _.pick(input, ['id', 'password']),
      ],
    ];

    test.each(tests)(`Test n°%#`, (options, expected) => {
      const { user: model } = models;
      expect(sanitizeEntity(input, { ...options, model })).toStrictEqual(expected);
    });
  });

  describe('With private attributes', () => {
    describe('When options.privateAttributes exists in model, the attributes in options.privateAttributes must be hidden', () => {
      const tests = [
        [{ withPrivate: false, isOutput: true, includeFields: null }, _.pick(input, ['lastname'])],
        [{ withPrivate: false, isOutput: false, includeFields: null }, input],
        [{ withPrivate: false, isOutput: true, includeFields: ['firstname'] }, {}],
        [{ withPrivate: false, isOutput: true, includeFields: ['email', 'firstname'] }, {}],
        [{ withPrivate: false, isOutput: true, includeFields: ['password'] }, {}],
        [
          { withPrivate: true, isOutput: true, includeFields: null },
          _.pick(input, ['id', 'email', 'firstname', 'lastname']),
        ],
        [{ withPrivate: true, isOutput: false, includeFields: null }, input],
        [
          { withPrivate: true, isOutput: true, includeFields: ['firstname'] },
          _.pick(input, ['id', 'firstname']),
        ],
        [
          { withPrivate: true, isOutput: true, includeFields: ['email', 'firstname'] },
          _.pick(input, ['id', 'email', 'firstname']),
        ],
        [{ withPrivate: true, isOutput: true, includeFields: ['password'] }, _.pick(input, ['id'])],
        [
          { withPrivate: true, isOutput: false, includeFields: ['firstname'] },
          _.pick(input, ['id', 'firstname']),
        ],
        [
          { withPrivate: true, isOutput: false, includeFields: ['email', 'firstname'] },
          _.pick(input, ['id', 'email', 'firstname']),
        ],
        [
          { withPrivate: true, isOutput: false, includeFields: ['password'] },
          _.pick(input, ['id', 'password']),
        ],
      ];

      const model = {
        ...models.user,
        options: {
          ...models.user.options,
          privateAttributes: ['firstname'],
        },
        privateAttributes: [].concat(models.user.privateAttributes, ['firstname'], ['id']),
      };

      test.each(tests)(`Test n°%#`, (options, expected) => {
        expect(sanitizeEntity(input, { ...options, model })).toStrictEqual(expected);
      });
    });
  });

  describe('With relation', () => {
    const tests = [
      [
        inputWithRelation,
        { withPrivate: false, isOutput: true, includeFields: null },
        _.pick(inputWithRelation, [
          'id',
          'firstname',
          'lastname',
          'article.name',
          'article.content',
        ]),
      ],
      [
        inputWithRelation,
        { withPrivate: false, isOutput: true, includeFields: ['firstname', 'lastname'] },
        _.pick(inputWithRelation, ['id', 'firstname', 'lastname']),
      ],
      [
        inputWithRelation,
        { withPrivate: false, isOutput: true, includeFields: ['article'] },
        _.pick(inputWithRelation, ['id', 'article.name', 'article.content']),
      ],
      [
        inputWithRelation,
        { withPrivate: false, isOutput: true, includeFields: ['article.name'] },
        _.pick(inputWithRelation, ['id', 'article.name']),
      ],
      [
        inputWithRelation,
        { withPrivate: true, isOutput: true, includeFields: null },
        _.pick(inputWithRelation, ['id', 'email', 'firstname', 'lastname', 'article']),
      ],
      [
        { ...inputWithRelation, article: _.times(3, () => _.clone(article)) },
        { withPrivate: false, isOutput: true, includeFields: null },
        {
          ..._.pick(inputWithRelation, ['id', 'firstname', 'lastname']),
          article: _.times(3, () => _.pick(article, ['name', 'content'])),
        },
      ],
    ];

    test.each(tests)(`Test n°%#`, (source, options, expected) => {
      const { userRel: model } = models;
      expect(sanitizeEntity(source, { ...options, model })).toStrictEqual(expected);
    });
  });

  describe('With Dynamic Zone', () => {
    test('Dynamic zone null', () => {
      const { userDz: model } = models;
      const dataSource = { ...inputWithDz, dz: null };

      const expected = _.pick(dataSource, ['id', 'firstname', 'lastname', 'dz']);

      expect(sanitizeEntity(dataSource, { model })).toStrictEqual(expected);
    });

    test('Dynamic zone with a basic component', () => {
      const { userDz: model } = models;

      const expected = {
        ..._.pick(inputWithDz, ['id', 'firstname', 'lastname']),
        dz: inputWithDz.dz.map(comp => _.pick(comp, ['__component', 'name', 'content'])),
      };

      expect(sanitizeEntity(inputWithDz, { model })).toStrictEqual(expected);
    });
  });

  describe('Edge cases', () => {
    test('It returns the input data if the model is nil and isOutput false', () => {
      expect(sanitizeEntity(input, { model: null, isOutput: false })).toEqual(input);
      expect(sanitizeEntity(input, { model: undefined, isOutput: false })).toEqual(input);
    });

    test('It returns null if the model is nil and isOutput true', () => {
      expect(sanitizeEntity(input, { model: null })).toBeNull();
      expect(sanitizeEntity(input, { model: undefined })).toBeNull();
    });

    test(`It returns the input data as-is if it's not an object or an array`, () => {
      const { user: model } = models;
      expect(sanitizeEntity('foobar', { model })).toBe('foobar');
      expect(sanitizeEntity(undefined, { model })).toBeUndefined();
      expect(sanitizeEntity(null, { model })).toBeNull();
    });

    test('It sanitizes all entries if the input is an array', () => {
      const dataSource = [_.clone(input), _.clone(input)];

      const expected = [
        _.pick(input, 'id', 'firstname', 'lastname'),
        _.pick(input, 'id', 'firstname', 'lastname'),
      ];

      const { user: model } = models;

      expect(sanitizeEntity(dataSource, { model })).toStrictEqual(expected);
    });

    test(`It should put the relation value as-is if it's nil'`, () => {
      const dataSource = { ...inputWithRelation, article: null };
      const expected = _.omit(dataSource, ['email', 'password']);

      const { userRel: model } = models;

      expect(sanitizeEntity(dataSource, { model })).toStrictEqual(expected);
    });

    test(`It should make sure that legacy data parsing returns an object`, () => {
      const dataSource = {
        toJSON: jest.fn(() => input),
      };

      const invalidDataSource = {
        toJSON: jest.fn(() => null),
      };

      const { user: model } = models;

      expect(sanitizeEntity(dataSource, { model })).toStrictEqual(
        _.omit(input, ['email', 'password'])
      );
      expect(dataSource.toJSON).toHaveBeenCalled();

      expect(sanitizeEntity(invalidDataSource, { model })).toBeNull();
      expect(invalidDataSource.toJSON).toHaveBeenCalled();
    });

    test('It should handle custom fields', () => {
      const dataSource = { ...input, foo: 'bar' };
      const expected = _.omit(dataSource, ['email', 'password']);
      const { user: model } = models;

      expect(sanitizeEntity(dataSource, { model })).toStrictEqual(expected);
    });
  });
});
