Skip to content

Commit b7d230d

Browse files
committed
V9 unit test fixes and v9-specific validation constants
Fix SSO test missing group_map_name param, task client assertion, batch test mock patches. Add v9-specific validation constants with custom validator error messages (separate from pydantic messages in legacy). Made-with: Cursor
1 parent 3575b88 commit b7d230d

18 files changed

Lines changed: 804 additions & 105 deletions

tests_v9/unit/aio/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import pytest
1010
import pytest_asyncio
1111

12-
from pyatlan_v9.client.aio.atlan import AsyncAtlanClient
1312
from pyatlan.client.common import AsyncApiCaller
13+
from pyatlan_v9.client.aio.atlan import AsyncAtlanClient
1414

1515

1616
@pytest.fixture(autouse=True)
@@ -42,4 +42,3 @@ def mock_async_api_caller():
4242
def mock_async_custom_metadata_cache():
4343
with patch.object(AsyncAtlanClient, "custom_metadata_cache") as cache:
4444
yield cache
45-

tests_v9/unit/aio/test_atlan_tag_name.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# SPDX-License-Identifier: Apache-2.0
22
# Copyright 2025 Atlan Pte. Ltd.
3-
import json as json_lib
43

5-
import pytest
64
import msgspec
5+
import pytest
6+
77
import pyatlan.cache.aio.atlan_tag_cache
8+
from pyatlan.model.constants import DELETED_
89
from pyatlan_v9.client.aio.atlan import AsyncAtlanClient
910
from pyatlan_v9.model.aio.core import AsyncAtlanRequest, AsyncAtlanResponse
1011
from pyatlan_v9.model.assets import Purpose
11-
from pyatlan_v9.model.assets.purpose import _purpose_from_nested, PurposeNested
12-
from pyatlan.model.constants import DELETED_
12+
from pyatlan_v9.model.assets.purpose import PurposeNested, _purpose_from_nested
1313
from pyatlan_v9.model.core import AtlanTagName
1414

1515
ATLAN_TAG_ID = "yiB7RLvdC2yeryLPjaDeHM"
@@ -79,8 +79,7 @@ def _assert_asset_tags(asset, is_retranslated=False):
7979
assert str(tags[2].type_name) == DELETED_
8080
if not is_retranslated:
8181
assert (
82-
tags[2].source_tag_attachments
83-
and len(tags[2].source_tag_attachments) == 1
82+
tags[2].source_tag_attachments and len(tags[2].source_tag_attachments) == 1
8483
)
8584
assert str(tags[3].type_name) == DELETED_
8685
if not is_retranslated:

tests_v9/unit/aio/test_audit_search.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,22 @@
77

88
import pytest
99

10-
from pyatlan_v9.client.aio.audit import V9AsyncAuditClient as AsyncAuditClient
1110
from pyatlan.client.common import AsyncApiCaller
1211
from pyatlan.client.common.audit import LOGGER
12+
from pyatlan_v9.client.aio.audit import V9AsyncAuditClient as AsyncAuditClient
1313
from pyatlan_v9.errors import InvalidRequestError
1414
from pyatlan_v9.model.aio.audit import AsyncAuditSearchResults
1515
from pyatlan_v9.model.audit import AuditSearchRequest
1616
from pyatlan_v9.model.enums import SortOrder
17-
from pyatlan_v9.model.search import DSL
18-
from pyatlan_v9.model.search import Bool, SortItem, Term
19-
20-
SEARCH_RESPONSES_DIR = Path(__file__).parent.parent.parent.parent / "tests" / "unit" / "data" / "search_responses"
17+
from pyatlan_v9.model.search import DSL, Bool, SortItem, Term
18+
19+
SEARCH_RESPONSES_DIR = (
20+
Path(__file__).parent.parent.parent.parent
21+
/ "tests"
22+
/ "unit"
23+
/ "data"
24+
/ "search_responses"
25+
)
2126
AUDIT_SEARCH_PAGING_JSON = "audit_search_paging.json"
2227

2328

tests_v9/unit/aio/test_client.py

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@
1010
import pytest
1111
from httpx import Headers
1212

13-
from pyatlan_v9.client.aio.asset import V9AsyncAssetClient as AsyncAssetClient
1413
from pyatlan.client.aio.batch import AsyncBatch
14+
from pyatlan.client.common import Search
15+
from pyatlan.client.common.asset import LOGGER as SHARED_LOGGER
16+
from pyatlan.model.aio.asset import AsyncIndexSearchResults
17+
from pyatlan.utils import get_python_version
18+
from pyatlan_v9.client.aio.asset import V9AsyncAssetClient as AsyncAssetClient
1519
from pyatlan_v9.client.aio.atlan import AsyncAtlanClient
1620
from pyatlan_v9.client.aio.group import V9AsyncGroupClient as AsyncGroupClient
17-
from pyatlan_v9.client.aio.search_log import V9AsyncSearchLogClient as AsyncSearchLogClient
21+
from pyatlan_v9.client.aio.search_log import (
22+
V9AsyncSearchLogClient as AsyncSearchLogClient,
23+
)
1824
from pyatlan_v9.client.aio.typedef import V9AsyncTypeDefClient as AsyncTypeDefClient
1925
from pyatlan_v9.client.aio.user import V9AsyncUserClient as AsyncUserClient
2026
from pyatlan_v9.client.asset import CustomMetadataHandling
21-
from pyatlan.client.common import Search
22-
from pyatlan.client.common.asset import LOGGER as SHARED_LOGGER
2327
from pyatlan_v9.errors import (
2428
ERROR_CODE_FOR_HTTP_STATUS,
2529
ApiError,
@@ -28,10 +32,6 @@
2832
InvalidRequestError,
2933
NotFoundError,
3034
)
31-
from pyatlan.model.aio.asset import AsyncIndexSearchResults
32-
from pyatlan.model.assets import (
33-
AtlasGlossaryTerm as LegacyAtlasGlossaryTerm,
34-
)
3535
from pyatlan_v9.model.assets import (
3636
Asset,
3737
AtlasGlossary,
@@ -49,7 +49,6 @@
4949
AtlanConnectorType,
5050
CertificateStatus,
5151
LineageDirection,
52-
SaveSemantic,
5352
SortOrder,
5453
)
5554
from pyatlan_v9.model.fluent_search import CompoundQuery, FluentSearch
@@ -60,18 +59,6 @@
6059
from pyatlan_v9.model.search_log import SearchLogRequest
6160
from pyatlan_v9.model.typedef import EnumDef
6261
from pyatlan_v9.model.user import AtlanUser, UserRequest
63-
from pyatlan.utils import get_python_version
64-
from tests.unit.constants import (
65-
TEST_ADMIN_CLIENT_METHODS,
66-
TEST_ASSET_CLIENT_METHODS_ASYNC,
67-
TEST_AUDIT_CLIENT_METHODS,
68-
TEST_GROUP_CLIENT_METHODS,
69-
TEST_ROLE_CLIENT_METHODS,
70-
TEST_SL_CLIENT_METHODS,
71-
TEST_TOKEN_CLIENT_METHODS,
72-
TEST_TYPEDEF_CLIENT_METHODS,
73-
TEST_USER_CLIENT_METHODS,
74-
)
7562
from tests.unit.model.constants import (
7663
CONNECTION_NAME,
7764
CONNECTOR_TYPE,
@@ -84,6 +71,17 @@
8471
PERSONA_NAME,
8572
PURPOSE_NAME,
8673
)
74+
from tests_v9.unit.constants import (
75+
TEST_ADMIN_CLIENT_METHODS,
76+
TEST_ASSET_CLIENT_METHODS_ASYNC,
77+
TEST_AUDIT_CLIENT_METHODS,
78+
TEST_GROUP_CLIENT_METHODS,
79+
TEST_ROLE_CLIENT_METHODS,
80+
TEST_SL_CLIENT_METHODS,
81+
TEST_TOKEN_CLIENT_METHODS,
82+
TEST_TYPEDEF_CLIENT_METHODS,
83+
TEST_USER_CLIENT_METHODS,
84+
)
8785

8886
V9_TEST_ASSET_CLIENT_METHODS_ASYNC = {
8987
**TEST_ASSET_CLIENT_METHODS_ASYNC,
@@ -374,7 +372,9 @@ async def test_append_terms_invalid_parameters_raises_error(
374372
),
375373
],
376374
)
377-
@patch("pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock)
375+
@patch(
376+
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock
377+
)
378378
@pytest.mark.asyncio
379379
async def test_append_terms_asset_retrieval_errors(
380380
mock_aexecute,
@@ -407,10 +407,12 @@ async def test_append_with_valid_guid_and_no_terms_returns_asset():
407407
terms = []
408408

409409
with patch(
410-
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock
410+
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async",
411+
new_callable=AsyncMock,
411412
) as mock_aexecute:
412413
with patch(
413-
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save", new_callable=AsyncMock
414+
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save",
415+
new_callable=AsyncMock,
414416
) as mock_save:
415417
# Set up async mock for search results
416418
mock_results = AsyncMock()
@@ -447,10 +449,12 @@ async def test_append_with_valid_guid_when_no_terms_present_returns_asset_with_g
447449
terms = [AtlasGlossaryTerm(qualified_name="term1")]
448450

449451
with patch(
450-
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock
452+
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async",
453+
new_callable=AsyncMock,
451454
) as mock_aexecute:
452455
with patch(
453-
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save", new_callable=AsyncMock
456+
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save",
457+
new_callable=AsyncMock,
454458
) as mock_save:
455459
# Set up async mock for search results
456460
mock_results = AsyncMock()
@@ -490,10 +494,12 @@ async def test_append_with_valid_guid_when_terms_present_returns_asset_with_comb
490494
terms = [new_term]
491495

492496
with patch(
493-
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock
497+
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async",
498+
new_callable=AsyncMock,
494499
) as mock_aexecute:
495500
with patch(
496-
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save", new_callable=AsyncMock
501+
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save",
502+
new_callable=AsyncMock,
497503
) as mock_save:
498504
# Set up async mock for search results
499505
mock_results = AsyncMock()
@@ -604,7 +610,9 @@ async def test_replace_terms_invalid_parameters_raises_error(
604610
),
605611
],
606612
)
607-
@patch("pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock)
613+
@patch(
614+
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock
615+
)
608616
@pytest.mark.asyncio
609617
async def test_replace_terms_asset_retrieval_errors(
610618
mock_aexecute,
@@ -640,10 +648,12 @@ async def test_replace_terms():
640648
terms = [AtlasGlossaryTerm(qualified_name="new_term")]
641649

642650
with patch(
643-
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock
651+
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async",
652+
new_callable=AsyncMock,
644653
) as mock_aexecute:
645654
with patch(
646-
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save", new_callable=AsyncMock
655+
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save",
656+
new_callable=AsyncMock,
647657
) as mock_save:
648658
# Set up async mock for search results
649659
mock_results = AsyncMock()
@@ -750,7 +760,9 @@ async def test_remove_terms_invalid_parameters_raises_error(
750760
),
751761
],
752762
)
753-
@patch("pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock)
763+
@patch(
764+
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock
765+
)
754766
@pytest.mark.asyncio
755767
async def test_remove_terms_asset_retrieval_errors(
756768
mock_aexecute,
@@ -789,10 +801,12 @@ async def test_remove_with_valid_guid_when_terms_present_returns_asset_with_term
789801
table.attributes.meanings = [existing_term, other_term]
790802

791803
with patch(
792-
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async", new_callable=AsyncMock
804+
"pyatlan_v9.model.fluent_search.FluentSearch.execute_async",
805+
new_callable=AsyncMock,
793806
) as mock_aexecute:
794807
with patch(
795-
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save", new_callable=AsyncMock
808+
"pyatlan_v9.client.aio.asset.V9AsyncAssetClient.save",
809+
new_callable=AsyncMock,
796810
) as mock_save:
797811
# Set up async mock for search results
798812
mock_results = AsyncMock()
@@ -1147,7 +1161,9 @@ def get_request(*args, **kwargs):
11471161
GLOSSARY_NAME,
11481162
None,
11491163
"1 validation error for FindCategoryByName\nname\n ensure this value has at least 1 characters",
1150-
marks=pytest.mark.skip(reason="v9: name validation happens deeper in call chain, not at model level"),
1164+
marks=pytest.mark.skip(
1165+
reason="v9: name validation happens deeper in call chain, not at model level"
1166+
),
11511167
),
11521168
(
11531169
1,
@@ -1448,7 +1464,9 @@ def get_request(*args, **kwargs):
14481464
GLOSSARY_NAME,
14491465
None,
14501466
"1 validation error for FindTermByName\nname\n ensure this value has at least 1 characters",
1451-
marks=pytest.mark.skip(reason="v9: name validation happens deeper in call chain, not at model level"),
1467+
marks=pytest.mark.skip(
1468+
reason="v9: name validation happens deeper in call chain, not at model level"
1469+
),
14521470
),
14531471
(
14541472
1,
@@ -2117,7 +2135,9 @@ async def test_user_create_with_info(
21172135
mock_async_api_caller.reset_mock()
21182136

21192137

2120-
@pytest.mark.skip(reason="Legacy AsyncTypeDefClient returns pydantic EnumDef, can't compare with v9 msgspec EnumDef")
2138+
@pytest.mark.skip(
2139+
reason="Legacy AsyncTypeDefClient returns pydantic EnumDef, can't compare with v9 msgspec EnumDef"
2140+
)
21212141
@pytest.mark.asyncio
21222142
async def test_typedef_get_by_name(mock_async_api_caller, type_def_get_by_name_json):
21232143
client = AsyncTypeDefClient(mock_async_api_caller)
@@ -2576,8 +2596,8 @@ async def test_add_when_capture_failure_false_then_exception_raised(
25762596
assert 0 == len(sut.created)
25772597
assert 0 == len(sut.updated)
25782598

2579-
@patch.object(LegacyAtlasGlossaryTerm, "trim_to_required")
2580-
@patch.object(LegacyAtlasGlossaryTerm, "ref_by_guid")
2599+
@patch.object(AtlasGlossaryTerm, "trim_to_required")
2600+
@patch.object(AtlasGlossaryTerm, "ref_by_guid")
25812601
@pytest.mark.asyncio
25822602
async def test_term_add(
25832603
self, mock_ref_by_guid, mock_trim_to_required, mock_async_atlan_client

tests_v9/unit/aio/test_client_proxy.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,12 @@ def test_async_atlan_client_proxy_with_ssl_cert_file_from_env(
158158

159159
# Use the fake certificate file
160160
fake_cert_path = str(
161-
Path(__file__).parent.parent.parent.parent / "tests" / "unit" / "data" / "fake_certificates" / "fake-cert.pem"
161+
Path(__file__).parent.parent.parent.parent
162+
/ "tests"
163+
/ "unit"
164+
/ "data"
165+
/ "fake_certificates"
166+
/ "fake-cert.pem"
162167
)
163168
monkeypatch.setenv("SSL_CERT_FILE", fake_cert_path)
164169

@@ -180,7 +185,12 @@ def test_async_atlan_client_explicit_args_override_env_vars(
180185

181186
# Use the fake certificate file
182187
fake_cert_path = str(
183-
Path(__file__).parent.parent.parent.parent / "tests" / "unit" / "data" / "fake_certificates" / "fake-cert.pem"
188+
Path(__file__).parent.parent.parent.parent
189+
/ "tests"
190+
/ "unit"
191+
/ "data"
192+
/ "fake_certificates"
193+
/ "fake-cert.pem"
184194
)
185195

186196
# Set environment variables

tests_v9/unit/aio/test_credential_client.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@
55
import msgspec
66
import pytest
77

8-
from pyatlan_v9.client.aio.credential import V9AsyncCredentialClient as AsyncCredentialClient
98
from pyatlan.client.common import AsyncApiCaller
10-
from pyatlan_v9.errors import InvalidRequestError
11-
from pyatlan_v9.model.credential import (
12-
Credential,
13-
CredentialListResponse,
14-
CredentialResponse,
15-
CredentialTestResponse,
9+
from pyatlan_v9.client.aio.credential import (
10+
V9AsyncCredentialClient as AsyncCredentialClient,
1611
)
12+
from pyatlan_v9.errors import InvalidRequestError
13+
from pyatlan_v9.model.credential import Credential, CredentialResponse
1714

1815
TEST_MISSING_TOKEN_ID = (
1916
"ATLAN-PYTHON-400-032 No ID was provided when attempting to update the API token."
@@ -43,13 +40,12 @@ def _to_dict(obj):
4340
"""Convert msgspec struct or Pydantic model to dict, preserving alias keys."""
4441
if isinstance(obj, msgspec.Struct):
4542
return msgspec.to_builtins(obj)
46-
elif hasattr(obj, 'dict'):
43+
elif hasattr(obj, "dict"):
4744
return obj.dict()
4845
else:
4946
return obj
5047

5148

52-
5349
@pytest.fixture()
5450
def mock_api_caller():
5551
return Mock(spec=AsyncApiCaller)

tests_v9/unit/aio/test_file_client.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88
import pytest
99

10+
from pyatlan.client.common import AsyncApiCaller
1011
from pyatlan_v9.client.aio.atlan import AsyncAtlanClient
1112
from pyatlan_v9.client.aio.file import V9AsyncFileClient as AsyncFileClient
12-
from pyatlan.client.common import AsyncApiCaller
1313
from pyatlan_v9.errors import InvalidRequestError
1414
from pyatlan_v9.model.file import PresignedURLRequest
15-
from tests.unit.constants import TEST_FILE_CLIENT_METHODS
15+
from tests_v9.unit.constants import TEST_FILE_CLIENT_METHODS
1616

1717
TEST_DATA_DIR = Path(__file__).parent.parent.parent.parent / "tests" / "unit" / "data"
1818
UPLOAD_FILE_PATH = str(TEST_DATA_DIR / "file_requests/upload.txt")
@@ -98,7 +98,9 @@ async def __anext__(self):
9898
raise StopAsyncIteration
9999

100100
mock_response.aiter_raw = lambda: AsyncBytesIterator(b"test data 12345.\n")
101-
mock_response.aiter_bytes = lambda chunk_size=8192: AsyncBytesIterator(b"test data 12345.\n")
101+
mock_response.aiter_bytes = lambda chunk_size=8192: AsyncBytesIterator(
102+
b"test data 12345.\n"
103+
)
102104
mock_response.aread = AsyncMock(return_value=b"test data 12345.\n")
103105

104106
async_context_manager = AsyncMock()

0 commit comments

Comments
 (0)