Skip to main content

Testing SearchQuery Across TypeScript, Python, and REST

A SearchQuery written in TypeScript must return the same records as the same query written in Python or issued as a raw REST call. This is called query parity. Parity tests catch SDK bugs, version drift, and silent behavioral differences before they reach production.

This tutorial shows how to structure parity-driven tests, common failure modes, and a repeatable test harness you can adapt to any record type.


The parity contract

Every records.find() call maps to an underlying POST /records/search. The contract is:

Given the same SearchQuery body, every SDK surface and the REST API must return the same data[] and total.

The three variables that can break parity:

  1. Serialization — an SDK may serialize a field name differently than the REST contract expects
  2. Default values — an SDK may silently inject defaults (e.g. limit: 20) that the raw query did not include
  3. Case sensitivity — label names, relationship types, and field names are case-sensitive

Step 1: Set up a shared test fixture

All parity tests should run against live data created in a dedicated test project.

# tests/fixtures/setup.py
from rushdb import RushDB
import os

db = RushDB(os.environ["RUSHDB_TEST_API_KEY"], base_url="https://api.rushdb.com/api/v1")

def seed_test_data():
return db.records.import_json({
"label": "PRODUCT",
"data": [
{"sku": "P-001", "name": "Widget Alpha", "category": "widgets", "price": 29.99, "inStock": True},
{"sku": "P-002", "name": "Widget Beta", "category": "widgets", "price": 49.99, "inStock": False},
{"sku": "P-003", "name": "Gadget One", "category": "gadgets", "price": 99.00, "inStock": True}
]
})

def teardown_test_data():
db.records.delete_many({
"labels": ["PRODUCT"],
"where": {"sku": {"$in": ["P-001", "P-002", "P-003"]}}
})

Step 2: Write a parity test

Compare results from the TypeScript SDK against a raw REST call to verify the same payload produces the same results.

# tests/parity/test_product_search.py
import pytest
import requests
import os
from tests.fixtures.setup import db, seed_test_data, teardown_test_data

QUERY = {
"labels": ["PRODUCT"],
"where": {"category": "widgets", "inStock": True},
"orderBy": {"price": "asc"}
}

@pytest.fixture(scope="module", autouse=True)
def test_data():
seed_test_data()
yield
teardown_test_data()

def test_sdk_returns_in_stock_widgets():
result = db.records.find(QUERY)
assert result.total == 1
assert result.data[0]["sku"] == "P-001"

def test_rest_returns_same_result():
resp = requests.post(
"https://api.rushdb.com/api/v1/records/search",
json=QUERY,
headers={"Authorization": f"Bearer {os.environ['RUSHDB_TEST_API_KEY']}"}
)
body = resp.json()
assert body["total"] == 1
assert body["data"][0]["sku"] == "P-001"

def test_sdk_and_rest_return_same_id():
sdk_result = db.records.find(QUERY)
rest_result = requests.post(
"https://api.rushdb.com/api/v1/records/search",
json=QUERY,
headers={"Authorization": f"Bearer {os.environ['RUSHDB_TEST_API_KEY']}"}
).json()
assert sdk_result.data[0].id == rest_result["data"][0]["__id"]

Step 3: Test select expression parity

select queries are the most likely to diverge between SDK versions. Test them explicitly.

def test_select_count_by_category():
result = db.records.find({
"labels": ["PRODUCT"],
"select": {
"count": {"$count": "*"},
"category": "$record.category"
},
"groupBy": ["category", "count"],
"orderBy": {"count": "desc"}
})
assert result.data[0]["category"] == "widgets"
assert result.data[0]["count"] == 2

Step 4: Test transaction parity

Verify that transactional writes and rollbacks behave identically across surfaces.

def test_rollback_does_not_persist():
tx = db.transactions.begin()
try:
db.records.create("PRODUCT", {"sku": "ROLLBACK-TEST", "category": "test"}, transaction=tx)
db.transactions.rollback(tx)
except Exception:
db.transactions.rollback(tx)

result = db.records.find({"labels": ["PRODUCT"], "where": {"sku": "ROLLBACK-TEST"}})
assert result.total == 0

Common parity failure modes

SymptomLikely cause
SDK returns 0, REST returns NLabel name casing mismatch (article vs ARTICLE)
REST returns 0, SDK returns NSDK is injecting a default where clause
Results differ between SDK versionsSDK updated default limit or sort order
total correct but data[] differsorderBy field not specified — sort is non-deterministic
Python returns float, REST returns intPython SDK deserializing numeric field differently
Transaction test flakyTwo tests sharing the same natural key collide across runs

Next steps