diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c68dc66 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +## Translation Table for Disconnect JSON Response + +When calling `disconnect_mac_address`, `count`, `count_success` and `count_failed` may be returned. + +When `count` is `0`, no active session was found. This is a success, +since the `mac` is not connected. + +Otherwise, look for `count_success` or `count_failed`. + + count - 0 - no active session found (success) + count - 1 - count_success 1 = found and disconnected active session + count - 1 - count_failed 1 = found active session, but could not disconnect + +The test device disconnects and reconnects every 30 seconds or so. There may not always be an active session to disconnect. + +Sometimes the attempt to pro-actively disconnect comes too late, as the device was already logging out on it's own, or because the previous ban happened to catch it earlier, and before our second call to disconnect the session can reach the API. + +These are still success cases, as the device is successfully disconnected, preventing any further malicious traffic from it. diff --git a/cassettes/test_404_disable_mac.yaml b/cassettes/test_404_disable_mac.yaml index 15848a1..6132b8f 100644 --- a/cassettes/test_404_disable_mac.yaml +++ b/cassettes/test_404_disable_mac.yaml @@ -30,7 +30,7 @@ interactions: Content-Type: - application/json Date: - - Mon, 17 Nov 2025 22:02:31 GMT + - Mon, 26 Jan 2026 17:53:03 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -82,7 +82,7 @@ interactions: Content-Type: - application/problem+json Date: - - Mon, 17 Nov 2025 22:02:31 GMT + - Mon, 26 Jan 2026 17:53:04 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: diff --git a/cassettes/test_404_enable_mac.yaml b/cassettes/test_404_enable_mac.yaml index c545c36..09e52f2 100644 --- a/cassettes/test_404_enable_mac.yaml +++ b/cassettes/test_404_enable_mac.yaml @@ -30,7 +30,7 @@ interactions: Content-Type: - application/json Date: - - Mon, 17 Nov 2025 22:02:33 GMT + - Mon, 26 Jan 2026 17:52:28 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -82,7 +82,7 @@ interactions: Content-Type: - application/problem+json Date: - - Mon, 17 Nov 2025 22:02:34 GMT + - Mon, 26 Jan 2026 17:52:29 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: diff --git a/cassettes/test_connectivity.yaml b/cassettes/test_connectivity.yaml index bf2cf93..212a2cb 100644 --- a/cassettes/test_connectivity.yaml +++ b/cassettes/test_connectivity.yaml @@ -30,7 +30,7 @@ interactions: Content-Type: - application/json Date: - - Mon, 17 Nov 2025 22:02:28 GMT + - Mon, 26 Jan 2026 17:53:35 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: diff --git a/cassettes/test_disable_mac.yaml b/cassettes/test_disable_mac.yaml index 0776e75..d7ca263 100644 --- a/cassettes/test_disable_mac.yaml +++ b/cassettes/test_disable_mac.yaml @@ -30,7 +30,7 @@ interactions: Content-Type: - application/json Date: - - Mon, 17 Nov 2025 22:02:29 GMT + - Mon, 26 Jan 2026 17:51:42 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -71,7 +71,7 @@ interactions: response: body: string: '{"id":4524,"mac_address":"123123123123","status":"Known","randomized_mac":true,"attributes":{},"added_at":"Nov - 15, 2023 14:52:34 CST","updated_at":"Nov 17, 2025 15:02:29 CST","_links":{"self":{"href":"https:\/\/cleaned.example.edu\/api\/endpoint\/4524"}}}' + 15, 2023 14:52:34 CST","updated_at":"Jan 26, 2026 11:50:00 CST","_links":{"self":{"href":"https:\/\/notauri.edu\/api\/endpoint\/4524"}}}' headers: Cache-Control: - no-store, no-cache, must-revalidate @@ -82,7 +82,7 @@ interactions: Content-Type: - application/hal+json Date: - - Mon, 17 Nov 2025 22:02:30 GMT + - Mon, 26 Jan 2026 17:51:43 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -129,7 +129,7 @@ interactions: body: string: '{"id":4524,"mac_address":"123123123123","status":"Disabled","randomized_mac":true,"attributes":{"Disabled By":"TESTING","Disabled Reason":"Still testing..."},"added_at":"Nov 15, 2023 - 14:52:34 CST","updated_at":"Nov 17, 2025 16:02:30 CST","_links":{"self":{"href":"https:\/\/cleaned.example.edu\/api\/endpoint\/4524"}}}' + 14:52:34 CST","updated_at":"Jan 26, 2026 11:51:43 CST","_links":{"self":{"href":"https:\/\/notauri.edu\/api\/endpoint\/4524"}}}' headers: Cache-Control: - no-store, no-cache, must-revalidate @@ -140,7 +140,7 @@ interactions: Content-Type: - application/hal+json Date: - - Mon, 17 Nov 2025 22:02:30 GMT + - Mon, 26 Jan 2026 17:51:43 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: diff --git a/cassettes/test_disconnect_mac_address.yaml b/cassettes/test_disconnect_mac_address.yaml new file mode 100644 index 0000000..47846a0 --- /dev/null +++ b/cassettes/test_disconnect_mac_address.yaml @@ -0,0 +1,111 @@ +interactions: +- request: + body: '{"grant_type": "password", "username": "JOE", "password": "NOTAPASSWORD", + "client_id": "FAKEID", "client_secret": "NOTASECRET"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.5 + method: POST + uri: https://cleaned.example.edu/api/oauth + response: + body: + string: '{"access_token": "NOTASECRET"}' + headers: + Cache-Control: + - no-store + Connection: + - Keep-Alive + Content-Length: + - '172' + Content-Type: + - application/json + Date: + - Tue, 10 Mar 2026 20:25:17 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - 1;mode=block + - 1;mode=block + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer FAKE_TOKEN + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.5 + method: POST + uri: https://cleaned.example.edu/api/session-action/disconnect/mac/123123123123?async=false + response: + body: + string: '{"action_id":"async_action_1773174318368","status":"complete","count":1,"count_success":1,"count_failed":0,"count_queue":0,"_links":{"self":{"href":"https:\/\/cleaned.example.edu\/api\/session-action\/disconnect%2Fmac%2F123123123123"}}}' + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '251' + Content-Type: + - application/hal+json + Date: + - Tue, 10 Mar 2026 20:25:18 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - 1;mode=block + - 1;mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/cassettes/test_disconnect_mac_not_found.yaml b/cassettes/test_disconnect_mac_not_found.yaml new file mode 100644 index 0000000..5da5773 --- /dev/null +++ b/cassettes/test_disconnect_mac_not_found.yaml @@ -0,0 +1,111 @@ +interactions: +- request: + body: '{"grant_type": "password", "username": "JOE", "password": "NOTAPASSWORD", + "client_id": "FAKEID", "client_secret": "NOTASECRET"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.5 + method: POST + uri: https://cleaned.example.edu/api/oauth + response: + body: + string: '{"access_token": "NOTASECRET"}' + headers: + Cache-Control: + - no-store + Connection: + - Keep-Alive + Content-Length: + - '172' + Content-Type: + - application/json + Date: + - Tue, 10 Mar 2026 20:25:17 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - 1;mode=block + - 1;mode=block + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer FAKE_TOKEN + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - python-requests/2.32.5 + method: POST + uri: https://cleaned.example.edu/api/session-action/disconnect/mac/123123123123?async=false + response: + body: + string: '{"action_id":"async_action_1773174318368","status":"failed","count":0,"count_success":0,"count_failed":0,"count_queue":0,"_links":{"self":{"href":"https:\/\/cleaned.example.edu\/api\/session-action\/disconnect%2Fmac%2F123123123123"}}}' + headers: + Cache-Control: + - no-store, no-cache, must-revalidate + Connection: + - Keep-Alive + Content-Length: + - '251' + Content-Type: + - application/hal+json + Date: + - Tue, 10 Mar 2026 20:25:18 GMT + Expires: + - Thu, 19 Nov 1981 08:52:00 GMT + Keep-Alive: + - timeout=4, max=500 + Pragma: + - no-cache + Server: + - Apache + Set-Cookie: NO-COOKIE-FOR-YOU + Vary: + - X-Forwarded-For + X-Content-Type-Options: + - nosniff + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - 1;mode=block + - 1;mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/cassettes/test_enable_mac.yaml b/cassettes/test_enable_mac.yaml index a336e37..13ea955 100644 --- a/cassettes/test_enable_mac.yaml +++ b/cassettes/test_enable_mac.yaml @@ -30,7 +30,7 @@ interactions: Content-Type: - application/json Date: - - Mon, 17 Nov 2025 22:02:32 GMT + - Mon, 26 Jan 2026 17:49:59 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -70,20 +70,19 @@ interactions: uri: https://cleaned.example.edu/api/endpoint/mac-address/123123123123 response: body: - string: '{"id":4524,"mac_address":"123123123123","status":"Disabled","randomized_mac":true,"attributes":{"Disabled - By":"TESTING","Disabled Reason":"Still testing..."},"added_at":"Nov 15, 2023 - 14:52:34 CST","updated_at":"Nov 17, 2025 16:02:30 CST","_links":{"self":{"href":"https:\/\/cleaned.example.edu\/api\/endpoint\/4524"}}}' + string: '{"id":4524,"mac_address":"123123123123","status":"Known","randomized_mac":true,"attributes":{},"added_at":"Nov + 15, 2023 14:52:34 CST","updated_at":"Jan 26, 2026 11:49:20 CST","_links":{"self":{"href":"https:\/\/notauri.edu\/api\/endpoint\/4524"}}}' headers: Cache-Control: - no-store, no-cache, must-revalidate Connection: - Keep-Alive Content-Length: - - '330' + - '267' Content-Type: - application/hal+json Date: - - Mon, 17 Nov 2025 22:02:32 GMT + - Mon, 26 Jan 2026 17:50:00 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -128,7 +127,7 @@ interactions: response: body: string: '{"id":4524,"mac_address":"123123123123","status":"Known","randomized_mac":true,"attributes":{},"added_at":"Nov - 15, 2023 14:52:34 CST","updated_at":"Nov 17, 2025 16:02:33 CST","_links":{"self":{"href":"https:\/\/cleaned.example.edu\/api\/endpoint\/4524"}}}' + 15, 2023 14:52:34 CST","updated_at":"Jan 26, 2026 11:50:00 CST","_links":{"self":{"href":"https:\/\/notauri.edu\/api\/endpoint\/4524"}}}' headers: Cache-Control: - no-store, no-cache, must-revalidate @@ -139,7 +138,7 @@ interactions: Content-Type: - application/hal+json Date: - - Mon, 17 Nov 2025 22:02:33 GMT + - Mon, 26 Jan 2026 17:50:00 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: diff --git a/cassettes/test_get_info_for_mac_address.yaml b/cassettes/test_get_info_for_mac_address.yaml index 9b9ee49..65b6798 100644 --- a/cassettes/test_get_info_for_mac_address.yaml +++ b/cassettes/test_get_info_for_mac_address.yaml @@ -30,7 +30,7 @@ interactions: Content-Type: - application/json Date: - - Mon, 17 Nov 2025 22:02:34 GMT + - Mon, 26 Jan 2026 17:55:57 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -70,8 +70,9 @@ interactions: uri: https://cleaned.example.edu/api/endpoint?filter=%7B%22mac_address%22%3A%20%22123123123123%22%7D response: body: - string: '{"_links":{"self":{"href":"https:\/\/cleaned.example.edu\/api\/endpoint?calculate_count=false\u0026offset=0\u0026limit=25\u0026sort=%2Bid\u0026filter=%7B%22mac_address%22%3A+%22123123123123%22%7D"},"first":{"href":"https:\/\/cleaned.example.edu\/api\/endpoint?calculate_count=false\u0026offset=0\u0026limit=25\u0026sort=%2Bid\u0026filter=%7B%22mac_address%22%3A+%22123123123123%22%7D"}},"_embedded":{"items":[{"id":4524,"mac_address":"123123123123","status":"Known","randomized_mac":true,"attributes":{},"added_at":"Nov - 15, 2023 14:52:34 CST","updated_at":"Nov 17, 2025 16:02:33 CST","_links":{"self":{"href":"https:\/\/cleaned.example.edu\/api\/endpoint\/4524"}}}]}}' + string: '{"_links":{"self":{"href":"https:\/\/notauri.edu\/api\/endpoint?calculate_count=false\u0026offset=0\u0026limit=25\u0026sort=%2Bid\u0026filter=%7B%22mac_address%22%3A+%22123123123123%22%7D"},"first":{"href":"https:\/\/notauri.edu\/api\/endpoint?calculate_count=false\u0026offset=0\u0026limit=25\u0026sort=%2Bid\u0026filter=%7B%22mac_address%22%3A+%22123123123123%22%7D"}},"_embedded":{"items":[{"id":4524,"mac_address":"123123123123","status":"Disabled","randomized_mac":true,"attributes":{"Disabled + By":"TESTING","Disabled Reason":"Still testing..."},"added_at":"Nov 15, 2023 + 14:52:34 CST","updated_at":"Jan 26, 2026 11:51:43 CST","_links":{"self":{"href":"https:\/\/notauri.edu\/api\/endpoint\/4524"}}}]}}' headers: Cache-Control: - no-store, no-cache, must-revalidate @@ -82,7 +83,7 @@ interactions: Content-Type: - application/hal+json Date: - - Mon, 17 Nov 2025 22:02:35 GMT + - Mon, 26 Jan 2026 17:55:57 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -134,7 +135,7 @@ interactions: Content-Type: - application/problem+json Date: - - Mon, 17 Nov 2025 22:02:35 GMT + - Mon, 26 Jan 2026 17:55:58 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -186,7 +187,7 @@ interactions: Content-Type: - application/problem+json Date: - - Mon, 17 Nov 2025 22:02:36 GMT + - Mon, 26 Jan 2026 17:55:58 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -238,7 +239,7 @@ interactions: Content-Type: - application/problem+json Date: - - Mon, 17 Nov 2025 22:02:36 GMT + - Mon, 26 Jan 2026 17:55:58 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: @@ -290,7 +291,7 @@ interactions: Content-Type: - application/problem+json Date: - - Mon, 17 Nov 2025 22:02:37 GMT + - Mon, 26 Jan 2026 17:55:58 GMT Expires: - Thu, 19 Nov 1981 08:52:00 GMT Keep-Alive: diff --git a/src/clearpass/client.py b/src/clearpass/client.py index 59dd2c3..b409e15 100644 --- a/src/clearpass/client.py +++ b/src/clearpass/client.py @@ -1,8 +1,14 @@ +import json import logging import re import requests -import json -import urllib.parse + +from urllib.parse import ( + SplitResult, + quote, + urlencode, + urlunsplit, +) from clearpass.exceptions import TokenError @@ -54,6 +60,7 @@ def hyphenate_mac(macstring): class APIConnection(): def __init__(self, username, password, endpoint, client_id, client_secret): self._baseurl = f"https://{endpoint}/" + self._endpoint = endpoint self._authurl = f"{self._baseurl}api/oauth" self._authpayload = { @@ -111,19 +118,36 @@ def postheaders(self): self._postheaders.update(self.getheaders) return self._postheaders - def _put_api(self, resource, payload): + def _put_api(self, resource, **kwargs): return requests.put( url=f"{self._baseurl}api/{resource}", headers=self.postheaders, - data=json.dumps(payload), verify=False, + **kwargs ) - def _get_api(self, resource, filter=None): + def _post_api(self, resource, **kwargs): + return requests.post( + url=f"{self._baseurl}api/{resource}", + headers=self.postheaders, + verify=False, + **kwargs + ) + + def _get_api(self, resource, **kwargs): url = f"{self._baseurl}api/{resource}" - if filter: - url += f"?filter={urllib.parse.quote(json.dumps(filter))}" + params = {} + for parameter, value in kwargs.items(): + if value is not None: + params[parameter] = json.dumps(value) + url = urlunsplit(SplitResult( + scheme="https", + netloc=f"{self._endpoint}", + path=f"api/{resource}", + query=urlencode(params, quote_via=quote), + fragment=None, + )) return requests.get( url=url, headers=self.getheaders, @@ -176,14 +200,13 @@ def set_mac_address( if attributes is not None: data["attributes"] = attributes - return self._put_api(f"endpoint/{mac_id}", data) + return self._put_api(f"endpoint/{mac_id}", json=data) def enable_mac_address(self, mac): mac_id = self.get_mac_id(mac) res = self.set_mac_address(mac_id, mac, status="Known") - if res.status_code == 404: - raise ValueError(f"{mac} not found.") - return res + logger.debug(f'Enable mac ({mac}) result: {res.text}') + res.raise_for_status() def disable_mac_address(self, mac, disabled_by, reason): mac_id = self.get_mac_id(mac) @@ -193,6 +216,15 @@ def disable_mac_address(self, mac, disabled_by, reason): "Disabled By": disabled_by, "Disabled Reason": reason }) - if res.status_code == 404: - raise ValueError(f"{mac} not found.") - return res + + logger.debug(f'Disable mac ({mac}) result: {res.text}') + res.raise_for_status() + + def disconnect_mac_address(self, mac): + res = self._post_api( + f"session-action/disconnect/mac/{mac}?async=false", + json={} + ) + logger.debug(f'Disconnect mac ({mac}) result: {res.text}') + res.raise_for_status() + return res.json().get('count_success') diff --git a/tests/conftest.py b/tests/conftest.py index 892a92c..eadfcda 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,9 +7,8 @@ import logging from clearpass.client import APIConnection -from vcr_cleaner import CleanYAMLSerializer -from vcr_cleaner.filters import if_uri_endswith -from vcr_cleaner.cleaners.uri import clean_domains +from vcr_cleaner import CleanYAMLSerializer, filters +from vcr_cleaner.cleaners import uri # Set up logger logger = logging.getLogger(__name__) @@ -19,6 +18,7 @@ CASSETTE_ENDPOINT = "cleaned.example.edu" CASSETTE_CLIENT_ID = "FAKEID" CASSETTE_CLIENT_SECRET = "NOTASECRET" # pragma: allowlist secret +URL = f"https://{CASSETTE_ENDPOINT}" CLEANER_SALT = 'salty' CLEANER_JWT_TOKEN = {'exp': datetime.datetime(2049, 6, 25)} @@ -26,10 +26,11 @@ # To record, `export VCR_RECORD=True` VCR_RECORD = "VCR_RECORD" in os.environ MAC_404 = 'deadbeef1234' # pragma: allowlist secret -TEST_DATA = {'mac': '123123123123', # pragma: allowlist secret - 'disabled_by': 'TESTING', - 'reason': 'Still testing...' - } +TEST_DATA = { + 'mac': '123123123123', # pragma: allowlist secret + 'disabled_by': 'TESTING', + 'reason': 'Still testing...' +} @pytest.fixture @@ -93,6 +94,14 @@ def remove_creds(request): return request +def clean_test_mac(request: dict, response: dict): + test_mac = os.environ.get('CLEARPASS_MAC', '') + request['uri'] = request['uri'].replace(test_mac, TEST_DATA['mac']) + response['body']['string'] = ( + response['body']['string'].replace(test_mac, TEST_DATA['mac']) + ) + + @pytest.fixture def cassette(request) -> vcr.cassette.Cassette: my_vcr = vcr.VCR( @@ -105,9 +114,17 @@ def cassette(request) -> vcr.cassette.Cassette: yaml_cleaner = CleanYAMLSerializer() my_vcr.register_serializer("cleanyaml", yaml_cleaner) - yaml_cleaner.register_cleaner(clean_domains('illinois.edu')) - yaml_cleaner.register_cleaner(if_uri_endswith("/api/oauth", clean_token)) + yaml_cleaner.register_cleaner(uri.clean_domains( + os.environ.get("CLEARPASS_ENDPOINT", CASSETTE_ENDPOINT), + CASSETTE_ENDPOINT, + ) + ) + yaml_cleaner.register_cleaner(filters.if_uri_contains( + f"{URL}/api/oauth", + clean_token), + ) yaml_cleaner.register_cleaner(clean_cookie) + yaml_cleaner.register_cleaner(clean_test_mac) with my_vcr.use_cassette(f'{request.function.__name__}.yaml', serializer='cleanyaml') as tape: diff --git a/tests/test_connector.py b/tests/test_connector.py index 215ff32..47a781b 100644 --- a/tests/test_connector.py +++ b/tests/test_connector.py @@ -1,6 +1,10 @@ +import os import pytest from conftest import TEST_DATA, MAC_404 +# NOTE: Each tests cassette must be played in full in order for +# a test to pass. See the assert statements in conftest.py:133. + def test_connectivity(cassette, clearpass_client): assert clearpass_client.test_connectivity() @@ -14,8 +18,7 @@ def test_failed_connectivity(cassette, clearpass_client): def test_disable_mac(cassette, clearpass_client): - result = clearpass_client.disable_mac_address(**TEST_DATA) - assert result + clearpass_client.disable_mac_address(**TEST_DATA) def test_404_disable_mac(cassette, clearpass_client): @@ -28,8 +31,7 @@ def test_404_disable_mac(cassette, clearpass_client): def test_enable_mac(cassette, clearpass_client): - result = clearpass_client.enable_mac_address(mac=TEST_DATA['mac']) - assert result + clearpass_client.enable_mac_address(mac=TEST_DATA['mac']) def test_404_enable_mac(cassette, clearpass_client): @@ -40,4 +42,21 @@ def test_404_enable_mac(cassette, clearpass_client): def test_get_info_for_mac_address(cassette, clearpass_client): result = clearpass_client.get_info_for_mac_address(mac=TEST_DATA['mac']) - assert result + + assert 'id' in result['endpointinfo'][0] + assert 'mac_address' in result['endpointinfo'][0] + assert 'status' in result['endpointinfo'][0] + assert result['endpointinfo'][0]['mac_address'] == TEST_DATA['mac'] + assert result['endpointinfo'][0]['status'] == 'Disabled' + + +def test_disconnect_mac_address(cassette, clearpass_client): + count = clearpass_client.disconnect_mac_address( + mac=os.environ.get("CLEARPASS_MAC", TEST_DATA['mac'])) + assert count == 1 + + +def test_disconnect_mac_not_found(cassette, clearpass_client): + count = clearpass_client.disconnect_mac_address( + mac=os.environ.get("CLEARPASS_MAC", TEST_DATA['mac'])) + assert count == 0