mirror of
https://github.com/thousandeyes/thousandeyes-sdk-python.git
synced 2026-08-04 00:46:52 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75929e93d0 | ||
|
|
7e7c1c4c0a |
@ -12,7 +12,7 @@ This API provides the following operations to manage your organization:
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ Manage Cloud and Enterprise Agents available to your account in ThousandEyes.
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ For more information about the alerts, see [Alerts](https://docs.thousandeyes.co
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ For more information about monitors, see [Inside-Out BGP Visibility](https://doc
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ Manage connectors and operations.
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Optional, Type
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
@ -135,6 +135,22 @@ class ApiException(OpenApiException):
|
||||
pass
|
||||
self.headers = http_resp.getheaders()
|
||||
|
||||
@classmethod
|
||||
def exception_class_for_http_status(cls, status: int) -> Type["ApiException"]:
|
||||
if status == 400:
|
||||
return BadRequestException
|
||||
if status == 401:
|
||||
return UnauthorizedException
|
||||
if status == 403:
|
||||
return ForbiddenException
|
||||
if status == 404:
|
||||
return NotFoundException
|
||||
if status == 429:
|
||||
return TooManyRequestsException
|
||||
if 500 <= status <= 599:
|
||||
return ServiceException
|
||||
return ApiException
|
||||
|
||||
@classmethod
|
||||
def from_response(
|
||||
cls,
|
||||
@ -143,24 +159,8 @@ class ApiException(OpenApiException):
|
||||
body: Optional[str],
|
||||
data: Optional[Any],
|
||||
) -> Self:
|
||||
if http_resp.status == 400:
|
||||
raise BadRequestException(http_resp=http_resp, body=body, data=data)
|
||||
|
||||
if http_resp.status == 401:
|
||||
raise UnauthorizedException(http_resp=http_resp, body=body, data=data)
|
||||
|
||||
if http_resp.status == 403:
|
||||
raise ForbiddenException(http_resp=http_resp, body=body, data=data)
|
||||
|
||||
if http_resp.status == 404:
|
||||
raise NotFoundException(http_resp=http_resp, body=body, data=data)
|
||||
|
||||
if http_resp.status == 429:
|
||||
raise TooManyRequestsException(http_resp=http_resp, body=body, data=data)
|
||||
|
||||
if 500 <= http_resp.status <= 599:
|
||||
raise ServiceException(http_resp=http_resp, body=body, data=data)
|
||||
raise ApiException(http_resp=http_resp, body=body, data=data)
|
||||
exc_class = cls.exception_class_for_http_status(http_resp.status)
|
||||
raise exc_class(http_resp=http_resp, body=body, data=data)
|
||||
|
||||
def __str__(self):
|
||||
"""Custom error messages for exception"""
|
||||
|
||||
6
thousandeyes-sdk-core/test/conftest.py
Normal file
6
thousandeyes-sdk-core/test/conftest.py
Normal file
@ -0,0 +1,6 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_core_test_support = Path(__file__).resolve().parent
|
||||
if str(_core_test_support) not in sys.path:
|
||||
sys.path.insert(0, str(_core_test_support))
|
||||
1
thousandeyes-sdk-core/test/sdk_test_support/__init__.py
Normal file
1
thousandeyes-sdk-core/test/sdk_test_support/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Test-only helpers for SDK integration tests. Not shipped in the published package.
|
||||
228
thousandeyes-sdk-core/test/sdk_test_support/mock_server.py
Normal file
228
thousandeyes-sdk-core/test/sdk_test_support/mock_server.py
Normal file
@ -0,0 +1,228 @@
|
||||
# Copyright 2024 Cisco Systems, Inc. and its affiliates
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from typing import Any, Dict, Mapping, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from sdk_test_support.mock_server_types import OperationExpectation
|
||||
|
||||
OPERATION_ID_HEADER = "X-TE-Operation-Id"
|
||||
ERROR_STATUS_HEADER = "X-TE-Error-Status"
|
||||
AUTHORIZATION_HEADER = "Authorization"
|
||||
|
||||
|
||||
def _normalize_scalar(value: Any) -> Any:
|
||||
if isinstance(value, str) and "T" in value:
|
||||
try:
|
||||
return datetime.fromisoformat(value.replace("Z", "+00:00")).isoformat().replace("+00:00", "Z")
|
||||
except ValueError:
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
def _normalize_json(value: Any) -> Any:
|
||||
if isinstance(value, dict):
|
||||
return {key: _normalize_json(value[key]) for key in sorted(value.keys())}
|
||||
if isinstance(value, list):
|
||||
return [_normalize_json(item) for item in value]
|
||||
return _normalize_scalar(value)
|
||||
|
||||
|
||||
def _json_body_matches(expected: Any, actual: Any) -> bool:
|
||||
if isinstance(expected, dict) and isinstance(actual, dict):
|
||||
for key in actual:
|
||||
if key not in expected:
|
||||
return False
|
||||
if not _json_body_matches(expected[key], actual[key]):
|
||||
return False
|
||||
return True
|
||||
if isinstance(expected, list) and isinstance(actual, list):
|
||||
if len(expected) != len(actual):
|
||||
return False
|
||||
return all(_json_body_matches(expected_item, actual_item)
|
||||
for expected_item, actual_item in zip(expected, actual))
|
||||
return _normalize_json(expected) == _normalize_json(actual)
|
||||
|
||||
|
||||
class MockApiServer:
|
||||
def __init__(self, manifest: Mapping[str, OperationExpectation], host: str = "127.0.0.1", port: int = 0):
|
||||
self._manifest = dict(manifest)
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._server: Optional[ThreadingHTTPServer] = None
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
if self._server is None:
|
||||
raise RuntimeError("MockApiServer has not been started")
|
||||
return f"http://{self._host}:{self._server.server_port}"
|
||||
|
||||
def start(self) -> None:
|
||||
if self._server is not None:
|
||||
return
|
||||
|
||||
manifest = self._manifest
|
||||
handler = _build_handler(manifest)
|
||||
self._server = ThreadingHTTPServer((self._host, self._port), handler)
|
||||
self._thread = threading.Thread(target=self._server.serve_forever, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
def stop(self) -> None:
|
||||
if self._server is None:
|
||||
return
|
||||
self._server.shutdown()
|
||||
self._server.server_close()
|
||||
if self._thread is not None:
|
||||
self._thread.join(timeout=5)
|
||||
self._server = None
|
||||
self._thread = None
|
||||
|
||||
def __enter__(self) -> "MockApiServer":
|
||||
self.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb) -> None:
|
||||
self.stop()
|
||||
|
||||
|
||||
def _build_handler(manifest: Mapping[str, OperationExpectation]):
|
||||
class MockApiRequestHandler(BaseHTTPRequestHandler):
|
||||
def log_message(self, format: str, *args) -> None:
|
||||
return
|
||||
|
||||
def do_GET(self) -> None:
|
||||
self._handle_request("GET")
|
||||
|
||||
def do_POST(self) -> None:
|
||||
self._handle_request("POST")
|
||||
|
||||
def do_PUT(self) -> None:
|
||||
self._handle_request("PUT")
|
||||
|
||||
def do_PATCH(self) -> None:
|
||||
self._handle_request("PATCH")
|
||||
|
||||
def do_DELETE(self) -> None:
|
||||
self._handle_request("DELETE")
|
||||
|
||||
def _handle_request(self, method: str) -> None:
|
||||
auth_error = _validate_authorization(self.headers.get(AUTHORIZATION_HEADER))
|
||||
if auth_error is not None:
|
||||
self._write_json(auth_error, 401)
|
||||
return
|
||||
|
||||
operation_id = self.headers.get(OPERATION_ID_HEADER)
|
||||
if not operation_id:
|
||||
self._write_json({"detail": f"Missing required header {OPERATION_ID_HEADER}"}, 400)
|
||||
return
|
||||
|
||||
expectation = manifest.get(operation_id)
|
||||
if expectation is None:
|
||||
self._write_json({"detail": f"Unknown operation id {operation_id}"}, 400)
|
||||
return
|
||||
|
||||
if expectation.method.upper() != method.upper():
|
||||
self._write_json(
|
||||
{"detail": f"Unexpected HTTP method {method} for operation {operation_id}"},
|
||||
400,
|
||||
)
|
||||
return
|
||||
|
||||
parsed = urlparse(self.path)
|
||||
if not _path_matches(expectation.path, parsed.path):
|
||||
self._write_json({"detail": "Path does not match operation expectation"}, 400)
|
||||
return
|
||||
|
||||
error_status_header = self.headers.get(ERROR_STATUS_HEADER)
|
||||
if error_status_header:
|
||||
self._handle_error_response(expectation, error_status_header)
|
||||
return
|
||||
|
||||
body_bytes = _read_body(self)
|
||||
if expectation.request_body_example is not None:
|
||||
if not body_bytes:
|
||||
self._write_json({"detail": "Expected request body"}, 400)
|
||||
return
|
||||
try:
|
||||
request_json = json.loads(body_bytes.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
self._write_json({"detail": "Invalid JSON request body"}, 400)
|
||||
return
|
||||
if not _json_body_matches(expectation.request_body_example, request_json):
|
||||
self._write_json({"detail": "Request body does not match OAS example"}, 400)
|
||||
return
|
||||
|
||||
if expectation.success_body is None:
|
||||
self.send_response(expectation.success_status)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
self._write_json(
|
||||
expectation.success_body,
|
||||
expectation.success_status,
|
||||
expectation.success_content_type,
|
||||
)
|
||||
|
||||
def _handle_error_response(self, expectation: OperationExpectation, error_status_header: str) -> None:
|
||||
error_response = expectation.error_responses.get(error_status_header)
|
||||
if error_response is None:
|
||||
self._write_json(
|
||||
{"detail": f"No configured error response for status {error_status_header}"},
|
||||
400,
|
||||
)
|
||||
return
|
||||
if error_response.body is None:
|
||||
self.send_response(error_response.status)
|
||||
self.end_headers()
|
||||
return
|
||||
self._write_json(error_response.body, error_response.status, error_response.content_type)
|
||||
|
||||
def _write_json(self, body: Any, status: int, content_type: str = "application/json") -> None:
|
||||
payload = json.dumps(body).encode("utf-8")
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", content_type)
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
self.end_headers()
|
||||
self.wfile.write(payload)
|
||||
|
||||
return MockApiRequestHandler
|
||||
|
||||
|
||||
def _validate_authorization(value: Optional[str]) -> Optional[Dict[str, str]]:
|
||||
if value is None or not value.strip():
|
||||
return {"detail": f"Missing or empty required header {AUTHORIZATION_HEADER}"}
|
||||
return None
|
||||
|
||||
|
||||
def _read_body(handler: BaseHTTPRequestHandler) -> bytes:
|
||||
length = handler.headers.get("Content-Length")
|
||||
if not length:
|
||||
return b""
|
||||
return handler.rfile.read(int(length))
|
||||
|
||||
|
||||
def _path_matches(template: str, actual_path: str) -> bool:
|
||||
pattern = re.sub(r"\{[^/]+\}", r"[^/]+", template)
|
||||
pattern = f"^{pattern}$"
|
||||
return re.match(pattern, actual_path) is not None
|
||||
@ -0,0 +1,41 @@
|
||||
# Copyright 2024 Cisco Systems, Inc. and its affiliates
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ErrorResponseExpectation:
|
||||
status: int
|
||||
body: Any
|
||||
content_type: str = "application/problem+json"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OperationExpectation:
|
||||
operation_id: str
|
||||
method: str
|
||||
path: str
|
||||
success_status: int
|
||||
success_body: Any = None
|
||||
success_content_type: str = "application/json"
|
||||
request_body_example: Any = None
|
||||
path_param_examples: Dict[str, str] = field(default_factory=dict)
|
||||
query_param_examples: Dict[str, str] = field(default_factory=dict)
|
||||
error_responses: Dict[str, ErrorResponseExpectation] = field(default_factory=dict)
|
||||
28
thousandeyes-sdk-core/test/test_exceptions.py
Normal file
28
thousandeyes-sdk-core/test/test_exceptions.py
Normal file
@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
|
||||
from thousandeyes_sdk.core.exceptions import (
|
||||
ApiException,
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
NotFoundException,
|
||||
ServiceException,
|
||||
TooManyRequestsException,
|
||||
UnauthorizedException,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("status", "expected"),
|
||||
[
|
||||
(400, BadRequestException),
|
||||
(401, UnauthorizedException),
|
||||
(403, ForbiddenException),
|
||||
(404, NotFoundException),
|
||||
(429, TooManyRequestsException),
|
||||
(500, ServiceException),
|
||||
(503, ServiceException),
|
||||
(418, ApiException),
|
||||
],
|
||||
)
|
||||
def test_exception_class_for_http_status(status, expected):
|
||||
assert ApiException.exception_class_for_http_status(status) is expected
|
||||
315
thousandeyes-sdk-core/test/test_mock_server.py
Normal file
315
thousandeyes-sdk-core/test/test_mock_server.py
Normal file
@ -0,0 +1,315 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from sdk_test_support.mock_server import (
|
||||
AUTHORIZATION_HEADER,
|
||||
ERROR_STATUS_HEADER,
|
||||
OPERATION_ID_HEADER,
|
||||
MockApiServer,
|
||||
)
|
||||
from sdk_test_support.mock_server_types import ErrorResponseExpectation, OperationExpectation
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def manifest():
|
||||
return {
|
||||
"createAlertRule": OperationExpectation(
|
||||
operation_id="createAlertRule",
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
request_body_example={"ruleName": "Example"},
|
||||
success_status=201,
|
||||
success_body={"ruleId": "1"},
|
||||
error_responses={
|
||||
"400": ErrorResponseExpectation(
|
||||
status=400,
|
||||
body={"title": "Bad Request", "status": 400},
|
||||
)
|
||||
},
|
||||
),
|
||||
"deleteAlertRule": OperationExpectation(
|
||||
operation_id="deleteAlertRule",
|
||||
method="DELETE",
|
||||
path="/alerts/rules/{ruleId}",
|
||||
path_param_examples={"ruleId": "127094"},
|
||||
success_status=204,
|
||||
success_body=None,
|
||||
),
|
||||
"getAlertRule": OperationExpectation(
|
||||
operation_id="getAlertRule",
|
||||
method="GET",
|
||||
path="/alerts/rules/{ruleId}",
|
||||
path_param_examples={"ruleId": "127094"},
|
||||
success_status=200,
|
||||
success_body={"ruleId": "127094", "ruleName": "Example"},
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _request(server: MockApiServer, *, method: str, path: str, headers=None, body=None):
|
||||
import urllib.request
|
||||
|
||||
request_headers = {
|
||||
AUTHORIZATION_HEADER: "Bearer test-token",
|
||||
OPERATION_ID_HEADER: "createAlertRule",
|
||||
}
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
|
||||
data = None
|
||||
if body is not None:
|
||||
data = json.dumps(body).encode("utf-8")
|
||||
request_headers["Content-Type"] = "application/json"
|
||||
|
||||
request = urllib.request.Request(
|
||||
server.base_url + path,
|
||||
data=data,
|
||||
headers=request_headers,
|
||||
method=method,
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request) as response:
|
||||
return response.status, response.read()
|
||||
except urllib.error.HTTPError as exc:
|
||||
return exc.code, exc.read()
|
||||
|
||||
|
||||
def test_mock_server_happy_path(manifest):
|
||||
with MockApiServer(manifest) as server:
|
||||
status, body = _request(
|
||||
server,
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
body={"ruleName": "Example"},
|
||||
)
|
||||
assert status == 201
|
||||
assert json.loads(body.decode("utf-8")) == {"ruleId": "1"}
|
||||
|
||||
|
||||
def test_mock_server_rejects_missing_authorization(manifest):
|
||||
with MockApiServer(manifest) as server:
|
||||
status, _ = _request(
|
||||
server,
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
headers={AUTHORIZATION_HEADER: ""},
|
||||
body={"ruleName": "Example"},
|
||||
)
|
||||
assert status == 401
|
||||
|
||||
|
||||
def test_mock_server_rejects_invalid_request_body(manifest):
|
||||
with MockApiServer(manifest) as server:
|
||||
status, _ = _request(
|
||||
server,
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
body={"ruleName": "Wrong"},
|
||||
)
|
||||
assert status == 400
|
||||
|
||||
|
||||
def test_mock_server_error_path(manifest):
|
||||
with MockApiServer(manifest) as server:
|
||||
status, body = _request(
|
||||
server,
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
headers={ERROR_STATUS_HEADER: "400"},
|
||||
body={"unexpected": True},
|
||||
)
|
||||
assert status == 400
|
||||
assert json.loads(body.decode("utf-8"))["title"] == "Bad Request"
|
||||
|
||||
|
||||
def test_mock_server_ignores_readonly_fields_in_expected_body(manifest):
|
||||
readonly_manifest = {
|
||||
**manifest,
|
||||
"createAlertRule": OperationExpectation(
|
||||
operation_id="createAlertRule",
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
request_body_example={"ruleName": "Example", "ruleId": "read-only"},
|
||||
success_status=201,
|
||||
success_body={"ruleId": "1"},
|
||||
),
|
||||
}
|
||||
with MockApiServer(readonly_manifest) as server:
|
||||
status, body = _request(
|
||||
server,
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
body={"ruleName": "Example"},
|
||||
)
|
||||
assert status == 201
|
||||
assert json.loads(body.decode("utf-8")) == {"ruleId": "1"}
|
||||
|
||||
|
||||
def test_mock_server_no_content_response(manifest):
|
||||
with MockApiServer(manifest) as server:
|
||||
import urllib.request
|
||||
|
||||
request = urllib.request.Request(
|
||||
server.base_url + "/alerts/rules/127094",
|
||||
headers={
|
||||
AUTHORIZATION_HEADER: "Bearer test-token",
|
||||
OPERATION_ID_HEADER: "deleteAlertRule",
|
||||
},
|
||||
method="DELETE",
|
||||
)
|
||||
with urllib.request.urlopen(request) as response:
|
||||
assert response.status == 204
|
||||
assert response.read() == b""
|
||||
|
||||
|
||||
def test_mock_server_matches_path_variable(manifest):
|
||||
with MockApiServer(manifest) as server:
|
||||
status, body = _request(
|
||||
server,
|
||||
method="GET",
|
||||
path="/alerts/rules/127094",
|
||||
headers={OPERATION_ID_HEADER: "getAlertRule"},
|
||||
)
|
||||
assert status == 200
|
||||
assert json.loads(body.decode("utf-8")) == {"ruleId": "127094", "ruleName": "Example"}
|
||||
|
||||
|
||||
def test_mock_server_rejects_path_missing_path_variable(manifest):
|
||||
with MockApiServer(manifest) as server:
|
||||
status, body = _request(
|
||||
server,
|
||||
method="GET",
|
||||
path="/alerts/rules",
|
||||
headers={OPERATION_ID_HEADER: "getAlertRule"},
|
||||
)
|
||||
assert status == 400
|
||||
assert json.loads(body.decode("utf-8"))["detail"] == "Path does not match operation expectation"
|
||||
|
||||
|
||||
def test_mock_server_accepts_equivalent_iso8601_datetime_formats(manifest):
|
||||
datetime_manifest = {
|
||||
**manifest,
|
||||
"createAlertRule": OperationExpectation(
|
||||
operation_id="createAlertRule",
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
request_body_example={
|
||||
"ruleName": "Example",
|
||||
"startDate": "2017-07-01T05:00:00Z",
|
||||
},
|
||||
success_status=201,
|
||||
success_body={"ruleId": "1"},
|
||||
),
|
||||
}
|
||||
with MockApiServer(datetime_manifest) as server:
|
||||
status, body = _request(
|
||||
server,
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
body={
|
||||
"ruleName": "Example",
|
||||
"startDate": "2017-07-01T05:00:00+00:00",
|
||||
},
|
||||
)
|
||||
assert status == 201
|
||||
assert json.loads(body.decode("utf-8")) == {"ruleId": "1"}
|
||||
|
||||
|
||||
def test_mock_server_ignores_readonly_fields_in_nested_request_objects(manifest):
|
||||
nested_manifest = {
|
||||
**manifest,
|
||||
"createAlertRule": OperationExpectation(
|
||||
operation_id="createAlertRule",
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
request_body_example={
|
||||
"ruleName": "Example",
|
||||
"widgets": [
|
||||
{
|
||||
"title": "Widget Title",
|
||||
"id": "read-only-id",
|
||||
"embedUrl": "https://example.com/embed",
|
||||
}
|
||||
],
|
||||
},
|
||||
success_status=201,
|
||||
success_body={"ruleId": "1"},
|
||||
),
|
||||
}
|
||||
with MockApiServer(nested_manifest) as server:
|
||||
status, body = _request(
|
||||
server,
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
body={
|
||||
"ruleName": "Example",
|
||||
"widgets": [{"title": "Widget Title"}],
|
||||
},
|
||||
)
|
||||
assert status == 201
|
||||
assert json.loads(body.decode("utf-8")) == {"ruleId": "1"}
|
||||
|
||||
|
||||
def test_integration_error_assertion_fails_when_deserialized_error_does_not_match_oas_example():
|
||||
"""Generated error-path tests compare ApiException.data to the OAS error example."""
|
||||
import unittest
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
class Error(BaseModel):
|
||||
title: str
|
||||
status: int
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
def to_json(self) -> str:
|
||||
return self.model_dump_json()
|
||||
|
||||
mismatched_manifest = {
|
||||
"createAlertRule": OperationExpectation(
|
||||
operation_id="createAlertRule",
|
||||
method="POST",
|
||||
path="/alerts/rules",
|
||||
request_body_example={"ruleName": "Example"},
|
||||
success_status=201,
|
||||
success_body={"ruleId": "1"},
|
||||
error_responses={
|
||||
"400": ErrorResponseExpectation(
|
||||
status=400,
|
||||
body={"title": "Wrong Title", "status": 400},
|
||||
)
|
||||
},
|
||||
),
|
||||
}
|
||||
oas_example = {"title": "Bad Request", "status": 400}
|
||||
|
||||
with MockApiServer(mismatched_manifest) as server:
|
||||
request = urllib.request.Request(
|
||||
server.base_url + "/alerts/rules",
|
||||
data=json.dumps({"unexpected": True}).encode("utf-8"),
|
||||
headers={
|
||||
AUTHORIZATION_HEADER: "Bearer test-token",
|
||||
OPERATION_ID_HEADER: "createAlertRule",
|
||||
ERROR_STATUS_HEADER: "400",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
with pytest.raises(urllib.error.HTTPError) as http_error:
|
||||
urllib.request.urlopen(request)
|
||||
wire_body = json.loads(http_error.value.read().decode("utf-8"))
|
||||
|
||||
exception_data = Error.model_validate(wire_body)
|
||||
|
||||
def assert_constructed_model_matches_example_json(model, loaded_json):
|
||||
test_case = unittest.TestCase()
|
||||
test_case.assertEqual(
|
||||
json.dumps(loaded_json, sort_keys=True),
|
||||
json.dumps(json.loads(model.to_json()), sort_keys=True),
|
||||
)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
assert_constructed_model_matches_example_json(exception_data, oas_example)
|
||||
@ -13,7 +13,7 @@ For more information about credentials, see [Working With Secure Credentials](ht
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ Manage ThousandEyes Dashboards.
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ To access Emulation API operations, the following permissions are required:
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ For more information about Endpoint Agents, see [Endpoint Agents](https://docs.t
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ class Platform(str, Enum):
|
||||
PHONEOS = 'phoneos'
|
||||
ELUX = 'elux'
|
||||
CISCO_MINUS_WIRELESS = 'cisco-wireless'
|
||||
CISCO_MINUS_WIRELESS_MINUS_CLOUD = 'cisco-wireless-cloud'
|
||||
LINUX = 'linux'
|
||||
MAC = 'mac'
|
||||
ANDROID = 'android'
|
||||
|
||||
@ -12,7 +12,7 @@ The URLs for these API test data endpoints are provided within the test definiti
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ Manage labels applied to endpoint agents using this API.
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ Retrieve results for scheduled and dynamic tests on endpoint agents.
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**results** | [**List[HttpEndpointTestResult]**](HttpEndpointTestResult.md) | | [optional]
|
||||
**total_hits** | **int** | Total number of measurements that match the search criteria. | [optional]
|
||||
**test** | [**EndpointHttpServerTest**](EndpointHttpServerTest.md) | | [optional]
|
||||
**start_date** | **datetime** | (Optional) When passing `window` or `startDate` parameter, the client will also receive the `startDate` field indicating the UTC start date of the data's time range being retrieved (ISO date-time format). | [optional] [readonly]
|
||||
**end_date** | **datetime** | (Optional) When passing `window` or `endDate` parameter, the client will also receive the `endDate` field indicating the UTC end date of the data's time range being retrieved (ISO date-time format). | [optional] [readonly]
|
||||
|
||||
@ -11,6 +11,7 @@ Name | Type | Description | Notes
|
||||
**phy_mode** | **str** | Wireless network PHY mode. | [optional] [readonly]
|
||||
**rssi** | **int** | Wireless network RSSI. | [optional] [readonly]
|
||||
**noise** | **int** | Wireless network noise. | [optional] [readonly]
|
||||
**snr** | **int** | Wireless network signal-to-noise ratio (SNR), in dB. | [optional] [readonly]
|
||||
**quality** | **int** | Wireless network quality. | [optional] [readonly]
|
||||
**tx_rate** | **int** | Wireless network transmitted rate. | [optional] [readonly]
|
||||
**vendor** | **str** | Wireless network device vendor. | [optional] [readonly]
|
||||
|
||||
@ -17,7 +17,7 @@ import re # noqa: F401
|
||||
import json
|
||||
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field, StrictInt
|
||||
from typing import Any, ClassVar, Dict, List, Optional
|
||||
from thousandeyes_sdk.endpoint_test_results.models.endpoint_http_server_test import EndpointHttpServerTest
|
||||
from thousandeyes_sdk.endpoint_test_results.models.http_endpoint_test_result import HttpEndpointTestResult
|
||||
@ -30,11 +30,12 @@ class HttpEndpointTestResults(BaseModel):
|
||||
HttpEndpointTestResults
|
||||
""" # noqa: E501
|
||||
results: Optional[List[HttpEndpointTestResult]] = None
|
||||
total_hits: Optional[StrictInt] = Field(default=None, description="Total number of measurements that match the search criteria.", alias="totalHits")
|
||||
test: Optional[EndpointHttpServerTest] = None
|
||||
start_date: Optional[datetime] = Field(default=None, description="(Optional) When passing `window` or `startDate` parameter, the client will also receive the `startDate` field indicating the UTC start date of the data's time range being retrieved (ISO date-time format).", alias="startDate")
|
||||
end_date: Optional[datetime] = Field(default=None, description="(Optional) When passing `window` or `endDate` parameter, the client will also receive the `endDate` field indicating the UTC end date of the data's time range being retrieved (ISO date-time format).", alias="endDate")
|
||||
links: Optional[PaginationNextAndSelfLink] = Field(default=None, alias="_links")
|
||||
__properties: ClassVar[List[str]] = ["results", "test", "startDate", "endDate", "_links"]
|
||||
__properties: ClassVar[List[str]] = ["results", "totalHits", "test", "startDate", "endDate", "_links"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
@ -106,6 +107,7 @@ class HttpEndpointTestResults(BaseModel):
|
||||
|
||||
_obj = cls.model_validate({
|
||||
"results": [HttpEndpointTestResult.from_dict(_item) for _item in obj["results"]] if obj.get("results") is not None else None,
|
||||
"totalHits": obj.get("totalHits"),
|
||||
"test": EndpointHttpServerTest.from_dict(obj["test"]) if obj.get("test") is not None else None,
|
||||
"startDate": obj.get("startDate"),
|
||||
"endDate": obj.get("endDate"),
|
||||
|
||||
@ -31,10 +31,11 @@ class NetworkWirelessProfile(BaseModel):
|
||||
phy_mode: Optional[StrictStr] = Field(default=None, description="Wireless network PHY mode.", alias="phyMode")
|
||||
rssi: Optional[StrictInt] = Field(default=None, description="Wireless network RSSI.")
|
||||
noise: Optional[StrictInt] = Field(default=None, description="Wireless network noise.")
|
||||
snr: Optional[StrictInt] = Field(default=None, description="Wireless network signal-to-noise ratio (SNR), in dB.")
|
||||
quality: Optional[StrictInt] = Field(default=None, description="Wireless network quality.")
|
||||
tx_rate: Optional[StrictInt] = Field(default=None, description="Wireless network transmitted rate.", alias="txRate")
|
||||
vendor: Optional[StrictStr] = Field(default=None, description="Wireless network device vendor.")
|
||||
__properties: ClassVar[List[str]] = ["ssid", "bssid", "channel", "phyMode", "rssi", "noise", "quality", "txRate", "vendor"]
|
||||
__properties: ClassVar[List[str]] = ["ssid", "bssid", "channel", "phyMode", "rssi", "noise", "snr", "quality", "txRate", "vendor"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
@ -76,6 +77,7 @@ class NetworkWirelessProfile(BaseModel):
|
||||
* OpenAPI `readOnly` fields are excluded.
|
||||
* OpenAPI `readOnly` fields are excluded.
|
||||
* OpenAPI `readOnly` fields are excluded.
|
||||
* OpenAPI `readOnly` fields are excluded.
|
||||
"""
|
||||
excluded_fields: Set[str] = set([
|
||||
"ssid",
|
||||
@ -84,6 +86,7 @@ class NetworkWirelessProfile(BaseModel):
|
||||
"phy_mode",
|
||||
"rssi",
|
||||
"noise",
|
||||
"snr",
|
||||
"quality",
|
||||
"tx_rate",
|
||||
"vendor",
|
||||
@ -112,6 +115,7 @@ class NetworkWirelessProfile(BaseModel):
|
||||
"phyMode": obj.get("phyMode"),
|
||||
"rssi": obj.get("rssi"),
|
||||
"noise": obj.get("noise"),
|
||||
"snr": obj.get("snr"),
|
||||
"quality": obj.get("quality"),
|
||||
"txRate": obj.get("txRate"),
|
||||
"vendor": obj.get("vendor")
|
||||
|
||||
@ -30,6 +30,7 @@ class Platform(str, Enum):
|
||||
PHONEOS = 'phoneos'
|
||||
ELUX = 'elux'
|
||||
CISCO_MINUS_WIRELESS = 'cisco-wireless'
|
||||
CISCO_MINUS_WIRELESS_MINUS_CLOUD = 'cisco-wireless-cloud'
|
||||
LINUX = 'linux'
|
||||
MAC = 'mac'
|
||||
ANDROID = 'android'
|
||||
|
||||
@ -93,6 +93,7 @@ class TestHTTPServerEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"username" : "username",
|
||||
"sslVersionId" : "0"
|
||||
},
|
||||
"totalHits" : 12,
|
||||
"endDate" : "2022-07-18T22:00:54Z",
|
||||
"_links" : {
|
||||
"next" : {
|
||||
@ -193,6 +194,7 @@ class TestHTTPServerEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -321,6 +323,7 @@ class TestHTTPServerEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -518,6 +521,7 @@ class TestHTTPServerEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -646,6 +650,7 @@ class TestHTTPServerEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -843,6 +848,7 @@ class TestHTTPServerEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -971,6 +977,7 @@ class TestHTTPServerEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
|
||||
@ -553,6 +553,7 @@ class TestLocalNetworkEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -836,6 +837,7 @@ class TestLocalNetworkEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
|
||||
@ -206,6 +206,7 @@ class TestNetworkDynamicEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -347,6 +348,7 @@ class TestNetworkDynamicEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -676,6 +678,7 @@ class TestNetworkDynamicEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -890,6 +893,7 @@ class TestNetworkDynamicEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -1115,6 +1119,7 @@ class TestNetworkDynamicEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -1257,6 +1262,7 @@ class TestNetworkDynamicEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
|
||||
@ -228,6 +228,7 @@ class TestNetworkEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -358,6 +359,7 @@ class TestNetworkEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -546,6 +548,7 @@ class TestNetworkEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -676,6 +679,7 @@ class TestNetworkEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -967,6 +971,7 @@ class TestNetworkEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -1170,6 +1175,7 @@ class TestNetworkEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -1384,6 +1390,7 @@ class TestNetworkEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -1515,6 +1522,7 @@ class TestNetworkEndpointScheduledTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
|
||||
@ -717,6 +717,7 @@ class TestRealUserEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
@ -911,6 +912,7 @@ class TestRealUserEndpointTestResultsApi(unittest.TestCase):
|
||||
"wirelessProfile" : {
|
||||
"rssi" : -38,
|
||||
"bssid" : "4c:ba:ba:f4:fa:fa",
|
||||
"snr" : 57,
|
||||
"vendor" : "Cisco",
|
||||
"txRate" : 130,
|
||||
"channel" : 1,
|
||||
|
||||
@ -4,7 +4,7 @@ Manage endpoint agent dynamic and scheduled tests using the Endpoint Tests API.
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ With the Events API, you can perform the following tasks on the ThousandEyes pla
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ The response does not include the immediate test results. Use the Test Results e
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ For more information about Internet Insights, see the [Internet Insights](https:
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ Creates a new test snapshot in ThousandEyes.
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ For more information about ThousandEyes for OpenTelemetry, see the [product docu
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ Things to note with the ThousandEyes Tags API:
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ Get test result metrics for Network and Application Synthetics tests.
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ This API allows you to list, create, edit, and delete Network and Application Sy
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ Refer to the Usage API operations for detailed usage instructions and optional p
|
||||
|
||||
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 7.0.94
|
||||
- API version: 7.0.96
|
||||
- Generator version: 7.6.0
|
||||
- Build package: com.thousandeyes.api.codegen.ThousandeyesPythonGenerator
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user