2026-07-15 10:48:18 +03:30
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
|
|
|
|
|
import grpc # noqa: I001
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from daena.api.grpc_server import DaenaGRPCServer
|
|
|
|
|
from daena.api.pb2 import daena_pb2 as pb2
|
|
|
|
|
from daena.api.pb2 import daena_pb2_grpc as pb2_grpc
|
2026-10-14 01:56:37 +03:30
|
|
|
from daena.config import DaenaConfig, PipelineConfig, SinkConfig, SourceConfig
|
2026-07-15 10:48:18 +03:30
|
|
|
from daena.core.runtime import DaenaRuntime
|
2026-10-14 01:56:37 +03:30
|
|
|
from daena.persistence.models import RecordState
|
2026-07-15 10:48:18 +03:30
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
2026-10-14 01:56:37 +03:30
|
|
|
async def grpc_env(tmp_path):
|
2026-07-15 10:48:18 +03:30
|
|
|
config = DaenaConfig()
|
|
|
|
|
config.sources = [
|
|
|
|
|
SourceConfig(name="dummy", type="dummy", options={"interval": 0.2, "message": "test"})
|
|
|
|
|
]
|
|
|
|
|
config.sinks = [SinkConfig(name="log", type="dummy")]
|
2026-10-14 01:56:37 +03:30
|
|
|
config.persistence.lmdb.path = str(tmp_path / "lmdb")
|
|
|
|
|
config.pipeline = PipelineConfig(batch_size=100, flush_interval=5.0, max_queue_size=10_000)
|
2026-07-15 10:48:18 +03:30
|
|
|
config.api.grpc.port = 18642
|
|
|
|
|
|
|
|
|
|
runtime = DaenaRuntime(config)
|
|
|
|
|
await runtime.start()
|
|
|
|
|
|
|
|
|
|
server = DaenaGRPCServer(runtime, host="127.0.0.1", port=18642)
|
|
|
|
|
await server.start()
|
|
|
|
|
|
|
|
|
|
channel = grpc.aio.insecure_channel("127.0.0.1:18642")
|
|
|
|
|
stub = pb2_grpc.DaenaStub(channel)
|
|
|
|
|
|
|
|
|
|
yield runtime, stub, server
|
|
|
|
|
|
|
|
|
|
await channel.close()
|
|
|
|
|
await server.stop()
|
|
|
|
|
await runtime.stop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_get_status(grpc_env) -> None:
|
|
|
|
|
_, stub, _ = grpc_env
|
|
|
|
|
resp = await stub.GetStatus(pb2.Empty(), timeout=5)
|
|
|
|
|
assert resp.service == "daena"
|
2026-10-14 02:00:31 +03:30
|
|
|
assert resp.version == "0.2.0"
|
2026-07-15 10:48:18 +03:30
|
|
|
assert resp.running is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_get_health(grpc_env) -> None:
|
|
|
|
|
_, stub, _ = grpc_env
|
|
|
|
|
resp = await stub.GetHealth(pb2.Empty(), timeout=5)
|
|
|
|
|
assert resp.healthy is True
|
|
|
|
|
assert resp.running is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_get_config(grpc_env) -> None:
|
|
|
|
|
_, stub, _ = grpc_env
|
|
|
|
|
resp = await stub.GetConfig(pb2.Empty(), timeout=5)
|
|
|
|
|
assert resp.config_json != ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_list_plugins(grpc_env) -> None:
|
|
|
|
|
_, stub, _ = grpc_env
|
|
|
|
|
resp = await stub.ListPlugins(pb2.Empty(), timeout=5)
|
|
|
|
|
assert len(resp.sources.items) > 0
|
|
|
|
|
assert len(resp.sinks.items) > 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_get_queue_state(grpc_env) -> None:
|
|
|
|
|
_, stub, _ = grpc_env
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
resp = await stub.GetQueueState(pb2.Empty(), timeout=5)
|
|
|
|
|
assert resp.total > 0
|
2026-10-14 01:56:37 +03:30
|
|
|
assert resp.pending + resp.delivered > 0
|
2026-07-15 10:48:18 +03:30
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_list_pending_records(grpc_env) -> None:
|
|
|
|
|
runtime, stub, _ = grpc_env
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
req = pb2.Pagination(limit=10, offset=0)
|
|
|
|
|
resp = await stub.ListPendingRecords(req, timeout=5)
|
|
|
|
|
assert resp.count > 0
|
|
|
|
|
assert len(resp.records) > 0
|
|
|
|
|
assert resp.records[0].record_id != ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_list_sinks(grpc_env) -> None:
|
|
|
|
|
_, stub, _ = grpc_env
|
|
|
|
|
resp = await stub.ListSinks(pb2.Empty(), timeout=5)
|
|
|
|
|
assert len(resp.sinks) >= 1
|
|
|
|
|
assert resp.sinks[0].name == "log"
|
|
|
|
|
|
|
|
|
|
|
2026-10-14 01:56:37 +03:30
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_replay_dead(grpc_env) -> None:
|
|
|
|
|
runtime, stub, _ = grpc_env
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
assert runtime.backend is not None
|
|
|
|
|
stored = None
|
|
|
|
|
for state in (RecordState.pending, RecordState.delivered, RecordState.failed):
|
|
|
|
|
found = runtime.backend.list_by_state(state, limit=1)
|
|
|
|
|
if found:
|
|
|
|
|
stored = found[0]
|
|
|
|
|
break
|
|
|
|
|
assert stored is not None
|
|
|
|
|
for sink in stored.deliveries:
|
|
|
|
|
runtime.backend.dead(stored.record_id, "grpc replay", sink=sink)
|
|
|
|
|
resp = await stub.ReplayDead(
|
|
|
|
|
pb2.ReplayRequest(record_id=stored.record_id, limit=10),
|
|
|
|
|
timeout=5,
|
|
|
|
|
)
|
|
|
|
|
assert resp.replayed == 1
|
|
|
|
|
again = runtime.backend.get(stored.record_id)
|
|
|
|
|
assert again is not None
|
|
|
|
|
assert again.deliveries["log"].state == RecordState.pending
|
|
|
|
|
|
|
|
|
|
|
2026-07-15 10:48:18 +03:30
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_reload_config(grpc_env) -> None:
|
|
|
|
|
_, stub, _ = grpc_env
|
|
|
|
|
resp = await stub.ReloadConfig(pb2.Empty(), timeout=10)
|
|
|
|
|
assert resp.success is True
|