pact-foundation/pact-js

View on GitHub
src/v3/matchers.spec.ts

Summary

Maintainability
C
1 day
Test Coverage
import * as chai from 'chai';
import * as MatchersV3 from './matchers';

const { expect } = chai;

describe('V3 Matchers', () => {
  it('compiles with nested examples from issue 1054', () => {
    interface Foo {
      a: string;
    }

    const f: Foo = { a: 'working example' };

    MatchersV3.like(f);
    MatchersV3.like({ ...f });
    MatchersV3.like(Object.freeze(f));

    interface Room {
      id: string;
      foo: Foo;
    }

    const r: Room = { id: 'some guid', foo: { a: 'example' } };

    MatchersV3.like(r);
    MatchersV3.like({ ...r });
    MatchersV3.like(Object.freeze(f));
    MatchersV3.reify(r);
  });

  describe('#like', () => {
    it('returns a JSON representation of a like matcher', () => {
      const result = MatchersV3.like({
        a: 'b',
      });
      expect(result).to.deep.equal({
        'pact:matcher:type': 'type',
        value: {
          a: 'b',
        },
      });
    });
  });

  describe('#eachKeylike', () => {
    it('returns a JSON representation of an eachKeyLike matcher', () => {
      const result = MatchersV3.eachKeyLike('004', {
        id: '004',
      });
      expect(result).to.deep.equal({
        'pact:matcher:type': 'values',
        value: {
          '004': {
            id: '004',
          },
        },
      });
    });
  });

  describe('#eachLike', () => {
    describe('with no min', () => {
      it('returns a JSON representation of an eachLike matcher', () => {
        const result = MatchersV3.eachLike({
          a: 'b',
        });
        expect(result).to.deep.equal({
          min: 1,
          'pact:matcher:type': 'type',
          value: [
            {
              a: 'b',
            },
          ],
        });
      });
    });

    describe('with min', () => {
      it('returns a JSON representation of an eachLike matcher', () => {
        const result = MatchersV3.eachLike(
          {
            a: 'b',
          },
          3
        );
        expect(result).to.deep.equal({
          min: 3,
          'pact:matcher:type': 'type',
          value: [
            {
              a: 'b',
            },
            {
              a: 'b',
            },
            {
              a: 'b',
            },
          ],
        });
      });
    });
  });

  describe('#atLeastOneLike', () => {
    describe('with no examples', () => {
      it('returns a JSON representation of an atLeastOneLike matcher', () => {
        const result = MatchersV3.atLeastOneLike({
          a: 'b',
        });
        expect(result).to.deep.equal({
          'pact:matcher:type': 'type',
          min: 1,
          value: [
            {
              a: 'b',
            },
          ],
        });
      });
    });

    describe('when provided examples', () => {
      it('returns a JSON representation of an atLeastOneLike matcher with the correct number of examples', () => {
        const result = MatchersV3.atLeastOneLike(
          {
            a: 'b',
          },
          4
        );
        expect(result).to.deep.equal({
          'pact:matcher:type': 'type',
          min: 1,
          value: [{ a: 'b' }, { a: 'b' }, { a: 'b' }, { a: 'b' }],
        });
      });
    });
  });

  describe('#atLeastLike', () => {
    describe('with no examples', () => {
      it('returns a JSON representation of an atLeastLike matcher', () => {
        const result = MatchersV3.atLeastLike(
          {
            a: 'b',
          },
          2
        );
        expect(result).to.deep.equal({
          'pact:matcher:type': 'type',
          min: 2,
          value: [{ a: 'b' }, { a: 'b' }],
        });
      });
    });

    describe('when provided examples', () => {
      it('returns a JSON representation of an atLeastLike matcher with the correct number of examples', () => {
        const result = MatchersV3.atLeastLike(
          {
            a: 'b',
          },
          2,
          4
        );
        expect(result).to.deep.equal({
          'pact:matcher:type': 'type',
          min: 2,
          value: [{ a: 'b' }, { a: 'b' }, { a: 'b' }, { a: 'b' }],
        });
      });
    });

    it('throws an error if the number of examples is less than the minimum', () => {
      expect(() => MatchersV3.atLeastLike({ a: 'b' }, 4, 2)).to.throw(
        'atLeastLike has a minimum of 4 but 2 elements were requested. Make sure the count is greater than or equal to the min.'
      );
    });
  });

  describe('#atMostLike', () => {
    describe('with no examples', () => {
      it('returns a JSON representation of an atMostLike matcher', () => {
        const result = MatchersV3.atMostLike(
          {
            a: 'b',
          },
          2
        );
        expect(result).to.deep.equal({
          'pact:matcher:type': 'type',
          max: 2,
          value: [{ a: 'b' }],
        });
      });
    });

    describe('when provided examples', () => {
      it('returns a JSON representation of an atMostLike matcher with the correct number of examples', () => {
        const result = MatchersV3.atMostLike(
          {
            a: 'b',
          },
          4,
          4
        );
        expect(result).to.deep.equal({
          'pact:matcher:type': 'type',
          max: 4,
          value: [{ a: 'b' }, { a: 'b' }, { a: 'b' }, { a: 'b' }],
        });
      });
    });

    it('throws an error if the number of examples is more than the maximum', () => {
      expect(() => MatchersV3.atMostLike({ a: 'b' }, 2, 4)).to.throw(
        'atMostLike has a maximum of 2 but 4 elements where requested. Make sure the count is less than or equal to the max.'
      );
    });
  });

  describe('#constrainedArrayLike', () => {
    describe('with no examples', () => {
      it('returns a JSON representation of an constrainedArrayLike matcher', () => {
        const result = MatchersV3.constrainedArrayLike(
          {
            a: 'b',
          },
          2,
          4
        );
        expect(result).to.deep.equal({
          'pact:matcher:type': 'type',
          min: 2,
          max: 4,
          value: [{ a: 'b' }, { a: 'b' }],
        });
      });
    });

    describe('when provided examples', () => {
      it('returns a JSON representation of an constrainedArrayLike matcher with the correct number of examples', () => {
        const result = MatchersV3.constrainedArrayLike(
          {
            a: 'b',
          },
          2,
          4,
          3
        );
        expect(result).to.deep.equal({
          'pact:matcher:type': 'type',
          min: 2,
          max: 4,
          value: [{ a: 'b' }, { a: 'b' }, { a: 'b' }],
        });
      });
    });

    it('throws an error if the number of examples is less than the minimum', () => {
      expect(() =>
        MatchersV3.constrainedArrayLike({ a: 'b' }, 4, 6, 2)
      ).to.throw(
        'constrainedArrayLike has a minimum of 4 but 2 elements where requested. Make sure the count is greater than or equal to the min.'
      );
    });

    it('throws an error if the number of examples is more than the maximum', () => {
      expect(() =>
        MatchersV3.constrainedArrayLike({ a: 'b' }, 4, 6, 8)
      ).to.throw(
        'constrainedArrayLike has a maximum of 6 but 8 elements where requested. Make sure the count is less than or equal to the max.'
      );
    });
  });

  describe('#integer', () => {
    it('returns a JSON representation of an integer matcher', () => {
      const result = MatchersV3.integer(100);
      expect(result).to.deep.equal({
        'pact:matcher:type': 'integer',
        value: 100,
      });
    });

    describe('when the example is zero', () => {
      it('returns a JSON representation of an integer matcher', () => {
        const result = MatchersV3.integer(0);
        expect(result).to.deep.equal({
          'pact:matcher:type': 'integer',
          value: 0,
        });
      });
    });

    describe('when no example is given', () => {
      it('also includes a random integer generator', () => {
        const result = MatchersV3.integer();
        expect(result).to.deep.equal({
          'pact:matcher:type': 'integer',
          'pact:generator:type': 'RandomInt',
          value: 101,
        });
      });
    });
  });

  describe('#decimal', () => {
    it('returns a JSON representation of an decimal matcher', () => {
      const result = MatchersV3.decimal(100.3);
      expect(result).to.deep.equal({
        'pact:matcher:type': 'decimal',
        value: 100.3,
      });
    });

    describe('when the example is zero', () => {
      it('returns a JSON representation of an integer matcher', () => {
        const result = MatchersV3.decimal(0.0);
        expect(result).to.deep.equal({
          'pact:matcher:type': 'decimal',
          value: 0.0,
        });
      });
    });

    describe('when no example is given', () => {
      it('also includes a random decimal generator', () => {
        const result = MatchersV3.decimal();
        expect(result).to.deep.equal({
          'pact:matcher:type': 'decimal',
          'pact:generator:type': 'RandomDecimal',
          value: 12.34,
        });
      });
    });
  });

  describe('#number', () => {
    it('returns a JSON representation of an number matcher', () => {
      const result = MatchersV3.number(100.3);
      expect(result).to.deep.equal({
        'pact:matcher:type': 'number',
        value: 100.3,
      });
    });

    describe('when no example is given', () => {
      it('also includes a random integer generator', () => {
        const result = MatchersV3.number();
        expect(result).to.deep.equal({
          'pact:matcher:type': 'number',
          'pact:generator:type': 'RandomInt',
          value: 1234,
        });
      });
    });
  });

  describe('#boolean', () => {
    it('returns a JSON representation of a like matcher', () => {
      const result = MatchersV3.boolean(true);
      expect(result).to.deep.equal({
        'pact:matcher:type': 'type',
        value: true,
      });
    });
  });

  describe('#string', () => {
    it('returns a JSON representation of a like matcher', () => {
      const result = MatchersV3.string('true');
      expect(result).to.deep.equal({
        'pact:matcher:type': 'type',
        value: 'true',
      });
    });
  });

  describe('#regex', () => {
    it('returns a JSON representation of a regex matcher', () => {
      const result = MatchersV3.regex('\\d+', '1234');
      expect(result).to.deep.equal({
        'pact:matcher:type': 'regex',
        regex: '\\d+',
        value: '1234',
      });
    });

    describe('when given a regular expression', () => {
      it('returns a JSON representation of a regex matcher', () => {
        const result = MatchersV3.regex(/\d+/, '1234');
        expect(result).to.deep.equal({
          'pact:matcher:type': 'regex',
          regex: '\\d+',
          value: '1234',
        });
      });
    });
  });

  describe('#equal', () => {
    it('returns a JSON representation of an equality matcher', () => {
      const result = MatchersV3.equal('true');
      expect(result).to.deep.equal({
        'pact:matcher:type': 'equality',
        value: 'true',
      });
    });
  });

  describe('#datetime', () => {
    describe('when an example is given', () => {
      it('returns a JSON representation of a datetime matcher', () => {
        const result = MatchersV3.datetime(
          "yyyy-MM-dd'T'HH:mm:ss.SSSX",
          '2016-02-11T09:46:56.023Z'
        );
        expect(result).to.deep.equal({
          'pact:matcher:type': 'timestamp',
          format: "yyyy-MM-dd'T'HH:mm:ss.SSSX",
          value: '2016-02-11T09:46:56.023Z',
        });
      });
    });
  });

  describe('#time', () => {
    it('returns a JSON representation of a time matcher', () => {
      const result = MatchersV3.time('HH:mm:ss', '09:46:56');
      expect(result).to.deep.equal({
        'pact:generator:type': 'Time',
        'pact:matcher:type': 'time',
        format: 'HH:mm:ss',
        value: '09:46:56',
      });
    });
  });

  describe('#date', () => {
    it('returns a JSON representation of a date matcher', () => {
      const result = MatchersV3.date('yyyy-MM-dd', '2016-02-11');
      expect(result).to.deep.equal({
        'pact:generator:type': 'Date',
        'pact:matcher:type': 'date',
        format: 'yyyy-MM-dd',
        value: '2016-02-11',
      });
    });
  });

  describe('#includes', () => {
    it('returns a JSON representation of an include matcher', () => {
      const result = MatchersV3.includes('true');
      expect(result).to.deep.equal({
        'pact:matcher:type': 'include',
        value: 'true',
      });
    });
  });

  describe('#nullValue', () => {
    it('returns a JSON representation of an null matcher', () => {
      const result = MatchersV3.nullValue();
      expect(result).to.deep.equal({
        'pact:matcher:type': 'null',
      });
    });
  });

  describe('#url', () => {
    it('returns a JSON representation of a regex matcher for the URL', () => {
      const result = MatchersV3.url2('http://localhost:8080', [
        'users',
        '1234',
        'posts',
        'latest',
      ]);
      expect(result).to.deep.equal({
        'pact:matcher:type': 'regex',
        regex: '.*(\\/users\\/1234\\/posts\\/latest)$',
        value: 'http://localhost:8080/users/1234/posts/latest',
      });
    });

    describe('when provided with a regex matcher', () => {
      it('returns a JSON representation of a regex matcher for the URL', () => {
        const result = MatchersV3.url2('http://localhost:8080', [
          'users',
          MatchersV3.regex('\\d+', '1234'),
          'posts',
          'latest',
        ]);
        expect(result).to.deep.equal({
          'pact:matcher:type': 'regex',
          regex: '.*(\\/users\\/\\d+\\/posts\\/latest)$',
          value: 'http://localhost:8080/users/1234/posts/latest',
        });
      });
    });

    describe('when provided with a regular expression', () => {
      it('returns a JSON representation of a regex matcher for the URL', () => {
        const result = MatchersV3.url2('http://localhost:8080', [
          'users',
          /\d+/,
          'posts',
          'latest',
        ]);
        expect(result).to.deep.contain({
          'pact:matcher:type': 'regex',
          regex: '.*(\\/users\\/\\d+\\/posts\\/latest)$',
        });
        expect(result.value).to.match(/\/users\/\d+\/posts\/latest$/);
      });
    });

    describe('when no base URL is provided', () => {
      it('returns regex matcher and a MockServerURL generator', () => {
        const result = MatchersV3.url([
          'users',
          MatchersV3.regex('\\d+', '1234'),
          'posts',
          'latest',
        ]);
        expect(result).to.deep.equal({
          'pact:matcher:type': 'regex',
          'pact:generator:type': 'MockServerURL',
          regex: '.*(\\/users\\/\\d+\\/posts\\/latest)$',
          value: 'http://localhost:8080/users/1234/posts/latest',
          example: 'http://localhost:8080/users/1234/posts/latest',
        });
      });
    });
  });

  describe('#uuid', () => {
    it('returns a JSON representation of an regex matcher for UUIDs', () => {
      const result = MatchersV3.uuid('ba4bd1bc-5556-11eb-9286-d71bc5b507be');
      expect(result).to.deep.equal({
        'pact:matcher:type': 'regex',
        regex: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
        value: 'ba4bd1bc-5556-11eb-9286-d71bc5b507be',
      });
    });

    it('throws an exception if the example value does not match the UUID regex', () => {
      expect(() => MatchersV3.uuid('not a uuid')).to.throw();
      expect(() => MatchersV3.uuid('ba4bd1bc-5556-11eb-9286')).to.throw();
      expect(() =>
        MatchersV3.uuid('ba4bd1bc-5556-11eb-9286-d71bc5b507be-1234')
      ).to.throw();
    });

    it('if no example is provided, it sets up a generator', () => {
      const result = MatchersV3.uuid();
      expect(result).to.deep.equal({
        'pact:matcher:type': 'regex',
        'pact:generator:type': 'Uuid',
        regex: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
        value: 'e2490de5-5bd3-43d5-b7c4-526e33f71304',
      });
    });
  });

  describe('#reify', () => {
    describe('when given an object with no matchers', () => {
      const object = {
        value: 'data', // field name of value should not be only determination of isMatcher()
        more: 'strings',
        an: ['array'],
        someObject: {
          withData: true,
          withNumber: 1,
        },
      };

      it('returns just that object', () => {
        expect(MatchersV3.reify(object)).to.deep.equal(object);
      });
    });

    describe('when given an object with null values', () => {
      const object = {
        some: 'data',
        more: null,
        an: [null],
        someObject: {
          withData: true,
          withNumber: 1,
          andNull: null,
        },
      };

      it('returns just that object', () => {
        expect(MatchersV3.reify(object)).to.deep.equal(object);
      });
    });

    describe('when given an object with some matchers', () => {
      const someMatchers = {
        some: MatchersV3.like('data'),
        more: 'strings',
        an: ['array'],
        another: MatchersV3.eachLike('this'),
        someObject: {
          withData: MatchersV3.like(true),
          withTerm: MatchersV3.regex('this|that', 'this'),
          withNumber: 1,
          withAnotherNumber: MatchersV3.like(2),
        },
      };

      const expected = {
        some: 'data',
        more: 'strings',
        an: ['array'],
        another: ['this'],
        someObject: {
          withData: true,
          withTerm: 'this',
          withNumber: 1,
          withAnotherNumber: 2,
        },
      };

      it('returns without matching guff', () => {
        expect(MatchersV3.reify(someMatchers)).to.deep.equal(expected);
      });
    });

    describe('when given a simple matcher', () => {
      it('removes all matching guff', () => {
        const expected = 'myawesomeword';

        const matcher = MatchersV3.regex('\\w+', 'myawesomeword');

        expect(MatchersV3.isMatcher(matcher)).to.eq(true);
        expect(MatchersV3.reify(matcher)).to.eql(expected);
      });
    });
    describe('when given a complex nested object with matchers', () => {
      it('removes all matching guff', () => {
        const o = MatchersV3.like({
          stringMatcher: {
            awesomeSetting: MatchersV3.like('a string'),
          },
          anotherStringMatcher: {
            nestedSetting: {
              anotherStringMatcherSubSetting: MatchersV3.like(true),
            },
            anotherSetting: MatchersV3.regex('this|that', 'this'),
          },
          arrayMatcher: {
            lotsOfValueregex: MatchersV3.atLeastOneLike('useful', 3),
          },
          arrayOfMatcherregex: {
            lotsOfValueregex: MatchersV3.atLeastOneLike(
              {
                foo: 'bar',
                baz: MatchersV3.like('bat'),
              },
              3
            ),
          },
        });

        const expected = {
          stringMatcher: {
            awesomeSetting: 'a string',
          },
          anotherStringMatcher: {
            nestedSetting: {
              anotherStringMatcherSubSetting: true,
            },
            anotherSetting: 'this',
          },
          arrayMatcher: {
            lotsOfValueregex: ['useful', 'useful', 'useful'],
          },
          arrayOfMatcherregex: {
            lotsOfValueregex: [
              {
                baz: 'bat',
                foo: 'bar',
              },
              {
                baz: 'bat',
                foo: 'bar',
              },
              {
                baz: 'bat',
                foo: 'bar',
              },
            ],
          },
        };

        expect(MatchersV3.reify(o)).to.deep.equal(expected);
      });
    });

    describe('when given a matcher and field named "value"', () => {
      it('should not be isMatcher"', () => {
        const resultMatcher = MatchersV3.like({
          a: 'b',
          c: MatchersV3.atLeastOneLike({ value: '1', a: 'b', aa: 'bb' }),
        });

        const result = MatchersV3.reify(resultMatcher);
        expect(result).to.deep.equal({
          a: 'b',
          c: [{ value: '1', a: 'b', aa: 'bb' }],
        });
      });
    });
  });
});