cloud-dog-api-kit 0.13.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 (98) hide show
  1. cloud_dog_api_kit/__init__.py +170 -0
  2. cloud_dog_api_kit/a2a/__init__.py +53 -0
  3. cloud_dog_api_kit/a2a/card.py +138 -0
  4. cloud_dog_api_kit/a2a/events.py +1123 -0
  5. cloud_dog_api_kit/a2a/gateway.py +105 -0
  6. cloud_dog_api_kit/a2a/skill_audit.py +107 -0
  7. cloud_dog_api_kit/auth/__init__.py +35 -0
  8. cloud_dog_api_kit/auth/dependency.py +121 -0
  9. cloud_dog_api_kit/auth/rbac.py +107 -0
  10. cloud_dog_api_kit/auth/service_auth.py +54 -0
  11. cloud_dog_api_kit/clients/__init__.py +29 -0
  12. cloud_dog_api_kit/clients/circuit_breaker.py +39 -0
  13. cloud_dog_api_kit/clients/http_client.py +127 -0
  14. cloud_dog_api_kit/clients/retry.py +83 -0
  15. cloud_dog_api_kit/compat/__init__.py +37 -0
  16. cloud_dog_api_kit/compat/envelope.py +120 -0
  17. cloud_dog_api_kit/compat/profile.py +102 -0
  18. cloud_dog_api_kit/compat/routes.py +90 -0
  19. cloud_dog_api_kit/config.py +54 -0
  20. cloud_dog_api_kit/correlation/__init__.py +50 -0
  21. cloud_dog_api_kit/correlation/context.py +118 -0
  22. cloud_dog_api_kit/correlation/middleware.py +133 -0
  23. cloud_dog_api_kit/envelopes/__init__.py +37 -0
  24. cloud_dog_api_kit/envelopes/error.py +87 -0
  25. cloud_dog_api_kit/envelopes/success.py +84 -0
  26. cloud_dog_api_kit/errors/__init__.py +51 -0
  27. cloud_dog_api_kit/errors/exceptions.py +184 -0
  28. cloud_dog_api_kit/errors/handler.py +102 -0
  29. cloud_dog_api_kit/errors/taxonomy.py +62 -0
  30. cloud_dog_api_kit/factory.py +157 -0
  31. cloud_dog_api_kit/idempotency/__init__.py +28 -0
  32. cloud_dog_api_kit/idempotency/middleware.py +118 -0
  33. cloud_dog_api_kit/idempotency/store.py +100 -0
  34. cloud_dog_api_kit/lifecycle/__init__.py +39 -0
  35. cloud_dog_api_kit/lifecycle/hooks.py +75 -0
  36. cloud_dog_api_kit/lifecycle/shutdown.py +178 -0
  37. cloud_dog_api_kit/mcp/__init__.py +122 -0
  38. cloud_dog_api_kit/mcp/async_jobs.py +126 -0
  39. cloud_dog_api_kit/mcp/client_sdk.py +235 -0
  40. cloud_dog_api_kit/mcp/client_transport/__init__.py +47 -0
  41. cloud_dog_api_kit/mcp/client_transport/base.py +98 -0
  42. cloud_dog_api_kit/mcp/client_transport/exceptions.py +37 -0
  43. cloud_dog_api_kit/mcp/client_transport/http_jsonrpc.py +405 -0
  44. cloud_dog_api_kit/mcp/client_transport/legacy_sse.py +320 -0
  45. cloud_dog_api_kit/mcp/client_transport/stdio.py +322 -0
  46. cloud_dog_api_kit/mcp/client_transport/streamable_http.py +748 -0
  47. cloud_dog_api_kit/mcp/contract.py +113 -0
  48. cloud_dog_api_kit/mcp/error_mapper.py +84 -0
  49. cloud_dog_api_kit/mcp/gateway.py +117 -0
  50. cloud_dog_api_kit/mcp/legacy_sse.py +129 -0
  51. cloud_dog_api_kit/mcp/session.py +96 -0
  52. cloud_dog_api_kit/mcp/sync_handler.py +269 -0
  53. cloud_dog_api_kit/mcp/tool_audit.py +136 -0
  54. cloud_dog_api_kit/mcp/tool_router.py +180 -0
  55. cloud_dog_api_kit/mcp/transport.py +1041 -0
  56. cloud_dog_api_kit/middleware/__init__.py +39 -0
  57. cloud_dog_api_kit/middleware/cors.py +74 -0
  58. cloud_dog_api_kit/middleware/logging.py +98 -0
  59. cloud_dog_api_kit/middleware/request_size_limit.py +86 -0
  60. cloud_dog_api_kit/middleware/timeout.py +78 -0
  61. cloud_dog_api_kit/middleware/timing.py +52 -0
  62. cloud_dog_api_kit/openapi/__init__.py +30 -0
  63. cloud_dog_api_kit/openapi/customise.py +69 -0
  64. cloud_dog_api_kit/openapi/route.py +46 -0
  65. cloud_dog_api_kit/routers/__init__.py +41 -0
  66. cloud_dog_api_kit/routers/crud.py +173 -0
  67. cloud_dog_api_kit/routers/health.py +160 -0
  68. cloud_dog_api_kit/routers/jobs.py +69 -0
  69. cloud_dog_api_kit/routers/version.py +46 -0
  70. cloud_dog_api_kit/schemas/__init__.py +36 -0
  71. cloud_dog_api_kit/schemas/envelopes.py +37 -0
  72. cloud_dog_api_kit/schemas/filters.py +103 -0
  73. cloud_dog_api_kit/schemas/pagination.py +148 -0
  74. cloud_dog_api_kit/streaming/__init__.py +28 -0
  75. cloud_dog_api_kit/streaming/events.py +47 -0
  76. cloud_dog_api_kit/streaming/jsonl.py +68 -0
  77. cloud_dog_api_kit/streaming/sse.py +102 -0
  78. cloud_dog_api_kit/testing/__init__.py +46 -0
  79. cloud_dog_api_kit/testing/conformance.py +156 -0
  80. cloud_dog_api_kit/testing/fixtures.py +90 -0
  81. cloud_dog_api_kit/testing/flows/__init__.py +32 -0
  82. cloud_dog_api_kit/testing/flows/auth_flow.py +41 -0
  83. cloud_dog_api_kit/testing/flows/crud_flow.py +50 -0
  84. cloud_dog_api_kit/testing/flows/job_flow.py +42 -0
  85. cloud_dog_api_kit/testing/flows/streaming_flow.py +42 -0
  86. cloud_dog_api_kit/traceability_ids.py +84 -0
  87. cloud_dog_api_kit/versioning/__init__.py +30 -0
  88. cloud_dog_api_kit/versioning/header.py +52 -0
  89. cloud_dog_api_kit/web/__init__.py +7 -0
  90. cloud_dog_api_kit/web/proxy.py +222 -0
  91. cloud_dog_api_kit/webhook/__init__.py +29 -0
  92. cloud_dog_api_kit/webhook/signature.py +149 -0
  93. cloud_dog_api_kit-0.13.0.dist-info/METADATA +27 -0
  94. cloud_dog_api_kit-0.13.0.dist-info/RECORD +98 -0
  95. cloud_dog_api_kit-0.13.0.dist-info/WHEEL +4 -0
  96. cloud_dog_api_kit-0.13.0.dist-info/licenses/LICENCE +190 -0
  97. cloud_dog_api_kit-0.13.0.dist-info/licenses/LICENSE +176 -0
  98. cloud_dog_api_kit-0.13.0.dist-info/licenses/NOTICE +7 -0
@@ -0,0 +1,148 @@
1
+ # Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # cloud_dog_api_kit — Pagination models and dependency
16
+ #
17
+ # Licence: Proprietary — Cloud-Dog AI Platform
18
+ # Owner: Cloud-Dog AI
19
+ # Description: PaginationParams dataclass and FastAPI dependency for extracting
20
+ # pagination, sort, and filter parameters from list requests.
21
+ # Related requirements: FR4.3
22
+ # Related architecture: CC1.10
23
+
24
+ """Pagination models and FastAPI dependency."""
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import Any, Generic, TypeVar
29
+ from dataclasses import dataclass
30
+
31
+ from fastapi import Query
32
+ from pydantic import BaseModel
33
+
34
+
35
+ @dataclass
36
+ class PaginationParams:
37
+ """Pagination, sorting, and filtering parameters.
38
+
39
+ Attributes:
40
+ offset: The starting offset. Defaults to 0.
41
+ limit: The page size. Defaults to 50.
42
+ sort: The field to sort by, or None for default ordering.
43
+ sort_dir: Sort direction — ``asc`` or ``desc``. Defaults to ``asc``.
44
+
45
+ Related tests: UT1.13_PaginationModels, UT1.14_PaginationDependency
46
+ """
47
+
48
+ offset: int = 0
49
+ limit: int = 50
50
+ sort: str | None = None
51
+ sort_dir: str = "asc"
52
+
53
+
54
+ def get_pagination(
55
+ offset: int = Query(default=0, ge=0, description="Starting offset"),
56
+ limit: int = Query(default=50, ge=1, le=1000, description="Page size"),
57
+ sort: str | None = Query(default=None, description="Sort field (e.g., created_at:desc)"),
58
+ ) -> PaginationParams:
59
+ """FastAPI dependency for extracting pagination + sort parameters.
60
+
61
+ Parses the ``sort`` parameter if it contains a colon-separated direction
62
+ (e.g., ``created_at:desc``).
63
+
64
+ Args:
65
+ offset: Starting offset (query param).
66
+ limit: Page size (query param).
67
+ sort: Sort specification (query param).
68
+
69
+ Returns:
70
+ A populated PaginationParams instance.
71
+
72
+ Related tests: UT1.14_PaginationDependency
73
+ """
74
+ sort_field = sort
75
+ sort_dir = "asc"
76
+ if sort and ":" in sort:
77
+ parts = sort.rsplit(":", 1)
78
+ sort_field = parts[0]
79
+ if parts[1].lower() in ("asc", "desc"):
80
+ sort_dir = parts[1].lower()
81
+
82
+ return PaginationParams(
83
+ offset=offset,
84
+ limit=limit,
85
+ sort=sort_field,
86
+ sort_dir=sort_dir,
87
+ )
88
+
89
+
90
+ class PageInfo(BaseModel):
91
+ """Pagination metadata for list responses.
92
+
93
+ Related tests: UT1.13_PaginationModels
94
+ """
95
+
96
+ limit: int
97
+ offset: int
98
+ total: int | None = None
99
+ has_more: bool
100
+ cursor: str | None = None
101
+
102
+
103
+ T = TypeVar("T")
104
+
105
+
106
+ class PaginatedData(BaseModel, Generic[T]):
107
+ """Paginated list data within the success envelope.
108
+
109
+ Related tests: UT1.13_PaginationModels
110
+ """
111
+
112
+ items: list[T]
113
+ page: PageInfo
114
+
115
+
116
+ def paginated_envelope(
117
+ items: list[Any],
118
+ limit: int,
119
+ offset: int,
120
+ total: int | None = None,
121
+ has_more: bool = False,
122
+ cursor: str | None = None,
123
+ request_id: str = "",
124
+ correlation_id: str | None = None,
125
+ version: str = "v1",
126
+ ) -> dict[str, Any]:
127
+ """Build a paginated success response envelope dictionary.
128
+
129
+ Related tests: UT1.13_PaginationModels
130
+ """
131
+ return {
132
+ "ok": True,
133
+ "data": {
134
+ "items": items,
135
+ "page": {
136
+ "limit": limit,
137
+ "offset": offset,
138
+ "total": total,
139
+ "has_more": has_more,
140
+ "cursor": cursor,
141
+ },
142
+ },
143
+ "meta": {
144
+ "request_id": request_id,
145
+ "correlation_id": correlation_id,
146
+ "version": version,
147
+ },
148
+ }
@@ -0,0 +1,28 @@
1
+ # Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # cloud_dog_api_kit — Streaming helpers (SSE, JSONL)
16
+ #
17
+ # Licence: Proprietary — Cloud-Dog AI Platform
18
+ # Owner: Cloud-Dog AI
19
+ # Description: Helpers for creating SSE and JSONL streaming endpoints.
20
+ # Related requirements: FR8.1, FR8.2, FR8.3
21
+ # Related architecture: CC1.14
22
+
23
+ """Streaming helpers for cloud_dog_api_kit."""
24
+
25
+ from cloud_dog_api_kit.streaming.sse import create_sse_endpoint, SSEEvent
26
+ from cloud_dog_api_kit.streaming.jsonl import create_jsonl_endpoint
27
+
28
+ __all__ = ["create_sse_endpoint", "create_jsonl_endpoint", "SSEEvent"]
@@ -0,0 +1,47 @@
1
+ # Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # cloud_dog_api_kit — Streaming event models
16
+ #
17
+ # Licence: Proprietary — Cloud-Dog AI Platform
18
+ # Owner: Cloud-Dog AI
19
+ # Description: Standard SSE event model and serialisation.
20
+ # Related requirements: FR8.2
21
+ # Related architecture: SA1
22
+
23
+ """Streaming event models for cloud_dog_api_kit."""
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ from dataclasses import dataclass
29
+ from typing import Any
30
+
31
+
32
+ @dataclass
33
+ class SSEEvent:
34
+ """Standard server-sent event payload.
35
+
36
+ Related tests: UT1.22_SSEEventModel
37
+ """
38
+
39
+ type: str
40
+ data: Any = None
41
+ request_id: str = ""
42
+ job_id: str | None = None
43
+
44
+ def to_sse(self) -> str:
45
+ """Serialise to SSE wire format."""
46
+ payload = {"type": self.type, "data": self.data, "request_id": self.request_id, "job_id": self.job_id}
47
+ return f"event: {self.type}\ndata: {json.dumps(payload, ensure_ascii=False)}\n\n"
@@ -0,0 +1,68 @@
1
+ # Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # cloud_dog_api_kit — JSONL streaming helpers
16
+ #
17
+ # Licence: Proprietary — Cloud-Dog AI Platform
18
+ # Owner: Cloud-Dog AI
19
+ # Description: Line-delimited JSON streaming endpoint helpers.
20
+ # Related requirements: FR8.1, FR8.3
21
+ # Related architecture: CC1.14
22
+
23
+ """JSONL streaming helpers for cloud_dog_api_kit."""
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ from typing import AsyncGenerator
29
+
30
+ from starlette.responses import StreamingResponse
31
+
32
+ from cloud_dog_api_kit.correlation.context import get_request_id
33
+
34
+
35
+ async def _jsonl_generator(data_generator: AsyncGenerator) -> AsyncGenerator[str, None]:
36
+ """Wrap an async generator to produce JSONL output.
37
+
38
+ Args:
39
+ data_generator: Async generator yielding dicts or serialisable objects.
40
+
41
+ Yields:
42
+ JSON Lines strings (one JSON object per line).
43
+ """
44
+ request_id = get_request_id()
45
+ async for item in data_generator:
46
+ if isinstance(item, dict):
47
+ item["request_id"] = request_id
48
+ yield json.dumps(item, default=str) + "\n"
49
+ else:
50
+ yield json.dumps({"data": item, "request_id": request_id}, default=str) + "\n"
51
+
52
+
53
+ def create_jsonl_endpoint(data_generator: AsyncGenerator) -> StreamingResponse:
54
+ """Create a StreamingResponse for JSONL output.
55
+
56
+ Args:
57
+ data_generator: Async generator yielding data items.
58
+
59
+ Returns:
60
+ A StreamingResponse with ``application/x-ndjson`` content type.
61
+
62
+ Related tests: UT1.21_JSONLStreaming
63
+ """
64
+ return StreamingResponse(
65
+ content=_jsonl_generator(data_generator),
66
+ media_type="application/x-ndjson",
67
+ headers={"Cache-Control": "no-cache"},
68
+ )
@@ -0,0 +1,102 @@
1
+ # Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # cloud_dog_api_kit — SSE streaming helpers
16
+ #
17
+ # Licence: Proprietary — Cloud-Dog AI Platform
18
+ # Owner: Cloud-Dog AI
19
+ # Description: Server-Sent Events (SSE) streaming endpoint helpers with
20
+ # standard event types per PS-20.
21
+ # Related requirements: FR8.1, FR8.2, FR8.3
22
+ # Related architecture: CC1.14
23
+
24
+ """SSE streaming helpers for cloud_dog_api_kit."""
25
+
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ from typing import AsyncGenerator
30
+
31
+ from starlette.responses import StreamingResponse
32
+
33
+ from cloud_dog_api_kit.correlation.context import get_request_id
34
+ from cloud_dog_api_kit.streaming.events import SSEEvent
35
+
36
+ STANDARD_EVENT_TYPES = frozenset({"started", "delta", "progress", "tool_call", "completed", "error"})
37
+
38
+
39
+ async def _sse_generator(
40
+ event_generator: AsyncGenerator,
41
+ event_type_field: str = "type",
42
+ ) -> AsyncGenerator[str, None]:
43
+ """Wrap an async generator to produce SSE-formatted output.
44
+
45
+ Args:
46
+ event_generator: Async generator yielding event dicts or SSEEvent objects.
47
+ event_type_field: The key in event dicts that specifies the event type.
48
+
49
+ Yields:
50
+ SSE-formatted strings.
51
+ """
52
+ request_id = get_request_id()
53
+ try:
54
+ async for event in event_generator:
55
+ if isinstance(event, SSEEvent):
56
+ if not event.request_id:
57
+ event.request_id = request_id
58
+ yield event.to_sse()
59
+ elif isinstance(event, dict):
60
+ sse_event = SSEEvent(
61
+ type=event.get(event_type_field, "delta"),
62
+ data=event.get("data", event),
63
+ request_id=request_id,
64
+ job_id=event.get("job_id"),
65
+ )
66
+ yield sse_event.to_sse()
67
+ else:
68
+ yield f"event: delta\ndata: {json.dumps({'data': str(event), 'request_id': request_id})}\n\n"
69
+
70
+ # Send completion event
71
+ completion = SSEEvent(type="completed", request_id=request_id)
72
+ yield completion.to_sse()
73
+
74
+ except Exception as exc:
75
+ error_event = SSEEvent(
76
+ type="error",
77
+ data={"message": str(exc)},
78
+ request_id=request_id,
79
+ )
80
+ yield error_event.to_sse()
81
+
82
+
83
+ def create_sse_endpoint(
84
+ event_generator: AsyncGenerator,
85
+ event_type_field: str = "type",
86
+ ) -> StreamingResponse:
87
+ """Create a StreamingResponse for SSE output.
88
+
89
+ Args:
90
+ event_generator: Async generator yielding events.
91
+ event_type_field: The key in event dicts for event type.
92
+
93
+ Returns:
94
+ A StreamingResponse with ``text/event-stream`` content type.
95
+
96
+ Related tests: UT1.20_SSEStreaming, ST1.9_StreamingEndToEnd
97
+ """
98
+ return StreamingResponse(
99
+ content=_sse_generator(event_generator, event_type_field),
100
+ media_type="text/event-stream",
101
+ headers={"Cache-Control": "no-cache", "Connection": "keep-alive"},
102
+ )
@@ -0,0 +1,46 @@
1
+ # Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # cloud_dog_api_kit — Test scaffolding (fixtures, conformance, flow templates)
16
+ #
17
+ # Licence: Proprietary — Cloud-Dog AI Platform
18
+ # Owner: Cloud-Dog AI
19
+ # Description: Reusable test fixtures, conformance validators, and baseline
20
+ # flow templates for verifying API compliance.
21
+ # Related requirements: FR16.1, FR16.2, FR16.3
22
+ # Related architecture: CC1.19
23
+
24
+ """Test scaffolding for cloud_dog_api_kit."""
25
+
26
+ from cloud_dog_api_kit.testing.fixtures import create_test_client, create_auth_headers
27
+ from cloud_dog_api_kit.testing.conformance import (
28
+ validate_error_envelope,
29
+ validate_success_envelope,
30
+ validate_pagination_response,
31
+ validate_correlation_id,
32
+ )
33
+ from cloud_dog_api_kit.testing.flows import AuthFlow, CRUDFlow, JobFlow, StreamingFlow
34
+
35
+ __all__ = [
36
+ "create_test_client",
37
+ "create_auth_headers",
38
+ "validate_error_envelope",
39
+ "validate_success_envelope",
40
+ "validate_pagination_response",
41
+ "validate_correlation_id",
42
+ "AuthFlow",
43
+ "CRUDFlow",
44
+ "JobFlow",
45
+ "StreamingFlow",
46
+ ]
@@ -0,0 +1,156 @@
1
+ # Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # cloud_dog_api_kit — Conformance test helpers
16
+ #
17
+ # Licence: Proprietary — Cloud-Dog AI Platform
18
+ # Owner: Cloud-Dog AI
19
+ # Description: Conformance validators for verifying API responses match
20
+ # the standard envelope schemas, pagination format, and correlation ID rules.
21
+ # Related requirements: FR16.2
22
+ # Related architecture: CC1.19
23
+
24
+ """Conformance test helpers for cloud_dog_api_kit."""
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import Any
29
+
30
+
31
+ def validate_success_envelope(response_json: dict[str, Any]) -> list[str]:
32
+ """Validate a response against the success envelope schema.
33
+
34
+ Args:
35
+ response_json: The parsed JSON response body.
36
+
37
+ Returns:
38
+ A list of validation error messages. Empty if valid.
39
+
40
+ Related tests: UT1.35_ConformanceValidators
41
+ """
42
+ errors: list[str] = []
43
+ if not isinstance(response_json, dict):
44
+ return ["Response is not a dict"]
45
+
46
+ if response_json.get("ok") is not True:
47
+ errors.append("Missing or false 'ok' field")
48
+
49
+ if "data" not in response_json:
50
+ errors.append("Missing 'data' field")
51
+
52
+ if "meta" not in response_json:
53
+ errors.append("Missing 'meta' field")
54
+ else:
55
+ meta = response_json["meta"]
56
+ if not isinstance(meta, dict):
57
+ errors.append("'meta' is not a dict")
58
+ else:
59
+ if "request_id" not in meta:
60
+ errors.append("Missing 'meta.request_id'")
61
+
62
+ return errors
63
+
64
+
65
+ def validate_error_envelope(response_json: dict[str, Any]) -> list[str]:
66
+ """Validate a response against the error envelope schema.
67
+
68
+ Args:
69
+ response_json: The parsed JSON response body.
70
+
71
+ Returns:
72
+ A list of validation error messages. Empty if valid.
73
+
74
+ Related tests: UT1.35_ConformanceValidators
75
+ """
76
+ errors: list[str] = []
77
+ if not isinstance(response_json, dict):
78
+ return ["Response is not a dict"]
79
+
80
+ if response_json.get("ok") is not False:
81
+ errors.append("'ok' field should be false")
82
+
83
+ if "error" not in response_json:
84
+ errors.append("Missing 'error' field")
85
+ else:
86
+ error = response_json["error"]
87
+ if not isinstance(error, dict):
88
+ errors.append("'error' is not a dict")
89
+ else:
90
+ if "code" not in error:
91
+ errors.append("Missing 'error.code'")
92
+ if "message" not in error:
93
+ errors.append("Missing 'error.message'")
94
+ if "retryable" not in error:
95
+ errors.append("Missing 'error.retryable'")
96
+
97
+ if "meta" not in response_json:
98
+ errors.append("Missing 'meta' field")
99
+
100
+ return errors
101
+
102
+
103
+ def validate_pagination_response(response_json: dict[str, Any]) -> list[str]:
104
+ """Validate a paginated list response.
105
+
106
+ Args:
107
+ response_json: The parsed JSON response body.
108
+
109
+ Returns:
110
+ A list of validation error messages. Empty if valid.
111
+
112
+ Related tests: UT1.35_ConformanceValidators
113
+ """
114
+ errors = validate_success_envelope(response_json)
115
+ if errors:
116
+ return errors
117
+
118
+ data = response_json.get("data", {})
119
+ if "items" not in data:
120
+ errors.append("Missing 'data.items'")
121
+ elif not isinstance(data["items"], list):
122
+ errors.append("'data.items' is not a list")
123
+
124
+ if "page" not in data:
125
+ errors.append("Missing 'data.page'")
126
+ else:
127
+ page = data["page"]
128
+ if not isinstance(page, dict):
129
+ errors.append("'data.page' is not a dict")
130
+ else:
131
+ for field in ("limit", "offset", "has_more"):
132
+ if field not in page:
133
+ errors.append(f"Missing 'data.page.{field}'")
134
+
135
+ return errors
136
+
137
+
138
+ def validate_correlation_id(response_headers: dict[str, str]) -> list[str]:
139
+ """Validate that correlation ID is present in response headers.
140
+
141
+ Args:
142
+ response_headers: The HTTP response headers.
143
+
144
+ Returns:
145
+ A list of validation error messages. Empty if valid.
146
+
147
+ Related tests: UT1.35_ConformanceValidators
148
+ """
149
+ errors: list[str] = []
150
+ # Check for X-Request-Id (case-insensitive)
151
+ header_keys_lower = {k.lower(): v for k, v in response_headers.items()}
152
+ if "x-request-id" not in header_keys_lower:
153
+ errors.append("Missing X-Request-Id response header")
154
+ elif not header_keys_lower["x-request-id"]:
155
+ errors.append("Empty X-Request-Id response header")
156
+ return errors
@@ -0,0 +1,90 @@
1
+ # Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # cloud_dog_api_kit — Reusable test fixtures
16
+ #
17
+ # Licence: Proprietary — Cloud-Dog AI Platform
18
+ # Owner: Cloud-Dog AI
19
+ # Description: Reusable test fixtures for API testing — configured TestClient
20
+ # with auth headers and database session protocol.
21
+ # Related requirements: FR16.1
22
+ # Related architecture: CC1.19
23
+
24
+ """Reusable test fixtures for cloud_dog_api_kit."""
25
+
26
+ from __future__ import annotations
27
+
28
+
29
+ from fastapi import FastAPI
30
+ from httpx import ASGITransport, AsyncClient
31
+
32
+
33
+ def create_test_client(
34
+ app: FastAPI,
35
+ base_url: str = "http://test",
36
+ api_key: str | None = None,
37
+ bearer_token: str | None = None,
38
+ ) -> AsyncClient:
39
+ """Create a configured async test client for a FastAPI application.
40
+
41
+ Args:
42
+ app: The FastAPI application.
43
+ base_url: Base URL for the test client.
44
+ api_key: Optional API key to include in all requests.
45
+ bearer_token: Optional Bearer token to include in all requests.
46
+
47
+ Returns:
48
+ A configured httpx.AsyncClient.
49
+
50
+ Related tests: UT1.34_TestFixtures
51
+ """
52
+ headers: dict[str, str] = {}
53
+ if api_key:
54
+ headers["X-API-Key"] = api_key
55
+ if bearer_token:
56
+ headers["Authorization"] = f"Bearer {bearer_token}"
57
+
58
+ transport = ASGITransport(app=app)
59
+ return AsyncClient(
60
+ transport=transport,
61
+ base_url=base_url,
62
+ headers=headers,
63
+ )
64
+
65
+
66
+ def create_auth_headers(
67
+ api_key: str | None = None,
68
+ bearer_token: str | None = None,
69
+ app_id: str | None = None,
70
+ ) -> dict[str, str]:
71
+ """Create standard authentication headers.
72
+
73
+ Args:
74
+ api_key: API key value.
75
+ bearer_token: Bearer token value.
76
+ app_id: Application ID for service-to-service calls.
77
+
78
+ Returns:
79
+ A dictionary of HTTP headers.
80
+
81
+ Related tests: UT1.34_TestFixtures
82
+ """
83
+ headers: dict[str, str] = {}
84
+ if api_key:
85
+ headers["X-API-Key"] = api_key
86
+ if bearer_token:
87
+ headers["Authorization"] = f"Bearer {bearer_token}"
88
+ if app_id:
89
+ headers["X-App-Id"] = app_id
90
+ return headers