@@ -1893,3 +1893,162 @@ async def test_none_context_no_parentheses(self):
18931893 client = _AsyncODataClient (auth , "https://example.crm.dynamics.com" , config = config )
18941894 headers = await client ._headers ()
18951895 assert "(" not in headers ["User-Agent" ]
1896+
1897+
1898+ class TestAsyncRetrieveMetadataChanges :
1899+ """_AsyncODataClient._retrieve_metadata_changes / _get_column typed (#203)."""
1900+
1901+ def _client_with_attrs (self , entities ):
1902+ client = _make_client ()
1903+ client ._request_metadata_with_retry = AsyncMock (return_value = _resp (json_data = {"EntityMetadata" : entities }))
1904+ return client
1905+
1906+ async def test_typed_read_single_request_with_projection_and_criteria (self ):
1907+ client = self ._client_with_attrs (
1908+ [
1909+ {
1910+ "LogicalName" : "account" ,
1911+ "Attributes" : [
1912+ {
1913+ "@odata.type" : "#Microsoft.Dynamics.CRM.StringAttributeMetadata" ,
1914+ "LogicalName" : "emailaddress1" ,
1915+ "MaxLength" : 100 ,
1916+ }
1917+ ],
1918+ }
1919+ ]
1920+ )
1921+ attrs = await client ._retrieve_metadata_changes ("account" , attribute_logical_name = "emailaddress1" )
1922+ assert len (attrs ) == 1
1923+ assert attrs [0 ]["MaxLength" ] == 100
1924+ call = client ._request_metadata_with_retry .call_args
1925+ assert call .args [0 ] == "get"
1926+ assert "RetrieveMetadataChanges(Query=@p1)" in call .args [1 ]
1927+ query = json .loads (call .kwargs ["params" ]["@p1" ])
1928+ assert query ["Criteria" ]["Conditions" ][0 ]["Value" ]["Value" ] == "account"
1929+ assert query ["AttributeQuery" ]["Criteria" ]["Conditions" ][0 ]["Value" ]["Value" ] == "emailaddress1"
1930+ assert "MaxLength" in query ["AttributeQuery" ]["Properties" ]["PropertyNames" ]
1931+
1932+ async def test_table_not_found_raises (self ):
1933+ client = self ._client_with_attrs ([])
1934+ with pytest .raises (MetadataError ):
1935+ await client ._retrieve_metadata_changes ("nope" )
1936+
1937+ async def test_get_column_typed_returns_single_attr (self ):
1938+ client = _make_client ()
1939+ client ._retrieve_metadata_changes = AsyncMock (return_value = [{"LogicalName" : "emailaddress1" , "MaxLength" : 100 }])
1940+ col = await client ._get_column ("account" , "emailaddress1" , typed = True )
1941+ assert col ["MaxLength" ] == 100
1942+
1943+ async def test_get_column_typed_empty_returns_none (self ):
1944+ client = _make_client ()
1945+ client ._retrieve_metadata_changes = AsyncMock (return_value = [])
1946+ assert await client ._get_column ("account" , "ghost" , typed = True ) is None
1947+
1948+
1949+ class TestAsyncGetColumn :
1950+ """_AsyncODataClient._get_column non-typed paths (#203)."""
1951+
1952+ def _client (self ):
1953+ client = _make_client ()
1954+ client ._get_entity_by_table_schema_name = AsyncMock (return_value = {"MetadataId" : "ent-1" })
1955+ client ._request = AsyncMock (return_value = _resp (json_data = {"LogicalName" : "emailaddress1" , "MaxLength" : 100 }))
1956+ return client
1957+
1958+ async def test_untyped_returns_json (self ):
1959+ client = self ._client ()
1960+ col = await client ._get_column ("account" , "emailaddress1" )
1961+ assert col ["MaxLength" ] == 100
1962+ assert "Attributes(LogicalName='emailaddress1')" in client ._request .call_args .args [1 ]
1963+
1964+ async def test_untyped_with_select_projects (self ):
1965+ client = self ._client ()
1966+ await client ._get_column ("account" , "emailaddress1" , select = ["MaxLength" ])
1967+ assert client ._request .call_args .kwargs ["params" ]["$select" ] == "MaxLength"
1968+
1969+ async def test_untyped_404_returns_none (self ):
1970+ client = self ._client ()
1971+ client ._request = AsyncMock (side_effect = HttpError ("nope" , 404 ))
1972+ assert await client ._get_column ("account" , "ghost" ) is None
1973+
1974+ async def test_untyped_other_error_propagates (self ):
1975+ client = self ._client ()
1976+ client ._request = AsyncMock (side_effect = HttpError ("boom" , 500 ))
1977+ with pytest .raises (HttpError ):
1978+ await client ._get_column ("account" , "emailaddress1" )
1979+
1980+ async def test_untyped_table_not_found_raises (self ):
1981+ client = self ._client ()
1982+ client ._get_entity_by_table_schema_name = AsyncMock (return_value = None )
1983+ with pytest .raises (MetadataError ):
1984+ await client ._get_column ("nope" , "emailaddress1" )
1985+
1986+
1987+ class TestAsyncUpdateAttribute :
1988+ """_AsyncODataClient._update_attribute (#202)."""
1989+
1990+ def _client (self ):
1991+ client = _make_client ()
1992+ client ._get_entity_by_table_schema_name = AsyncMock (return_value = {"MetadataId" : "ent-1" })
1993+ client ._request = AsyncMock (
1994+ return_value = _resp (
1995+ json_data = {
1996+ "@odata.type" : "#Microsoft.Dynamics.CRM.MemoAttributeMetadata" ,
1997+ "MetadataId" : "attr-1" ,
1998+ "SchemaName" : "new_Comment" ,
1999+ }
2000+ )
2001+ )
2002+ client ._execute_raw = AsyncMock ()
2003+ return client
2004+
2005+ async def test_update_issues_put_with_odata_type_and_override (self ):
2006+ client = self ._client ()
2007+ await client ._update_attribute (
2008+ "new_Feedback" , "new_Comment" , {"max_length" : 4000 , "display_name" : "Customer Comment" }
2009+ )
2010+ client ._execute_raw .assert_awaited_once ()
2011+ req = client ._execute_raw .call_args .args [0 ]
2012+ assert req .method == "PUT"
2013+ assert "Attributes(attr-1)" in req .url
2014+ body = json .loads (req .body )
2015+ assert body ["@odata.type" ] == "Microsoft.Dynamics.CRM.MemoAttributeMetadata"
2016+ assert body ["MetadataId" ] == "attr-1"
2017+ assert body ["MaxLength" ] == 4000
2018+ assert body ["DisplayName" ]["LocalizedLabels" ][0 ]["Label" ] == "Customer Comment"
2019+
2020+ async def test_update_unknown_key_raises (self ):
2021+ client = self ._client ()
2022+ with pytest .raises (ValueError ):
2023+ await client ._update_attribute ("new_Feedback" , "new_Comment" , {"bogus" : 1 })
2024+
2025+ async def test_update_empty_overrides_raises (self ):
2026+ client = self ._client ()
2027+ with pytest .raises (TypeError ):
2028+ await client ._update_attribute ("new_Feedback" , "new_Comment" , {})
2029+
2030+ async def test_update_table_not_found_raises (self ):
2031+ client = self ._client ()
2032+ client ._get_entity_by_table_schema_name = AsyncMock (return_value = None )
2033+ with pytest .raises (MetadataError ):
2034+ await client ._update_attribute ("nope" , "new_Comment" , {"max_length" : 10 })
2035+
2036+ async def test_update_column_get_404_raises_metadata_error (self ):
2037+ client = self ._client ()
2038+ client ._request = AsyncMock (side_effect = HttpError ("not found" , 404 ))
2039+ with pytest .raises (MetadataError ):
2040+ await client ._update_attribute ("new_Feedback" , "ghost" , {"max_length" : 10 })
2041+ client ._execute_raw .assert_not_awaited ()
2042+
2043+ async def test_update_column_get_500_propagates (self ):
2044+ client = self ._client ()
2045+ client ._request = AsyncMock (side_effect = HttpError ("boom" , 500 ))
2046+ with pytest .raises (HttpError ):
2047+ await client ._update_attribute ("new_Feedback" , "new_Comment" , {"max_length" : 10 })
2048+
2049+ async def test_update_column_missing_odata_type_raises (self ):
2050+ client = self ._client ()
2051+ client ._request = AsyncMock (return_value = _resp (json_data = {"SchemaName" : "new_Comment" }))
2052+ with pytest .raises (MetadataError ):
2053+ await client ._update_attribute ("new_Feedback" , "new_Comment" , {"max_length" : 10 })
2054+ client ._execute_raw .assert_not_awaited ()
0 commit comments