|
| 1 | +{ |
| 2 | + "schema_version": 1, |
| 3 | + "description": "Cross-SDK conformance cases. Every BabelQueue SDK core must satisfy these against the canonical wire envelope. Per-message fields (meta.id, trace_id, meta.created_at) are intrinsically unique and are NOT asserted by value.", |
| 4 | + "cases": [ |
| 5 | + { |
| 6 | + "name": "order-created", |
| 7 | + "file": "fixtures/order-created.json", |
| 8 | + "valid": true, |
| 9 | + "description": "A normal produced envelope.", |
| 10 | + "expect": { |
| 11 | + "urn": "urn:babel:orders:created", |
| 12 | + "data": { "order_id": 1042 }, |
| 13 | + "attempts": 0, |
| 14 | + "lang": "php", |
| 15 | + "schema_version": 1 |
| 16 | + } |
| 17 | + }, |
| 18 | + { |
| 19 | + "name": "urn-alias", |
| 20 | + "file": "fixtures/urn-alias.json", |
| 21 | + "valid": true, |
| 22 | + "description": "Consumers MUST accept 'urn' as an inbound alias for 'job'.", |
| 23 | + "expect": { |
| 24 | + "urn": "urn:babel:orders:created", |
| 25 | + "data": { "order_id": 1042 }, |
| 26 | + "attempts": 0, |
| 27 | + "lang": "go", |
| 28 | + "schema_version": 1 |
| 29 | + } |
| 30 | + }, |
| 31 | + { |
| 32 | + "name": "dead-lettered", |
| 33 | + "file": "fixtures/dead-lettered.json", |
| 34 | + "valid": true, |
| 35 | + "description": "A dead-lettered message: original preserved + additive dead_letter block.", |
| 36 | + "expect": { |
| 37 | + "urn": "urn:babel:orders:created", |
| 38 | + "data": { "order_id": 1042 }, |
| 39 | + "attempts": 3, |
| 40 | + "lang": "php", |
| 41 | + "schema_version": 1, |
| 42 | + "dead_letter": { "reason": "failed", "original_queue": "orders" } |
| 43 | + } |
| 44 | + }, |
| 45 | + { |
| 46 | + "name": "unicode-and-numbers", |
| 47 | + "file": "fixtures/unicode-and-numbers.json", |
| 48 | + "valid": true, |
| 49 | + "description": "UTF-8 strings, integers, an exact float, boolean and null round-trip identically.", |
| 50 | + "expect": { |
| 51 | + "urn": "urn:babel:catalog:item.indexed", |
| 52 | + "data": { "title": "Café — naïve ☕", "qty": 7, "price_cents": 1299, "ratio": 0.5, "active": true, "note": null }, |
| 53 | + "attempts": 2, |
| 54 | + "lang": "python", |
| 55 | + "schema_version": 1 |
| 56 | + } |
| 57 | + }, |
| 58 | + { |
| 59 | + "name": "invalid-unknown-schema-version", |
| 60 | + "file": "fixtures/invalid-unknown-schema-version.json", |
| 61 | + "valid": false, |
| 62 | + "reason": "meta.schema_version is not a version this SDK supports" |
| 63 | + }, |
| 64 | + { |
| 65 | + "name": "invalid-missing-urn", |
| 66 | + "file": "fixtures/invalid-missing-urn.json", |
| 67 | + "valid": false, |
| 68 | + "reason": "no 'job' or 'urn' — the message has no identity" |
| 69 | + } |
| 70 | + ], |
| 71 | + "sqs": { |
| 72 | + "description": "Amazon SQS binding conformance (broker-bindings.md §3). Every SDK that ships an SQS transport must satisfy these. The envelope body stays byte-identical (the 'cases' above); these lock the native projection + reconciliation the binding adds. Per-message values reuse fixtures/order-created.json so the expected attributes are deterministic.", |
| 73 | + "attribute_projection": { |
| 74 | + "description": "On produce, the transport MUST project these native MessageAttributes from the envelope (a redundant, routable view of the body; ids/strings are DataType String, counters are DataType Number). Applies to every SQS-producing SDK.", |
| 75 | + "envelope_file": "fixtures/order-created.json", |
| 76 | + "message_attributes": { |
| 77 | + "bq-job": { "DataType": "String", "StringValue": "urn:babel:orders:created" }, |
| 78 | + "bq-trace-id": { "DataType": "String", "StringValue": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b" }, |
| 79 | + "bq-message-id": { "DataType": "String", "StringValue": "f1e2d3c4-b5a6-4789-90ab-cdef01234567" }, |
| 80 | + "bq-schema-version": { "DataType": "Number", "StringValue": "1" }, |
| 81 | + "bq-source-lang": { "DataType": "String", "StringValue": "php" }, |
| 82 | + "bq-created-at": { "DataType": "Number", "StringValue": "1749132727000" } |
| 83 | + } |
| 84 | + }, |
| 85 | + "attempts_reconciliation": { |
| 86 | + "description": "On consume, attempts = max(body.attempts, ApproximateReceiveCount - 1): a first delivery reads 0, an absent/garbage count is ignored, a runtime-incremented count is never lowered. Applies to SDKs that reconcile the envelope body on consume (the framework-less/runtime transports). A drop-in driver that surfaces the broker's native delivery count instead (e.g. Laravel's SqsJob.attempts() = ApproximateReceiveCount) is exempt — it documents that divergence.", |
| 87 | + "cases": [ |
| 88 | + { "name": "first-delivery", "body_attempts": 0, "approximate_receive_count": "1", "expected_attempts": 0 }, |
| 89 | + { "name": "third-delivery", "body_attempts": 0, "approximate_receive_count": "3", "expected_attempts": 2 }, |
| 90 | + { "name": "native-exceeds-body", "body_attempts": 2, "approximate_receive_count": "5", "expected_attempts": 4 }, |
| 91 | + { "name": "never-lower-runtime", "body_attempts": 5, "approximate_receive_count": "1", "expected_attempts": 5 }, |
| 92 | + { "name": "garbage-count-ignored", "body_attempts": 4, "approximate_receive_count": "not-a-number", "expected_attempts": 4 }, |
| 93 | + { "name": "absent-count", "body_attempts": 3, "approximate_receive_count": null, "expected_attempts": 3 } |
| 94 | + ] |
| 95 | + } |
| 96 | + }, |
| 97 | + "asb": { |
| 98 | + "description": "Azure Service Bus binding conformance (broker-bindings.md §4). Every SDK that ships an ASB transport must satisfy these. The envelope body stays byte-identical (the 'cases' above); these lock the native projection + reconciliation the binding adds. Per-message values reuse fixtures/order-created.json so the expected projection is deterministic.", |
| 99 | + "property_projection": { |
| 100 | + "description": "On produce, the transport MUST project these native Service Bus message fields from the envelope: Subject = job (the URN), CorrelationId = trace_id, MessageId = meta.id, ContentType = application/json, plus the bq- ApplicationProperties as native AMQP-typed values (numbers stay numbers, not strings). Applies to every ASB-producing SDK.", |
| 101 | + "envelope_file": "fixtures/order-created.json", |
| 102 | + "message": { |
| 103 | + "subject": "urn:babel:orders:created", |
| 104 | + "correlation_id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b", |
| 105 | + "message_id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567", |
| 106 | + "content_type": "application/json" |
| 107 | + }, |
| 108 | + "application_properties": { |
| 109 | + "bq-schema-version": 1, |
| 110 | + "bq-source-lang": "php", |
| 111 | + "bq-created-at": 1749132727000 |
| 112 | + } |
| 113 | + }, |
| 114 | + "attempts_reconciliation": { |
| 115 | + "description": "On consume, attempts = max(body.attempts, DeliveryCount - 1): a first delivery (DeliveryCount 1) reads 0, a runtime-incremented body count is never lowered, and DeliveryCount <= 1 leaves the body's own count untouched (the runtime retries by republishing with attempts+1). DeliveryCount is the native 1-based ASB redelivery counter; the rule is identical across the native-consumer SDKs (.NET/Java/Node) and the Transport+App SDKs (Python/Go).", |
| 116 | + "cases": [ |
| 117 | + { "name": "first-delivery", "body_attempts": 0, "delivery_count": 1, "expected_attempts": 0 }, |
| 118 | + { "name": "third-delivery", "body_attempts": 0, "delivery_count": 3, "expected_attempts": 2 }, |
| 119 | + { "name": "native-exceeds-body", "body_attempts": 2, "delivery_count": 5, "expected_attempts": 4 }, |
| 120 | + { "name": "never-lower-runtime", "body_attempts": 5, "delivery_count": 2, "expected_attempts": 5 }, |
| 121 | + { "name": "first-delivery-keeps-body", "body_attempts": 4, "delivery_count": 1, "expected_attempts": 4 }, |
| 122 | + { "name": "zero-count-keeps-body", "body_attempts": 3, "delivery_count": 0, "expected_attempts": 3 } |
| 123 | + ] |
| 124 | + } |
| 125 | + }, |
| 126 | + "pulsar": { |
| 127 | + "description": "Apache Pulsar binding conformance (broker-bindings.md §5). Every SDK that ships a Pulsar transport must satisfy these. The envelope body stays byte-identical (the 'cases' above); these lock the native projection + reconciliation the binding adds. Per-message values reuse fixtures/order-created.json so the expected projection is deterministic.", |
| 128 | + "property_projection": { |
| 129 | + "description": "On produce, the transport MUST project these native Pulsar message properties from the envelope, all string->string (Pulsar properties are string-typed, so numbers are stringified): bq-job = job (the URN), bq-trace-id = trace_id, bq-message-id = meta.id, bq-schema-version = str(meta.schema_version), bq-source-lang = meta.lang, bq-attempts = str(attempts). The payload is the byte-identical envelope; the native publishTime mirrors meta.created_at (broker-set, body authoritative). Applies to every Pulsar-producing SDK.", |
| 130 | + "envelope_file": "fixtures/order-created.json", |
| 131 | + "properties": { |
| 132 | + "bq-job": "urn:babel:orders:created", |
| 133 | + "bq-trace-id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b", |
| 134 | + "bq-message-id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567", |
| 135 | + "bq-schema-version": "1", |
| 136 | + "bq-source-lang": "php", |
| 137 | + "bq-attempts": "0" |
| 138 | + } |
| 139 | + }, |
| 140 | + "attempts_reconciliation": { |
| 141 | + "description": "On consume, attempts = max(body.attempts, RedeliveryCount): Pulsar's RedeliveryCount is 0-based (0 on first delivery) so it maps directly with NO -1, a runtime-incremented body count is never lowered, and RedeliveryCount 0 leaves the body's own count untouched (the runtime retries by republishing with attempts+1, which resets the broker's redelivery count to 0). The rule is identical across the native-consumer SDKs (.NET/Java/Node) and the Transport+App SDKs (Python/Go).", |
| 142 | + "cases": [ |
| 143 | + { "name": "first-delivery", "body_attempts": 0, "redelivery_count": 0, "expected_attempts": 0 }, |
| 144 | + { "name": "third-delivery", "body_attempts": 0, "redelivery_count": 2, "expected_attempts": 2 }, |
| 145 | + { "name": "native-exceeds-body", "body_attempts": 2, "redelivery_count": 5, "expected_attempts": 5 }, |
| 146 | + { "name": "never-lower-runtime", "body_attempts": 5, "redelivery_count": 1, "expected_attempts": 5 }, |
| 147 | + { "name": "first-delivery-keeps-body", "body_attempts": 4, "redelivery_count": 0, "expected_attempts": 4 }, |
| 148 | + { "name": "native-equals-body", "body_attempts": 3, "redelivery_count": 3, "expected_attempts": 3 } |
| 149 | + ] |
| 150 | + } |
| 151 | + } |
| 152 | +} |
0 commit comments