Pact-js: Pact-js doesn't support date matching

Created on 20 Oct 2017  路  14Comments  路  Source: pact-foundation/pact-js

Software versions

  • OS: Mac OSX 10.12.6
  • Consumer Pact library: pact-js 4.0.0
  • Node Version: v8.4.0

Issue

I'm not sure if this is the right repository for this issue. I'm opening it here, because I'm hitting it while using pact-js.

As far as I can see we currently have to roll our own matchers when trying to match dates. I'd prefer not to think about date formats and be able to express "match a date that is compliant with RFC-2822" or something similar.

Unfortunately, the RFC/ISO formats are not a good fit for regex, and the resulting regex can be long. Even if we just want to stick the ES standard (which is a subset of ISO 8601), a regex is not easy.

If we don't want to roll our own regex, it's good to find one that someone else has written and tested. However, that there aren't many premade Ruby regexes for dates. I think this is because Ruby provides a bunch of nice date parsing methods already.

There are these helpers, which almost do what we want, but I'm not across the Pact ecosystem enough to know if they can be easily exposed to pact-js.

Current behaviour

Even simple date formats end up with a long regex:

  willRespondWith: {
        status: 200,
        headers: headers.response,
        body: {
             startDate: term({
                 matcher: 'dddd-[0-1]d-[0-3]d',
                 generate: '2012-01-12'
             })
        }
   }

Matching or requiring timezones would result in a much more complex regex. This is a disadvantage, because communicating dates without timezone information easily leads to bugs. So, I'd like to be able to express dates with a format:

Proposals

It would be nice to be able to say:

  willRespondWith: {
        status: 200,
        headers: headers.response,
        body: {
              // require a specific date format
              startDate: likeDate({ 
                  format: 'YYYY-MM-DD Z',
                  generate: '2012-01-12 +10:00'
              })
        }
   }

I could also see a use for something like this:

  willRespondWith: {
        status: 200,
        headers: headers.response,
        body: { 
         // Allow any ISO 8601 date
         startDate:  likeIso8601Date('2009-05-19 14:39:22-06:00')
        }
   }

or even cooler, for flexible matching:

  willRespondWith: {
        status: 200,
        headers: headers.response,
        body: {
              // Allow any ISO 8601 date that includes a timezone
              startDate: likeIso8601Date({
                requireTimezone: true,
                generate: '2009-05-19 14:39:22-06:00'
              }),
        }
   }
enhancement

Most helpful comment

All the matchers really are, are "standard" regular expressions. You can see the actualy expressions in the source.

So doing something like this:

provider.addInteraction({
  ...
   "date": Matchers.iso8601DateTimeWithMillis()
  ...
})

will ensure that a provider must return a string in the date field that looks like 2015-08-06T16:53:10.123+01:0.

All 14 comments

Thanks Tim, we'll add a bunch of matchers similar to PactJVM. Perhaps this could be added as a recommendation to the pact-specification.

I'll mark for enhancement, but won't start work on this until the TypeScript implementation is finished.

I like your likeDate proposal, however that looks like it might be a stretch to implement. I'll have to think on it. The trick would be to be able to automatically generate an enforceable regex from your date format string (in this case, YYYY-MM-DD Z) to send over the wire. Granted, it is almost 3am, but my brain will need some time to come up with a way of doing this. I

Certainly, a series of iso8601DateTime type matchers would be doable. I'll be adding these in soon anyway to match JVM at least as close as I can.

Libraries like date-fns might help with implementation, and/or inspiration :)

FYI have created a number of simple matchers as per your second option. These will be available in the next release, which I hope to push out in the next week or so.

| method | description |
|--------|-------------|
| boolean | Match a boolean value (using equality) |
| integer | Will match all numbers that are integers (both ints and longs)|
| decimal | Will match all real numbers (floating point and decimal)|
| hexadecimal | Will match all hexadecimal encoded strings |
| iso8601Date | Will match string containing basic ISO8601 dates (e.g. 2016-01-01)|
| iso8601DateTime | Will match string containing ISO 8601 formatted dates (e.g. 2015-08-06T16:53:10+01:00)|
| iso8601DateTimeWithMillis | Will match string containing ISO 8601 formatted dates, enforcing millisecond precision (e.g. 2015-08-06T16:53:10.123+01:00)|
| rfc3339Timestamp | Will match a string containing an RFC3339 formatted timestapm (e.g. Mon, 31 Oct 2016 15:21:41 -0400)|
| iso8601Time | Will match string containing times (e.g. T22:44:30.652Z)|
| ipv4Address | Will match string containing IP4 formatted address |
| ipv6Address | Will match string containing IP6 formatted address |
| uuid | Will match strings containing UUIDs |

For the v4 matching, we'll have to wait a bit longer I'm afraid (nothing we can do from JS land I can think of to address it, as it's not pure regex based).

Thanks! Those new matchers will suit our needs perfectly!

You're welcome! ;)

These are all string matchers, right? Because we don't currently do matching on numbers or booleans. We can add it, but it won't work until the change is made in the Ruby impl.

Correct. The integer and decimal matchers are simply convenience matchers for the like matcher (the decimal defaults to 13.01 to ensure it's a float!!)

Are you happy for me to close this now @TimothyJones ? 5.x.x is now in train
https://www.npmjs.com/package/@pact-foundation/pact (will run concurrently with the 4.x.x branch for a little bit as there were some significant changes).

Sounds good! Thanks!

馃憤

Hi @mefellows, good work here!! Can you provide some examples about how to use those matchers, I can't find anything in the documentation

All the matchers really are, are "standard" regular expressions. You can see the actualy expressions in the source.

So doing something like this:

provider.addInteraction({
  ...
   "date": Matchers.iso8601DateTimeWithMillis()
  ...
})

will ensure that a provider must return a string in the date field that looks like 2015-08-06T16:53:10.123+01:0.

Was this page helpful?
0 / 5 - 0 ratings