michaelkryukov / mongomock_motor

Library for mocking AsyncIOMotorClient built on top of mongomock.
MIT License
102 stars 24 forks source link

Beanie Query Syntax Not Recognized #17

Closed liamptiernan closed 2 years ago

liamptiernan commented 2 years ago

The Beanie .find(), .find_many() and .find_one() methods, when used with the Beanie search criteria syntax always return every document, regardless of the search criteria.

Using your test as an example and adjusting slightly, if we create two documents and then make a query that should only return one, we still get both documents returned. Example below:

@pytest.mark.anyio
async def test_beanie():
    client = AsyncMongoMockClient('mongodb://user:pass@host:27017', connectTimeoutMS=250)

    await init_beanie(database=client.beanie_test, document_models=[Product])

    chocolate = Category(name='Chocolate', description='A preparation of roasted and ground cacao seeds.')
    tonybar = Product(name="Tony's", price=5.95, category=chocolate)
    newbar = Product(name="Test", price=5.95, category=chocolate)
    await tonybar.insert()
    await newbar.insert()

    find_many = Product.find_many(Product.name == "Test")
    assert find_many.motor_cursor
    assert await find_many.count() == 1 # This assert fails: 2 != 1

Additionally, you can see this happening in your test as it stands because of a typo. Line 37, there's a typo; "Chocolade" instead of "Chocolate". This query should return no documents, but your asserts on 38 and 39 still pass because the beanie syntax is essentially ignored.

I've found that these methods still work with pymongo syntax. For instance, modifying the search in my example above:

@pytest.mark.anyio
async def test_beanie():
    client = AsyncMongoMockClient('mongodb://user:pass@host:27017', connectTimeoutMS=250)

    await init_beanie(database=client.beanie_test, document_models=[Product])

    chocolate = Category(name='Chocolate', description='A preparation of roasted and ground cacao seeds.')
    tonybar = Product(name="Tony's", price=5.95, category=chocolate)
    newbar = Product(name="Test", price=5.95, category=chocolate)
    await tonybar.insert()
    await newbar.insert()

    find_many = Product.find_many({"name": "Test"}) # using pymongo syntax here
    assert find_many.motor_cursor
    assert await find_many.count() == 1 # This now works with the pymongo syntax.

This test will pass because it uses pymongo syntax.

In short, it seems that the native beanie search syntax is essentially omitted and all documents are always returned in the case of .find() and the first document is always returned in the case of .find_one(). I may try and poke around a bit to see if I can work something out myself. Let me know your thoughts!

michaelkryukov commented 2 years ago

I looked into the issue and found out that beanie returns not str in keys, but class inherited by str. Looks like mongo can work with that, but mongomock can't. I've implemented normalization of ExpressionField to str (and updated the test), and now everything works. I'm not 100% sure that's safe, but I don't really see downsides at the moment