Newer
Older
navi-1 / tests / unit / store / test_kv_store.py
"""Unit tests for navi.store.KvStore."""

import pytest

from navi.store import KvStore
from tests.conftest_factory import FakeConnection, FakePool, FakeRecord


class TestKvStore:
    @pytest.fixture
    def store(self):
        s = KvStore(pool=FakePool())
        s._initialized = True
        return s

    @pytest.mark.asyncio
    async def test_get_missing(self, store):
        store._pool._conn.enqueue(None)
        result = await store.get(None, "sess1", "todo", "task1")
        assert result is None

    @pytest.mark.asyncio
    async def test_set_and_get(self, store):
        store._pool._conn.enqueue("OK")
        await store.set(None, "sess1", "todo", "task1", "value1")

        store._pool._conn.enqueue(FakeRecord(value="value1"))
        result = await store.get(None, "sess1", "todo", "task1")
        assert result == "value1"

    @pytest.mark.asyncio
    async def test_set_overwrites(self, store):
        store._pool._conn.enqueue("OK")
        await store.set(None, "sess1", "todo", "key", "first")

        store._pool._conn.enqueue("OK")
        await store.set(None, "sess1", "todo", "key", "second")

        store._pool._conn.enqueue(FakeRecord(value="second"))
        result = await store.get(None, "sess1", "todo", "key")
        assert result == "second"

    @pytest.mark.asyncio
    async def test_get_all(self, store):
        store._pool._conn.enqueue([
            FakeRecord(key="a", value="1"),
            FakeRecord(key="b", value="2"),
        ])
        result = await store.get_all(None, "sess1", "todo")
        assert result == {"a": "1", "b": "2"}

    @pytest.mark.asyncio
    async def test_delete(self, store):
        store._pool._conn.enqueue("OK")
        await store.set(None, "sess1", "todo", "key", "val")

        store._pool._conn.enqueue("OK")
        await store.delete(None, "sess1", "todo", "key")

        store._pool._conn.enqueue(None)
        result = await store.get(None, "sess1", "todo", "key")
        assert result is None

    @pytest.mark.asyncio
    async def test_clear_scope(self, store):
        store._pool._conn.enqueue("OK")
        await store.set(None, "sess1", "todo", "key1", "val1")
        store._pool._conn.enqueue("OK")
        await store.set(None, "sess1", "todo", "key2", "val2")

        store._pool._conn.enqueue("OK")
        await store.clear_scope(None, "sess1", "todo")

        store._pool._conn.enqueue([])
        result = await store.get_all(None, "sess1", "todo")
        assert result == {}

    @pytest.mark.asyncio
    async def test_null_user_id_normalized(self, store):
        """None user_id is normalized to '' before queries — prevents duplicate
        rows because PostgreSQL treats NULL as distinct in unique constraints."""
        store._pool._conn.enqueue("OK")
        await store.set(None, "sess1", "todo", "key", "val")

        # Verify the query used '' not NULL
        calls = store._pool._conn.calls
        execute_call = [c for c in calls if c[0] == "execute"][0]
        assert execute_call[2][0] == ""  # first arg is user_id = ''

    @pytest.mark.asyncio
    async def test_different_sessions_isolated(self, store):
        store._pool._conn.enqueue("OK")
        await store.set(None, "sess1", "todo", "key", "val1")

        store._pool._conn.enqueue("OK")
        await store.set(None, "sess2", "todo", "key", "val2")

        store._pool._conn.enqueue(FakeRecord(value="val1"))
        result1 = await store.get(None, "sess1", "todo", "key")

        store._pool._conn.enqueue(FakeRecord(value="val2"))
        result2 = await store.get(None, "sess2", "todo", "key")

        assert result1 == "val1"
        assert result2 == "val2"