diff --git a/notebooks/dispersion_tutorial.ipynb b/notebooks/dispersion_tutorial.ipynb index 6109776..97ca9d8 100644 --- a/notebooks/dispersion_tutorial.ipynb +++ b/notebooks/dispersion_tutorial.ipynb @@ -24,7 +24,7 @@ ], "source": [ "from src.utils import secure_fisher_yates_shuffle, matrix_to_list, list_to_matrix\n", - "from src.user_interface import UserInterface\n", + "from src.user_interface import UserKeypad\n", "from IPython.display import Markdown, display\n", "from src.models import KeypadSize\n", "\n", @@ -43,13 +43,13 @@ "\n", "keypad_size = KeypadSize(numb_of_keys=5, attrs_per_key=4)\n", "attrs = [1, 10, 11, 100]\n", - "interface = []\n", + "keypad = []\n", "for key_numb in range(1,keypad_size.numb_of_keys+1):\n", - " interface.extend([key_numb*attr for attr in attrs])\n", + " keypad.extend([key_numb * attr for attr in attrs])\n", "\n", - "demo_interface = UserInterface(keypad_size=keypad_size, interface=interface)\n", + "demo_interface = UserKeypad(keypad_size=keypad_size, keypad=keypad)\n", "\n", - "display(Markdown(keypad_md_table(demo_interface.interface, keypad_size)))\n" + "display(Markdown(keypad_md_table(demo_interface.keypad, keypad_size)))\n" ], "metadata": { "collapsed": false, @@ -73,7 +73,7 @@ } ], "source": [ - "demo_interface_matrix = list_to_matrix(demo_interface.interface, demo_interface.keypad_size.attrs_per_key)\n", + "demo_interface_matrix = list_to_matrix(demo_interface.keypad, demo_interface.keypad_size.attrs_per_key)\n", "shuffled_keys = secure_fisher_yates_shuffle(demo_interface_matrix)\n", "shuffled_keys_list = matrix_to_list(shuffled_keys)\n", "display(Markdown(keypad_md_table(shuffled_keys_list, keypad_size)))\n" @@ -101,7 +101,7 @@ ], "source": [ "attr_rotation = secure_fisher_yates_shuffle(list(range(keypad_size.numb_of_keys)))[:keypad_size.attrs_per_key]\n", - "dispersed_interface = UserInterface.random_attribute_rotation(\n", + "dispersed_interface = UserKeypad.random_attribute_rotation(\n", " shuffled_keys,\n", " attr_rotation\n", ")\n", diff --git a/src/customer.py b/src/customer.py index 4fc310b..ee14c07 100644 --- a/src/customer.py +++ b/src/customer.py @@ -38,7 +38,7 @@ class Customer: for idx in range(passcode_len): key_numb = selected_keys[idx] set_idx = set_vals_idx[idx] - selected_attr_idx = user.user_interface.get_attr_idx_by_keynumb_setidx(key_numb, set_idx) + selected_attr_idx = user.user_keypad.get_attr_idx_by_keynumb_setidx(key_numb, set_idx) presumed_selected_attributes_idx.append(selected_attr_idx) enciphered_attr = user.user_keys.encipher_salt_hash_code(presumed_selected_attributes_idx, self.attributes) diff --git a/src/nkode_api.py b/src/nkode_api.py index 3ddfdce..a02e9ff 100644 --- a/src/nkode_api.py +++ b/src/nkode_api.py @@ -7,7 +7,7 @@ from src.models import NKodePolicy, KeypadSize from src.user import User from src.user_cipher_keys import UserCipherKeys from src.user_signup_session import UserSignupSession -from src.user_interface import UserInterface +from src.user_keypad import UserKeypad from src.customer_attributes import CustomerAttributes @@ -26,21 +26,21 @@ class NKodeAPI: self.customers[new_customer.customer_id] = new_customer return new_customer.customer_id - def generate_signup_interface(self, customer_id: UUID) -> Tuple[UUID, List[int]]: + def generate_signup_keypad(self, customer_id: UUID) -> Tuple[UUID, List[int]]: if customer_id not in self.customers.keys(): raise ValueError(f"Customer with ID '{customer_id}' does not exist") customer = self.customers[customer_id] - login_interface = UserInterface.create(customer.attributes.keypad_size) - set_interface = login_interface.sign_up_interface() + login_keypad = UserKeypad.create(customer.attributes.keypad_size) + set_keypad = login_keypad.sign_up_keypad() new_session = UserSignupSession( session_id=uuid4(), - login_interface=login_interface, - set_interface=set_interface.interface, + login_keypad=login_keypad, + set_keypad=set_keypad.keypad, customer_id=customer_id, - keypad_size=set_interface.keypad_size, + keypad_size=set_keypad.keypad_size, ) self.signup_sessions[new_session.session_id] = new_session - return new_session.session_id, new_session.set_interface + return new_session.session_id, new_session.set_keypad def set_nkode( self, @@ -57,7 +57,7 @@ class NKodeAPI: if session_id not in self.signup_sessions.keys(): raise ValueError(f"Session ID {session_id} not found") self.signup_sessions[session_id].set_user_nkode(username, key_selection) - return self.signup_sessions[session_id].confirm_interface + return self.signup_sessions[session_id].confirm_keypad def confirm_nkode( self, @@ -85,21 +85,21 @@ class NKodeAPI: username=username, enciphered_passcode=enciphered_passcode, user_keys=new_user_keys, - user_interface=self.signup_sessions[session_id].login_interface, + user_keypad=self.signup_sessions[session_id].login_keypad, ) self.customers[customer_id].add_new_user(new_user) del self.signup_sessions[session_id] return True - def get_login_interface(self, username: str, customer_id: UUID) -> List[int]: + def get_login_keypad(self, username: str, customer_id: UUID) -> List[int]: if customer_id not in self.customers.keys(): raise ValueError("Customer ID not found") customer = self.customers[customer_id] if username not in customer.users.keys(): raise ValueError("Username not found") user = customer.users[username] - user.user_interface.partial_interface_shuffle() - return user.user_interface.interface + user.user_keypad.partial_keypad_shuffle() + return user.user_keypad.keypad def login(self, customer_id: UUID, username: str, key_selection: List[int]) -> bool: if customer_id not in self.customers.keys(): diff --git a/src/user.py b/src/user.py index 6ce67c7..dad959c 100644 --- a/src/user.py +++ b/src/user.py @@ -2,7 +2,7 @@ from dataclasses import dataclass, field from src.models import EncipheredNKode from src.customer_attributes import CustomerAttributes from src.user_cipher_keys import UserCipherKeys -from src.user_interface import UserInterface +from src.user_keypad import UserKeypad from src.utils import xor_lists @@ -11,7 +11,7 @@ class User: username: str enciphered_passcode: EncipheredNKode user_keys: UserCipherKeys - user_interface: UserInterface + user_keypad: UserKeypad renew: bool = field(default=False) def renew_keys(self, sets_xor: list[int], attrs_xor: list[int]): @@ -19,11 +19,11 @@ class User: self.user_keys.set_key = xor_lists(self.user_keys.set_key, sets_xor) self.user_keys.alpha_key = xor_lists(self.user_keys.alpha_key, attrs_xor) - def refresh_passcode(self, passcode_attr_idx: list[int], customer_interface: CustomerAttributes): + def refresh_passcode(self, passcode_attr_idx: list[int], customer_attributes: CustomerAttributes): self.user_keys = UserCipherKeys.create( - customer_interface.keypad_size, - customer_interface.set_vals, + customer_attributes.keypad_size, + customer_attributes.set_vals, self.user_keys.max_nkode_len ) - self.enciphered_passcode = self.user_keys.encipher_nkode(passcode_attr_idx, customer_interface) + self.enciphered_passcode = self.user_keys.encipher_nkode(passcode_attr_idx, customer_attributes) self.renew = False diff --git a/src/user_cipher_keys.py b/src/user_cipher_keys.py index 700c99b..a4a4d3f 100644 --- a/src/user_cipher_keys.py +++ b/src/user_cipher_keys.py @@ -64,13 +64,13 @@ class UserCipherKeys: def encipher_nkode( self, passcode_attr_idx: list[int], - customer_interface: CustomerAttributes + customer_attributes: CustomerAttributes ) -> EncipheredNKode: - passcode_attrs = [customer_interface.attr_vals[idx] for idx in passcode_attr_idx] - passcode_sets = [customer_interface.get_attr_set_val(attr) for attr in passcode_attrs] - mask = self.encipher_mask(passcode_sets, customer_interface) - code = self.encipher_salt_hash_code(passcode_attr_idx, customer_interface) + passcode_attrs = [customer_attributes.attr_vals[idx] for idx in passcode_attr_idx] + passcode_sets = [customer_attributes.get_attr_set_val(attr) for attr in passcode_attrs] + mask = self.encipher_mask(passcode_sets, customer_attributes) + code = self.encipher_salt_hash_code(passcode_attr_idx, customer_attributes) return EncipheredNKode( code=code, mask=mask @@ -79,10 +79,10 @@ class UserCipherKeys: def encipher_salt_hash_code( self, passcode_attr_idx: list[int], - customer_interface: CustomerAttributes, + customer_attributes: CustomerAttributes, ) -> str: passcode_len = len(passcode_attr_idx) - passcode_attrs = [customer_interface.attr_vals[idx] for idx in passcode_attr_idx] + passcode_attrs = [customer_attributes.attr_vals[idx] for idx in passcode_attr_idx] passcode_cipher = self.pass_key.copy() @@ -96,10 +96,10 @@ class UserCipherKeys: def encipher_mask( self, passcode_sets: list[int], - customer_interface: CustomerAttributes + customer_attributes: CustomerAttributes ) -> str: - padded_passcode_sets = self.pad_user_mask(passcode_sets, customer_interface.set_vals) - set_idx = [customer_interface.get_set_index(set_val) for set_val in padded_passcode_sets] + padded_passcode_sets = self.pad_user_mask(passcode_sets, customer_attributes.set_vals) + set_idx = [customer_attributes.get_set_index(set_val) for set_val in padded_passcode_sets] mask_set_keys = [self.set_key[idx] for idx in set_idx] ciphered_mask = xor_lists(mask_set_keys, padded_passcode_sets) ciphered_mask = xor_lists(ciphered_mask, self.mask_key) diff --git a/src/user_interface.py b/src/user_keypad.py similarity index 55% rename from src/user_interface.py rename to src/user_keypad.py index 9ea72d5..4efe5c6 100644 --- a/src/user_interface.py +++ b/src/user_keypad.py @@ -4,94 +4,94 @@ from src.models import KeypadSize from src.utils import list_to_matrix, secure_fisher_yates_shuffle, matrix_to_list, matrix_transpose @dataclass -class UserInterface: - interface: list[int] +class UserKeypad: + keypad: list[int] keypad_size: KeypadSize @classmethod - def create(cls, keypad_size: KeypadSize) -> 'UserInterface': - interface = UserInterface( - interface=list(range(keypad_size.numb_of_attrs)), + def create(cls, keypad_size: KeypadSize) -> 'UserKeypad': + keypad = UserKeypad( + keypad=list(range(keypad_size.numb_of_attrs)), keypad_size=keypad_size ) - interface.random_interface_shuffle() - return interface + keypad.random_keypad_shuffle() + return keypad - def sign_up_interface(self): + def sign_up_keypad(self): if self.keypad_size.is_dispersable: raise ValueError("Keypad size is dispersable") - self.random_interface_shuffle() - interface_matrix = self.interface_keypad_matrix() - attr_set_view = matrix_transpose(interface_matrix) + self.random_keypad_shuffle() + keypad_matrix = self.keypad_matrix() + attr_set_view = matrix_transpose(keypad_matrix) attr_set_view = secure_fisher_yates_shuffle(attr_set_view) attr_set_view = attr_set_view[:self.keypad_size.numb_of_keys] - interface_matrix = matrix_transpose(attr_set_view) - return UserInterface( - interface=matrix_to_list(interface_matrix), + keypad_matrix = matrix_transpose(attr_set_view) + return UserKeypad( + keypad=matrix_to_list(keypad_matrix), keypad_size=KeypadSize( numb_of_keys=self.keypad_size.numb_of_keys, attrs_per_key=self.keypad_size.numb_of_keys ) ) - def interface_keypad_matrix(self) -> list[list[int]]: - return list_to_matrix(self.interface, self.keypad_size.attrs_per_key) + def keypad_matrix(self) -> list[list[int]]: + return list_to_matrix(self.keypad, self.keypad_size.attrs_per_key) - def random_interface_shuffle(self): - keypad_view = self.interface_keypad_matrix() + def random_keypad_shuffle(self): + keypad_view = self.keypad_matrix() keypad_view = secure_fisher_yates_shuffle(keypad_view) set_view = matrix_transpose(keypad_view) set_view = [secure_fisher_yates_shuffle(attr_set) for attr_set in set_view] keypad_view = matrix_transpose(set_view) - self.interface = matrix_to_list(keypad_view) + self.keypad = matrix_to_list(keypad_view) - def disperse_interface(self): + def disperse_keypad(self): if not self.keypad_size.is_dispersable: raise ValueError("Keypad size is not dispersable") - user_interface_matrix = list_to_matrix(self.interface, self.keypad_size.attrs_per_key) - shuffled_keys = secure_fisher_yates_shuffle(user_interface_matrix) + user_keypad_matrix = list_to_matrix(self.keypad, self.keypad_size.attrs_per_key) + shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix) attr_rotation = secure_fisher_yates_shuffle(list(range(self.keypad_size.numb_of_keys)))[ :self.keypad_size.attrs_per_key] - dispersed_interface = self.random_attribute_rotation( + dispersed_keypad = self.random_attribute_rotation( shuffled_keys, attr_rotation, ) - self.interface = matrix_to_list(dispersed_interface) + self.keypad = matrix_to_list(dispersed_keypad) - def partial_interface_shuffle(self): + def partial_keypad_shuffle(self): # TODO: this should be split shuffle numb_of_selected_sets = self.keypad_size.attrs_per_key // 2 # randomly shuffle half the sets. if attrs_per_key is odd, randomly add one 50% of the time numb_of_selected_sets += choice([0, 1]) if (self.keypad_size.attrs_per_key & 1) == 1 else 0 selected_sets = secure_fisher_yates_shuffle(list(range(self.keypad_size.attrs_per_key)))[:numb_of_selected_sets] - user_interface_matrix = self.interface_keypad_matrix() - shuffled_keys = secure_fisher_yates_shuffle(user_interface_matrix) - interface_by_sets = [] + user_keypad_matrix = self.keypad_matrix() + shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix) + keypad_by_sets = [] for idx, attrs in enumerate(matrix_transpose(shuffled_keys)): if idx in selected_sets: - interface_by_sets.append(secure_fisher_yates_shuffle(attrs)) + keypad_by_sets.append(secure_fisher_yates_shuffle(attrs)) else: - interface_by_sets.append(attrs) - self.interface = matrix_to_list(matrix_transpose(interface_by_sets)) + keypad_by_sets.append(attrs) + self.keypad = matrix_to_list(matrix_transpose(keypad_by_sets)) @staticmethod def random_attribute_rotation( - user_interface: list[list[int]], + user_keypad: list[list[int]], attr_rotation: list[int] ) -> list[list[int]]: - transposed_user_interface = matrix_transpose(user_interface) - if len(attr_rotation) != len(transposed_user_interface): - raise ValueError("attr_rotation must be the same length as the transposed user interface") - for idx, attr_set in enumerate(transposed_user_interface): + transposed_user_keypad = matrix_transpose(user_keypad) + if len(attr_rotation) != len(transposed_user_keypad): + raise ValueError("attr_rotation must be the same length as the transposed user keypad") + for idx, attr_set in enumerate(transposed_user_keypad): rotation = attr_rotation[idx] - transposed_user_interface[idx] = attr_set[rotation:] + attr_set[:rotation] - return matrix_transpose(transposed_user_interface) + transposed_user_keypad[idx] = attr_set[rotation:] + attr_set[:rotation] + return matrix_transpose(transposed_user_keypad) def attribute_adjacency_graph(self) -> dict[int, set[int]]: - user_interface_keypad = self.interface_keypad_matrix() + user_keypad_keypad = self.keypad_matrix() graph = {} - for key in user_interface_keypad: + for key in user_keypad_keypad: for attr in key: graph[attr] = set(key) graph[attr].remove(attr) @@ -102,5 +102,5 @@ class UserInterface: raise ValueError(f"key_numb must be between 0 and {self.keypad_size.numb_of_keys - 1}") if not (0 <= set_idx < self.keypad_size.attrs_per_key): raise ValueError(f"set_idx must be between 0 and {self.keypad_size.attrs_per_key - 1}") - keypad_attr_idx = self.interface_keypad_matrix() + keypad_attr_idx = self.keypad_matrix() return keypad_attr_idx[key_numb][set_idx] diff --git a/src/user_signup_session.py b/src/user_signup_session.py index 539f5fd..b9d14ed 100644 --- a/src/user_signup_session.py +++ b/src/user_signup_session.py @@ -2,17 +2,17 @@ from uuid import UUID from pydantic import BaseModel -from src.user_interface import UserInterface +from src.user_keypad import UserKeypad from src.models import KeypadSize class UserSignupSession(BaseModel): session_id: UUID customer_id: UUID - login_interface: UserInterface + login_keypad: UserKeypad keypad_size: KeypadSize - set_interface: list[int] | None = None - confirm_interface: list[int] | None = None + set_keypad: list[int] | None = None + confirm_keypad: list[int] | None = None set_key_entry: list[int] | None = None username: str | None = None @@ -23,10 +23,10 @@ class UserSignupSession(BaseModel): set_key_entry = self.set_key_entry if len(set_key_entry) != len(confirm_key_entry): raise ValueError("Key entry lengths must match") - set_interface = self.set_interface - confirm_interface = self.confirm_interface - set_key_vals = [set_interface[key * attrs_per_key:(key + 1) * attrs_per_key] for key in set_key_entry] - confirm_key_vals = [confirm_interface[key * attrs_per_key:(key + 1) * attrs_per_key] for key in + set_keypad = self.set_keypad + confirm_keypad = self.confirm_keypad + set_key_vals = [set_keypad[key * attrs_per_key:(key + 1) * attrs_per_key] for key in set_key_entry] + confirm_key_vals = [confirm_keypad[key * attrs_per_key:(key + 1) * attrs_per_key] for key in confirm_key_entry] passcode = [] for idx in range(len(set_key_entry)): @@ -41,12 +41,12 @@ class UserSignupSession(BaseModel): def set_user_nkode(self, username: str, key_selection: list[int]): if not all(0 <= key <= self.keypad_size.numb_of_keys for key in key_selection): raise ValueError("Key values must be within valid range") - set_interface = UserInterface( - interface=self.set_interface, + set_keypad = UserKeypad( + keypad=self.set_keypad, keypad_size=self.keypad_size ) - set_interface.disperse_interface() + set_keypad.disperse_keypad() self.username = username self.set_key_entry = key_selection - self.confirm_interface = set_interface.interface + self.confirm_keypad = set_keypad.keypad diff --git a/src/utils.py b/src/utils.py index 8a28da2..3a099f2 100644 --- a/src/utils.py +++ b/src/utils.py @@ -29,8 +29,8 @@ def list_to_matrix(lst: list[int], cols: int) -> list[list[int]]: return [lst[i:i + cols] for i in range(0, len(lst), cols)] -def matrix_transpose(interface: list[list[int]]) -> list[list[int]]: - return [list(row) for row in zip(*interface)] +def matrix_transpose(mat: list[list[int]]) -> list[list[int]]: + return [list(row) for row in zip(*mat)] def int_array_to_bytes(int_arr: list[int], byte_size: int = 2) -> bytes: diff --git a/test/test_nkode_api.py b/test/test_nkode_api.py index 186438c..13b2df3 100644 --- a/test/test_nkode_api.py +++ b/test/test_nkode_api.py @@ -16,14 +16,14 @@ def test_create_new_user_and_renew_keys(nkode_api, keypad_size, passocode_len): username = "test_username" nkode_policy = NKodePolicy() # default policy customer_id = nkode_api.create_new_customer(keypad_size, nkode_policy) - session_id, set_interface = nkode_api.generate_signup_interface(customer_id) - user_passcode = set_interface[:passocode_len] + session_id, set_keypad = nkode_api.generate_signup_keypad(customer_id) + user_passcode = set_keypad[:passocode_len] - signup_key_selection = lambda interface: [interface.index(attr) // keypad_size.numb_of_keys for attr in user_passcode] - set_key_selection = signup_key_selection(set_interface) + signup_key_selection = lambda keypad: [keypad.index(attr) // keypad_size.numb_of_keys for attr in user_passcode] + set_key_selection = signup_key_selection(set_keypad) - confirm_interface = nkode_api.set_nkode(username, customer_id, set_key_selection, session_id) - confirm_key_selection = signup_key_selection(confirm_interface) + confirm_keypad = nkode_api.set_nkode(username, customer_id, set_key_selection, session_id) + confirm_key_selection = signup_key_selection(confirm_keypad) successful_confirm = nkode_api.confirm_nkode( username, customer_id, @@ -32,21 +32,21 @@ def test_create_new_user_and_renew_keys(nkode_api, keypad_size, passocode_len): ) assert successful_confirm - sign_in_key_selection = lambda interface: [interface.index(attr) // keypad_size.attrs_per_key for attr in user_passcode] - login_interface = nkode_api.get_login_interface(username, customer_id) - login_key_selection = sign_in_key_selection(login_interface) + sign_in_key_selection = lambda keypad: [keypad.index(attr) // keypad_size.attrs_per_key for attr in user_passcode] + login_keypad = nkode_api.get_login_keypad(username, customer_id) + login_key_selection = sign_in_key_selection(login_keypad) successful_login = nkode_api.login(customer_id, username, login_key_selection) assert successful_login successful_renew = nkode_api.renew_attributes(customer_id) assert successful_renew - login_interface = nkode_api.get_login_interface(username, customer_id) - login_key_selection = sign_in_key_selection(login_interface) + login_keypad = nkode_api.get_login_keypad(username, customer_id) + login_key_selection = sign_in_key_selection(login_keypad) successful_login = nkode_api.login(customer_id, username, login_key_selection) assert successful_login - login_interface = nkode_api.get_login_interface(username, customer_id) - login_key_selection = sign_in_key_selection(login_interface) + login_keypad = nkode_api.get_login_keypad(username, customer_id) + login_key_selection = sign_in_key_selection(login_keypad) successful_login = nkode_api.login(customer_id, username, login_key_selection) assert successful_login diff --git a/test/test_nkode_interface.py b/test/test_nkode_interface.py index da3d4d4..7998e77 100644 --- a/test/test_nkode_interface.py +++ b/test/test_nkode_interface.py @@ -1,5 +1,5 @@ import pytest -from src.user_interface import UserInterface +from src.user_keypad import UserKeypad from src.models import KeypadSize @@ -8,8 +8,8 @@ from src.models import KeypadSize [KeypadSize(numb_of_keys=10, attrs_per_key=11)] ) def test_attr_set_idx(keypad_size): - user_interface = UserInterface.create(keypad_size) + user_keypad = UserKeypad.create(keypad_size) for attr_idx in range(keypad_size.numb_of_attrs): - user_interface_idx = user_interface.interface[attr_idx] + user_keypad_idx = user_keypad.keypad[attr_idx] - assert (attr_idx % keypad_size.attrs_per_key == user_interface_idx % keypad_size.attrs_per_key) + assert (attr_idx % keypad_size.attrs_per_key == user_keypad_idx % keypad_size.attrs_per_key) diff --git a/test/test_user_interface.py b/test/test_user_interface.py deleted file mode 100644 index 563d4ec..0000000 --- a/test/test_user_interface.py +++ /dev/null @@ -1,35 +0,0 @@ -import pytest -from src.user_interface import UserInterface -from src.models import KeypadSize - - -@pytest.fixture() -def user_interface(): - return UserInterface.create(keypad_size=KeypadSize(attrs_per_key=7, numb_of_keys=10)) - - -def test_dispersion(user_interface): - for _ in range(10000): - pre_dispersion_graph = user_interface.attribute_adjacency_graph() - user_interface.disperse_interface() - post_dispersion_graph = user_interface.attribute_adjacency_graph() - for attr, adj_graph in pre_dispersion_graph.items(): - assert (adj_graph.isdisjoint(post_dispersion_graph[attr])) - - -def test_shuffle_attrs(user_interface): - """there's no easy way to test this. At some point we'll have to run this code thousands of time to see if we get - expected statistical outcomes like: - - every attribute gets to every key with a uniform distribution - - every attribute is adjacent to every other attribute with uniform distribution - - the order in which the attributes move from key to key is random (i.e. the distance traveled is uniform) - """ - pre_shuffle_interface = user_interface.interface - user_interface.partial_interface_shuffle() - post_shuffle_interface = user_interface.interface - assert (not all( - post_shuffle_interface[idx] == pre_shuffle_interface[idx] for idx in range(len(post_shuffle_interface)) - )) - assert (not all( - post_shuffle_interface[idx] != pre_shuffle_interface[idx] for idx in range(len(post_shuffle_interface)) - )) diff --git a/test/test_user_keypad.py b/test/test_user_keypad.py new file mode 100644 index 0000000..c1f2439 --- /dev/null +++ b/test/test_user_keypad.py @@ -0,0 +1,35 @@ +import pytest +from src.user_keypad import UserKeypad +from src.models import KeypadSize + + +@pytest.fixture() +def user_keypad(): + return UserKeypad.create(keypad_size=KeypadSize(attrs_per_key=7, numb_of_keys=10)) + + +def test_dispersion(user_keypad): + for _ in range(10000): + pre_dispersion_graph = user_keypad.attribute_adjacency_graph() + user_keypad.disperse_keypad() + post_dispersion_graph = user_keypad.attribute_adjacency_graph() + for attr, adj_graph in pre_dispersion_graph.items(): + assert (adj_graph.isdisjoint(post_dispersion_graph[attr])) + + +def test_shuffle_attrs(user_keypad): + """there's no easy way to test this. At some point we'll have to run this code thousands of time to see if we get + expected statistical outcomes like: + - every attribute gets to every key with a uniform distribution + - every attribute is adjacent to every other attribute with uniform distribution + - the order in which the attributes move from key to key is random (i.e. the distance traveled is uniform) + """ + pre_shuffle_keypad = user_keypad.keypad + user_keypad.partial_keypad_shuffle() + post_shuffle_keypad = user_keypad.keypad + assert (not all( + post_shuffle_keypad[idx] == pre_shuffle_keypad[idx] for idx in range(len(post_shuffle_keypad)) + )) + assert (not all( + post_shuffle_keypad[idx] != pre_shuffle_keypad[idx] for idx in range(len(post_shuffle_keypad)) + ))