mockarty 0.1.0__py3-none-any.whl

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.
Files changed (54) hide show
  1. mockarty/__init__.py +207 -0
  2. mockarty/_base_client.py +122 -0
  3. mockarty/api/__init__.py +73 -0
  4. mockarty/api/_base.py +75 -0
  5. mockarty/api/agent_tasks.py +105 -0
  6. mockarty/api/collections.py +86 -0
  7. mockarty/api/contracts.py +328 -0
  8. mockarty/api/environments.py +117 -0
  9. mockarty/api/folders.py +92 -0
  10. mockarty/api/fuzzing.py +436 -0
  11. mockarty/api/generator.py +101 -0
  12. mockarty/api/health.py +64 -0
  13. mockarty/api/imports.py +66 -0
  14. mockarty/api/mocks.py +396 -0
  15. mockarty/api/namespace_settings.py +207 -0
  16. mockarty/api/namespaces.py +80 -0
  17. mockarty/api/perf.py +296 -0
  18. mockarty/api/proxy.py +47 -0
  19. mockarty/api/recorder.py +333 -0
  20. mockarty/api/stats.py +57 -0
  21. mockarty/api/stores.py +125 -0
  22. mockarty/api/tags.py +48 -0
  23. mockarty/api/templates.py +76 -0
  24. mockarty/api/testruns.py +82 -0
  25. mockarty/api/undefined.py +85 -0
  26. mockarty/async_client.py +310 -0
  27. mockarty/builders/__init__.py +7 -0
  28. mockarty/builders/mock_builder.py +461 -0
  29. mockarty/client.py +312 -0
  30. mockarty/errors.py +56 -0
  31. mockarty/models/__init__.py +120 -0
  32. mockarty/models/admin.py +136 -0
  33. mockarty/models/common.py +131 -0
  34. mockarty/models/condition.py +50 -0
  35. mockarty/models/contexts.py +180 -0
  36. mockarty/models/contract.py +78 -0
  37. mockarty/models/folders.py +23 -0
  38. mockarty/models/fuzzing.py +45 -0
  39. mockarty/models/generator.py +39 -0
  40. mockarty/models/imports.py +20 -0
  41. mockarty/models/mock.py +223 -0
  42. mockarty/models/recorder.py +36 -0
  43. mockarty/models/store.py +28 -0
  44. mockarty/models/tags.py +21 -0
  45. mockarty/models/templates.py +19 -0
  46. mockarty/models/testrun.py +26 -0
  47. mockarty/models/undefined.py +26 -0
  48. mockarty/testing/__init__.py +7 -0
  49. mockarty/testing/fixtures.py +73 -0
  50. mockarty-0.1.0.dist-info/METADATA +232 -0
  51. mockarty-0.1.0.dist-info/RECORD +54 -0
  52. mockarty-0.1.0.dist-info/WHEEL +4 -0
  53. mockarty-0.1.0.dist-info/entry_points.txt +2 -0
  54. mockarty-0.1.0.dist-info/licenses/LICENSE +21 -0
mockarty/__init__.py ADDED
@@ -0,0 +1,207 @@
1
+ # Copyright (c) 2024-2026 Mockarty. All rights reserved.
2
+
3
+ """Mockarty Python SDK -- Official client for the Mockarty multi-protocol mock server.
4
+
5
+ Quick start::
6
+
7
+ from mockarty import MockartyClient, MockBuilder, AssertAction
8
+
9
+ with MockartyClient(base_url="http://localhost:5770") as client:
10
+ mock = (
11
+ MockBuilder.http("/api/users/:id", "GET")
12
+ .id("user-get")
13
+ .respond(200, body={"id": "$.pathParam.id", "name": "$.fake.FirstName"})
14
+ .build()
15
+ )
16
+ result = client.mocks.create(mock)
17
+ print(f"Created mock: {result.mock.id}")
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from mockarty.api.agent_tasks import AgentTaskAPI, AsyncAgentTaskAPI
23
+ from mockarty.api.environments import AsyncEnvironmentAPI, EnvironmentAPI
24
+ from mockarty.api.folders import AsyncFolderAPI, FolderAPI
25
+ from mockarty.api.namespace_settings import (
26
+ AsyncNamespaceSettingsAPI,
27
+ NamespaceSettingsAPI,
28
+ )
29
+ from mockarty.api.proxy import AsyncProxyAPI, ProxyAPI
30
+ from mockarty.api.stats import AsyncStatsAPI, StatsAPI
31
+ from mockarty.api.tags import AsyncTagAPI, TagAPI
32
+ from mockarty.api.undefined import AsyncUndefinedAPI, UndefinedAPI
33
+ from mockarty.async_client import AsyncMockartyClient
34
+ from mockarty.builders.mock_builder import MockBuilder, OneOfBuilder
35
+ from mockarty.client import MockartyClient
36
+ from mockarty.errors import (
37
+ MockartyAPIError,
38
+ MockartyConflictError,
39
+ MockartyConnectionError,
40
+ MockartyError,
41
+ MockartyForbiddenError,
42
+ MockartyNotFoundError,
43
+ MockartyRateLimitError,
44
+ MockartyServerError,
45
+ MockartyTimeoutError,
46
+ MockartyUnauthorizedError,
47
+ )
48
+ from mockarty.models.common import (
49
+ Collection,
50
+ ErrorResponse,
51
+ HealthResponse,
52
+ MockLogs,
53
+ Page,
54
+ PerfComparison,
55
+ PerfConfig,
56
+ PerfResult,
57
+ PerfTask,
58
+ RequestLog,
59
+ TestRunResult,
60
+ )
61
+ from mockarty.models.condition import AssertAction, Condition
62
+ from mockarty.models.contexts import (
63
+ GraphQLRequestContext,
64
+ GrpcRequestContext,
65
+ HttpRequestContext,
66
+ KafkaRequestContext,
67
+ MCPRequestContext,
68
+ RabbitMQRequestContext,
69
+ SmtpRequestContext,
70
+ SoapRequestContext,
71
+ SocketRequestContext,
72
+ SSERequestContext,
73
+ )
74
+ from mockarty.models.contract import (
75
+ Contract,
76
+ ContractConfig,
77
+ ContractResult,
78
+ ContractValidationRequest,
79
+ ContractValidationResult,
80
+ ContractViolation,
81
+ )
82
+ from mockarty.models.folders import MockFolder
83
+ from mockarty.models.fuzzing import FuzzingConfig, FuzzingResult, FuzzingRun
84
+ from mockarty.models.generator import GeneratorPreview, GeneratorRequest, GeneratorResponse
85
+ from mockarty.models.imports import ImportResult
86
+ from mockarty.models.mock import (
87
+ Callback,
88
+ ContentResponse,
89
+ Extract,
90
+ Mock,
91
+ OneOf,
92
+ Proxy,
93
+ SaveMockResponse,
94
+ )
95
+ from mockarty.models.recorder import RecorderEntry, RecorderSession
96
+ from mockarty.models.store import DeleteFromStoreRequest, StoreData, StoreEntry
97
+ from mockarty.models.tags import Tag
98
+ from mockarty.models.templates import TemplateFile
99
+ from mockarty.models.testrun import TestRun
100
+ from mockarty.models.undefined import UndefinedRequest
101
+
102
+ __version__ = "0.1.0"
103
+
104
+ __all__ = [
105
+ # Clients
106
+ "MockartyClient",
107
+ "AsyncMockartyClient",
108
+ # Builders
109
+ "MockBuilder",
110
+ "OneOfBuilder",
111
+ # Core models
112
+ "Mock",
113
+ "ContentResponse",
114
+ "OneOf",
115
+ "Proxy",
116
+ "Callback",
117
+ "Extract",
118
+ "SaveMockResponse",
119
+ # Conditions
120
+ "Condition",
121
+ "AssertAction",
122
+ # Protocol contexts
123
+ "HttpRequestContext",
124
+ "GrpcRequestContext",
125
+ "MCPRequestContext",
126
+ "SocketRequestContext",
127
+ "SoapRequestContext",
128
+ "GraphQLRequestContext",
129
+ "SSERequestContext",
130
+ "KafkaRequestContext",
131
+ "RabbitMQRequestContext",
132
+ "SmtpRequestContext",
133
+ # Common models
134
+ "Page",
135
+ "HealthResponse",
136
+ "ErrorResponse",
137
+ "RequestLog",
138
+ "MockLogs",
139
+ "Collection",
140
+ "TestRunResult",
141
+ "PerfConfig",
142
+ "PerfTask",
143
+ "PerfResult",
144
+ "PerfComparison",
145
+ # Store models
146
+ "StoreEntry",
147
+ "StoreData",
148
+ "DeleteFromStoreRequest",
149
+ # Generator models
150
+ "GeneratorRequest",
151
+ "GeneratorPreview",
152
+ "GeneratorResponse",
153
+ # Fuzzing models
154
+ "FuzzingConfig",
155
+ "FuzzingRun",
156
+ "FuzzingResult",
157
+ # Contract models
158
+ "Contract",
159
+ "ContractConfig",
160
+ "ContractResult",
161
+ "ContractValidationRequest",
162
+ "ContractValidationResult",
163
+ "ContractViolation",
164
+ # Recorder models
165
+ "RecorderSession",
166
+ "RecorderEntry",
167
+ # Template models
168
+ "TemplateFile",
169
+ # Import models
170
+ "ImportResult",
171
+ # Test run models
172
+ "TestRun",
173
+ # Tag models
174
+ "Tag",
175
+ # Folder models
176
+ "MockFolder",
177
+ # Undefined request models
178
+ "UndefinedRequest",
179
+ # API resource classes
180
+ "TagAPI",
181
+ "AsyncTagAPI",
182
+ "FolderAPI",
183
+ "AsyncFolderAPI",
184
+ "UndefinedAPI",
185
+ "AsyncUndefinedAPI",
186
+ "StatsAPI",
187
+ "AsyncStatsAPI",
188
+ "AgentTaskAPI",
189
+ "AsyncAgentTaskAPI",
190
+ "NamespaceSettingsAPI",
191
+ "AsyncNamespaceSettingsAPI",
192
+ "ProxyAPI",
193
+ "AsyncProxyAPI",
194
+ "EnvironmentAPI",
195
+ "AsyncEnvironmentAPI",
196
+ # Errors
197
+ "MockartyError",
198
+ "MockartyAPIError",
199
+ "MockartyNotFoundError",
200
+ "MockartyUnauthorizedError",
201
+ "MockartyForbiddenError",
202
+ "MockartyConflictError",
203
+ "MockartyRateLimitError",
204
+ "MockartyServerError",
205
+ "MockartyConnectionError",
206
+ "MockartyTimeoutError",
207
+ ]
@@ -0,0 +1,122 @@
1
+ # Copyright (c) 2024-2026 Mockarty. All rights reserved.
2
+
3
+ """Shared logic for sync and async Mockarty clients."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ from typing import Any
9
+
10
+ import httpx
11
+
12
+ from mockarty.errors import (
13
+ MockartyAPIError,
14
+ MockartyConflictError,
15
+ MockartyConnectionError,
16
+ MockartyError,
17
+ MockartyForbiddenError,
18
+ MockartyNotFoundError,
19
+ MockartyRateLimitError,
20
+ MockartyServerError,
21
+ MockartyTimeoutError,
22
+ MockartyUnauthorizedError,
23
+ )
24
+
25
+ # Maps HTTP status codes to specific exception classes.
26
+ _STATUS_EXCEPTION_MAP: dict[int, type[MockartyAPIError]] = {
27
+ 401: MockartyUnauthorizedError,
28
+ 403: MockartyForbiddenError,
29
+ 404: MockartyNotFoundError,
30
+ 409: MockartyConflictError,
31
+ 429: MockartyRateLimitError,
32
+ }
33
+
34
+ DEFAULT_BASE_URL = "http://localhost:5770"
35
+ DEFAULT_NAMESPACE = "sandbox"
36
+ DEFAULT_TIMEOUT = 30.0
37
+ DEFAULT_MAX_RETRIES = 3
38
+
39
+
40
+ def resolve_base_url(base_url: str | None) -> str:
41
+ """Resolve the base URL from the argument or the environment."""
42
+ if base_url is not None:
43
+ return base_url.rstrip("/")
44
+ return os.environ.get("MOCKARTY_BASE_URL", DEFAULT_BASE_URL).rstrip("/")
45
+
46
+
47
+ def resolve_api_key(api_key: str | None) -> str | None:
48
+ """Resolve the API key from the argument or the environment."""
49
+ if api_key is not None:
50
+ return api_key
51
+ return os.environ.get("MOCKARTY_API_KEY")
52
+
53
+
54
+ def build_headers(api_key: str | None, namespace: str) -> dict[str, str]:
55
+ """Build default headers for every request."""
56
+ headers: dict[str, str] = {
57
+ "Content-Type": "application/json",
58
+ "Accept": "application/json",
59
+ "X-Namespace": namespace,
60
+ }
61
+ if api_key:
62
+ headers["Authorization"] = f"Bearer {api_key}"
63
+ return headers
64
+
65
+
66
+ def raise_for_status(response: httpx.Response) -> None:
67
+ """Inspect an httpx response and raise the appropriate SDK exception."""
68
+ if response.is_success:
69
+ return
70
+
71
+ status = response.status_code
72
+ request_id = response.headers.get("X-Request-Id")
73
+
74
+ # Try to extract error message from JSON body
75
+ try:
76
+ body = response.json()
77
+ message = body.get("error", response.text)
78
+ except Exception:
79
+ message = response.text or f"HTTP {status}"
80
+
81
+ exc_cls = _STATUS_EXCEPTION_MAP.get(status)
82
+ if exc_cls is not None:
83
+ raise exc_cls(status_code=status, message=message, request_id=request_id)
84
+
85
+ if status >= 500:
86
+ raise MockartyServerError(
87
+ status_code=status, message=message, request_id=request_id
88
+ )
89
+
90
+ raise MockartyAPIError(
91
+ status_code=status, message=message, request_id=request_id
92
+ )
93
+
94
+
95
+ def wrap_transport_error(exc: Exception) -> MockartyError: # type: ignore[return]
96
+ """Convert httpx transport errors into SDK exceptions."""
97
+ if isinstance(exc, httpx.TimeoutException):
98
+ raise MockartyTimeoutError(str(exc)) from exc
99
+ if isinstance(exc, httpx.ConnectError):
100
+ raise MockartyConnectionError(str(exc)) from exc
101
+ if isinstance(exc, (httpx.HTTPStatusError, httpx.HTTPError)):
102
+ raise MockartyConnectionError(str(exc)) from exc
103
+ raise MockartyConnectionError(str(exc)) from exc
104
+
105
+
106
+ def build_transport(max_retries: int) -> httpx.HTTPTransport:
107
+ """Build an httpx transport with retry configuration."""
108
+ return httpx.HTTPTransport(retries=max_retries)
109
+
110
+
111
+ def build_async_transport(max_retries: int) -> httpx.AsyncHTTPTransport:
112
+ """Build an async httpx transport with retry configuration."""
113
+ return httpx.AsyncHTTPTransport(retries=max_retries)
114
+
115
+
116
+ def serialize_body(obj: Any) -> Any:
117
+ """Serialize a Pydantic model or dict to a JSON-compatible dict."""
118
+ if obj is None:
119
+ return None
120
+ if hasattr(obj, "model_dump"):
121
+ return obj.model_dump(by_alias=True, exclude_none=True)
122
+ return obj
@@ -0,0 +1,73 @@
1
+ # Copyright (c) 2024-2026 Mockarty. All rights reserved.
2
+
3
+ """API resource classes for Mockarty client."""
4
+
5
+ from mockarty.api.agent_tasks import AgentTaskAPI, AsyncAgentTaskAPI
6
+ from mockarty.api.collections import AsyncCollectionAPI, CollectionAPI
7
+ from mockarty.api.contracts import AsyncContractAPI, ContractAPI
8
+ from mockarty.api.environments import AsyncEnvironmentAPI, EnvironmentAPI
9
+ from mockarty.api.folders import AsyncFolderAPI, FolderAPI
10
+ from mockarty.api.fuzzing import AsyncFuzzingAPI, FuzzingAPI
11
+ from mockarty.api.generator import AsyncGeneratorAPI, GeneratorAPI
12
+ from mockarty.api.health import AsyncHealthAPI, HealthAPI
13
+ from mockarty.api.imports import AsyncImportAPI, ImportAPI
14
+ from mockarty.api.mocks import AsyncMockAPI, MockAPI
15
+ from mockarty.api.namespace_settings import (
16
+ AsyncNamespaceSettingsAPI,
17
+ NamespaceSettingsAPI,
18
+ )
19
+ from mockarty.api.namespaces import AsyncNamespaceAPI, NamespaceAPI
20
+ from mockarty.api.perf import AsyncPerfAPI, PerfAPI
21
+ from mockarty.api.proxy import AsyncProxyAPI, ProxyAPI
22
+ from mockarty.api.recorder import AsyncRecorderAPI, RecorderAPI
23
+ from mockarty.api.stats import AsyncStatsAPI, StatsAPI
24
+ from mockarty.api.stores import AsyncStoreAPI, StoreAPI
25
+ from mockarty.api.tags import AsyncTagAPI, TagAPI
26
+ from mockarty.api.templates import AsyncTemplateAPI, TemplateAPI
27
+ from mockarty.api.testruns import AsyncTestRunAPI, TestRunAPI
28
+ from mockarty.api.undefined import AsyncUndefinedAPI, UndefinedAPI
29
+
30
+ __all__ = [
31
+ "AgentTaskAPI",
32
+ "AsyncAgentTaskAPI",
33
+ "AsyncCollectionAPI",
34
+ "AsyncContractAPI",
35
+ "AsyncEnvironmentAPI",
36
+ "AsyncFolderAPI",
37
+ "AsyncFuzzingAPI",
38
+ "AsyncGeneratorAPI",
39
+ "AsyncHealthAPI",
40
+ "AsyncImportAPI",
41
+ "AsyncMockAPI",
42
+ "AsyncNamespaceAPI",
43
+ "AsyncNamespaceSettingsAPI",
44
+ "AsyncPerfAPI",
45
+ "AsyncProxyAPI",
46
+ "AsyncRecorderAPI",
47
+ "AsyncStatsAPI",
48
+ "AsyncStoreAPI",
49
+ "AsyncTagAPI",
50
+ "AsyncTemplateAPI",
51
+ "AsyncTestRunAPI",
52
+ "AsyncUndefinedAPI",
53
+ "CollectionAPI",
54
+ "ContractAPI",
55
+ "EnvironmentAPI",
56
+ "FolderAPI",
57
+ "FuzzingAPI",
58
+ "GeneratorAPI",
59
+ "HealthAPI",
60
+ "ImportAPI",
61
+ "MockAPI",
62
+ "NamespaceAPI",
63
+ "NamespaceSettingsAPI",
64
+ "PerfAPI",
65
+ "ProxyAPI",
66
+ "RecorderAPI",
67
+ "StatsAPI",
68
+ "StoreAPI",
69
+ "TagAPI",
70
+ "TemplateAPI",
71
+ "TestRunAPI",
72
+ "UndefinedAPI",
73
+ ]
mockarty/api/_base.py ADDED
@@ -0,0 +1,75 @@
1
+ # Copyright (c) 2024-2026 Mockarty. All rights reserved.
2
+
3
+ """Base classes for sync and async API resources."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any
8
+
9
+ import httpx
10
+
11
+ from mockarty._base_client import raise_for_status, serialize_body, wrap_transport_error
12
+
13
+
14
+ class SyncAPIBase:
15
+ """Base class for synchronous API resource groups."""
16
+
17
+ def __init__(self, client: httpx.Client, namespace: str) -> None:
18
+ self._client = client
19
+ self._namespace = namespace
20
+
21
+ def _request(
22
+ self,
23
+ method: str,
24
+ path: str,
25
+ *,
26
+ json: Any = None,
27
+ params: dict[str, Any] | None = None,
28
+ headers: dict[str, str] | None = None,
29
+ ) -> httpx.Response:
30
+ """Execute an HTTP request and raise on errors."""
31
+ kwargs: dict[str, Any] = {"params": params}
32
+ if json is not None:
33
+ kwargs["json"] = serialize_body(json)
34
+ if headers:
35
+ kwargs["headers"] = headers
36
+
37
+ try:
38
+ response = self._client.request(method, path, **kwargs)
39
+ except Exception as exc:
40
+ wrap_transport_error(exc)
41
+
42
+ raise_for_status(response)
43
+ return response
44
+
45
+
46
+ class AsyncAPIBase:
47
+ """Base class for asynchronous API resource groups."""
48
+
49
+ def __init__(self, client: httpx.AsyncClient, namespace: str) -> None:
50
+ self._client = client
51
+ self._namespace = namespace
52
+
53
+ async def _request(
54
+ self,
55
+ method: str,
56
+ path: str,
57
+ *,
58
+ json: Any = None,
59
+ params: dict[str, Any] | None = None,
60
+ headers: dict[str, str] | None = None,
61
+ ) -> httpx.Response:
62
+ """Execute an async HTTP request and raise on errors."""
63
+ kwargs: dict[str, Any] = {"params": params}
64
+ if json is not None:
65
+ kwargs["json"] = serialize_body(json)
66
+ if headers:
67
+ kwargs["headers"] = headers
68
+
69
+ try:
70
+ response = await self._client.request(method, path, **kwargs)
71
+ except Exception as exc:
72
+ wrap_transport_error(exc)
73
+
74
+ raise_for_status(response)
75
+ return response
@@ -0,0 +1,105 @@
1
+ # Copyright (c) 2024-2026 Mockarty. All rights reserved.
2
+
3
+ """Agent tasks API resource for AI-assisted mock generation."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any
8
+
9
+ from mockarty.api._base import AsyncAPIBase, SyncAPIBase
10
+
11
+
12
+ class AgentTaskAPI(SyncAPIBase):
13
+ """Synchronous Agent Task API resource."""
14
+
15
+ def list(self) -> list[dict[str, Any]]:
16
+ """List all agent tasks."""
17
+ resp = self._request("GET", "/api/v1/agent-tasks")
18
+ data = resp.json()
19
+ if isinstance(data, list):
20
+ return data
21
+ if isinstance(data, dict):
22
+ return data.get("items") or data.get("tasks") or []
23
+ return []
24
+
25
+ def get(self, task_id: str) -> dict[str, Any]:
26
+ """Get an agent task by ID."""
27
+ resp = self._request("GET", f"/api/v1/agent-tasks/{task_id}")
28
+ return resp.json()
29
+
30
+ def submit(self, task: dict[str, Any]) -> dict[str, Any]:
31
+ """Submit a new agent task."""
32
+ resp = self._request("POST", "/api/v1/agent-tasks", json=task)
33
+ return resp.json()
34
+
35
+ def cancel(self, task_id: str) -> None:
36
+ """Cancel a running agent task."""
37
+ self._request("POST", f"/api/v1/agent-tasks/{task_id}/cancel")
38
+
39
+ def delete(self, task_id: str) -> None:
40
+ """Delete an agent task."""
41
+ self._request("DELETE", f"/api/v1/agent-tasks/{task_id}")
42
+
43
+ def clear_all(self) -> None:
44
+ """Delete all agent tasks."""
45
+ self._request("DELETE", "/api/v1/agent-tasks")
46
+
47
+ def rerun(self, task_id: str) -> dict[str, Any]:
48
+ """Re-run a completed agent task."""
49
+ resp = self._request("POST", f"/api/v1/agent-tasks/{task_id}/rerun")
50
+ return resp.json()
51
+
52
+ def export(self, task_id: str) -> bytes:
53
+ """Export an agent task result as raw bytes."""
54
+ resp = self._request("GET", f"/api/v1/agent-tasks/{task_id}/export")
55
+ return resp.content
56
+
57
+
58
+ class AsyncAgentTaskAPI(AsyncAPIBase):
59
+ """Asynchronous Agent Task API resource."""
60
+
61
+ async def list(self) -> list[dict[str, Any]]:
62
+ """List all agent tasks."""
63
+ resp = await self._request("GET", "/api/v1/agent-tasks")
64
+ data = resp.json()
65
+ if isinstance(data, list):
66
+ return data
67
+ if isinstance(data, dict):
68
+ return data.get("items") or data.get("tasks") or []
69
+ return []
70
+
71
+ async def get(self, task_id: str) -> dict[str, Any]:
72
+ """Get an agent task by ID."""
73
+ resp = await self._request("GET", f"/api/v1/agent-tasks/{task_id}")
74
+ return resp.json()
75
+
76
+ async def submit(self, task: dict[str, Any]) -> dict[str, Any]:
77
+ """Submit a new agent task."""
78
+ resp = await self._request("POST", "/api/v1/agent-tasks", json=task)
79
+ return resp.json()
80
+
81
+ async def cancel(self, task_id: str) -> None:
82
+ """Cancel a running agent task."""
83
+ await self._request("POST", f"/api/v1/agent-tasks/{task_id}/cancel")
84
+
85
+ async def delete(self, task_id: str) -> None:
86
+ """Delete an agent task."""
87
+ await self._request("DELETE", f"/api/v1/agent-tasks/{task_id}")
88
+
89
+ async def clear_all(self) -> None:
90
+ """Delete all agent tasks."""
91
+ await self._request("DELETE", "/api/v1/agent-tasks")
92
+
93
+ async def rerun(self, task_id: str) -> dict[str, Any]:
94
+ """Re-run a completed agent task."""
95
+ resp = await self._request(
96
+ "POST", f"/api/v1/agent-tasks/{task_id}/rerun"
97
+ )
98
+ return resp.json()
99
+
100
+ async def export(self, task_id: str) -> bytes:
101
+ """Export an agent task result as raw bytes."""
102
+ resp = await self._request(
103
+ "GET", f"/api/v1/agent-tasks/{task_id}/export"
104
+ )
105
+ return resp.content
@@ -0,0 +1,86 @@
1
+ # Copyright (c) 2024-2026 Mockarty. All rights reserved.
2
+
3
+ """Collection (API Tester) API resource."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from mockarty.api._base import AsyncAPIBase, SyncAPIBase
8
+ from mockarty.models.common import Collection, TestRunResult
9
+
10
+
11
+ class CollectionAPI(SyncAPIBase):
12
+ """Synchronous Collection API resource."""
13
+
14
+ def list(self) -> list[Collection]:
15
+ """List all test collections."""
16
+ resp = self._request("GET", "/api/v1/api-tester/collections")
17
+ data = resp.json()
18
+ if isinstance(data, list):
19
+ return [Collection.model_validate(c) for c in data]
20
+ if isinstance(data, dict):
21
+ items = data.get("items") or data.get("collections") or []
22
+ return [Collection.model_validate(c) for c in items]
23
+ return []
24
+
25
+ def get(self, collection_id: str) -> Collection:
26
+ """Get a single collection by ID."""
27
+ resp = self._request("GET", f"/api/v1/api-tester/collections/{collection_id}")
28
+ return Collection.model_validate(resp.json())
29
+
30
+ def execute(self, collection_id: str) -> TestRunResult:
31
+ """Execute all tests in a collection."""
32
+ resp = self._request("POST", f"/api/v1/api-tester/collections/{collection_id}/run")
33
+ return TestRunResult.model_validate(resp.json())
34
+
35
+ def execute_multiple(self, ids: list[str]) -> TestRunResult:
36
+ """Execute tests from multiple collections."""
37
+ resp = self._request(
38
+ "POST", "/api/v1/api-tester/collections/run-multiple", json={"collectionIds": ids}
39
+ )
40
+ return TestRunResult.model_validate(resp.json())
41
+
42
+ def export(self, collection_id: str) -> bytes:
43
+ """Export a collection as a downloadable archive."""
44
+ resp = self._request("GET", f"/api/v1/api-tester/collections/{collection_id}/export")
45
+ return resp.content
46
+
47
+
48
+ class AsyncCollectionAPI(AsyncAPIBase):
49
+ """Asynchronous Collection API resource."""
50
+
51
+ async def list(self) -> list[Collection]:
52
+ """List all test collections."""
53
+ resp = await self._request("GET", "/api/v1/api-tester/collections")
54
+ data = resp.json()
55
+ if isinstance(data, list):
56
+ return [Collection.model_validate(c) for c in data]
57
+ if isinstance(data, dict):
58
+ items = data.get("items") or data.get("collections") or []
59
+ return [Collection.model_validate(c) for c in items]
60
+ return []
61
+
62
+ async def get(self, collection_id: str) -> Collection:
63
+ """Get a single collection by ID."""
64
+ resp = await self._request("GET", f"/api/v1/api-tester/collections/{collection_id}")
65
+ return Collection.model_validate(resp.json())
66
+
67
+ async def execute(self, collection_id: str) -> TestRunResult:
68
+ """Execute all tests in a collection."""
69
+ resp = await self._request(
70
+ "POST", f"/api/v1/api-tester/collections/{collection_id}/run"
71
+ )
72
+ return TestRunResult.model_validate(resp.json())
73
+
74
+ async def execute_multiple(self, ids: list[str]) -> TestRunResult:
75
+ """Execute tests from multiple collections."""
76
+ resp = await self._request(
77
+ "POST", "/api/v1/api-tester/collections/run-multiple", json={"collectionIds": ids}
78
+ )
79
+ return TestRunResult.model_validate(resp.json())
80
+
81
+ async def export(self, collection_id: str) -> bytes:
82
+ """Export a collection as a downloadable archive."""
83
+ resp = await self._request(
84
+ "GET", f"/api/v1/api-tester/collections/{collection_id}/export"
85
+ )
86
+ return resp.content