Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m19s
202 lines
6.8 KiB
Python
202 lines
6.8 KiB
Python
import os
|
|
import pytest
|
|
from tinydb import Query
|
|
|
|
from db.db import (
|
|
MongoLockedTable,
|
|
_query_to_mongo_filter,
|
|
child_db,
|
|
task_db,
|
|
users_db,
|
|
refresh_tokens_db,
|
|
)
|
|
from db.mongo_client import get_mongo_client, get_mongo_db_name
|
|
|
|
|
|
# All tests in this module require the mongomock-backed MongoDB adapter.
|
|
pytestmark = pytest.mark.skipif(
|
|
os.environ.get('USE_MONGODB', 'true').lower() != 'true',
|
|
reason='MongoDB adapter tests require USE_MONGODB=true',
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clean_mongo_collections():
|
|
"""Truncate relevant collections before each test."""
|
|
child_db.truncate()
|
|
task_db.truncate()
|
|
users_db.truncate()
|
|
refresh_tokens_db.truncate()
|
|
yield
|
|
|
|
|
|
class TestQueryTranslation:
|
|
"""Unit tests for TinyDB Query -> MongoDB filter translation."""
|
|
|
|
def test_simple_equality(self):
|
|
q = Query()
|
|
assert _query_to_mongo_filter(q.id == 'abc') == {'_id': {'$eq': 'abc'}}
|
|
|
|
def test_field_other_than_id(self):
|
|
q = Query()
|
|
assert _query_to_mongo_filter(q.user_id == 'u1') == {
|
|
'user_id': {'$eq': 'u1'}
|
|
}
|
|
|
|
def test_and_query(self):
|
|
q = Query()
|
|
mongo_filter = _query_to_mongo_filter(
|
|
(q.id == 'abc') & (q.user_id == 'u1')
|
|
)
|
|
assert mongo_filter == {'_id': {'$eq': 'abc'}, 'user_id': {'$eq': 'u1'}}
|
|
|
|
def test_or_query(self):
|
|
q = Query()
|
|
mongo_filter = _query_to_mongo_filter(
|
|
(q.user_id == 'u1') | (q.user_id == None) # noqa: E711
|
|
)
|
|
assert mongo_filter == {
|
|
'$or': [
|
|
{'user_id': {'$eq': 'u1'}},
|
|
{'user_id': {'$eq': None}},
|
|
]
|
|
}
|
|
|
|
def test_and_with_nested_or(self):
|
|
q = Query()
|
|
mongo_filter = _query_to_mongo_filter(
|
|
(q.id == 'abc') & ((q.user_id == 'u1') | (q.user_id == None)) # noqa: E711
|
|
)
|
|
assert mongo_filter == {
|
|
'_id': {'$eq': 'abc'},
|
|
'$or': [
|
|
{'user_id': {'$eq': 'u1'}},
|
|
{'user_id': {'$eq': None}},
|
|
],
|
|
}
|
|
|
|
|
|
class TestMongoCrud:
|
|
"""CRUD tests against the mongomock-backed MongoLockedTable."""
|
|
|
|
def test_insert_maps_id_to_underscore_id(self):
|
|
child_db.insert({'id': 'c1', 'name': 'Alice', 'age': 8})
|
|
|
|
raw = get_mongo_client()[get_mongo_db_name()]['children'].find_one(
|
|
{'_id': 'c1'}
|
|
)
|
|
assert raw is not None
|
|
assert raw['_id'] == 'c1'
|
|
assert 'id' not in raw
|
|
assert raw['name'] == 'Alice'
|
|
|
|
def test_get_returns_document_without_underscore_id(self):
|
|
child_db.insert({'id': 'c1', 'name': 'Alice', 'age': 8})
|
|
|
|
doc = child_db.get(Query().id == 'c1')
|
|
assert doc is not None
|
|
assert doc['id'] == 'c1'
|
|
assert doc['name'] == 'Alice'
|
|
assert '_id' not in doc
|
|
|
|
def test_get_none_when_missing(self):
|
|
assert child_db.get(Query().id == 'missing') is None
|
|
|
|
def test_search_with_query(self):
|
|
child_db.insert({'id': 'c1', 'name': 'Alice', 'user_id': 'u1'})
|
|
child_db.insert({'id': 'c2', 'name': 'Bob', 'user_id': 'u1'})
|
|
child_db.insert({'id': 'c3', 'name': 'Carol', 'user_id': 'u2'})
|
|
|
|
results = child_db.search(Query().user_id == 'u1')
|
|
assert len(results) == 2
|
|
assert {r['id'] for r in results} == {'c1', 'c2'}
|
|
|
|
def test_search_with_or(self):
|
|
task_db.insert({'id': 't1', 'name': 'Default', 'user_id': None})
|
|
task_db.insert({'id': 't2', 'name': 'User task', 'user_id': 'u1'})
|
|
|
|
q = Query()
|
|
results = task_db.search((q.user_id == 'u1') | (q.user_id == None)) # noqa: E711
|
|
assert len(results) == 2
|
|
|
|
def test_update_modifies_matching_documents(self):
|
|
child_db.insert({'id': 'c1', 'name': 'Alice', 'points': 0})
|
|
child_db.insert({'id': 'c2', 'name': 'Bob', 'points': 0})
|
|
|
|
modified = child_db.update({'points': 10}, Query().id == 'c1')
|
|
# TinyDB returns a list of updated document ids; the adapter mirrors that.
|
|
assert modified == ['c1']
|
|
|
|
doc = child_db.get(Query().id == 'c1')
|
|
assert doc['points'] == 10
|
|
|
|
other = child_db.get(Query().id == 'c2')
|
|
assert other['points'] == 0
|
|
|
|
def test_update_does_not_overwrite_id(self):
|
|
child_db.insert({'id': 'c1', 'name': 'Alice'})
|
|
|
|
child_db.update({'id': 'c2', 'name': 'Alice Smith'}, Query().id == 'c1')
|
|
# The id field must remain unchanged; update should have stripped id.
|
|
assert child_db.get(Query().id == 'c1')['name'] == 'Alice Smith'
|
|
assert child_db.get(Query().id == 'c2') is None
|
|
|
|
def test_remove_deletes_matching_documents(self):
|
|
child_db.insert({'id': 'c1', 'name': 'Alice'})
|
|
child_db.insert({'id': 'c2', 'name': 'Bob'})
|
|
|
|
deleted = child_db.remove(Query().id == 'c1')
|
|
# TinyDB returns a list of removed document ids; the adapter mirrors that.
|
|
assert deleted == ['c1']
|
|
|
|
assert child_db.get(Query().id == 'c1') is None
|
|
assert child_db.get(Query().id == 'c2') is not None
|
|
|
|
def test_all_returns_all_documents(self):
|
|
child_db.insert({'id': 'c1', 'name': 'Alice'})
|
|
child_db.insert({'id': 'c2', 'name': 'Bob'})
|
|
|
|
docs = child_db.all()
|
|
assert len(docs) == 2
|
|
assert all('_id' not in d for d in docs)
|
|
|
|
def test_truncate_removes_all_documents(self):
|
|
child_db.insert({'id': 'c1', 'name': 'Alice'})
|
|
child_db.truncate()
|
|
assert child_db.all() == []
|
|
|
|
def test_insert_multiple(self):
|
|
ids = child_db.insert_multiple([
|
|
{'id': 'c1', 'name': 'Alice'},
|
|
{'id': 'c2', 'name': 'Bob'},
|
|
])
|
|
assert sorted(ids) == ['c1', 'c2']
|
|
assert len(child_db.all()) == 2
|
|
|
|
def test_unique_token_index(self):
|
|
refresh_tokens_db.insert({'id': 'r1', 'token': 'abc', 'user_id': 'u1'})
|
|
refresh_tokens_db.insert({'id': 'r2', 'token': 'def', 'user_id': 'u1'})
|
|
|
|
# mongomock does not enforce unique indexes by default, but we verify
|
|
# both records are readable.
|
|
assert refresh_tokens_db.get(Query().token == 'abc') is not None
|
|
assert refresh_tokens_db.get(Query().token == 'def') is not None
|
|
|
|
def test_user_id_secondary_index_is_created(self):
|
|
# Insert and query via the secondary index path used by the app.
|
|
users_db.insert({'id': 'u1', 'email': 'a@example.com', 'verified': True})
|
|
users_db.insert({'id': 'u2', 'email': 'b@example.com', 'verified': False})
|
|
|
|
found = users_db.search(Query().verified == True) # noqa: E712
|
|
assert len(found) == 1
|
|
assert found[0]['id'] == 'u1'
|
|
|
|
|
|
class TestAdapterApi:
|
|
"""Tests that the adapter exposes the expected LockedTable-compatible API."""
|
|
|
|
def test_close_is_noop(self):
|
|
# Existing cleanup fixtures call ``*_db.close()``; ensure it does not
|
|
# raise for the MongoDB-backed adapter.
|
|
child_db.close()
|