|
1 | 1 | """Classes for defining request and response data that is variable.""" |
2 | 2 | import six |
| 3 | +import datetime |
| 4 | + |
| 5 | +from enum import Enum |
3 | 6 |
|
4 | 7 |
|
5 | 8 | class Matcher(object): |
@@ -225,3 +228,174 @@ def get_generated_values(input): |
225 | 228 | return input.generate()['data']['generate'] |
226 | 229 | else: |
227 | 230 | raise ValueError('Unknown type: %s' % type(input)) |
| 231 | + |
| 232 | + |
| 233 | +class Format: |
| 234 | + """ |
| 235 | + Class of regular expressions for common formats. |
| 236 | +
|
| 237 | + Example: |
| 238 | +
|
| 239 | + >>> from pact import Consumer, Provider |
| 240 | + >>> from pact.matchers import Format |
| 241 | + >>> pact = Consumer('consumer').has_pact_with(Provider('provider')) |
| 242 | + >>> (pact.given('the current user is logged in as `tester`') |
| 243 | + ... .upon_receiving('a request for the user profile') |
| 244 | + ... .with_request('get', '/profile') |
| 245 | + ... .will_respond_with(200, body={ |
| 246 | + ... 'id': Format().identifier, |
| 247 | + ... 'lastUpdated': Format().time |
| 248 | + ... })) |
| 249 | +
|
| 250 | + Would expect `id` to be any valid int and `lastUpdated` to be a valid time. |
| 251 | + When the consumer runs this contract, the value of that will be returned |
| 252 | + is the second value passed to Term in the given function, for the time |
| 253 | + example it would be datetime.datetime(2000, 2, 1, 12, 30, 0, 0).time() |
| 254 | + """ |
| 255 | + |
| 256 | + def __init__(self): |
| 257 | + """Create a new Formatter.""" |
| 258 | + self.identifier = self.integer_or_identifier() |
| 259 | + self.integer = self.integer_or_identifier() |
| 260 | + self.decimal = self.decimal() |
| 261 | + self.ip_address = self.ip_address() |
| 262 | + self.hexadecimal = self.hexadecimal() |
| 263 | + self.ipv6_address = self.ipv6_address() |
| 264 | + self.uuid = self.uuid() |
| 265 | + self.timestamp = self.timestamp() |
| 266 | + self.date = self.date() |
| 267 | + self.time = self.time() |
| 268 | + |
| 269 | + def integer_or_identifier(self): |
| 270 | + """ |
| 271 | + Match any integer. |
| 272 | +
|
| 273 | + :return: a Like object with an integer. |
| 274 | + :rtype: Like |
| 275 | + """ |
| 276 | + return Like(1) |
| 277 | + |
| 278 | + def decimal(self): |
| 279 | + """ |
| 280 | + Match any decimal. |
| 281 | +
|
| 282 | + :return: a Like object with a decimal. |
| 283 | + :rtype: Like |
| 284 | + """ |
| 285 | + return Like(1.0) |
| 286 | + |
| 287 | + def ip_address(self): |
| 288 | + """ |
| 289 | + Match any ip address. |
| 290 | +
|
| 291 | + :return: a Term object with an ip address regex. |
| 292 | + :rtype: Term |
| 293 | + """ |
| 294 | + return Term(self.Regexes.ip_address.value, '127.0.0.1') |
| 295 | + |
| 296 | + def hexadecimal(self): |
| 297 | + """ |
| 298 | + Match any hexadecimal. |
| 299 | +
|
| 300 | + :return: a Term object with a hexdecimal regex. |
| 301 | + :rtype: Term |
| 302 | + """ |
| 303 | + return Term(self.Regexes.hexadecimal.value, '3F') |
| 304 | + |
| 305 | + def ipv6_address(self): |
| 306 | + """ |
| 307 | + Match any ipv6 address. |
| 308 | +
|
| 309 | + :return: a Term object with an ipv6 address regex. |
| 310 | + :rtype: Term |
| 311 | + """ |
| 312 | + return Term(self.Regexes.ipv6_address.value, '::ffff:192.0.2.128') |
| 313 | + |
| 314 | + def uuid(self): |
| 315 | + """ |
| 316 | + Match any uuid. |
| 317 | +
|
| 318 | + :return: a Term object with a uuid regex. |
| 319 | + :rtype: Term |
| 320 | + """ |
| 321 | + return Term( |
| 322 | + self.Regexes.uuid.value, 'fc763eba-0905-41c5-a27f-3934ab26786c' |
| 323 | + ) |
| 324 | + |
| 325 | + def timestamp(self): |
| 326 | + """ |
| 327 | + Match any timestamp. |
| 328 | +
|
| 329 | + :return: a Term object with a timestamp regex. |
| 330 | + :rtype: Term |
| 331 | + """ |
| 332 | + return Term( |
| 333 | + self.Regexes.timestamp.value, datetime.datetime( |
| 334 | + 2000, 2, 1, 12, 30, 0, 0 |
| 335 | + ) |
| 336 | + ) |
| 337 | + |
| 338 | + def date(self): |
| 339 | + """ |
| 340 | + Match any date. |
| 341 | +
|
| 342 | + :return: a Term object with a date regex. |
| 343 | + :rtype: Term |
| 344 | + """ |
| 345 | + return Term( |
| 346 | + self.Regexes.date.value, datetime.datetime( |
| 347 | + 2000, 2, 1, 12, 30, 0, 0 |
| 348 | + ).date() |
| 349 | + ) |
| 350 | + |
| 351 | + def time(self): |
| 352 | + """ |
| 353 | + Match any time. |
| 354 | +
|
| 355 | + :return: a Term object with a time regex. |
| 356 | + :rtype: Term |
| 357 | + """ |
| 358 | + return Term( |
| 359 | + self.Regexes.time_regex.value, datetime.datetime( |
| 360 | + 2000, 2, 1, 12, 30, 0, 0 |
| 361 | + ).time() |
| 362 | + ) |
| 363 | + |
| 364 | + class Regexes(Enum): |
| 365 | + """Regex Enum for common formats.""" |
| 366 | + |
| 367 | + ip_address = r'(\d{1,3}\.)+\d{1,3}' |
| 368 | + hexadecimal = r'[0-9a-fA-F]+' |
| 369 | + ipv6_address = r'(\A([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,6}\Z)|' \ |
| 370 | + r'(\A([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}\Z)|(\A([0-9a-f]' \ |
| 371 | + r'{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}\Z)|(\A([0-9a-f]{1,4}:)' \ |
| 372 | + r'{1,4}(:[0-9a-f]{1,4}){1,3}\Z)|(\A([0-9a-f]{1,4}:){1,5}(:[0-' \ |
| 373 | + r'9a-f]{1,4}){1,2}\Z)|(\A([0-9a-f]{1,4}:){1,6}(:[0-9a-f]{1,4})' \ |
| 374 | + r'{1,1}\Z)|(\A(([0-9a-f]{1,4}:){1,7}|:):\Z)|(\A:(:[0-9a-f]{1,4})' \ |
| 375 | + r'{1,7}\Z)|(\A((([0-9a-f]{1,4}:){6})(25[0-5]|2[0-4]\d|[0-1]' \ |
| 376 | + r'?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})\Z)|(\A(([0-9a-f]' \ |
| 377 | + r'{1,4}:){5}[0-9a-f]{1,4}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25' \ |
| 378 | + r'[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})\Z)|(\A([0-9a-f]{1,4}:){5}:[' \ |
| 379 | + r'0-9a-f]{1,4}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4' \ |
| 380 | + r']\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,1}(:[0-9a-f]' \ |
| 381 | + r'{1,4}){1,4}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]' \ |
| 382 | + r'\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}' \ |
| 383 | + r'){1,3}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0' \ |
| 384 | + r'-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,' \ |
| 385 | + r'2}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]' \ |
| 386 | + r'?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,1}:' \ |
| 387 | + r'(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?' \ |
| 388 | + r'\d)){3}\Z)|(\A(([0-9a-f]{1,4}:){1,5}|:):(25[0-5]|2[0-4]\d|[0' \ |
| 389 | + r'-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A:(:[' \ |
| 390 | + r'0-9a-f]{1,4}){1,5}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]' \ |
| 391 | + r'|2[0-4]\d|[0-1]?\d?\d)){3}\Z)' |
| 392 | + uuid = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' |
| 393 | + timestamp = r'^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3(' \ |
| 394 | + r'[12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-' \ |
| 395 | + r'9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2' \ |
| 396 | + r'[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d' \ |
| 397 | + r'([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$' |
| 398 | + date = r'^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|' \ |
| 399 | + r'0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|' \ |
| 400 | + r'[12]\d{2}|3([0-5]\d|6[1-6])))?)' |
| 401 | + time_regex = r'^(T\d\d:\d\d(:\d\d)?(\.\d+)?(([+-]\d\d:\d\d)|Z)?)?$' |
0 commit comments