From f1f280d48c64f24a2e2ca111ec75148c8e60fc0f Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 28 Jul 2026 12:22:48 +0100 Subject: [PATCH] Normalize ISO8601 datetimes in mock server request body matching. Treat Z and +00:00 as equivalent when comparing serialized SDK request bodies to OAS examples, fixing alert suppression window integration tests. Co-authored-by: Cursor --- .../test/sdk_test_support/mock_server.py | 12 +++++++- .../test/test_mock_server.py | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/thousandeyes-sdk-core/test/sdk_test_support/mock_server.py b/thousandeyes-sdk-core/test/sdk_test_support/mock_server.py index 37354bca..8479728d 100644 --- a/thousandeyes-sdk-core/test/sdk_test_support/mock_server.py +++ b/thousandeyes-sdk-core/test/sdk_test_support/mock_server.py @@ -19,6 +19,7 @@ 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 @@ -30,12 +31,21 @@ 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 value + return _normalize_scalar(value) def _json_body_matches(expected: Any, actual: Any) -> bool: diff --git a/thousandeyes-sdk-core/test/test_mock_server.py b/thousandeyes-sdk-core/test/test_mock_server.py index d847a309..940c05ba 100644 --- a/thousandeyes-sdk-core/test/test_mock_server.py +++ b/thousandeyes-sdk-core/test/test_mock_server.py @@ -185,3 +185,32 @@ def test_mock_server_rejects_path_missing_path_variable(manifest): ) 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"}