Skip to content

Commit d298f0c

Browse files
committed
test: fix duplicate operations in golden files and add operation
invariants Fixed 4 failing /construction/parse golden tests that had duplicate operations at index 3. Tests expected 5 operations but API correctly returned 4. Added operation invariant tests to prevent ordering bugs: - assert_operations_ordered() - ensures operations sorted by index - assert_operations_sequential() - ensures indices [0, 1, 2, ...] Covers /search/transactions, /block, and /block/transaction endpoints. This would have caught the bug where /construction/parse returns operations in processing order (inputs → outputs → certs → withdrawals) instead of sorted by operation_identifier.index.
1 parent 45c05cd commit d298f0c

File tree

7 files changed

+114
-77
lines changed

7 files changed

+114
-77
lines changed

tests/data-endpoints/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,16 @@ def get_error_message(error_response):
160160
details_message = error_response.get("details", {}).get("message", "")
161161
return (message + " " + details_message).strip()
162162

163+
164+
def assert_operations_ordered(operations):
165+
"""Operations must be sorted by operation_identifier.index."""
166+
indices = [op["operation_identifier"]["index"] for op in operations]
167+
assert indices == sorted(indices), f"Operations not ordered: {indices}"
168+
169+
170+
def assert_operations_sequential(operations):
171+
"""Operation indices must be [0, 1, 2, ..., n-1]."""
172+
indices = [op["operation_identifier"]["index"] for op in operations]
173+
expected = list(range(len(operations)))
174+
assert indices == expected, f"Expected {expected}, got {indices}"
175+

tests/data-endpoints/test_block_endpoints.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import pytest
88
import allure
9-
from conftest import get_error_message
9+
from conftest import get_error_message, assert_operations_ordered, assert_operations_sequential
1010

1111
pytestmark = pytest.mark.pr
1212

@@ -314,3 +314,75 @@ def test_invalid_transaction_hash_returns_error(self, client, network):
314314
transaction_identifier={"hash": "invalid_hash"},
315315
)
316316
assert response.status_code == 500
317+
318+
319+
@allure.feature("Block")
320+
@allure.story("Operation Invariants")
321+
class TestBlockOperationInvariants:
322+
"""Operations must be ordered and sequential in all block transactions."""
323+
324+
def test_block_operations_ordered_by_index(self, client, network):
325+
"""Operations in /block response must be sorted by index."""
326+
# Find block with transactions
327+
search = client.search_transactions(network=network)
328+
block_id = search.json()["transactions"][0]["block_identifier"]
329+
330+
response = client.block(network=network, block_identifier={"index": block_id["index"]})
331+
assert response.status_code == 200
332+
333+
for tx in response.json()["block"]["transactions"]:
334+
assert_operations_ordered(tx["operations"])
335+
336+
def test_block_operations_sequential_indices(self, client, network):
337+
"""Operations in /block must have sequential indices [0, 1, 2, ...]."""
338+
# Find block with transactions
339+
search = client.search_transactions(network=network)
340+
block_id = search.json()["transactions"][0]["block_identifier"]
341+
342+
response = client.block(network=network, block_identifier={"index": block_id["index"]})
343+
assert response.status_code == 200
344+
345+
for tx in response.json()["block"]["transactions"]:
346+
assert_operations_sequential(tx["operations"])
347+
348+
349+
@allure.feature("Block Transaction")
350+
@allure.story("Operation Invariants")
351+
class TestBlockTransactionOperationInvariants:
352+
"""Operations must be ordered and sequential in /block/transaction."""
353+
354+
def test_block_transaction_operations_ordered_by_index(self, client, network):
355+
"""Operations in /block/transaction must be sorted by index."""
356+
# Find transaction
357+
search = client.search_transactions(network=network)
358+
block_tx = search.json()["transactions"][0]
359+
360+
response = client.block_transaction(
361+
network=network,
362+
block_identifier={
363+
"index": block_tx["block_identifier"]["index"],
364+
"hash": block_tx["block_identifier"]["hash"]
365+
},
366+
transaction_identifier={"hash": block_tx["transaction"]["transaction_identifier"]["hash"]}
367+
)
368+
assert response.status_code == 200
369+
370+
assert_operations_ordered(response.json()["transaction"]["operations"])
371+
372+
def test_block_transaction_operations_sequential_indices(self, client, network):
373+
"""Operations in /block/transaction must have sequential indices."""
374+
# Find transaction
375+
search = client.search_transactions(network=network)
376+
block_tx = search.json()["transactions"][0]
377+
378+
response = client.block_transaction(
379+
network=network,
380+
block_identifier={
381+
"index": block_tx["block_identifier"]["index"],
382+
"hash": block_tx["block_identifier"]["hash"]
383+
},
384+
transaction_identifier={"hash": block_tx["transaction"]["transaction_identifier"]["hash"]}
385+
)
386+
assert response.status_code == 200
387+
388+
assert_operations_sequential(response.json()["transaction"]["operations"])

tests/data-endpoints/test_search_transactions.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import os
77
import pytest
88
import allure
9-
from conftest import get_error_message
9+
from conftest import get_error_message, assert_operations_ordered, assert_operations_sequential
1010

1111

1212
# Network configuration from environment - works on ANY network
@@ -793,3 +793,29 @@ def test_native_asset_filtering_with_policy_id(self, client, network, network_da
793793
f"Transaction must contain asset with policyId {asset['policy_id']} "
794794
f"and symbol {asset['symbol_hex']}. Found assets: {assets_in_tx}"
795795
)
796+
797+
798+
class TestOperationInvariants:
799+
"""Operations must be ordered and sequential in all transactions."""
800+
801+
@allure.feature("Search Transactions")
802+
@allure.story("Operation Invariants")
803+
def test_operations_ordered_by_index(self, client, network):
804+
"""Operations array must be sorted by operation_identifier.index."""
805+
response = client.search_transactions(network=network, limit=10)
806+
assert response.status_code == 200
807+
808+
for block_tx in response.json()["transactions"]:
809+
operations = block_tx["transaction"]["operations"]
810+
assert_operations_ordered(operations)
811+
812+
@allure.feature("Search Transactions")
813+
@allure.story("Operation Invariants")
814+
def test_operations_sequential_indices(self, client, network):
815+
"""Operation indices must be [0, 1, 2, ..., n-1] with no gaps."""
816+
response = client.search_transactions(network=network, limit=10)
817+
assert response.status_code == 200
818+
819+
for block_tx in response.json()["transactions"]:
820+
operations = block_tx["transaction"]["operations"]
821+
assert_operations_sequential(operations)

tests/integration/golden_examples/rosetta_java/construction/parse/complex_transactions/combined_stake_and_drep_delegation.json

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,25 +91,6 @@
9191
"type": "key_hash"
9292
}
9393
}
94-
},
95-
{
96-
"operation_identifier": {
97-
"index": 3
98-
},
99-
"type": "dRepVoteDelegation",
100-
"account": {
101-
"address": "stake_test1uql3k2h9kskfma8y53vth7wmptmlw8zxx67cx7c3pwj8sqs6zl0wr"
102-
},
103-
"metadata": {
104-
"staking_credential": {
105-
"hex_bytes": "fdbee86a702c49d23f45ab80e697b93ec48b744d5f413ef59c338c3f7b2d2de8",
106-
"curve_type": "edwards25519"
107-
},
108-
"drep": {
109-
"id": "f72050aa252ccc6bf75747184910c0b9386298167656f935d6b6c26a",
110-
"type": "key_hash"
111-
}
112-
}
11394
}
11495
],
11596
"account_identifier_signers": []

tests/integration/golden_examples/rosetta_java/construction/parse/complex_transactions/drep_delegation_with_native_assets.json

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -144,25 +144,6 @@
144144
"type": "script_hash"
145145
}
146146
}
147-
},
148-
{
149-
"operation_identifier": {
150-
"index": 3
151-
},
152-
"type": "dRepVoteDelegation",
153-
"account": {
154-
"address": "stake_test1uql3k2h9kskfma8y53vth7wmptmlw8zxx67cx7c3pwj8sqs6zl0wr"
155-
},
156-
"metadata": {
157-
"staking_credential": {
158-
"hex_bytes": "fdbee86a702c49d23f45ab80e697b93ec48b744d5f413ef59c338c3f7b2d2de8",
159-
"curve_type": "edwards25519"
160-
},
161-
"drep": {
162-
"id": "2d4cb680b5f400d3521d272b4295d61150e0eff3950ef4285406a953",
163-
"type": "script_hash"
164-
}
165-
}
166147
}
167148
],
168149
"account_identifier_signers": []

tests/integration/golden_examples/rosetta_java/construction/parse/complex_transactions/drep_delegation_with_stake_key_registration.json

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,24 +89,6 @@
8989
"type": "abstain"
9090
}
9191
}
92-
},
93-
{
94-
"operation_identifier": {
95-
"index": 3
96-
},
97-
"type": "dRepVoteDelegation",
98-
"account": {
99-
"address": "stake_test1uql3k2h9kskfma8y53vth7wmptmlw8zxx67cx7c3pwj8sqs6zl0wr"
100-
},
101-
"metadata": {
102-
"staking_credential": {
103-
"hex_bytes": "fdbee86a702c49d23f45ab80e697b93ec48b744d5f413ef59c338c3f7b2d2de8",
104-
"curve_type": "edwards25519"
105-
},
106-
"drep": {
107-
"type": "abstain"
108-
}
109-
}
11092
}
11193
],
11294
"account_identifier_signers": []

tests/integration/golden_examples/rosetta_java/construction/parse/complex_transactions/withdrawal_with_drep_delegation.json

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,25 +55,6 @@
5555
}
5656
}
5757
},
58-
{
59-
"operation_identifier": {
60-
"index": 3
61-
},
62-
"type": "dRepVoteDelegation",
63-
"status": "",
64-
"account": {
65-
"address": "stake_test1uql3k2h9kskfma8y53vth7wmptmlw8zxx67cx7c3pwj8sqs6zl0wr"
66-
},
67-
"metadata": {
68-
"staking_credential": {
69-
"hex_bytes": "fdbee86a702c49d23f45ab80e697b93ec48b744d5f413ef59c338c3f7b2d2de8",
70-
"curve_type": "edwards25519"
71-
},
72-
"drep": {
73-
"type": "no_confidence"
74-
}
75-
}
76-
},
7758
{
7859
"operation_identifier": {
7960
"index": 2
@@ -102,6 +83,7 @@
10283
"index": 3
10384
},
10485
"type": "dRepVoteDelegation",
86+
"status": "",
10587
"account": {
10688
"address": "stake_test1uql3k2h9kskfma8y53vth7wmptmlw8zxx67cx7c3pwj8sqs6zl0wr"
10789
},

0 commit comments

Comments
 (0)