hookbase 2.1.1__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hookbase-2.1.1 → hookbase-2.3.0}/PKG-INFO +1 -1
- hookbase-2.3.0/src/hookbase/_version.py +1 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/__init__.py +12 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/destinations.py +79 -2
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/endpoints.py +3 -0
- hookbase-2.3.0/tests/resources/test_destinations.py +193 -0
- hookbase-2.1.1/src/hookbase/_version.py +0 -1
- hookbase-2.1.1/tests/resources/test_destinations.py +0 -72
- {hookbase-2.1.1 → hookbase-2.3.0}/.github/workflows/python-publish.yml +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/.gitignore +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/LICENSE +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/README.md +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/examples/async_usage.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/examples/basic_inbound.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/examples/error_handling.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/examples/send_webhooks.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/examples/verify_webhooks.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/pyproject.toml +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/__init__.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/_client.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/_constants.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/_pagination.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/client.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/errors.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/_base.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/analytics.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/api_keys.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/applications.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/common.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/cron_jobs.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/deliveries.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/dlq.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/event_types.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/events.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/filters.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/messages.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/organizations.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/portal_tokens.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/routes.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/schemas.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/sources.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/subscriptions.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/transforms.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/models/tunnels.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/py.typed +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/__init__.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/_base.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/analytics.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/api_keys.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/applications.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/cron_jobs.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/deliveries.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/destinations.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/dlq.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/endpoints.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/event_types.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/events.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/filters.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/message_log.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/messages.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/organizations.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/portal_tokens.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/routes.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/schemas.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/sources.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/subscriptions.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/transforms.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/resources/tunnels.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/src/hookbase/webhook.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/__init__.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/conftest.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/resources/__init__.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/resources/test_applications.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/resources/test_dlq.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/resources/test_messages.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/resources/test_sources.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/test_client.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/test_errors.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/test_pagination.py +0 -0
- {hookbase-2.1.1 → hookbase-2.3.0}/tests/test_webhook.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.3.0"
|
|
@@ -18,9 +18,15 @@ from .deliveries import (
|
|
|
18
18
|
)
|
|
19
19
|
from .destinations import (
|
|
20
20
|
AuthType,
|
|
21
|
+
AzureBlobConfig,
|
|
21
22
|
CreateDestinationParams,
|
|
22
23
|
Destination,
|
|
24
|
+
DestinationType,
|
|
25
|
+
FieldMapping,
|
|
26
|
+
GCSConfig,
|
|
23
27
|
HttpMethod,
|
|
28
|
+
R2Config,
|
|
29
|
+
S3Config,
|
|
24
30
|
TestResult,
|
|
25
31
|
UpdateDestinationParams,
|
|
26
32
|
)
|
|
@@ -123,9 +129,15 @@ __all__ = [
|
|
|
123
129
|
"ReplayResult",
|
|
124
130
|
# Destinations
|
|
125
131
|
"AuthType",
|
|
132
|
+
"AzureBlobConfig",
|
|
126
133
|
"CreateDestinationParams",
|
|
127
134
|
"Destination",
|
|
135
|
+
"DestinationType",
|
|
136
|
+
"FieldMapping",
|
|
137
|
+
"GCSConfig",
|
|
128
138
|
"HttpMethod",
|
|
139
|
+
"R2Config",
|
|
140
|
+
"S3Config",
|
|
129
141
|
"TestResult",
|
|
130
142
|
"UpdateDestinationParams",
|
|
131
143
|
# DLQ
|
|
@@ -9,6 +9,52 @@ from ._base import HookbaseModel
|
|
|
9
9
|
|
|
10
10
|
HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE"]
|
|
11
11
|
AuthType = Literal["none", "basic", "bearer", "api_key", "custom_header"]
|
|
12
|
+
DestinationType = Literal["http", "s3", "r2", "gcs", "azure_blob"]
|
|
13
|
+
FileFormat = Literal["json", "jsonl"]
|
|
14
|
+
PartitionBy = Literal["date", "hour", "source"]
|
|
15
|
+
FieldMappingType = Literal["string", "number", "boolean", "timestamp", "json"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FieldMapping(HookbaseModel):
|
|
19
|
+
source: str
|
|
20
|
+
target: str
|
|
21
|
+
type: FieldMappingType
|
|
22
|
+
default: str | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class S3Config(HookbaseModel):
|
|
26
|
+
bucket: str
|
|
27
|
+
region: str
|
|
28
|
+
access_key_id: str
|
|
29
|
+
secret_access_key: str
|
|
30
|
+
prefix: str | None = None
|
|
31
|
+
file_format: FileFormat | None = None
|
|
32
|
+
partition_by: PartitionBy | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class R2Config(HookbaseModel):
|
|
36
|
+
bucket: str
|
|
37
|
+
prefix: str | None = None
|
|
38
|
+
file_format: FileFormat | None = None
|
|
39
|
+
partition_by: PartitionBy | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class GCSConfig(HookbaseModel):
|
|
43
|
+
bucket: str
|
|
44
|
+
project_id: str
|
|
45
|
+
service_account_key: str
|
|
46
|
+
prefix: str | None = None
|
|
47
|
+
file_format: FileFormat | None = None
|
|
48
|
+
partition_by: PartitionBy | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AzureBlobConfig(HookbaseModel):
|
|
52
|
+
account_name: str
|
|
53
|
+
account_key: str
|
|
54
|
+
container_name: str
|
|
55
|
+
prefix: str | None = None
|
|
56
|
+
file_format: FileFormat | None = None
|
|
57
|
+
partition_by: PartitionBy | None = None
|
|
12
58
|
|
|
13
59
|
|
|
14
60
|
class Destination(HookbaseModel):
|
|
@@ -17,7 +63,8 @@ class Destination(HookbaseModel):
|
|
|
17
63
|
name: str
|
|
18
64
|
slug: str
|
|
19
65
|
description: str | None = None
|
|
20
|
-
|
|
66
|
+
type: DestinationType = "http"
|
|
67
|
+
url: str = ""
|
|
21
68
|
method: HttpMethod = "POST"
|
|
22
69
|
headers: dict[str, str] | None = None
|
|
23
70
|
auth_type: str | None = "none"
|
|
@@ -28,6 +75,9 @@ class Destination(HookbaseModel):
|
|
|
28
75
|
rate_limit: int | None = None
|
|
29
76
|
rate_limit_window: int | None = None
|
|
30
77
|
is_active: bool = True
|
|
78
|
+
use_static_ip: bool = True
|
|
79
|
+
config: dict[str, Any] | None = None
|
|
80
|
+
field_mapping: list[FieldMapping] | None = None
|
|
31
81
|
delivery_count: int = 0
|
|
32
82
|
last_delivery_at: str | None = None
|
|
33
83
|
created_at: str = ""
|
|
@@ -53,6 +103,26 @@ class Destination(HookbaseModel):
|
|
|
53
103
|
return None
|
|
54
104
|
return v
|
|
55
105
|
|
|
106
|
+
@field_validator("config", mode="before")
|
|
107
|
+
@classmethod
|
|
108
|
+
def parse_config(cls, v: Any) -> Any:
|
|
109
|
+
if isinstance(v, str):
|
|
110
|
+
try:
|
|
111
|
+
return json.loads(v)
|
|
112
|
+
except (json.JSONDecodeError, ValueError):
|
|
113
|
+
return None
|
|
114
|
+
return v
|
|
115
|
+
|
|
116
|
+
@field_validator("field_mapping", mode="before")
|
|
117
|
+
@classmethod
|
|
118
|
+
def parse_field_mapping(cls, v: Any) -> Any:
|
|
119
|
+
if isinstance(v, str):
|
|
120
|
+
try:
|
|
121
|
+
return json.loads(v)
|
|
122
|
+
except (json.JSONDecodeError, ValueError):
|
|
123
|
+
return None
|
|
124
|
+
return v
|
|
125
|
+
|
|
56
126
|
@field_validator("is_active", mode="before")
|
|
57
127
|
@classmethod
|
|
58
128
|
def parse_is_active(cls, v: Any) -> Any:
|
|
@@ -65,7 +135,8 @@ class CreateDestinationParams(HookbaseModel):
|
|
|
65
135
|
name: str
|
|
66
136
|
slug: str | None = None
|
|
67
137
|
description: str | None = None
|
|
68
|
-
|
|
138
|
+
type: DestinationType | None = None
|
|
139
|
+
url: str | None = None
|
|
69
140
|
method: HttpMethod | None = None
|
|
70
141
|
headers: dict[str, str] | None = None
|
|
71
142
|
auth_type: AuthType | None = None
|
|
@@ -75,6 +146,9 @@ class CreateDestinationParams(HookbaseModel):
|
|
|
75
146
|
retry_interval: int | None = None
|
|
76
147
|
rate_limit: int | None = None
|
|
77
148
|
rate_limit_window: int | None = None
|
|
149
|
+
config: dict[str, Any] | None = None
|
|
150
|
+
field_mapping: list[FieldMapping] | None = None
|
|
151
|
+
use_static_ip: bool | None = None
|
|
78
152
|
|
|
79
153
|
|
|
80
154
|
class UpdateDestinationParams(HookbaseModel):
|
|
@@ -91,6 +165,9 @@ class UpdateDestinationParams(HookbaseModel):
|
|
|
91
165
|
rate_limit: int | None = None
|
|
92
166
|
rate_limit_window: int | None = None
|
|
93
167
|
is_active: bool | None = None
|
|
168
|
+
config: dict[str, Any] | None = None
|
|
169
|
+
field_mapping: list[FieldMapping] | None = None
|
|
170
|
+
use_static_ip: bool | None = None
|
|
94
171
|
|
|
95
172
|
|
|
96
173
|
class TestResult(HookbaseModel):
|
|
@@ -30,6 +30,7 @@ class WebhookEndpoint(HookbaseModel):
|
|
|
30
30
|
return {}
|
|
31
31
|
return v
|
|
32
32
|
metadata: dict[str, Any] | None = None
|
|
33
|
+
use_static_ip: bool = True
|
|
33
34
|
total_messages: int = 0
|
|
34
35
|
total_successes: int = 0
|
|
35
36
|
total_failures: int = 0
|
|
@@ -49,6 +50,7 @@ class CreateEndpointParams(HookbaseModel):
|
|
|
49
50
|
rate_limit_period: int | None = None
|
|
50
51
|
headers: dict[str, str] | None = None
|
|
51
52
|
metadata: dict[str, Any] | None = None
|
|
53
|
+
use_static_ip: bool | None = None
|
|
52
54
|
|
|
53
55
|
|
|
54
56
|
class UpdateEndpointParams(HookbaseModel):
|
|
@@ -60,6 +62,7 @@ class UpdateEndpointParams(HookbaseModel):
|
|
|
60
62
|
rate_limit_period: int | None = None
|
|
61
63
|
headers: dict[str, str] | None = None
|
|
62
64
|
metadata: dict[str, Any] | None = None
|
|
65
|
+
use_static_ip: bool | None = None
|
|
63
66
|
|
|
64
67
|
|
|
65
68
|
class EndpointStats(HookbaseModel):
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import respx
|
|
5
|
+
|
|
6
|
+
from hookbase import Hookbase
|
|
7
|
+
from hookbase.models import Destination, FieldMapping, TestResult
|
|
8
|
+
|
|
9
|
+
from ..conftest import make_paginated_response
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def mock_api():
|
|
14
|
+
with respx.mock(base_url="https://api.hookbase.app") as mock:
|
|
15
|
+
yield mock
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def client(mock_api):
|
|
20
|
+
c = Hookbase(api_key="whr_test")
|
|
21
|
+
yield c
|
|
22
|
+
c.close()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
DEST_DATA = {
|
|
26
|
+
"id": "dst_1",
|
|
27
|
+
"organizationId": "org_1",
|
|
28
|
+
"name": "Backend",
|
|
29
|
+
"slug": "backend",
|
|
30
|
+
"type": "http",
|
|
31
|
+
"url": "https://example.com/webhooks",
|
|
32
|
+
"method": "POST",
|
|
33
|
+
"authType": "none",
|
|
34
|
+
"timeout": 30,
|
|
35
|
+
"retryCount": 3,
|
|
36
|
+
"retryInterval": 60,
|
|
37
|
+
"isActive": True,
|
|
38
|
+
"config": None,
|
|
39
|
+
"fieldMapping": None,
|
|
40
|
+
"deliveryCount": 100,
|
|
41
|
+
"createdAt": "2024-01-01T00:00:00Z",
|
|
42
|
+
"updatedAt": "2024-01-01T00:00:00Z",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
S3_DEST_DATA = {
|
|
46
|
+
"id": "dst_s3",
|
|
47
|
+
"organizationId": "org_1",
|
|
48
|
+
"name": "S3 Archive",
|
|
49
|
+
"slug": "s3-archive",
|
|
50
|
+
"type": "s3",
|
|
51
|
+
"url": "",
|
|
52
|
+
"method": "POST",
|
|
53
|
+
"authType": "none",
|
|
54
|
+
"timeout": 30,
|
|
55
|
+
"retryCount": 3,
|
|
56
|
+
"retryInterval": 60,
|
|
57
|
+
"isActive": True,
|
|
58
|
+
"config": {
|
|
59
|
+
"bucket": "my-webhooks",
|
|
60
|
+
"region": "us-east-1",
|
|
61
|
+
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
|
|
62
|
+
"secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
63
|
+
"prefix": "events/",
|
|
64
|
+
"fileFormat": "jsonl",
|
|
65
|
+
"partitionBy": "date",
|
|
66
|
+
},
|
|
67
|
+
"fieldMapping": [
|
|
68
|
+
{"source": "$.id", "target": "event_id", "type": "string"},
|
|
69
|
+
{"source": "$.payload", "target": "data", "type": "json"},
|
|
70
|
+
{"source": "$.timestamp", "target": "received_at", "type": "timestamp", "default": "now()"},
|
|
71
|
+
],
|
|
72
|
+
"deliveryCount": 0,
|
|
73
|
+
"createdAt": "2024-01-01T00:00:00Z",
|
|
74
|
+
"updatedAt": "2024-01-01T00:00:00Z",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_list_destinations(mock_api, client):
|
|
79
|
+
mock_api.get("/api/destinations").respond(200, json=make_paginated_response(
|
|
80
|
+
[DEST_DATA], data_key="destinations", total=1,
|
|
81
|
+
))
|
|
82
|
+
page = client.destinations.list()
|
|
83
|
+
assert len(page) == 1
|
|
84
|
+
assert page.data[0].name == "Backend"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_get_destination(mock_api, client):
|
|
88
|
+
mock_api.get("/api/destinations/dst_1").respond(200, json={"destination": DEST_DATA})
|
|
89
|
+
dest = client.destinations.get("dst_1")
|
|
90
|
+
assert isinstance(dest, Destination)
|
|
91
|
+
assert dest.url == "https://example.com/webhooks"
|
|
92
|
+
assert dest.type == "http"
|
|
93
|
+
assert dest.config is None
|
|
94
|
+
assert dest.field_mapping is None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_get_warehouse_destination(mock_api, client):
|
|
98
|
+
mock_api.get("/api/destinations/dst_s3").respond(200, json={"destination": S3_DEST_DATA})
|
|
99
|
+
dest = client.destinations.get("dst_s3")
|
|
100
|
+
assert isinstance(dest, Destination)
|
|
101
|
+
assert dest.type == "s3"
|
|
102
|
+
assert dest.config is not None
|
|
103
|
+
assert dest.config["bucket"] == "my-webhooks"
|
|
104
|
+
assert dest.config["region"] == "us-east-1"
|
|
105
|
+
assert dest.field_mapping is not None
|
|
106
|
+
assert len(dest.field_mapping) == 3
|
|
107
|
+
assert dest.field_mapping[0].source == "$.id"
|
|
108
|
+
assert dest.field_mapping[0].target == "event_id"
|
|
109
|
+
assert dest.field_mapping[0].type == "string"
|
|
110
|
+
assert dest.field_mapping[2].default == "now()"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_create_destination(mock_api, client):
|
|
114
|
+
mock_api.post("/api/destinations").respond(200, json={"destination": DEST_DATA})
|
|
115
|
+
dest = client.destinations.create({"name": "Backend", "url": "https://example.com/webhooks"})
|
|
116
|
+
assert dest.id == "dst_1"
|
|
117
|
+
assert dest.type == "http"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_create_warehouse_destination(mock_api, client):
|
|
121
|
+
mock_api.post("/api/destinations").respond(200, json={"destination": S3_DEST_DATA})
|
|
122
|
+
dest = client.destinations.create({
|
|
123
|
+
"name": "S3 Archive",
|
|
124
|
+
"type": "s3",
|
|
125
|
+
"config": {
|
|
126
|
+
"bucket": "my-webhooks",
|
|
127
|
+
"region": "us-east-1",
|
|
128
|
+
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
|
|
129
|
+
"secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
130
|
+
},
|
|
131
|
+
"fieldMapping": [
|
|
132
|
+
{"source": "$.id", "target": "event_id", "type": "string"},
|
|
133
|
+
],
|
|
134
|
+
})
|
|
135
|
+
assert dest.id == "dst_s3"
|
|
136
|
+
assert dest.type == "s3"
|
|
137
|
+
assert dest.config is not None
|
|
138
|
+
assert dest.field_mapping is not None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_update_warehouse_destination_config(mock_api, client):
|
|
142
|
+
updated = {**S3_DEST_DATA, "config": {**S3_DEST_DATA["config"], "bucket": "new-bucket"}}
|
|
143
|
+
mock_api.patch("/api/destinations/dst_s3").respond(200, json={"destination": updated})
|
|
144
|
+
client.destinations.update("dst_s3", {
|
|
145
|
+
"config": {
|
|
146
|
+
"bucket": "new-bucket",
|
|
147
|
+
"region": "us-east-1",
|
|
148
|
+
"accessKeyId": "AKIA",
|
|
149
|
+
"secretAccessKey": "secret",
|
|
150
|
+
},
|
|
151
|
+
"fieldMapping": [
|
|
152
|
+
{"source": "$.body", "target": "payload", "type": "json"},
|
|
153
|
+
],
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_field_mapping_model():
|
|
158
|
+
fm = FieldMapping(source="$.id", target="event_id", type="string")
|
|
159
|
+
assert fm.source == "$.id"
|
|
160
|
+
assert fm.target == "event_id"
|
|
161
|
+
assert fm.type == "string"
|
|
162
|
+
assert fm.default is None
|
|
163
|
+
|
|
164
|
+
fm_with_default = FieldMapping(
|
|
165
|
+
source="$.ts", target="timestamp", type="timestamp", default="now()",
|
|
166
|
+
)
|
|
167
|
+
assert fm_with_default.default == "now()"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_destination_without_type_defaults():
|
|
171
|
+
"""Destinations without type field should default to http."""
|
|
172
|
+
data = {
|
|
173
|
+
"id": "dst_old",
|
|
174
|
+
"name": "Old Dest",
|
|
175
|
+
"slug": "old-dest",
|
|
176
|
+
"url": "https://example.com",
|
|
177
|
+
"method": "POST",
|
|
178
|
+
"isActive": True,
|
|
179
|
+
}
|
|
180
|
+
dest = Destination(**data)
|
|
181
|
+
assert dest.type == "http"
|
|
182
|
+
assert dest.config is None
|
|
183
|
+
assert dest.field_mapping is None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_test_destination(mock_api, client):
|
|
187
|
+
mock_api.post("/api/destinations/dst_1/test").respond(200, json={
|
|
188
|
+
"success": True, "statusCode": 200, "duration": 150.5,
|
|
189
|
+
})
|
|
190
|
+
result = client.destinations.test("dst_1")
|
|
191
|
+
assert isinstance(result, TestResult)
|
|
192
|
+
assert result.success is True
|
|
193
|
+
assert result.status_code == 200
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.1.1"
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
import respx
|
|
5
|
-
|
|
6
|
-
from hookbase import Hookbase
|
|
7
|
-
from hookbase.models import Destination, TestResult
|
|
8
|
-
|
|
9
|
-
from ..conftest import make_paginated_response
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@pytest.fixture
|
|
13
|
-
def mock_api():
|
|
14
|
-
with respx.mock(base_url="https://api.hookbase.app") as mock:
|
|
15
|
-
yield mock
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@pytest.fixture
|
|
19
|
-
def client(mock_api):
|
|
20
|
-
c = Hookbase(api_key="whr_test")
|
|
21
|
-
yield c
|
|
22
|
-
c.close()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
DEST_DATA = {
|
|
26
|
-
"id": "dst_1",
|
|
27
|
-
"organizationId": "org_1",
|
|
28
|
-
"name": "Backend",
|
|
29
|
-
"slug": "backend",
|
|
30
|
-
"url": "https://example.com/webhooks",
|
|
31
|
-
"method": "POST",
|
|
32
|
-
"authType": "none",
|
|
33
|
-
"timeout": 30,
|
|
34
|
-
"retryCount": 3,
|
|
35
|
-
"retryInterval": 60,
|
|
36
|
-
"isActive": True,
|
|
37
|
-
"deliveryCount": 100,
|
|
38
|
-
"createdAt": "2024-01-01T00:00:00Z",
|
|
39
|
-
"updatedAt": "2024-01-01T00:00:00Z",
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_list_destinations(mock_api, client):
|
|
44
|
-
mock_api.get("/api/destinations").respond(200, json=make_paginated_response(
|
|
45
|
-
[DEST_DATA], data_key="destinations", total=1,
|
|
46
|
-
))
|
|
47
|
-
page = client.destinations.list()
|
|
48
|
-
assert len(page) == 1
|
|
49
|
-
assert page.data[0].name == "Backend"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def test_get_destination(mock_api, client):
|
|
53
|
-
mock_api.get("/api/destinations/dst_1").respond(200, json={"destination": DEST_DATA})
|
|
54
|
-
dest = client.destinations.get("dst_1")
|
|
55
|
-
assert isinstance(dest, Destination)
|
|
56
|
-
assert dest.url == "https://example.com/webhooks"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_create_destination(mock_api, client):
|
|
60
|
-
mock_api.post("/api/destinations").respond(200, json={"destination": DEST_DATA})
|
|
61
|
-
dest = client.destinations.create({"name": "Backend", "url": "https://example.com/webhooks"})
|
|
62
|
-
assert dest.id == "dst_1"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def test_test_destination(mock_api, client):
|
|
66
|
-
mock_api.post("/api/destinations/dst_1/test").respond(200, json={
|
|
67
|
-
"success": True, "statusCode": 200, "duration": 150.5,
|
|
68
|
-
})
|
|
69
|
-
result = client.destinations.test("dst_1")
|
|
70
|
-
assert isinstance(result, TestResult)
|
|
71
|
-
assert result.success is True
|
|
72
|
-
assert result.status_code == 200
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|