You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

667 lines
22 KiB

3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
  1. """Test module for KeycloakUMA."""
  2. import re
  3. from inspect import iscoroutinefunction, signature
  4. import pytest
  5. from keycloak import KeycloakAdmin, KeycloakOpenIDConnection, KeycloakUMA
  6. from keycloak.exceptions import (
  7. KeycloakDeleteError,
  8. KeycloakGetError,
  9. KeycloakPostError,
  10. KeycloakPutError,
  11. )
  12. from keycloak.uma_permissions import UMAPermission
  13. def test_keycloak_uma_init(oid_connection_with_authz: KeycloakOpenIDConnection) -> None:
  14. """
  15. Test KeycloakUMA's init method.
  16. :param oid_connection_with_authz: Keycloak OpenID connection manager with preconfigured authz
  17. :type oid_connection_with_authz: KeycloakOpenIDConnection
  18. """
  19. connection = oid_connection_with_authz
  20. uma = KeycloakUMA(connection=connection)
  21. assert isinstance(uma.connection, KeycloakOpenIDConnection)
  22. # should initially be empty
  23. assert uma._well_known is None
  24. assert uma.uma_well_known
  25. # should be cached after first reference
  26. assert uma._well_known is not None
  27. def test_uma_well_known(uma: KeycloakUMA) -> None:
  28. """
  29. Test the well_known method.
  30. :param uma: Keycloak UMA client
  31. :type uma: KeycloakUMA
  32. """
  33. res = uma.uma_well_known
  34. assert res is not None
  35. assert res != {}
  36. for key in ["resource_registration_endpoint"]:
  37. assert key in res
  38. def test_uma_resource_sets(uma: KeycloakUMA) -> None:
  39. """
  40. Test resource sets.
  41. :param uma: Keycloak UMA client
  42. :type uma: KeycloakUMA
  43. """
  44. # Check that only the default resource is present
  45. resource_sets = uma.resource_set_list()
  46. resource_set_list = list(resource_sets)
  47. assert len(resource_set_list) == 1, resource_set_list
  48. assert resource_set_list[0]["name"] == "Default Resource", resource_set_list[0]["name"]
  49. # Test query for resource sets
  50. resource_set_list_ids = uma.resource_set_list_ids()
  51. assert len(resource_set_list_ids) == 1
  52. resource_set_list_ids2 = uma.resource_set_list_ids(name="Default")
  53. assert resource_set_list_ids2 == resource_set_list_ids
  54. resource_set_list_ids2 = uma.resource_set_list_ids(name="Default Resource")
  55. assert resource_set_list_ids2 == resource_set_list_ids
  56. resource_set_list_ids = uma.resource_set_list_ids(name="Default", exact_name=True)
  57. assert len(resource_set_list_ids) == 0
  58. resource_set_list_ids = uma.resource_set_list_ids(first=1)
  59. assert len(resource_set_list_ids) == 0
  60. resource_set_list_ids = uma.resource_set_list_ids(scope="Invalid")
  61. assert len(resource_set_list_ids) == 0
  62. resource_set_list_ids = uma.resource_set_list_ids(owner="Invalid")
  63. assert len(resource_set_list_ids) == 0
  64. resource_set_list_ids = uma.resource_set_list_ids(resource_type="Invalid")
  65. assert len(resource_set_list_ids) == 0
  66. resource_set_list_ids = uma.resource_set_list_ids(name="Invalid")
  67. assert len(resource_set_list_ids) == 0
  68. resource_set_list_ids = uma.resource_set_list_ids(uri="Invalid")
  69. assert len(resource_set_list_ids) == 0
  70. resource_set_list_ids = uma.resource_set_list_ids(maximum=0)
  71. assert len(resource_set_list_ids) == 0
  72. # Test create resource set
  73. resource_to_create = {
  74. "name": "mytest",
  75. "scopes": ["test:read", "test:write"],
  76. "type": "urn:test",
  77. "uris": ["/some_resources/*"],
  78. }
  79. created_resource = uma.resource_set_create(resource_to_create)
  80. assert created_resource
  81. assert created_resource["_id"], created_resource
  82. assert set(resource_to_create).issubset(set(created_resource)), created_resource
  83. # Test getting resource with wildcard
  84. # Without matchingUri query option
  85. resource_set_list_ids = uma.resource_set_list_ids(uri="/some_resources/resource")
  86. assert len(resource_set_list_ids) == 0
  87. # With matchingUri query option
  88. resource_set_list_ids = uma.resource_set_list_ids(
  89. uri="/some_resources/resource",
  90. matchingUri=True,
  91. )
  92. assert len(resource_set_list_ids) == 1
  93. # Test create the same resource set
  94. with pytest.raises(KeycloakPostError) as err:
  95. uma.resource_set_create(resource_to_create)
  96. assert err.match(
  97. re.escape(
  98. '409: b\'{"error":"invalid_request","error_description":'
  99. '"Resource with name [mytest] already exists."}\'',
  100. ),
  101. )
  102. # Test get resource set
  103. latest_resource = uma.resource_set_read(created_resource["_id"])
  104. assert latest_resource["name"] == created_resource["name"]
  105. # Test update resource set
  106. latest_resource["name"] = "New Resource Name"
  107. res = uma.resource_set_update(created_resource["_id"], latest_resource)
  108. assert res == {}, res
  109. updated_resource = uma.resource_set_read(created_resource["_id"])
  110. assert updated_resource["name"] == "New Resource Name"
  111. # Test update resource set fail
  112. with pytest.raises(KeycloakPutError) as err:
  113. uma.resource_set_update(resource_id=created_resource["_id"], payload={"wrong": "payload"})
  114. assert err.match("Unrecognized field")
  115. # Test delete resource set
  116. res = uma.resource_set_delete(resource_id=created_resource["_id"])
  117. assert res == {}, res
  118. with pytest.raises(KeycloakGetError) as err:
  119. uma.resource_set_read(created_resource["_id"])
  120. err.match("404: b''")
  121. # Test delete fail
  122. with pytest.raises(KeycloakDeleteError) as err:
  123. uma.resource_set_delete(resource_id=created_resource["_id"])
  124. assert err.match("404: b''")
  125. def test_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin) -> None:
  126. """
  127. Test policies.
  128. :param uma: Keycloak UMA client
  129. :type uma: KeycloakUMA
  130. :param admin: Keycloak Admin client
  131. :type admin: KeycloakAdmin
  132. """
  133. # Create some required test data
  134. resource_to_create = {
  135. "name": "mytest",
  136. "scopes": ["test:read", "test:write"],
  137. "type": "urn:test",
  138. "ownerManagedAccess": True,
  139. }
  140. created_resource = uma.resource_set_create(resource_to_create)
  141. group_id = admin.create_group({"name": "UMAPolicyGroup"})
  142. role_id = admin.create_realm_role(payload={"name": "roleUMAPolicy"})
  143. other_client_id = admin.create_client({"name": "UMAOtherClient"})
  144. client = admin.get_client(other_client_id)
  145. resource_id = created_resource["_id"]
  146. # Create a role policy
  147. policy_to_create = {
  148. "name": "TestPolicyRole",
  149. "description": "Test resource policy description",
  150. "scopes": ["test:read", "test:write"],
  151. "roles": ["roleUMAPolicy"],
  152. }
  153. policy = uma.policy_resource_create(resource_id=resource_id, payload=policy_to_create)
  154. assert policy
  155. # Create a client policy
  156. policy_to_create = {
  157. "name": "TestPolicyClient",
  158. "description": "Test resource policy description",
  159. "scopes": ["test:read"],
  160. "clients": [client["clientId"]],
  161. }
  162. policy = uma.policy_resource_create(resource_id=resource_id, payload=policy_to_create)
  163. assert policy
  164. policy_to_create = {
  165. "name": "TestPolicyGroup",
  166. "description": "Test resource policy description",
  167. "scopes": ["test:read"],
  168. "groups": ["/UMAPolicyGroup"],
  169. }
  170. policy = uma.policy_resource_create(resource_id=resource_id, payload=policy_to_create)
  171. assert policy
  172. policies = uma.policy_query()
  173. assert len(policies) == 3
  174. policies = uma.policy_query(name="TestPolicyGroup")
  175. assert len(policies) == 1
  176. policy_id = policy["id"]
  177. uma.policy_delete(policy_id)
  178. with pytest.raises(KeycloakDeleteError) as err:
  179. uma.policy_delete(policy_id)
  180. assert err.match(
  181. '404: b\'{"error":"invalid_request","error_description":'
  182. '"Policy with .* does not exist"}\'',
  183. )
  184. policies = uma.policy_query()
  185. assert len(policies) == 2
  186. policy = policies[0]
  187. uma.policy_update(policy_id=policy["id"], payload=policy)
  188. policies = uma.policy_query()
  189. assert len(policies) == 2
  190. policies = uma.policy_query(name="Invalid")
  191. assert len(policies) == 0
  192. policies = uma.policy_query(scope="Invalid")
  193. assert len(policies) == 0
  194. policies = uma.policy_query(resource="Invalid")
  195. assert len(policies) == 0
  196. policies = uma.policy_query(first=3)
  197. assert len(policies) == 0
  198. policies = uma.policy_query(maximum=0)
  199. assert len(policies) == 0
  200. policies = uma.policy_query(name=policy["name"])
  201. assert len(policies) == 1
  202. policies = uma.policy_query(scope=policy["scopes"][0])
  203. assert len(policies) == 2
  204. policies = uma.policy_query(resource=resource_id)
  205. assert len(policies) == 2
  206. uma.resource_set_delete(resource_id)
  207. admin.delete_client(other_client_id)
  208. admin.delete_realm_role(role_id)
  209. admin.delete_group(group_id)
  210. def test_uma_access(uma: KeycloakUMA) -> None:
  211. """
  212. Test permission access checks.
  213. :param uma: Keycloak UMA client
  214. :type uma: KeycloakUMA
  215. """
  216. resource_to_create = {
  217. "name": "mytest",
  218. "scopes": ["read", "write"],
  219. "type": "urn:test",
  220. "ownerManagedAccess": True,
  221. }
  222. resource = uma.resource_set_create(resource_to_create)
  223. policy_to_create = {
  224. "name": "TestPolicy",
  225. "description": "Test resource policy description",
  226. "scopes": [resource_to_create["scopes"][0]],
  227. "clients": [uma.connection.client_id],
  228. }
  229. uma.policy_resource_create(resource_id=resource["_id"], payload=policy_to_create)
  230. token = uma.connection.token
  231. permissions = []
  232. assert uma.permissions_check(token["access_token"], permissions)
  233. permissions.append(UMAPermission(resource=resource_to_create["name"]))
  234. assert uma.permissions_check(token["access_token"], permissions)
  235. permissions.append(UMAPermission(resource="not valid"))
  236. assert not uma.permissions_check(token["access_token"], permissions)
  237. uma.resource_set_delete(resource["_id"])
  238. def test_uma_permission_ticket(uma: KeycloakUMA) -> None:
  239. """
  240. Test permission ticket generation.
  241. :param uma: Keycloak UMA client
  242. :type uma: KeycloakUMA
  243. """
  244. resource_to_create = {
  245. "name": "mytest",
  246. "scopes": ["read", "write"],
  247. "type": "urn:test",
  248. "ownerManagedAccess": True,
  249. }
  250. resource = uma.resource_set_create(resource_to_create)
  251. policy_to_create = {
  252. "name": "TestPolicy",
  253. "description": "Test resource policy description",
  254. "scopes": [resource_to_create["scopes"][0]],
  255. "clients": [uma.connection.client_id],
  256. }
  257. uma.policy_resource_create(resource_id=resource["_id"], payload=policy_to_create)
  258. permissions = (
  259. UMAPermission(resource=resource_to_create["name"], scope=resource_to_create["scopes"][0]),
  260. )
  261. response = uma.permission_ticket_create(permissions)
  262. rpt = uma.connection.keycloak_openid.token(
  263. grant_type="urn:ietf:params:oauth:grant-type:uma-ticket",
  264. ticket=response["ticket"],
  265. )
  266. assert rpt
  267. assert "access_token" in rpt
  268. permissions = (UMAPermission(resource="invalid"),)
  269. with pytest.raises(KeycloakPostError):
  270. uma.permission_ticket_create(permissions)
  271. uma.resource_set_delete(resource["_id"])
  272. # async function start
  273. @pytest.mark.asyncio
  274. async def test_a_uma_well_known(uma: KeycloakUMA) -> None:
  275. """
  276. Test the well_known method.
  277. :param uma: Keycloak UMA client
  278. :type uma: KeycloakUMA
  279. """
  280. res = uma.uma_well_known
  281. assert res is not None
  282. assert res != {}
  283. for key in ["resource_registration_endpoint"]:
  284. assert key in res
  285. @pytest.mark.asyncio
  286. async def test_a_uma_resource_sets(uma: KeycloakUMA) -> None:
  287. """
  288. Test resource sets.
  289. :param uma: Keycloak UMA client
  290. :type uma: KeycloakUMA
  291. """
  292. # Check that only the default resource is present
  293. resource_sets = uma.resource_set_list()
  294. resource_set_list = list(resource_sets)
  295. assert len(resource_set_list) == 1, resource_set_list
  296. assert resource_set_list[0]["name"] == "Default Resource", resource_set_list[0]["name"]
  297. # Test query for resource sets
  298. resource_set_list_ids = await uma.a_resource_set_list_ids()
  299. assert len(resource_set_list_ids) == 1
  300. resource_set_list_ids2 = await uma.a_resource_set_list_ids(name="Default")
  301. assert resource_set_list_ids2 == resource_set_list_ids
  302. resource_set_list_ids2 = await uma.a_resource_set_list_ids(name="Default Resource")
  303. assert resource_set_list_ids2 == resource_set_list_ids
  304. resource_set_list_ids = await uma.a_resource_set_list_ids(name="Default", exact_name=True)
  305. assert len(resource_set_list_ids) == 0
  306. resource_set_list_ids = await uma.a_resource_set_list_ids(first=1)
  307. assert len(resource_set_list_ids) == 0
  308. resource_set_list_ids = await uma.a_resource_set_list_ids(scope="Invalid")
  309. assert len(resource_set_list_ids) == 0
  310. resource_set_list_ids = await uma.a_resource_set_list_ids(owner="Invalid")
  311. assert len(resource_set_list_ids) == 0
  312. resource_set_list_ids = await uma.a_resource_set_list_ids(resource_type="Invalid")
  313. assert len(resource_set_list_ids) == 0
  314. resource_set_list_ids = await uma.a_resource_set_list_ids(name="Invalid")
  315. assert len(resource_set_list_ids) == 0
  316. resource_set_list_ids = await uma.a_resource_set_list_ids(uri="Invalid")
  317. assert len(resource_set_list_ids) == 0
  318. resource_set_list_ids = await uma.a_resource_set_list_ids(maximum=0)
  319. assert len(resource_set_list_ids) == 0
  320. # Test create resource set
  321. resource_to_create = {
  322. "name": "mytest",
  323. "scopes": ["test:read", "test:write"],
  324. "type": "urn:test",
  325. "uris": ["/some_resources/*"],
  326. }
  327. created_resource = await uma.a_resource_set_create(resource_to_create)
  328. assert created_resource
  329. assert created_resource["_id"], created_resource
  330. assert set(resource_to_create).issubset(set(created_resource)), created_resource
  331. # Test getting resource with wildcard
  332. # Without matchingUri query option
  333. resource_set_list_ids = await uma.a_resource_set_list_ids(uri="/some_resources/resource")
  334. assert len(resource_set_list_ids) == 0
  335. # With matchingUri query option
  336. resource_set_list_ids = await uma.a_resource_set_list_ids(
  337. uri="/some_resources/resource",
  338. matchingUri=True,
  339. )
  340. assert len(resource_set_list_ids) == 1
  341. # Test create the same resource set
  342. with pytest.raises(KeycloakPostError) as err:
  343. await uma.a_resource_set_create(resource_to_create)
  344. assert err.match(
  345. re.escape(
  346. '409: b\'{"error":"invalid_request","error_description":'
  347. '"Resource with name [mytest] already exists."}\'',
  348. ),
  349. )
  350. # Test get resource set
  351. latest_resource = await uma.a_resource_set_read(created_resource["_id"])
  352. assert latest_resource["name"] == created_resource["name"]
  353. # Test update resource set
  354. latest_resource["name"] = "New Resource Name"
  355. res = await uma.a_resource_set_update(created_resource["_id"], latest_resource)
  356. assert res == {}, res
  357. updated_resource = await uma.a_resource_set_read(created_resource["_id"])
  358. assert updated_resource["name"] == "New Resource Name"
  359. # Test update resource set fail
  360. with pytest.raises(KeycloakPutError) as err:
  361. uma.resource_set_update(resource_id=created_resource["_id"], payload={"wrong": "payload"})
  362. assert err.match("Unrecognized field")
  363. # Test delete resource set
  364. res = await uma.a_resource_set_delete(resource_id=created_resource["_id"])
  365. assert res == {}, res
  366. with pytest.raises(KeycloakGetError) as err:
  367. await uma.a_resource_set_read(created_resource["_id"])
  368. err.match("404: b''")
  369. # Test delete fail
  370. with pytest.raises(KeycloakDeleteError) as err:
  371. await uma.a_resource_set_delete(resource_id=created_resource["_id"])
  372. assert err.match("404: b''")
  373. @pytest.mark.asyncio
  374. async def test_a_uma_policy(uma: KeycloakUMA, admin: KeycloakAdmin) -> None:
  375. """
  376. Test policies.
  377. :param uma: Keycloak UMA client
  378. :type uma: KeycloakUMA
  379. :param admin: Keycloak Admin client
  380. :type admin: KeycloakAdmin
  381. """
  382. # Create some required test data
  383. resource_to_create = {
  384. "name": "mytest",
  385. "scopes": ["test:read", "test:write"],
  386. "type": "urn:test",
  387. "ownerManagedAccess": True,
  388. }
  389. created_resource = await uma.a_resource_set_create(resource_to_create)
  390. group_id = admin.create_group({"name": "UMAPolicyGroup"})
  391. role_id = admin.create_realm_role(payload={"name": "roleUMAPolicy"})
  392. other_client_id = admin.create_client({"name": "UMAOtherClient"})
  393. client = admin.get_client(other_client_id)
  394. resource_id = created_resource["_id"]
  395. # Create a role policy
  396. policy_to_create = {
  397. "name": "TestPolicyRole",
  398. "description": "Test resource policy description",
  399. "scopes": ["test:read", "test:write"],
  400. "roles": ["roleUMAPolicy"],
  401. }
  402. policy = await uma.a_policy_resource_create(resource_id=resource_id, payload=policy_to_create)
  403. assert policy
  404. # Create a client policy
  405. policy_to_create = {
  406. "name": "TestPolicyClient",
  407. "description": "Test resource policy description",
  408. "scopes": ["test:read"],
  409. "clients": [client["clientId"]],
  410. }
  411. policy = await uma.a_policy_resource_create(resource_id=resource_id, payload=policy_to_create)
  412. assert policy
  413. policy_to_create = {
  414. "name": "TestPolicyGroup",
  415. "description": "Test resource policy description",
  416. "scopes": ["test:read"],
  417. "groups": ["/UMAPolicyGroup"],
  418. }
  419. policy = await uma.a_policy_resource_create(resource_id=resource_id, payload=policy_to_create)
  420. assert policy
  421. policies = await uma.a_policy_query()
  422. assert len(policies) == 3
  423. policies = await uma.a_policy_query(name="TestPolicyGroup")
  424. assert len(policies) == 1
  425. policy_id = policy["id"]
  426. await uma.a_policy_delete(policy_id)
  427. with pytest.raises(KeycloakDeleteError) as err:
  428. await uma.a_policy_delete(policy_id)
  429. assert err.match(
  430. '404: b\'{"error":"invalid_request","error_description":'
  431. '"Policy with .* does not exist"}\'',
  432. )
  433. policies = await uma.a_policy_query()
  434. assert len(policies) == 2
  435. policy = policies[0]
  436. await uma.a_policy_update(policy_id=policy["id"], payload=policy)
  437. policies = await uma.a_policy_query()
  438. assert len(policies) == 2
  439. policies = await uma.a_policy_query(name="Invalid")
  440. assert len(policies) == 0
  441. policies = await uma.a_policy_query(scope="Invalid")
  442. assert len(policies) == 0
  443. policies = await uma.a_policy_query(resource="Invalid")
  444. assert len(policies) == 0
  445. policies = await uma.a_policy_query(first=3)
  446. assert len(policies) == 0
  447. policies = await uma.a_policy_query(maximum=0)
  448. assert len(policies) == 0
  449. policies = await uma.a_policy_query(name=policy["name"])
  450. assert len(policies) == 1
  451. policies = await uma.a_policy_query(scope=policy["scopes"][0])
  452. assert len(policies) == 2
  453. policies = await uma.a_policy_query(resource=resource_id)
  454. assert len(policies) == 2
  455. await uma.a_resource_set_delete(resource_id)
  456. await admin.a_delete_client(other_client_id)
  457. await admin.a_delete_realm_role(role_id)
  458. await admin.a_delete_group(group_id)
  459. @pytest.mark.asyncio
  460. async def test_a_uma_access(uma: KeycloakUMA) -> None:
  461. """
  462. Test permission access checks.
  463. :param uma: Keycloak UMA client
  464. :type uma: KeycloakUMA
  465. """
  466. resource_to_create = {
  467. "name": "mytest",
  468. "scopes": ["read", "write"],
  469. "type": "urn:test",
  470. "ownerManagedAccess": True,
  471. }
  472. resource = await uma.a_resource_set_create(resource_to_create)
  473. policy_to_create = {
  474. "name": "TestPolicy",
  475. "description": "Test resource policy description",
  476. "scopes": [resource_to_create["scopes"][0]],
  477. "clients": [uma.connection.client_id],
  478. }
  479. await uma.a_policy_resource_create(resource_id=resource["_id"], payload=policy_to_create)
  480. token = uma.connection.token
  481. permissions = []
  482. assert await uma.a_permissions_check(token["access_token"], permissions)
  483. permissions.append(UMAPermission(resource=resource_to_create["name"]))
  484. assert await uma.a_permissions_check(token["access_token"], permissions)
  485. permissions.append(UMAPermission(resource="not valid"))
  486. assert not await uma.a_permissions_check(token["access_token"], permissions)
  487. uma.resource_set_delete(resource["_id"])
  488. @pytest.mark.asyncio
  489. async def test_a_uma_permission_ticket(uma: KeycloakUMA) -> None:
  490. """
  491. Test permission ticket generation.
  492. :param uma: Keycloak UMA client
  493. :type uma: KeycloakUMA
  494. """
  495. resource_to_create = {
  496. "name": "mytest",
  497. "scopes": ["read", "write"],
  498. "type": "urn:test",
  499. "ownerManagedAccess": True,
  500. }
  501. resource = await uma.a_resource_set_create(resource_to_create)
  502. policy_to_create = {
  503. "name": "TestPolicy",
  504. "description": "Test resource policy description",
  505. "scopes": [resource_to_create["scopes"][0]],
  506. "clients": [uma.connection.client_id],
  507. }
  508. await uma.a_policy_resource_create(resource_id=resource["_id"], payload=policy_to_create)
  509. permissions = (
  510. UMAPermission(resource=resource_to_create["name"], scope=resource_to_create["scopes"][0]),
  511. )
  512. response = await uma.a_permission_ticket_create(permissions)
  513. rpt = await uma.connection.keycloak_openid.a_token(
  514. grant_type="urn:ietf:params:oauth:grant-type:uma-ticket",
  515. ticket=response["ticket"],
  516. )
  517. assert rpt
  518. assert "access_token" in rpt
  519. permissions = (UMAPermission(resource="invalid"),)
  520. with pytest.raises(KeycloakPostError):
  521. uma.permission_ticket_create(permissions)
  522. await uma.a_resource_set_delete(resource["_id"])
  523. def test_counter_part() -> None:
  524. """Test that each function has its async counter part."""
  525. uma_methods = [func for func in dir(KeycloakUMA) if callable(getattr(KeycloakUMA, func))]
  526. sync_methods = [
  527. method
  528. for method in uma_methods
  529. if not method.startswith("a_") and not method.startswith("_")
  530. ]
  531. async_methods = [
  532. method for method in uma_methods if iscoroutinefunction(getattr(KeycloakUMA, method))
  533. ]
  534. for method in sync_methods:
  535. async_method = f"a_{method}"
  536. assert (async_method in uma_methods) is True
  537. sync_sign = signature(getattr(KeycloakUMA, method))
  538. async_sign = signature(getattr(KeycloakUMA, async_method))
  539. assert sync_sign.parameters == async_sign.parameters
  540. for async_method in async_methods:
  541. if async_method[2:].startswith("_"):
  542. continue
  543. assert async_method[2:] in sync_methods