Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

webhook-template-matchers.md 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. # Webhook Template Matchers
  2. ## Principle
  3. Build typed templates with `webhookTemplate()` and compose matchers using `matchField`, `matchPartial`, and `matchPredicate`. All matchers on a template use AND semantics — every matcher must pass for a webhook to be considered a match. Templates are immutable value objects produced by a fluent builder.
  4. ## Template Factory Pattern
  5. Define template factories as pure functions that accept a test-scoped ID. This is the key pattern for parallel isolation — each factory call produces a template bound to a specific entity:
  6. ```typescript
  7. import { webhookTemplate } from '@seontechnologies/playwright-utils/webhook';
  8. // Template factories for movie webhooks
  9. // 15s timeout: the Kafka → HTTP webhook delivery pipeline can back up under
  10. // high CI concurrency (burn-in with many parallel workers). 10s was occasionally
  11. // not enough; 15s gives the pipeline headroom without slowing normal runs.
  12. const movieCreated = (movieId: number) =>
  13. webhookTemplate<{ event: string; data: { id: number } }>('movie.created')
  14. .matchField('event', 'movie.created')
  15. .matchField('data.id', movieId)
  16. .withTimeout(15_000)
  17. .withInterval(500)
  18. .build();
  19. const movieDeleted = (movieId: number) =>
  20. webhookTemplate<{ event: string; data: { id: number } }>('movie.deleted')
  21. .matchField('event', 'movie.deleted')
  22. .matchField('data.id', movieId)
  23. .withTimeout(15_000)
  24. .withInterval(500)
  25. .build();
  26. ```
  27. The ID parameter scopes each template to a specific entity, preventing parallel workers from matching each other's webhooks.
  28. ## Matcher Reference
  29. ### matchField — dot-path exact match
  30. Traverses dot-notation paths into the payload. Never throws if the path is missing — a missing path evaluates as non-matching.
  31. ```typescript
  32. webhookTemplate('order.created')
  33. .matchField('event', 'order.created') // top-level field
  34. .matchField('data.id', orderId) // nested path
  35. .matchField('data.status', 'pending') // nested string value
  36. .build();
  37. ```
  38. Matcher detail output: `field(data.id=42)`
  39. ### matchPartial — deep subset check
  40. Checks that the expected object is a subset of the received payload. Extra fields in the payload are ignored. Arrays use strict length matching.
  41. ```typescript
  42. const partialTemplate = webhookTemplate<{
  43. event: string;
  44. data: { id: number; name: string };
  45. }>('movie.created.partial')
  46. .matchPartial({ event: 'movie.created', data: { id: movieId } })
  47. .withTimeout(10_000)
  48. .withInterval(500)
  49. .build();
  50. ```
  51. Matcher detail output: `partial({"event":"movie.created","data":{"id":42}})`
  52. ### matchPredicate — arbitrary function
  53. Accepts any `(payload: T) => boolean` function. Always requires a human-readable description string — this appears in `WebhookTimeoutError.matcherDetails` for debugging.
  54. **ID-scoped parallel isolation** (prevents cross-worker contamination in `waitForCount`):
  55. ```typescript
  56. const batchTemplate = webhookTemplate<{
  57. event: string;
  58. data: { id: number };
  59. }>('movie.created.batch')
  60. .matchField('event', 'movie.created')
  61. .matchPredicate(`data.id is ${id1} or ${id2}`, (p) => p.data.id === id1 || p.data.id === id2)
  62. .withTimeout(15_000)
  63. .withInterval(500)
  64. .build();
  65. ```
  66. **Business data filtering**:
  67. ```typescript
  68. const highRatingTemplate = webhookTemplate<{
  69. event: string;
  70. data: { id: number; rating: number };
  71. }>('movie.created.high-rating')
  72. .matchField('event', 'movie.created')
  73. .matchPredicate(`data.id is ${movieId} and data.rating >= 9`, (p) => p.data.id === movieId && p.data.rating >= 9)
  74. .withTimeout(10_000)
  75. .withInterval(500)
  76. .build();
  77. ```
  78. Matcher detail output: `predicate(data.id is 42 and data.rating >= 9)`
  79. ## Combining Matchers
  80. All matchers use AND semantics — all must pass for the webhook to match:
  81. ```typescript
  82. // Combined field + partial: both matchers must pass
  83. const updateTemplate = webhookTemplate<{
  84. event: string;
  85. data: { id: number; name: string };
  86. }>('movie.updated')
  87. .matchField('event', 'movie.updated')
  88. .matchPartial({ data: { id: movieId, name: nameUpdate.name } })
  89. .withTimeout(10_000)
  90. .withInterval(500)
  91. .build();
  92. ```
  93. ## Per-Template Timeout and Interval
  94. Override the registry defaults on a per-template basis:
  95. ```typescript
  96. webhookTemplate('slow.pipeline.event')
  97. .matchField('event', 'slow.pipeline.event')
  98. .withTimeout(60_000) // 60s for slow delivery pipelines
  99. .withInterval(2_000) // poll every 2s
  100. .build();
  101. ```
  102. ## clone() for Base Template Variations
  103. > **Note**: `clone()` is available on the builder but is not used in the playwright-utils E2E suite. Use it when multiple tests share the same base template with slight field variations.
  104. ```typescript
  105. const base = webhookTemplate<OrderPayload>('order').matchField('event', 'order.completed');
  106. const forOrderA = base.clone().matchField('data.orderId', 'A').build();
  107. const forOrderB = base.clone().matchField('data.orderId', 'B').build();
  108. ```
  109. ## Builder API Summary
  110. | Method | Description |
  111. | --------------------------- | ------------------------------------------------------ |
  112. | `webhookTemplate<T>(name)` | Create a new builder with the given template name |
  113. | `.matchField(path, value)` | Add dot-path exact-match matcher |
  114. | `.matchPartial(expected)` | Add deep-subset matcher |
  115. | `.matchPredicate(desc, fn)` | Add arbitrary predicate matcher (description required) |
  116. | `.withTimeout(ms)` | Override registry default timeout |
  117. | `.withInterval(ms)` | Override registry default poll interval |
  118. | `.clone()` | Copy current builder state for variation |
  119. | `.build()` | Produce the immutable `WebhookTemplate<T>` object |
  120. ## Related Fragments
  121. - `webhook-waiting-querying.md` — waitFor, waitForCount, drain pattern
  122. - `webhook-timeout-error.md` — Reading matcherDetails in error output