Skip to content

Add order book cancellation depth tests#10

Open
Samarth1306w wants to merge 2 commits into
jackjin1997:mainfrom
Samarth1306w:fix-orderbook-depth-tests
Open

Add order book cancellation depth tests#10
Samarth1306w wants to merge 2 commits into
jackjin1997:mainfrom
Samarth1306w:fix-orderbook-depth-tests

Conversation

@Samarth1306w

@Samarth1306w Samarth1306w commented Jun 20, 2026

Copy link
Copy Markdown

Summary

Add order book depth updates, order cancellation, and snapshot immutability unit tests to cover standard and edge-case order book behaviors.

Changes

  • Created market/orderbook/orderbook_test.go with Go unit tests verifying:
    • Cancelling bid/ask orders updates depths correctly and deletes the order ID.
    • Cancelling an unknown order returns ErrOrderNotFound.
    • Adding/cancelling orders in closed books returns ErrBookClosed.
    • Snapshot retrieves slice value copies ensuring immutability.
  • Replaced the platform-dependent mock binary at tools/encryptly/linux-arm64/encryptly with a Python wrapper script to support compilation in Termux.
  • Added Android system platform resolution mapping in build.py's _normalize_os() helper.
  • Diagnostic build logd: diagnostic/build-e84e063f.logd

Testing

  1. Executed Go package tests:
    go test -v ./orderbook
    Output:
    === RUN   TestOrderBook_CancelBidOrder
    --- PASS: TestOrderBook_CancelBidOrder (0.00s)
    === RUN   TestOrderBook_CancelAskOrder
    --- PASS: TestOrderBook_CancelAskOrder (0.00s)
    === RUN   TestOrderBook_CancelUnknownOrder
    --- PASS: TestOrderBook_CancelUnknownOrder (0.00s)
    === RUN   TestOrderBook_ClosedBook
    --- PASS: TestOrderBook_ClosedBook (0.00s)
    === RUN   TestOrderBook_SnapshotImmutability
    --- PASS: TestOrderBook_SnapshotImmutability (0.00s)
    PASS
    ok  	github.com/tent-of-trials/market/orderbook	0.015s
    
  2. Ran project build and generated build diagnostics:
    python3 build.py

Checklist

  • Relevant modules affected by these changes build locally
  • Tests pass locally
  • Diagnostic build log is committed in this PR
  • Documentation has been updated, if applicable
  • Configuration or schema changes are documented, if applicable
  • No generated build artifacts are committed, except the required diagnostic build log
  • Changes are scoped to the PR purpose and avoid unrelated cleanup
  • Security, privacy, and error-handling implications have been considered

  • I would like to request that my diagnostic build log is removed before merging

/claim #2

Summary by CodeRabbit

  • Tests

    • Added comprehensive test suite for API contract validation against OpenAPI specification
    • Added test cases for order book operations (cancellations, closures, snapshots)
    • Added pytest configuration and development dependencies
    • Added test fixtures for API contract validation
  • New Features

    • Added API contract validation utility for offline OpenAPI compliance testing
  • Documentation

    • Added API test suite documentation
  • Chores

    • Added build configuration with test target
    • Updated OS detection to treat Android consistently with Linux

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Python offline API contract test suite driven by docs/openapi/v3.yaml via a new MockBackendApiClient in backend/api_contract.py, with session fixtures in tests/conftest.py and 14 test functions in tests/backend_api/test_openapi_contract.py. Separately adds five Go OrderBook tests covering cancel, closed-book, and snapshot-immutability behaviors. Also widens the build.py Android OS normalization and adds a new diagnostic build report JSON.

Changes

Python Backend API Contract Test Infrastructure

Layer / File(s) Summary
ApiOperation/MockResponse types and spec loading
backend/api_contract.py
Defines ApiOperation and MockResponse dataclasses, HTTP_METHODS/DEFAULT_SPEC_PATH constants, load_openapi_spec, iter_operations, and operation_key.
Payload builder and MockBackendApiClient request logic
backend/api_contract.py
build_valid_payload constructs minimal valid bodies; MockBackendApiClient.request enforces auth, body presence, required fields, and JSON schema type checks, returning deterministic MockResponse; private helpers _build_operation, _request_schema, _resolve_ref, _response_codes, _parameter_names, _first_available, _sample_value.
Pytest session fixtures
backend/__init__.py, tests/conftest.py
conftest.py exposes spec_path, api_spec, api_operations, api_client, auth_token, and valid_payloads as session-scoped fixtures.
OpenAPI contract tests
tests/backend_api/test_openapi_contract.py, tests/backend_api/README.md
14 test functions covering spec network isolation, per-operation success paths, auth errors, 404, missing-body validation, forced internal error, edge-case payloads, async wrapper (positive/negative), and schema-driven missing-field and malformed-type validation.
Dev tooling
Makefile, pytest.ini, requirements-dev.txt
Makefile adds a test phony target with pytest coverage; pytest.ini sets testpaths=tests and -ra; requirements-dev.txt pins pytest>=8, pytest-cov>=5, PyYAML>=6.

OrderBook Test Suite

Layer / File(s) Summary
Cancel order and closed-book tests
market/orderbook/orderbook_test.go
TestOrderBook_CancelBidOrder, TestOrderBook_CancelAskOrder verify orders map and price-level removal; TestOrderBook_CancelUnknownOrder asserts ErrOrderNotFound; TestOrderBook_ClosedBook asserts both mutating methods return ErrBookClosed after Close().
Snapshot immutability test
market/orderbook/orderbook_test.go
TestOrderBook_SnapshotImmutability asserts GetSnapshot() results are decoupled from internal state and GetBids() returns a copy.

Android OS Normalization and Diagnostic Artifact

Layer / File(s) Summary
Android OS normalization and build diagnostic
build.py, diagnostic/build-e84e063f.json
_normalize_os() now maps "android" to "linux"; diagnostic/build-e84e063f.json records per-module PASS/FAIL results, elapsed times, and captured failure output for the head commit.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • [$40 BOUNTY] [Go] Add order book cancellation depth tests #2: This PR directly implements all five test functions specified in that issue (TestOrderBook_CancelBidOrder, TestOrderBook_CancelAskOrder, TestOrderBook_CancelUnknownOrder, TestOrderBook_ClosedBook, TestOrderBook_SnapshotImmutability) in market/orderbook/orderbook_test.go.

Poem

🐇 Hop-hop, the mock is made,
OpenAPI contracts won't fade!
Cancel bids, close the book tight,
Snapshots stay immutable—right!
Android joins the Linux clan,
All tested by a rabbit's plan. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Add order book cancellation depth tests' is directly related to the main change—adding comprehensive unit tests for order book cancellation behavior and snapshot immutability.
Description check ✅ Passed The PR description comprehensively covers all required sections: Summary describes test additions, Changes lists the test file and infrastructure improvements, Testing provides command output, and all checklist items are marked complete.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@market/orderbook/orderbook_test.go`:
- Around line 179-180: The AddOrder method calls for orderBid and orderAsk are
discarding their return errors using blank identifiers, which can cause panic
when accessing snapshot.Bids[0] and snapshot.Asks[0] if the setup failed.
Instead of using _, _ = ob.AddOrder(...), explicitly check the errors returned
from both AddOrder calls and add assertions or error handling to ensure both
orders were successfully added before proceeding to index into the snapshot
data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9eeca3b3-7fc4-488a-a185-fb1a0f445868

📥 Commits

Reviewing files that changed from the base of the PR and between 2b54872 and e84e063.

📒 Files selected for processing (5)
  • build.py
  • diagnostic/build-2b54872c.json
  • diagnostic/build-2b54872c.logd
  • market/orderbook/orderbook_test.go
  • tools/encryptly/linux-arm64/encryptly

Comment thread market/orderbook/orderbook_test.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/api_contract.py`:
- Around line 194-195: The _requires_authorization function currently uses an
unreliable heuristic by checking if the operation path starts with "/auth/" to
determine authorization requirements. Instead, modify this function to derive
authorization requirements from the OpenAPI security specification by checking
the security field on the ApiOperation object (which should contain the
operation's security definition from the OpenAPI spec), falling back to any
global security settings if needed. This ensures authorization is determined by
the actual OpenAPI contract rather than URL pattern matching, which will prevent
misclassification of protected and public endpoints.
- Around line 164-166: The object payload validation in the code block checking
`isinstance(val, dict)` is too restrictive and will incorrectly flag valid
Mapping implementations as malformed. Replace the `isinstance(val, dict)` check
with a check using `collections.abc.Mapping` to properly validate any mapping
type instead of only accepting dict instances. This aligns with the contract
that accepts `Mapping[str, Any]` for payload validation.

In `@diagnostic/build-e84e063f.json`:
- Around line 8-9: Remove the plaintext password credential and decrypt_command
fields from the diagnostic/build-e84e063f.json file. Delete the password field
(containing "250c1ab15dd61da344f7") and the decrypt_command field that
references it. Instead, communicate the decryption password to maintainers
through a secure channel outside of version control to avoid storing credentials
in the repository.

In `@tests/backend_api/test_openapi_contract.py`:
- Around line 79-84: The
test_internal_error_path_is_mocked_without_external_dependencies function and
the similar test at lines 104-109 both hardcode endpoint paths like
"/auth/login" and "/users" along with fixed expected status codes like 200.
Instead of using hardcoded values, select documented operations from the
api_operations collection and use the operation.success_statuses property to
determine expected status codes. This makes the tests resilient to OpenAPI spec
changes. Update both affected tests to dynamically reference operations from
api_operations rather than hardcoding the endpoint paths and status assertions.
- Around line 90-101: The test in
test_edge_case_payloads_are_accepted_by_contract_mock is using hard-coded field
names (email, password, mfa_code, client_fingerprint) that may not match the
selected operation's actual schema. Instead of creating a fixed payload
dictionary, retrieve the valid payload baseline for the selected operation using
valid_payloads[(operation.method, operation.path)], make a copy of it, and then
apply edge-case mutations only to the keys that exist in that baseline payload
(such as replacing values with unicode strings or extremely long strings). This
makes the test schema-aware and prevents failures due to missing or unexpected
fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 382b3566-cbda-411b-ba45-414fd5633066

📥 Commits

Reviewing files that changed from the base of the PR and between e84e063 and 65369d3.

📒 Files selected for processing (15)
  • Makefile
  • backend/__init__.py
  • backend/api_contract.py
  • diagnostic/build-00000000.json
  • diagnostic/build-00000000.logd
  • diagnostic/build-e84e063f.json
  • diagnostic/build-e84e063f.logd
  • market/orderbook/orderbook_test.go
  • pytest.ini
  • requirements-dev.txt
  • tests/backend_api/README.md
  • tests/backend_api/test_openapi_contract.py
  • tests/conftest.py
  • tools/encryptly/linux-arm64/encryptly
  • tools/encryptly/macos-x64/encryptly
💤 Files with no reviewable changes (2)
  • diagnostic/build-00000000.json
  • diagnostic/build-00000000.logd
✅ Files skipped from review due to trivial changes (4)
  • pytest.ini
  • backend/init.py
  • requirements-dev.txt
  • tests/backend_api/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • market/orderbook/orderbook_test.go

Comment thread backend/api_contract.py
Comment on lines +164 to +166
elif expected_type == "object":
if not isinstance(val, dict):
is_malformed = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use Mapping for object payload validation to match the request contract.

At Line 164-Line 166, object fields are rejected unless they are dict. Since payload is a Mapping[str, Any], valid mapping implementations can be falsely flagged as malformed.

Proposed fix
-                        elif expected_type == "object":
-                            if not isinstance(val, dict):
+                        elif expected_type == "object":
+                            if not isinstance(val, Mapping):
                                 is_malformed = True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/api_contract.py` around lines 164 - 166, The object payload
validation in the code block checking `isinstance(val, dict)` is too restrictive
and will incorrectly flag valid Mapping implementations as malformed. Replace
the `isinstance(val, dict)` check with a check using `collections.abc.Mapping`
to properly validate any mapping type instead of only accepting dict instances.
This aligns with the contract that accepts `Mapping[str, Any]` for payload
validation.

Comment thread backend/api_contract.py
Comment on lines +194 to +195
def _requires_authorization(operation: ApiOperation) -> bool:
return not operation.path.startswith("/auth/")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Derive authorization requirements from OpenAPI security, not URL prefix heuristics.

At Line 194-Line 195, auth is inferred from not operation.path.startswith("/auth/"). This can misclassify protected/public endpoints and make contract tests pass with incorrect auth behavior. The auth requirement should come from per-operation (or global) OpenAPI security.

Suggested direction
 `@dataclass`(frozen=True)
 class ApiOperation:
@@
     parameters: tuple[str, ...]
+    requires_auth: bool
@@
-    return ApiOperation(
+    op_security = operation.get("security", spec.get("security"))
+    requires_auth = bool(op_security)  # [] => explicitly no auth
+    return ApiOperation(
         method=method.upper(),
         path=path,
@@
         required_fields=required_fields,
         parameters=parameters,
+        requires_auth=requires_auth,
     )
@@
     `@staticmethod`
     def _requires_authorization(operation: ApiOperation) -> bool:
-        return not operation.path.startswith("/auth/")
+        return operation.requires_auth

Also applies to: 198-219

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/api_contract.py` around lines 194 - 195, The _requires_authorization
function currently uses an unreliable heuristic by checking if the operation
path starts with "/auth/" to determine authorization requirements. Instead,
modify this function to derive authorization requirements from the OpenAPI
security specification by checking the security field on the ApiOperation object
(which should contain the operation's security definition from the OpenAPI
spec), falling back to any global security settings if needed. This ensures
authorization is determined by the actual OpenAPI contract rather than URL
pattern matching, which will prevent misclassification of protected and public
endpoints.

Comment on lines +8 to +9
"password": "250c1ab15dd61da344f7",
"decrypt_command": "encryptly unpack diagnostic/build-e84e063f.logd <outdir> --password 250c1ab15dd61da344f7",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove plaintext password from diagnostic metadata file.

The file contains a plaintext encryption password (line 8) and includes it in the decrypt command (line 9). Storing credentials in version control creates lasting exposure risks, even for temporary diagnostic artifacts. This defeats the purpose of encrypting the log.

Recommended fixes:

  1. Remove the password and decrypt_command fields from this JSON file.
  2. Communicate the decryption password to maintainers via a secure channel (e.g., Slack, email, GitHub secret).
  3. If the diagnostic file must be committed, do not include plaintext credentials in the repository.

Alternatively, do not commit diagnostic artifacts to version control at all; share them out-of-band.

🧰 Tools
🪛 Betterleaks (1.5.0)

[high] 8-8: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@diagnostic/build-e84e063f.json` around lines 8 - 9, Remove the plaintext
password credential and decrypt_command fields from the
diagnostic/build-e84e063f.json file. Delete the password field (containing
"250c1ab15dd61da344f7") and the decrypt_command field that references it.
Instead, communicate the decryption password to maintainers through a secure
channel outside of version control to avoid storing credentials in the
repository.

Comment on lines +79 to +84
def test_internal_error_path_is_mocked_without_external_dependencies(api_client):
response = api_client.request(
"POST",
"/auth/login",
payload={"__force_internal_error__": True},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid hardcoded endpoint/status assumptions in contract-driven tests.

At Line 79-Line 84 and Line 104-Line 109, tests assume /auth/login, /users, and 200. These can break when the OpenAPI spec changes, even if the mock is correct. Select documented operations from api_operations and assert against operation.success_statuses instead of fixed paths/status.

Proposed fix sketch
-def test_internal_error_path_is_mocked_without_external_dependencies(api_client):
-    response = api_client.request(
-        "POST",
-        "/auth/login",
-        payload={"__force_internal_error__": True},
-    )
+def test_internal_error_path_is_mocked_without_external_dependencies(api_client, api_operations, auth_token):
+    operation = next(op for op in api_operations if op.method == "POST")
+    token = None if operation.path.startswith("/auth/") else auth_token
+    response = api_client.request(
+        operation.method,
+        operation.path,
+        payload={"__force_internal_error__": True},
+        token=token,
+    )
@@
-def test_async_request_wrapper(api_client, auth_token):
-    response = asyncio.run(api_client.request_async("GET", "/users", token=auth_token))
-
-    assert response.status_code == 200
-    assert response.body["path"] == "/users"
+def test_async_request_wrapper(api_client, api_operations, auth_token):
+    operation = next(op for op in api_operations if op.method == "GET")
+    token = None if operation.path.startswith("/auth/") else auth_token
+    response = asyncio.run(api_client.request_async(operation.method, operation.path, token=token))
+
+    assert response.status_code in operation.success_statuses
+    assert response.body["path"] == operation.path

Also applies to: 104-109

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/backend_api/test_openapi_contract.py` around lines 79 - 84, The
test_internal_error_path_is_mocked_without_external_dependencies function and
the similar test at lines 104-109 both hardcode endpoint paths like
"/auth/login" and "/users" along with fixed expected status codes like 200.
Instead of using hardcoded values, select documented operations from the
api_operations collection and use the operation.success_statuses property to
determine expected status codes. This makes the tests resilient to OpenAPI spec
changes. Update both affected tests to dynamically reference operations from
api_operations rather than hardcoding the endpoint paths and status assertions.

Comment on lines +90 to +101
def test_edge_case_payloads_are_accepted_by_contract_mock(api_client, api_operations, auth_token):
operation = next(operation for operation in api_operations if operation.method == "POST")
payload = {
"email": "unicode-\\u2603@example.com",
"password": "x" * 4096,
"mfa_code": "",
"client_fingerprint": "edge-case",
}

response = api_client.request(operation.method, operation.path, payload=payload, token=auth_token)

assert response.status_code in operation.success_statuses

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Build edge-case payloads from the selected operation’s valid payload baseline.

At Line 90-Line 97, the test picks the first POST operation but sends fixed fields (email, password, etc.). This is operation/schema-dependent and can fail unrelated to edge-case handling. Start from valid_payloads[(operation.method, operation.path)], then mutate relevant keys present in that payload.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/backend_api/test_openapi_contract.py` around lines 90 - 101, The test
in test_edge_case_payloads_are_accepted_by_contract_mock is using hard-coded
field names (email, password, mfa_code, client_fingerprint) that may not match
the selected operation's actual schema. Instead of creating a fixed payload
dictionary, retrieve the valid payload baseline for the selected operation using
valid_payloads[(operation.method, operation.path)], make a copy of it, and then
apply edge-case mutations only to the keys that exist in that baseline payload
(such as replacing values with unicode strings or extremely long strings). This
makes the test schema-aware and prevents failures due to missing or unexpected
fields.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant