Skip to content

Commit 6179777

Browse files
committed
wip command_interface tests
1 parent d8231b5 commit 6179777

File tree

2 files changed

+149
-22
lines changed

2 files changed

+149
-22
lines changed

src/nrfcredstore/command_interface.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,32 @@ def __init__(self, comms: Comms):
2727
"""
2828
self.comms = comms
2929

30-
def write_raw(self, command):
30+
def write_raw(self, command: str):
3131
"""Write a raw line directly to the serial interface."""
3232
self.comms.write_line(command)
3333

3434
@abstractmethod
35-
def write_credential(self, sectag, cred_type, cred_text):
35+
def write_credential(self, sectag: int, cred_type: int, cred_text: str):
3636
"""Write a credential string to the command interface"""
3737
return
3838

3939
@abstractmethod
40-
def delete_credential(self, sectag, cred_type):
40+
def delete_credential(self, sectag: int, cred_type: int):
4141
"""Delete a credential using command interface"""
4242
return
4343

4444
@abstractmethod
45-
def check_credential_exists(self, sectag, cred_type, get_hash=True):
45+
def check_credential_exists(self, sectag: int, cred_type: int, get_hash=True):
4646
"""Verify that a credential is installed. If check_hash is true, retrieve the SHA hash."""
4747
return
4848

4949
@abstractmethod
50-
def calculate_expected_hash(self, cred_text):
50+
def calculate_expected_hash(self, cred_text: str):
5151
"""Returns the expected digest/hash for a given credential as a string"""
5252
return
5353

5454
@abstractmethod
55-
def get_csr(self, sectag = 0, cn = ""):
55+
def get_csr(self, sectag: int, attributes: str):
5656
"""Generate a private/public keypair and a corresponding Certificate Signing Request.
5757
5858
Returns:
@@ -78,7 +78,7 @@ def get_mfw_version(self):
7878
class ATCommandInterface(CredentialCommandInterface):
7979
shell = False
8080

81-
def _parse_sha(self, cmng_result_str):
81+
def _parse_sha(self, cmng_result_str: str):
8282
# Example AT%CMNG response:
8383
# %CMNG: 123,0,"2C43952EE9E000FF2ACC4E2ED0897C0A72AD5FA72C3D934E81741CBD54F05BD1"
8484
# The first item in " is the SHA.
@@ -88,7 +88,7 @@ def _parse_sha(self, cmng_result_str):
8888
logger.error(f'Could not parse credential hash: {cmng_result_str}')
8989
return None
9090

91-
def set_shell_mode(self, shell):
91+
def set_shell_mode(self, shell: bool):
9292
self.shell = shell
9393

9494
def detect_shell_mode(self):
@@ -107,7 +107,7 @@ def enable_error_codes(self):
107107
if not self.at_command('AT+CMEE=1', wait_for_result=True):
108108
logger.error("Failed to enable error codes.")
109109

110-
def at_command(self, at_command, wait_for_result=False, suppress_errors=False):
110+
def at_command(self, at_command: str, wait_for_result=False, suppress_errors=False):
111111
"""Write an AT command to the command interface. Optionally wait for OK"""
112112

113113
if self.shell:
@@ -124,17 +124,13 @@ def at_command(self, at_command, wait_for_result=False, suppress_errors=False):
124124
else:
125125
return True
126126

127-
def write_credential(self, sectag, cred_type, cred_text):
128-
result = self.at_command(f'AT%CMNG=0,{sectag},{cred_type},"{cred_text}"',
129-
wait_for_result=True)
130-
time.sleep(1)
131-
return result
127+
def write_credential(self, sectag: int, cred_type: int, cred_text: str):
128+
return self.at_command(f'AT%CMNG=0,{sectag},{cred_type},"{cred_text}"', wait_for_result=True)
132129

133-
def delete_credential(self, sectag, cred_type):
134-
# No output is expected beyond OK/ERROR in this case
130+
def delete_credential(self, sectag: int, cred_type: int):
135131
return self.at_command(f'AT%CMNG=3,{sectag},{cred_type}', wait_for_result=True)
136132

137-
def check_credential_exists(self, sectag, cred_type, get_hash=True):
133+
def check_credential_exists(self, sectag: int, cred_type: int, get_hash=True):
138134
self.at_command(f'AT%CMNG=1,{sectag},{cred_type}')
139135
retval, res = self.comms.expect_response("OK", "ERROR", "%CMNG")
140136
if retval and res:
@@ -145,7 +141,7 @@ def check_credential_exists(self, sectag, cred_type, get_hash=True):
145141

146142
return False, None
147143

148-
def calculate_expected_hash(self, cred_text):
144+
def calculate_expected_hash(self, cred_text: str):
149145
# AT Command host returns hex of SHA256 hash of credential plaintext
150146
return hashlib.sha256(cred_text.encode('utf-8')).hexdigest().upper()
151147

@@ -274,7 +270,7 @@ def calculate_expected_hash(self, cred_text):
274270
hash = hashlib.sha256(cred_text.encode('utf-8') + b'\x00')
275271
return base64.b64encode(hash.digest()).decode()
276272

277-
def get_csr(self, sectag = 0, cn = ""):
273+
def get_csr(self, sectag=0, attributes=""):
278274
raise RuntimeError("The TLS Credentials Shell does not support CSR generation")
279275

280276
def go_offline(self):

tests/test_command_interface.py

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44

55
from nrfcredstore.command_interface import CredentialCommandInterface, ATCommandInterface, TLSCredShellInterface
6+
from nrfcredstore.credstore import CredType
67

78
@pytest.fixture
89
def comms():
@@ -13,11 +14,141 @@ def comms():
1314
@pytest.fixture
1415
def at_command_interface(comms):
1516
"""Mock command interface"""
16-
command_interface = ATCommandInterface(comms)
17-
return command_interface
17+
interface = ATCommandInterface(comms)
18+
return interface
1819

19-
def test_write_raw(at_command_interface):
20+
@pytest.fixture
21+
def tls_cred_shell_interface(comms):
22+
"""Mock TLSCredShellInterface"""
23+
interface = TLSCredShellInterface(comms)
24+
return interface
25+
26+
def test_write_raw_at(at_command_interface):
2027
"""Test writing raw AT commands"""
2128
at_command_interface.comms.write_line = Mock()
2229
at_command_interface.write_raw('AT+TEST=1')
2330
at_command_interface.comms.write_line.assert_called_once_with('AT+TEST=1')
31+
32+
def test_write_raw_tls(tls_cred_shell_interface):
33+
"""Test writing raw TLS commands"""
34+
tls_cred_shell_interface.comms.write_line = Mock()
35+
tls_cred_shell_interface.write_raw('AT+TEST=1')
36+
tls_cred_shell_interface.comms.write_line.assert_called_once_with('AT+TEST=1')
37+
38+
def test_write_credential_at(at_command_interface):
39+
"""Test writing a credential using ATCommandInterface"""
40+
at_command_interface.comms.write_line = Mock()
41+
at_command_interface.comms.expect_response.return_value = (True, "")
42+
at_command_interface.write_credential(sectag=42, cred_type=CredType.CLIENT_CERT.value, cred_text='test_value')
43+
at_command_interface.comms.write_line.assert_called_once_with('AT%CMNG=0,42,1,"test_value"')
44+
45+
def test_delete_credential_at(at_command_interface):
46+
"""Test deleting a credential using ATCommandInterface"""
47+
at_command_interface.comms.write_line = Mock()
48+
at_command_interface.comms.expect_response.return_value = (True, "")
49+
at_command_interface.delete_credential(sectag=42, cred_type=CredType.CLIENT_CERT.value)
50+
at_command_interface.comms.write_line.assert_called_once_with('AT%CMNG=3,42,1')
51+
52+
def test_check_credential_exists_at(at_command_interface):
53+
"""Test checking if a credential exists using ATCommandInterface"""
54+
at_command_interface.comms.write_line = Mock()
55+
at_command_interface.comms.expect_response.return_value = (True, '%CMNG: 42,1,"8CEA57609B0F95C0D0F80383A7A21ECD1C6E102FDCC3CDCEB1948B0EA828601D"')
56+
exists, sha = at_command_interface.check_credential_exists(sectag=42, cred_type=CredType.CLIENT_CERT.value)
57+
assert exists is True
58+
assert sha == '8CEA57609B0F95C0D0F80383A7A21ECD1C6E102FDCC3CDCEB1948B0EA828601D'
59+
60+
def test_get_csr_at(at_command_interface):
61+
"""Test getting a CSR using ATCommandInterface"""
62+
at_command_interface.comms.write_line = Mock()
63+
at_command_interface.comms.expect_response.return_value = (True, '%KEYGEN: "foo.bar"')
64+
csr = at_command_interface.get_csr(sectag=42, attributes='O=Test,CN=Device')
65+
assert csr == 'foo.bar'
66+
at_command_interface.comms.write_line.assert_called_once_with('AT%KEYGEN=42,2,0,"O=Test,CN=Device"')
67+
68+
def test_get_csr_no_attributes(at_command_interface):
69+
"""Test getting a CSR without attributes using ATCommandInterface"""
70+
at_command_interface.comms.write_line = Mock()
71+
at_command_interface.comms.expect_response.return_value = (True, '%KEYGEN: "foo.bar"')
72+
csr = at_command_interface.get_csr(sectag=42)
73+
assert csr == 'foo.bar'
74+
at_command_interface.comms.write_line.assert_called_once_with('AT%KEYGEN=42,2,0')
75+
76+
def test_get_imei(at_command_interface):
77+
"""Test getting IMEI using ATCommandInterface"""
78+
at_command_interface.comms.write_line = Mock()
79+
at_command_interface.comms.expect_response.return_value = (True, '123456789012345')
80+
imei = at_command_interface.get_imei()
81+
assert imei == '123456789012345'
82+
at_command_interface.comms.write_line.assert_called_once_with('AT+CGSN')
83+
84+
def test_go_offline(at_command_interface):
85+
"""Test going offline using ATCommandInterface"""
86+
at_command_interface.comms.write_line = Mock()
87+
at_command_interface.comms.expect_response.return_value = (True, '')
88+
at_command_interface.go_offline()
89+
at_command_interface.comms.write_line.assert_called_once_with('AT+CFUN=4')
90+
91+
def test_get_model_id(at_command_interface):
92+
"""Test getting model ID using ATCommandInterface"""
93+
at_command_interface.comms.write_line = Mock()
94+
at_command_interface.comms.expect_response.return_value = (True, 'nRF9151-LACA')
95+
model_id = at_command_interface.get_model_id()
96+
assert model_id == 'nRF9151-LACA'
97+
at_command_interface.comms.write_line.assert_called_once_with('AT+CGMM')
98+
99+
def test_get_mfw_version(at_command_interface):
100+
"""Test getting MFW version using ATCommandInterface"""
101+
at_command_interface.comms.write_line = Mock()
102+
at_command_interface.comms.expect_response.return_value = (True, 'mfw_nrf91x1_2.0.2')
103+
mfw_version = at_command_interface.get_mfw_version()
104+
assert mfw_version == 'mfw_nrf91x1_2.0.2'
105+
at_command_interface.comms.write_line.assert_called_once_with('AT+CGMR')
106+
107+
def test_get_attestation_token(at_command_interface):
108+
"""Test getting attestation token using ATCommandInterface"""
109+
at_command_interface.comms.write_line = Mock()
110+
at_command_interface.comms.expect_response.return_value = (True, '%ATTESTTOKEN: "foo.bar"')
111+
attestation_token = at_command_interface.get_attestation_token()
112+
assert attestation_token == 'foo.bar'
113+
at_command_interface.comms.write_line.assert_called_once_with('AT%ATTESTTOKEN')
114+
115+
def test_enable_error_codes(at_command_interface):
116+
"""Test enabling error codes using ATCommandInterface"""
117+
at_command_interface.comms.write_line = Mock()
118+
at_command_interface.comms.expect_response.return_value = (True, '')
119+
at_command_interface.enable_error_codes()
120+
at_command_interface.comms.write_line.assert_called_once_with('AT+CMEE=1')
121+
122+
class MockCommsAT:
123+
"""Mock comms for ATCommandInterface"""
124+
def write_line(self, line):
125+
self.last_written = line
126+
127+
def expect_response(self, ok_str=None, error_str=None, store_str=None, timeout=15, suppress_errors=False):
128+
if self.last_written == 'AT+CGSN':
129+
return True, '123456789012345'
130+
else:
131+
return False, ''
132+
133+
def test_detect_shell_mode_at(at_command_interface):
134+
"""Test detecting shell mode using ATCommandInterface (AT Host)"""
135+
at_command_interface.comms = MockCommsAT()
136+
at_command_interface.detect_shell_mode()
137+
assert at_command_interface.shell == False
138+
139+
class MockCommsATShell:
140+
"""Mock comms for TLSCredShellInterface"""
141+
def write_line(self, line):
142+
self.last_written = line
143+
144+
def expect_response(self, ok_str=None, error_str=None, store_str=None, timeout=15, suppress_errors=False):
145+
if self.last_written == 'at AT+CGSN':
146+
return True, '123456789012345'
147+
else:
148+
return False, ''
149+
150+
def test_detect_shell_mode_shell(at_command_interface):
151+
"""Test detecting shell mode using ATCommandInterface (AT Shell)"""
152+
at_command_interface.comms = MockCommsATShell()
153+
at_command_interface.detect_shell_mode()
154+
assert at_command_interface.shell == True

0 commit comments

Comments
 (0)