WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 0e1118e

Browse files
committed
running. some tests not working
1 parent 8c5a399 commit 0e1118e

File tree

10 files changed

+1567
-18
lines changed

10 files changed

+1567
-18
lines changed
Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,84 @@
11
from functools import wraps
22
import time
3-
from typing import Callable, Dict, Optional
4-
5-
from prometheus_client import Counter, Gauge, Histogram, start_http_server
3+
from typing import Callable, Dict, Optional, Union
64

5+
from prometheus_client import Counter, Gauge, Histogram, REGISTRY, start_http_server
76

87
class MetricsRegistry:
9-
def __init__(self):
8+
def __init__(self, registry=None):
9+
# Use a provided registry or the default one
10+
self.registry = registry or REGISTRY
11+
1012
# Collector metrics
1113
self.webhook_received_total = Counter(
1214
"webhook_relay_received_total",
1315
"Total number of webhooks received",
14-
["source"]
16+
["source"],
17+
registry=self.registry
1518
)
1619
self.webhook_processing_time = Histogram(
1720
"webhook_relay_processing_seconds",
1821
"Time spent processing webhooks",
19-
["source"]
22+
["source"],
23+
registry=self.registry
2024
)
2125
self.queue_publish_total = Counter(
2226
"webhook_relay_queue_publish_total",
2327
"Total number of messages published to queue",
24-
["queue_type"]
28+
["queue_type"],
29+
registry=self.registry
2530
)
2631
self.queue_publish_errors = Counter(
2732
"webhook_relay_queue_publish_errors",
2833
"Total number of errors publishing to queue",
29-
["queue_type"]
34+
["queue_type"],
35+
registry=self.registry
3036
)
3137

3238
# Forwarder metrics
3339
self.queue_receive_total = Counter(
3440
"webhook_relay_queue_receive_total",
3541
"Total number of messages received from queue",
36-
["queue_type"]
42+
["queue_type"],
43+
registry=self.registry
3744
)
3845
self.queue_delete_total = Counter(
3946
"webhook_relay_queue_delete_total",
4047
"Total number of messages deleted from queue",
41-
["queue_type"]
48+
["queue_type"],
49+
registry=self.registry
4250
)
4351
self.forward_total = Counter(
4452
"webhook_relay_forward_total",
4553
"Total number of webhooks forwarded",
46-
["target"]
54+
["target"],
55+
registry=self.registry
4756
)
4857
self.forward_errors = Counter(
4958
"webhook_relay_forward_errors",
5059
"Total number of errors forwarding webhooks",
51-
["target", "status_code"]
60+
["target", "status_code"],
61+
registry=self.registry
5262
)
5363
self.forward_retry_total = Counter(
5464
"webhook_relay_forward_retry_total",
5565
"Total number of webhook forward retries",
56-
["target"]
66+
["target"],
67+
registry=self.registry
5768
)
5869
self.forward_latency = Histogram(
5970
"webhook_relay_forward_seconds",
6071
"Time spent forwarding webhooks",
61-
["target"]
72+
["target"],
73+
registry=self.registry
6274
)
6375

6476
# Common metrics
6577
self.up = Gauge(
6678
"webhook_relay_up",
6779
"Whether the webhook relay service is up",
68-
["component"]
80+
["component"],
81+
registry=self.registry
6982
)
7083

7184

@@ -80,19 +93,32 @@ def start_metrics_server(port: int = 9090, host: str = "127.0.0.1"):
8093

8194
def measure_time(
8295
metric: Histogram,
83-
labels: Optional[Dict[str, str]] = None
96+
labels: Optional[Union[Dict[str, str], Callable]] = None
8497
) -> Callable:
8598
"""Decorator to measure the execution time of a function."""
8699
def decorator(func):
87100
@wraps(func)
88101
async def wrapper(*args, **kwargs):
89-
labels_dict = labels or {}
102+
# Get the labels dictionary
103+
labels_dict = {}
104+
if callable(labels) and args:
105+
# If labels is a lambda/function and we have args (self)
106+
try:
107+
labels_dict = labels(args[0])
108+
except Exception as e:
109+
logger.error(f"Error getting labels from function: {e}")
110+
elif isinstance(labels, dict):
111+
labels_dict = labels
112+
90113
start_time = time.time()
91114
try:
92115
result = await func(*args, **kwargs)
93116
return result
94117
finally:
95118
duration = time.time() - start_time
96-
metric.labels(**labels_dict).observe(duration)
119+
try:
120+
metric.labels(**labels_dict).observe(duration)
121+
except Exception as e:
122+
logger.error(f"Error recording metric: {e}")
97123
return wrapper
98124
return decorator

tests/conftest.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import asyncio
2+
import json
3+
import os
4+
import pytest
5+
from unittest.mock import AsyncMock, MagicMock, patch
6+
7+
from fastapi.testclient import TestClient
8+
9+
from webhook_relay.common.config import (
10+
AWSSQSConfig,
11+
CollectorConfig,
12+
ForwarderConfig,
13+
GCPPubSubConfig,
14+
QueueType,
15+
)
16+
from webhook_relay.common.models import QueueMessage, WebhookMetadata, WebhookPayload
17+
from webhook_relay.common.queue import QueueClient
18+
from webhook_relay.collector.server import create_app
19+
20+
21+
class MockQueueClient(QueueClient):
22+
"""Mock implementation of QueueClient for testing."""
23+
24+
def __init__(self):
25+
self.sent_messages = []
26+
self.available_messages = []
27+
self.deleted_messages = []
28+
29+
# Create mocks that we can use to override behavior in tests
30+
self._send_message_mock = MagicMock(side_effect=self._send_message_impl)
31+
self._receive_message_mock = MagicMock(side_effect=self._receive_message_impl)
32+
self._delete_message_mock = MagicMock(side_effect=self._delete_message_impl)
33+
34+
async def send_message(self, payload):
35+
"""Implementation of abstract method that delegates to a mockable method."""
36+
return await self._send_message_mock(payload)
37+
38+
async def receive_message(self):
39+
"""Implementation of abstract method that delegates to a mockable method."""
40+
return await self._receive_message_mock()
41+
42+
async def delete_message(self, message_id):
43+
"""Implementation of abstract method that delegates to a mockable method."""
44+
return await self._delete_message_mock(message_id)
45+
46+
async def _send_message_impl(self, payload):
47+
"""Actual implementation for send_message."""
48+
message_id = f"mock-message-{len(self.sent_messages)}"
49+
self.sent_messages.append((message_id, payload))
50+
return message_id
51+
52+
async def _receive_message_impl(self):
53+
"""Actual implementation for receive_message."""
54+
if not self.available_messages:
55+
return None
56+
return self.available_messages.pop(0)
57+
58+
async def _delete_message_impl(self, message_id):
59+
"""Actual implementation for delete_message."""
60+
self.deleted_messages.append(message_id)
61+
return True
62+
63+
def add_message_to_queue(self, message):
64+
"""Helper method to add messages to the mock queue."""
65+
self.available_messages.append(message)
66+
67+
68+
@pytest.fixture
69+
def mock_queue_client():
70+
"""Fixture that provides a mock queue client."""
71+
return MockQueueClient()
72+
73+
74+
@pytest.fixture
75+
def sample_webhook_payload():
76+
"""Fixture that provides a sample webhook payload."""
77+
metadata = WebhookMetadata(
78+
source="github",
79+
headers={"X-GitHub-Event": "push"},
80+
signature="sha256=abc123",
81+
)
82+
content = {
83+
"repository": {"name": "test-repo", "full_name": "owner/test-repo"},
84+
"ref": "refs/heads/main",
85+
"commits": [
86+
{
87+
"id": "123456",
88+
"message": "Test commit",
89+
"author": {"name": "Test User", "email": "[email protected]"},
90+
}
91+
],
92+
}
93+
return WebhookPayload(metadata=metadata, content=content)
94+
95+
96+
@pytest.fixture
97+
def sample_queue_message(sample_webhook_payload):
98+
"""Fixture that provides a sample queue message."""
99+
return QueueMessage(
100+
id="test-message-id",
101+
payload=sample_webhook_payload,
102+
attempts=0,
103+
)
104+
105+
106+
@pytest.fixture
107+
def gcp_config():
108+
"""Fixture that provides a sample GCP configuration."""
109+
return GCPPubSubConfig(
110+
project_id="test-project",
111+
topic_id="test-topic",
112+
subscription_id="test-subscription",
113+
)
114+
115+
116+
@pytest.fixture
117+
def aws_config():
118+
"""Fixture that provides a sample AWS configuration."""
119+
return AWSSQSConfig(
120+
region_name="us-west-2",
121+
queue_url="https://sqs.us-west-2.amazonaws.com/123456789012/test-queue",
122+
)
123+
124+
125+
@pytest.fixture
126+
def collector_config():
127+
"""Fixture that provides a sample collector configuration."""
128+
return CollectorConfig(
129+
host="0.0.0.0",
130+
port=8000,
131+
log_level="INFO",
132+
queue_type=QueueType.GCP_PUBSUB,
133+
gcp_config=GCPPubSubConfig(
134+
project_id="test-project",
135+
topic_id="test-topic",
136+
),
137+
webhook_sources=[
138+
{"name": "github", "secret": "test-secret", "signature_header": "X-Hub-Signature-256"},
139+
{"name": "gitlab", "secret": "test-secret", "signature_header": "X-Gitlab-Token"},
140+
{"name": "custom"}, # No signature verification
141+
],
142+
)
143+
144+
145+
@pytest.fixture
146+
def forwarder_config():
147+
"""Fixture that provides a sample forwarder configuration."""
148+
return ForwarderConfig(
149+
log_level="INFO",
150+
queue_type=QueueType.GCP_PUBSUB,
151+
gcp_config=GCPPubSubConfig(
152+
project_id="test-project",
153+
topic_id="test-topic",
154+
subscription_id="test-subscription",
155+
),
156+
target_url="http://internal-service:8080/webhook",
157+
headers={"X-Webhook-Relay": "true", "Authorization": "Bearer test-token"},
158+
retry_attempts=3,
159+
retry_delay=1,
160+
timeout=5,
161+
)
162+
163+
164+
@pytest.fixture
165+
def collector_app(collector_config, mock_queue_client):
166+
"""Fixture that provides a configured collector FastAPI app."""
167+
with patch("webhook_relay.collector.app.get_app_config") as mock_get_config, \
168+
patch("webhook_relay.collector.app.get_queue_client") as mock_get_queue:
169+
mock_get_config.return_value = collector_config
170+
mock_get_queue.return_value = mock_queue_client
171+
app = create_app(collector_config)
172+
yield app
173+
174+
175+
@pytest.fixture
176+
def collector_client(collector_app):
177+
"""Fixture that provides a test client for the collector API."""
178+
return TestClient(collector_app)
179+
180+
181+
# Define a fixture to provide an async event loop for testing asynchronous functions
182+
@pytest.fixture
183+
def event_loop():
184+
loop = asyncio.get_event_loop_policy().new_event_loop()
185+
yield loop
186+
loop.close()

0 commit comments

Comments
 (0)