completed local setup with compose
Some checks failed
CI/CD Pipeline / Generate SBOM (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
CI/CD Pipeline / Policy Validation (push) Has been cancelled
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-firm-connectors) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-forms) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-hmrc) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ingestion) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-normalize-map) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ocr) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-indexer) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-reason) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rpa) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (ui-review) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (ui-review) (push) Has been cancelled
CI/CD Pipeline / Notifications (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Generate SBOM (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
CI/CD Pipeline / Policy Validation (push) Has been cancelled
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-firm-connectors) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-forms) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-hmrc) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ingestion) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-normalize-map) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ocr) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-indexer) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-reason) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rpa) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (ui-review) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (ui-review) (push) Has been cancelled
CI/CD Pipeline / Notifications (push) Has been cancelled
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
"""Tests for NATS event bus implementation."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from nats.js.api import ConsumerConfig
|
||||
|
||||
from libs.events.base import EventPayload
|
||||
from libs.events.nats_bus import NATSEventBus
|
||||
@@ -41,9 +41,12 @@ class TestNATSEventBus:
|
||||
assert nats_bus.servers == ["nats://localhost:4222"]
|
||||
assert nats_bus.stream_name == "TEST_STREAM"
|
||||
assert nats_bus.consumer_group == "test-group"
|
||||
assert nats_bus.dlq_stream_name == "TAX_AGENT_DLQ"
|
||||
assert nats_bus.max_retries == 3
|
||||
assert not nats_bus.running
|
||||
assert nats_bus.nc is None
|
||||
assert nats_bus.js is None
|
||||
assert nats_bus.dlq is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_initialization_with_multiple_servers(self):
|
||||
@@ -54,14 +57,21 @@ class TestNATSEventBus:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("libs.events.nats_bus.nats.connect")
|
||||
async def test_start(self, mock_connect, nats_bus):
|
||||
@patch("libs.events.nats_bus.DLQHandler")
|
||||
async def test_start(self, mock_dlq_cls, mock_connect, nats_bus):
|
||||
"""Test starting the NATS event bus."""
|
||||
# Mock NATS connection and JetStream
|
||||
mock_nc = AsyncMock()
|
||||
mock_js = AsyncMock()
|
||||
mock_nc.jetstream.return_value = mock_js
|
||||
# jetstream() is synchronous, so we mock it as a MagicMock or just set return value
|
||||
mock_nc.jetstream = MagicMock(return_value=mock_js)
|
||||
mock_connect.return_value = mock_nc
|
||||
|
||||
# Mock DLQ handler
|
||||
mock_dlq_instance = MagicMock()
|
||||
mock_dlq_instance.ensure_dlq_stream_exists = AsyncMock()
|
||||
mock_dlq_cls.return_value = mock_dlq_instance
|
||||
|
||||
# Mock stream info to simulate existing stream
|
||||
mock_js.stream_info.return_value = {"name": "TEST_STREAM"}
|
||||
|
||||
@@ -70,26 +80,40 @@ class TestNATSEventBus:
|
||||
assert nats_bus.running
|
||||
assert nats_bus.nc == mock_nc
|
||||
assert nats_bus.js == mock_js
|
||||
assert nats_bus.dlq == mock_dlq_instance
|
||||
|
||||
mock_connect.assert_called_once_with(servers=["nats://localhost:4222"])
|
||||
mock_dlq_instance.ensure_dlq_stream_exists.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("libs.events.nats_bus.nats.connect")
|
||||
async def test_start_creates_stream_if_not_exists(self, mock_connect, nats_bus):
|
||||
@patch("libs.events.nats_bus.DLQHandler")
|
||||
async def test_start_creates_stream_if_not_exists(
|
||||
self, mock_dlq_cls, mock_connect, nats_bus
|
||||
):
|
||||
"""Test that start creates stream if it doesn't exist."""
|
||||
# Mock NATS connection and JetStream
|
||||
mock_nc = AsyncMock()
|
||||
mock_js = AsyncMock()
|
||||
mock_nc.jetstream.return_value = mock_js
|
||||
mock_nc.jetstream = MagicMock(return_value=mock_js)
|
||||
mock_connect.return_value = mock_nc
|
||||
|
||||
# Mock DLQ handler
|
||||
mock_dlq_instance = MagicMock()
|
||||
mock_dlq_instance.ensure_dlq_stream_exists = AsyncMock()
|
||||
mock_dlq_cls.return_value = mock_dlq_instance
|
||||
|
||||
# Mock stream_info to raise NotFoundError, then add_stream
|
||||
from nats.js.errors import NotFoundError
|
||||
|
||||
mock_js.stream_info.side_effect = NotFoundError
|
||||
mock_js.add_stream = AsyncMock()
|
||||
|
||||
await nats_bus.start()
|
||||
|
||||
mock_js.add_stream.assert_called_once()
|
||||
call_args = mock_js.add_stream.call_args
|
||||
assert call_args[1]["subjects"] == ["TEST_STREAM.>"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_already_running(self, nats_bus):
|
||||
@@ -107,17 +131,22 @@ class TestNATSEventBus:
|
||||
# Setup mock objects
|
||||
mock_nc = AsyncMock()
|
||||
mock_subscription = AsyncMock()
|
||||
mock_task = AsyncMock()
|
||||
|
||||
# Create a real task for consumer_tasks
|
||||
async def dummy_task():
|
||||
pass
|
||||
|
||||
real_task = asyncio.create_task(dummy_task())
|
||||
|
||||
nats_bus.running = True
|
||||
nats_bus.nc = mock_nc
|
||||
nats_bus.subscriptions = {"test-topic": mock_subscription}
|
||||
nats_bus.consumer_tasks = [mock_task]
|
||||
nats_bus.consumer_tasks = [real_task]
|
||||
|
||||
await nats_bus.stop()
|
||||
|
||||
assert not nats_bus.running
|
||||
mock_task.cancel.assert_called_once()
|
||||
assert real_task.cancelled() or real_task.done()
|
||||
mock_subscription.unsubscribe.assert_called_once()
|
||||
mock_nc.close.assert_called_once()
|
||||
|
||||
@@ -129,7 +158,8 @@ class TestNATSEventBus:
|
||||
assert not nats_bus.running
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_publish(self, nats_bus, event_payload):
|
||||
@patch("libs.events.nats_bus.EventMetricsCollector")
|
||||
async def test_publish(self, mock_metrics, nats_bus, event_payload):
|
||||
"""Test publishing an event."""
|
||||
# Setup mock JetStream
|
||||
mock_js = AsyncMock()
|
||||
@@ -146,6 +176,10 @@ class TestNATSEventBus:
|
||||
assert call_args[1]["subject"] == "TEST_STREAM.test-topic"
|
||||
assert call_args[1]["payload"] == event_payload.to_json().encode()
|
||||
|
||||
# Verify metrics recorded
|
||||
mock_metrics.record_publish.assert_called_once()
|
||||
assert mock_metrics.record_publish.call_args[1]["success"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_publish_not_started(self, nats_bus, event_payload):
|
||||
"""Test publishing when event bus is not started."""
|
||||
@@ -153,7 +187,8 @@ class TestNATSEventBus:
|
||||
await nats_bus.publish("test-topic", event_payload)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_publish_failure(self, nats_bus, event_payload):
|
||||
@patch("libs.events.nats_bus.EventMetricsCollector")
|
||||
async def test_publish_failure(self, mock_metrics, nats_bus, event_payload):
|
||||
"""Test publishing failure."""
|
||||
# Setup mock JetStream that raises exception
|
||||
mock_js = AsyncMock()
|
||||
@@ -164,6 +199,10 @@ class TestNATSEventBus:
|
||||
|
||||
assert result is False
|
||||
|
||||
# Verify metrics recorded failure
|
||||
mock_metrics.record_publish.assert_called_once()
|
||||
assert mock_metrics.record_publish.call_args[1]["success"] is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_subscribe(self, nats_bus):
|
||||
"""Test subscribing to a topic."""
|
||||
@@ -184,11 +223,19 @@ class TestNATSEventBus:
|
||||
assert test_handler in nats_bus.handlers["test-topic"]
|
||||
assert "test-topic" in nats_bus.subscriptions
|
||||
mock_js.pull_subscribe.assert_called_once()
|
||||
|
||||
# Verify ConsumerConfig
|
||||
call_kwargs = mock_js.pull_subscribe.call_args[1]
|
||||
config = call_kwargs["config"]
|
||||
assert isinstance(config, ConsumerConfig)
|
||||
assert config.max_deliver == 5 # 3 retries + 2 buffer
|
||||
|
||||
mock_create_task.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_subscribe_not_started(self, nats_bus):
|
||||
"""Test subscribing when event bus is not started."""
|
||||
|
||||
async def test_handler(topic: str, payload: EventPayload) -> None:
|
||||
pass
|
||||
|
||||
@@ -220,7 +267,8 @@ class TestNATSEventBus:
|
||||
assert handler2 in nats_bus.handlers["test-topic"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_consume_messages(self, nats_bus, event_payload):
|
||||
@patch("libs.events.nats_bus.EventMetricsCollector")
|
||||
async def test_consume_messages(self, mock_metrics, nats_bus, event_payload):
|
||||
"""Test consuming messages from NATS."""
|
||||
# Setup mock subscription and message
|
||||
mock_subscription = AsyncMock()
|
||||
@@ -253,6 +301,10 @@ class TestNATSEventBus:
|
||||
assert received_payload.event_id == event_payload.event_id
|
||||
mock_message.ack.assert_called_once()
|
||||
|
||||
# Verify metrics
|
||||
mock_metrics.record_consume.assert_called_once()
|
||||
assert mock_metrics.record_consume.call_args[1]["success"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_factory_integration(self):
|
||||
"""Test that the factory can create a NATS event bus."""
|
||||
|
||||
Reference in New Issue
Block a user