-
-
Notifications
You must be signed in to change notification settings - Fork 14
Conversation
@coderabbitai review |
Actions performedReview triggered.
|
Warning Rate limit exceeded@JarbasAl has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 24 minutes and 14 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (2)
WalkthroughThe updates enhance the Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant ClientDatabase
participant AbstractDB
participant JsonDB
participant SQLiteDB
participant RedisDB
User->>+ClientDatabase: Create or update client
ClientDatabase->>+AbstractDB: Route to appropriate DB
AbstractDB->>JsonDB: If JSON backend
AbstractDB->>SQLiteDB: If SQLite backend
AbstractDB->>RedisDB: If Redis backend
JsonDB-->>-AbstractDB: Operation result
SQLiteDB-->>-AbstractDB: Operation result
RedisDB-->>-AbstractDB: Operation result
AbstractDB-->>-ClientDatabase: Operation result
ClientDatabase-->>-User: Confirm operation
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Outside diff range, codebase verification and nitpick comments (4)
test/unittests/test_db.py (2)
39-53
: New methodtest_update_entry
checks client update functionality.This method tests updating a client's name in the database. It's good to see comprehensive checks after the update to ensure the changes are reflected correctly. This method should also include assertions to verify that other client attributes are not unintentionally modified during the update process.
+ # Ensure other attributes are not changed + self.assertEqual(user.api_key, updated_user.api_key) + self.assertEqual(user.crypto_key, updated_user.crypto_key)
67-81
: Edge cases for client ID reuse are not covered intest_delete_and_reuse_client_id
.The current test checks for deletion and reuse of a client ID but does not cover edge cases such as attempting to reuse an ID that has not been properly deleted. Consider adding tests to cover these scenarios to ensure database integrity and consistency.
Analysis chain
Testing deletion and reuse of client ID in
test_delete_and_reuse_client_id
.This method tests important scenarios of deleting a client and ensuring that a new client can reuse the ID. It's crucial for database integrity and consistency. Ensure that the method covers all edge cases, such as attempting to reuse an ID that has not been properly deleted.
Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify edge cases for client ID reuse are tested. # Test: Search for additional tests on client ID reuse. rg --type python -A 5 $'test_delete_and_reuse_client_id' test/unittests/test_db.pyLength of output: 112
Script:
#!/bin/bash # Description: Verify edge cases for client ID reuse are tested. # Test: Search for the test_delete_and_reuse_client_id function definition and its context. rg 'def test_delete_and_reuse_client_id' -A 10 test/unittests/test_db.py # Test: Search for any other tests that might cover client ID reuse. rg 'client_id' -A 10 test/unittests/test_db.pyLength of output: 1350
hivemind_core/database.py (2)
Line range hint
1-28
: Refactoring ofcast_to_client_obj
to use decorator pattern.The refactoring of
cast_to_client_obj
to use a decorator pattern is a good practice as it enhances modularity and reusability. The function now can handle different return types more robustly. However, the error message could be more specific by listing the actual unknown keys.- raise RuntimeError(f"{func.__name__} returned a dict with unknown keys: {ret.keys()}") + unknown_keys = set(ret.keys()) - set(valid_kwargs) + raise RuntimeError(f"{func.__name__} returned a dict with unknown keys: {unknown_keys}")
Line range hint
257-346
: Comprehensive handling of client operations inClientDatabase
.The
ClientDatabase
class effectively abstracts the backend details and provides a unified interface for client operations. The error handling and use of the decorator pattern in methods likeget_client_by_api_key
andadd_client
are well-implemented. However, the methodadd_client
could be refactored to avoid deep nesting and improve readability.- if user: - item_id = self.db.get_item_id(user) - if name: - user["name"] = name - if blacklist: - user["blacklist"] = blacklist - if allowed_types: - user["allowed_types"] = allowed_types - if admin is not None: - user["is_admin"] = admin - if crypto_key: - user["crypto_key"] = crypto_key - if password: - user["password"] = password - self.db.update_item(item_id, user) - else: - user = Client( - api_key=key, - name=name, - blacklist=blacklist, - crypto_key=crypto_key, - client_id=self.total_clients() + 1, - is_admin=admin, - password=password, - allowed_types=allowed_types, - ) - self.db.add_item(user) + item_id = self.db.get_item_id(user) if user else None + user_data = { + "name": name, + "blacklist": blacklist, + "allowed_types": allowed_types, + "is_admin": admin, + "crypto_key": crypto_key, + "password": password + } + user_data = {k: v for k, v in user_data.items() if v is not None} + if item_id: + self.db.update_item(item_id, user_data) + else: + user = Client(api_key=key, **user_data, client_id=self.total_clients() + 1) + self.db.add_item(user)Tools
Ruff
166-166:
redis.commands.search.indexDefinition.IndexType
imported but unused; consider usingimportlib.util.find_spec
to test for availability(F401)
180-180: Do not use bare
except
(E722)
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files selected for processing (2)
- hivemind_core/database.py (9 hunks)
- test/unittests/test_db.py (1 hunks)
Additional context used
Ruff
test/unittests/test_db.py
61-61: f-string without any placeholders
Remove extraneous
f
prefix(F541)
hivemind_core/database.py
166-166:
redis.commands.search.indexDefinition.IndexType
imported but unused; consider usingimportlib.util.find_spec
to test for availability(F401)
180-180: Do not use bare
except
(E722)
Additional comments not posted (5)
test/unittests/test_db.py (3)
6-11
: Refactoring to useunittest.TestCase
and addition ofsetUp
method.The change from inheriting
TestCase
tounittest.TestCase
is a good practice as it clarifies the source ofTestCase
. The addition of thesetUp
method is also beneficial as it centralizes the initialization of variables used across multiple test methods, enhancing readability and maintainability.
54-58
: Testing for nonexistent entries intest_search_nonexistent_entry
.This method correctly tests the behavior of the system when searching for a nonexistent API key. It's crucial for robustness to ensure that the system handles such cases gracefully without errors.
13-38
: Comprehensive testing intest_add_entry
.This method now thoroughly tests adding, searching, and deleting a client in the database. Using
self
for instance variables is a good practice as it ensures that these variables are accessible across different test methods. However, ensure that the database operations are mocked or handled in a way that they do not affect the actual database during testing.hivemind_core/database.py (2)
Line range hint
47-86
: Enhancements toClient
class constructor with default values.The addition of default values and optional parameters in the
Client
class constructor is a positive change, making the class more flexible and robust. The error handling in__setitem__
and__getitem__
methods is also well-implemented, providing clear feedback on errors.
105-133
: Introduction ofAbstractDB
class and its concrete implementations.The introduction of an abstract base class
AbstractDB
with defined abstract methods is a good architectural decision. It enforces a consistent interface for all database backends. The concrete implementationsJsonDB
andRedisDB
are well-defined, adhering to the abstract base class's contract.
535ae9c
to
e968cef
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🔭 Outside diff range comments (1)
hivemind_core/scripts.py (1)
Line range hint
183-196
: Potentially invalid update invocation in “delete_client”.
You are calling “db.update_item(item_id, dict(...))”, but the AbstractDB interface defines “update_item(client: Client) → bool”. Passing “item_id” and a dictionary doesn’t match that signature. This likely causes runtime issues.Possible fix (example):
- Retrieve the client object via “get_client_by_api_key(...)” or iterate until you find the target,
- Update the client’s attributes, and
- Call “db.update_item(updated_client)”.
🧹 Nitpick comments (10)
test/unittests/test_db.py (4)
25-37
: Consider verifying all serialized fields in tests.
In the “test_client_serialization” method, you are checking for the "client_id" field in the serialized output. It might be helpful to also verify the presence of other critical fields such as "api_key", "name", etc. This ensures that serialization is capturing all attributes properly.
74-85
: Suggest verifying data after adding an item.
In “test_add_item” of TestJsonDB, after checking that the client ID exists in the database, consider also verifying whether the stored data matches all the client attributes.
99-112
: Enhance coverage for “search_by_value” test.
Currently, “test_search_by_value” checks searching by "name". Consider adding additional queries (e.g., by "description" or "is_admin") to increase confidence in the search functionality.
150-161
: Expand negative scenario testing in “test_delete_client”.
In addition to verifying a successful deletion scenario, consider adding a test where the client does not exist to ensure the method returns False (or behaves appropriately) in that situation.hivemind_core/database.py (4)
29-30
: Combine isinstance checks.
Per static analysis hint (SIM101), you could reduce duplication by combining the two isinstance checks into a single condition:- if isinstance(ret, str) or isinstance(ret, dict): + if isinstance(ret, (str, dict)):🧰 Tools
🪛 Ruff (0.8.2)
29-29: Multiple
isinstance
calls forret
, merge into a single callMerge
isinstance
calls forret
(SIM101)
137-140
: Avoid bare except and replace with context manager.
In “eq” method, the bare “except” can obscure debugging. Consider replacing with “contextlib.suppress” or at least specifying the exception type.- try: - other = cast2client(other) - except: - pass + from contextlib import suppress + with suppress(Exception): + other = cast2client(other)🧰 Tools
🪛 Ruff (0.8.2)
137-140: Use
contextlib.suppress(Exception)
instead oftry
-except
-pass
Replace with
contextlib.suppress(Exception)
(SIM105)
139-139: Do not use bare
except
(E722)
345-346
: Remove unused exception variable.
The local variable 'e' is never used in the exception block. You can remove it to avoid confusion.- except Exception as e: + except Exception: LOG.error(f"Failed to save {self._db.path}") return False🧰 Tools
🪛 Ruff (0.8.2)
345-345: Local variable
e
is assigned to but never usedRemove assignment to unused variable
e
(F841)
416-416
: Remove extraneous f-strings containing no placeholders.
You are using f-strings "f'client:*'" without any placeholders in lines 416 and 439. Please remove the “f” prefix to adhere to best practices.-for client_id in self.redis.scan_iter(f"client:*"): +for client_id in self.redis.scan_iter("client:*"):Also applies to: 439-439
🧰 Tools
🪛 Ruff (0.8.2)
416-416: f-string without any placeholders
Remove extraneous
f
prefix(F541)
hivemind_core/scripts.py (1)
64-65
: Provide coverage for partial or update-only additions.
In “add_client”, when a matching client is found, you update some fields conditionally. Consider adding a dedicated test to confirm that partial updates are applied correctly (especially for admin, blacklists, etc.).hivemind_core/service.py (1)
144-162
: Consider adding type hints and docstring.The client data retrieval block would benefit from type hints and documentation.
Consider this improvement:
-def open(self): +def open(self) -> None: + """ + Handle client connection and authentication. + + Validates the client's API key against the database and sets up client properties. + Closes the connection if validation fails. + """
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
README.md
(1 hunks)hivemind_core/database.py
(1 hunks)hivemind_core/protocol.py
(3 hunks)hivemind_core/scripts.py
(13 hunks)hivemind_core/service.py
(5 hunks)setup.py
(1 hunks)test/unittests/test_db.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
hivemind_core/database.py
29-29: Multiple isinstance
calls for ret
, merge into a single call
Merge isinstance
calls for ret
(SIM101)
137-140: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
139-139: Do not use bare except
(E722)
345-345: Local variable e
is assigned to but never used
Remove assignment to unused variable e
(F841)
416-416: f-string without any placeholders
Remove extraneous f
prefix
(F541)
439-439: f-string without any placeholders
Remove extraneous f
prefix
(F541)
🔇 Additional comments (5)
test/unittests/test_db.py (1)
64-66
: Good test coverage for list casting.
The “test_cast2client” method does a good job ensuring that casting a list of serialized clients results in a corresponding list of Client objects, maintaining data consistency.
README.md (1)
15-16
: LGTM! Documentation updates are clear and comprehensive.
The documentation changes effectively communicate the command-line interface usage and available options.
hivemind_core/service.py (2)
205-205
: LGTM! Database integration is properly handled.
The database parameter is correctly propagated through the service initialization.
Also applies to: 217-217, 220-220
267-267
: LGTM! Protocol binding includes database access.
The protocol binding correctly includes the database parameter for consistent access across components.
hivemind_core/protocol.py (1)
256-256
: LGTM! Database attribute properly typed.
The database attribute is correctly defined with proper typing.
allow several database backends Update test/unittests/test_db.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
345716e
to
5e93efe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caution
Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.
Actionable comments posted: 2
🧹 Nitpick comments (4)
hivemind_core/database.py (4)
35-38
: Optimize type checking in cast2client functionThe multiple
isinstance
checks can be merged for better readability and performance.- if isinstance(ret, str) or isinstance(ret, dict): + if isinstance(ret, (str, dict)): return Client.deserialize(ret)🧰 Tools
🪛 Ruff (0.8.2)
35-35: Multiple
isinstance
calls forret
, merge into a single callMerge
isinstance
calls forret
(SIM101)
348-353
: Enhance error logging in commit methodThe error message could be more informative by including the actual error details.
try: self._db.store() return True except Exception as e: - LOG.error(f"Failed to save {self._db.path}") + LOG.error(f"Failed to save {self._db.path}: {str(e)}") return False🧰 Tools
🪛 Ruff (0.8.2)
351-351: Local variable
e
is assigned to but never usedRemove assignment to unused variable
e
(F841)
483-529
: Consider splitting add_client method for better maintainabilityThe method handles both creation and update logic, making it complex. Consider splitting it into separate methods for better maintainability.
+ def _update_existing_client(self, user: Client, **kwargs) -> bool: + """Update an existing client with the provided attributes.""" + for key, value in kwargs.items(): + if value is not None: + setattr(user, key, value) + return self.db.update_item(user) + + def _create_new_client(self, **kwargs) -> Client: + """Create a new client with the provided attributes.""" + return Client( + client_id=self.total_clients() + 1, + **kwargs + ) + def add_client(self, name: str, key: str = "", admin: bool = False, intent_blacklist: Optional[List[str]] = None, skill_blacklist: Optional[List[str]] = None, message_blacklist: Optional[List[str]] = None, allowed_types: Optional[List[str]] = None, crypto_key: Optional[str] = None, password: Optional[str] = None) -> bool: if crypto_key is not None: crypto_key = crypto_key[:16] user = self.get_client_by_api_key(key) if user: - # Update the existing client object directly - if name: - user.name = name - if intent_blacklist: - user.intent_blacklist = intent_blacklist - # ... more updates ... - return self.db.update_item(user) + return self._update_existing_client(user, + name=name, + intent_blacklist=intent_blacklist, + skill_blacklist=skill_blacklist, + message_blacklist=message_blacklist, + allowed_types=allowed_types, + is_admin=admin, + crypto_key=crypto_key, + password=password) - user = Client( - api_key=key, - name=name, - intent_blacklist=intent_blacklist, - # ... more attributes ... - ) + user = self._create_new_client( + api_key=key, + name=name, + intent_blacklist=intent_blacklist, + skill_blacklist=skill_blacklist, + message_blacklist=message_blacklist, + crypto_key=crypto_key, + is_admin=admin, + password=password, + allowed_types=allowed_types) return self.db.add_item(user)
Line range hint
542-547
: Improve error handling in context manager exitThe error handling in exit should be more specific about the type of error that occurred.
def __exit__(self, _type, value, traceback): """Commits changes and Closes the session""" try: self.db.commit() except Exception as e: - LOG.error(e) + LOG.error(f"Failed to commit database changes on exit: {str(e)}") + # Consider if we should re-raise the exception + # raise🧰 Tools
🪛 Ruff (0.8.2)
35-35: Multiple
isinstance
calls forret
, merge into a single callMerge
isinstance
calls forret
(SIM101)
143-146: Use
contextlib.suppress(Exception)
instead oftry
-except
-pass
Replace with
contextlib.suppress(Exception)
(SIM105)
145-145: Do not use bare
except
(E722)
351-351: Local variable
e
is assigned to but never usedRemove assignment to unused variable
e
(F841)
423-423: f-string without any placeholders
Remove extraneous
f
prefix(F541)
446-446: f-string without any placeholders
Remove extraneous
f
prefix(F541)
🛑 Comments failed to post (2)
hivemind_core/database.py (2)
423-428: 🛠️ Refactor suggestion
Optimize Redis search implementation
The current implementation loads all clients and filters in memory. Consider using Redis SCAN with MATCH pattern for more efficient filtering.
res = [] - for client_id in self.redis.scan_iter(f"client:*"): + for client_id in self.redis.scan_iter("client:*"): client_data = self.redis.get(client_id) client = cast2client(client_data) if hasattr(client, key) and getattr(client, key) == val: res.append(client) return resConsider implementing Redis search indices for better performance with large datasets.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.res = [] for client_id in self.redis.scan_iter("client:*"): client_data = self.redis.get(client_id) client = cast2client(client_data) if hasattr(client, key) and getattr(client, key) == val: res.append(client) return res
🧰 Tools
🪛 Ruff (0.8.2)
423-423: f-string without any placeholders
Remove extraneous
f
prefix(F541)
143-147: 🛠️ Refactor suggestion
Improve error handling in eq method
The bare
except
clause silently swallows all exceptions, which could hide potential issues.try: other = cast2client(other) - except: + except (TypeError, ValueError) as e: + LOG.debug(f"Failed to cast object for comparison: {e}") passCommittable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.8.2)
143-146: Use
contextlib.suppress(Exception)
instead oftry
-except
-pass
Replace with
contextlib.suppress(Exception)
(SIM105)
145-145: Do not use bare
except
(E722)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (11)
test/unittests/test_db.py (2)
114-146
: Enhance Redis operation verification.While the tests verify that Redis methods are called, they should also verify the actual data being stored/deleted.
def test_add_item(self): client_data = { "client_id": 1, "api_key": "test_api_key", "name": "Test Client", "description": "A test client", "is_admin": False } client = Client(**client_data) self.db.add_item(client) - self.mock_redis.set.assert_called_once() + self.mock_redis.set.assert_called_once_with( + str(client.client_id), + client.serialize() + )
148-179
: Add test cases for error conditions.The test class should include cases for:
- Deleting non-existent clients
- Searching with invalid names
- Database connection failures
def test_delete_nonexistent_client(self): db = MagicMock() db.delete_item.return_value = False client_db = ClientDatabase(backend="json") client_db.db = db client_db.get_client_by_api_key = MagicMock() client_db.get_client_by_api_key.return_value = None result = client_db.delete_client("nonexistent_key") self.assertFalse(result)hivemind_core/service.py (1)
145-163
: Consider enhancing crypto key security.The crypto key handling could be improved:
- Add validation for minimum key length/complexity
- Consider implementing key rotation mechanism
- Add logging for failed crypto validation attempts
README.md (1)
229-233
: Add security considerations for database backends.Consider adding security recommendations for each backend:
- JSON: File permissions and encryption at rest
- SQLite: Access controls and backup strategy
- Redis: Authentication, SSL/TLS configuration, and network security
hivemind_core/database.py (5)
103-103
: Consider implementing the TODO comment usinginspect.signature
.The TODO comment suggests filtering kwargs using inspect. This can be implemented to ensure only valid attributes are passed to the Client constructor.
Would you like me to provide an implementation using
inspect.signature
to filter the kwargs?
261-268
: Reconsider the default implementation ofcommit
.The default implementation always returns
True
, which might give a false sense of success. Consider making it abstract or raisingNotImplementedError
to ensure implementations explicitly handle commits.- def commit(self) -> bool: - """ - Commit changes to the database. - - Returns: - True if the commit was successful, False otherwise. - """ - return True + @abc.abstractmethod + def commit(self) -> bool: + """ + Commit changes to the database. + + Returns: + True if the commit was successful, False otherwise. + """ + pass
538-538
: Consider implementing RediSearch for efficient searching.The TODO comment suggests adding proper search index support. RediSearch would provide efficient searching capabilities compared to the current linear scan approach.
Consider using RediSearch module to implement efficient searching:
- Create an index on client fields
- Use FT.SEARCH for efficient querying
- Implement secondary indices for frequently searched fields
381-402
: Add indices for frequently searched columns.Consider adding indices to improve search performance on commonly queried columns like
api_key
andname
.def _initialize_database(self): """Initialize the database schema.""" with self.conn: self.conn.execute(""" CREATE TABLE IF NOT EXISTS clients ( client_id INTEGER PRIMARY KEY, api_key TEXT NOT NULL, name TEXT, description TEXT, is_admin BOOLEAN DEFAULT FALSE, last_seen REAL DEFAULT -1, intent_blacklist TEXT, skill_blacklist TEXT, message_blacklist TEXT, allowed_types TEXT, crypto_key TEXT, password TEXT, can_broadcast BOOLEAN DEFAULT TRUE, can_escalate BOOLEAN DEFAULT TRUE, can_propagate BOOLEAN DEFAULT TRUE ) """) + # Add indices for frequently searched columns + self.conn.execute("CREATE INDEX IF NOT EXISTS idx_api_key ON clients(api_key)") + self.conn.execute("CREATE INDEX IF NOT EXISTS idx_name ON clients(name)")
354-359
: Improve error logging in commit method.The error message could be more informative by including the actual error details.
try: self._db.store() return True except Exception as e: - LOG.error(f"Failed to save {self._db.path}") + LOG.error(f"Failed to save {self._db.path}: {str(e)}") return False🧰 Tools
🪛 Ruff (0.8.2)
357-357: Local variable
e
is assigned to but never usedRemove assignment to unused variable
e
(F841)
hivemind_core/scripts.py (2)
78-80
: Improve error message for failed client addition.The error message could be more informative by including the database backend information.
- raise ValueError(f"Error adding User to database: {name}") + raise ValueError(f"Error adding user '{name}' to {db_backend} database")
46-47
: Add type hints to function parameters.Function parameters lack type hints, which would improve code maintainability and IDE support.
Example for one function:
-def add_client(name, access_key, password, crypto_key, - db_backend, db_name, db_folder, redis_host, redis_port): +def add_client( + name: Optional[str], + access_key: Optional[str], + password: Optional[str], + crypto_key: Optional[str], + db_backend: str, + db_name: str, + db_folder: str, + redis_host: str, + redis_port: int +) -> None:Also applies to: 126-127, 209-210, 259-259, 413-414, 496-497, 578-579, 660-661
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
README.md
(1 hunks)hivemind_core/database.py
(1 hunks)hivemind_core/protocol.py
(3 hunks)hivemind_core/scripts.py
(13 hunks)hivemind_core/service.py
(5 hunks)setup.py
(1 hunks)test/unittests/test_db.py
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- setup.py
- hivemind_core/protocol.py
🧰 Additional context used
🪛 LanguageTool
README.md
[style] ~54-~54: Consider a shorter alternative to avoid wordiness.
Context: ...provide this info on the client devices in order to connect ### Running the Server Start ...
(IN_ORDER_TO_PREMIUM)
🪛 Markdownlint (0.37.0)
README.md
61-61: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
98-98: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
112-112: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
126-126: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
140-140: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
155-155: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
169-169: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
182-182: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
196-196: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
209-209: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
🪛 Gitleaks (8.21.2)
README.md
49-49: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 Ruff (0.8.2)
hivemind_core/service.py
140-140: Undefined name client
(F821)
hivemind_core/database.py
41-41: Multiple isinstance
calls for ret
, merge into a single call
Merge isinstance
calls for ret
(SIM101)
149-152: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
151-151: Do not use bare except
(E722)
357-357: Local variable e
is assigned to but never used
Remove assignment to unused variable e
(F841)
591-591: f-string without any placeholders
Remove extraneous f
prefix
(F541)
614-614: f-string without any placeholders
Remove extraneous f
prefix
(F541)
🔇 Additional comments (1)
test/unittests/test_db.py (1)
8-67
: LGTM! Well-structured test coverage for Client class.
The test class provides comprehensive coverage of client operations including creation, serialization, and deserialization with consistent test data across methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
README.md (4)
50-51
: Enhance deprecation warning with migration guidance.The warning about encryption key deprecation should include:
- When it will be fully deprecated
- How to migrate to password-based authentication
- Which client versions support the new authentication method
139-141
: Enhance Redis backend example with connection details.The Redis example should include:
- Connection parameters (host, port)
- Authentication details (if required)
- Expected output
Example:
$ hivemind-core allow-msg --db-backend redis --redis-host localhost --redis-port 6379 # Expected output: Message types allowed: ['speak', 'recognize']🧰 Tools
🪛 Markdownlint (0.37.0)
140-140: null
Dollar signs used before commands without showing output(MD014, commands-show-output)
229-233
: Add operational guidance for database backends.Consider adding information about:
- Data persistence and backup strategies
- Performance characteristics and scaling limits
- Migration between backends
- Security considerations for each backend
225-254
: Consider reordering sections for better flow.The Protocol Support section should come before Database Backends since protocol version affects database interactions. This would provide readers with a better understanding of the system architecture before diving into specific implementations.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
README.md
(1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md
[style] ~54-~54: Consider a shorter alternative to avoid wordiness.
Context: ...provide this info on the client devices in order to connect ### Running the Server Start ...
(IN_ORDER_TO_PREMIUM)
🪛 Markdownlint (0.37.0)
README.md
61-61: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
98-98: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
112-112: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
126-126: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
140-140: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
155-155: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
169-169: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
182-182: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
196-196: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
209-209: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
🪛 Gitleaks (8.21.2)
README.md
49-49: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (1)
README.md (1)
41-52
: Avoid exposing sensitive credentials in documentation.
The example output shows actual API keys and encryption keys. Consider using placeholder values like <access_key>
instead of real credentials.
🧰 Tools
🪛 Gitleaks (8.21.2)
49-49: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
Update README.md
dfc81ff
to
982b41d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Nitpick comments (24)
hivemind_core/scripts.py (7)
12-26
: Ensure robust error handling for unrecognized database backends.
Currently, if db_backend is neither "redis" nor one of the recognized file-based backends, it falls into the else block. Although click constrains the db_backend to "redis", "json", or "sqlite", adding a validation or fallback would improve code resilience.
71-74
: Validate added client object.
After calling db.add_client, you validate success and raise a ValueError when it fails, but consider providing more context in the error message (e.g., reason for failure) for easier debugging.
157-167
: Use consistent dictionary key access for client objects.
Here you are using x["client_id"] to check ID. In other places, you call client methods or attributes (e.g., client.client_id). Make sure to maintain consistency.
Line range hint
247-283
: Provide user feedback on invalid database parameters.
Although you accept multiple database parameters, your code does not explicitly handle misconfiguration feedback. If, for example, Redis cannot connect, the user might not see a descriptive error. Consider adding a connection test or explicit warning messages.
Line range hint
356-409
: Unblacklisting logic clarity
Ensure that removing a skill from the blacklist is the exact opposite of blacklisting it. If future expansions add special rules, consider unifying them behind a single function or method.
Line range hint
416-469
: Apply consistent error messages for blacklisted items.
You print a generic error message when clients are already blacklisted or have an invalid API key. Keep those messages consistent across skill and intent blacklists for easier debugging.
Line range hint
476-529
: Handle concurrency for unlocking multiple blocked clients.
When unblacklisting multiple intents, consider concurrency or partial success scenarios. Use a more granular approach if needed (e.g., track which ones were successfully unblacklisted).test/unittests/test_db.py (6)
1-7
: Remove unnecessary imports.
Currently, all imports (json, os, unittest, patch, MagicMock) look valid. However, if you do not use certain modules or constants, consider removing them to keep your test file lean.
72-78
: Add locking or concurrency tests if needed.
For file-based JSON DB, read/write collisions could happen if multiple processes or threads access the file simultaneously. While that might be outside the scope, consider a test or note for concurrency usage.
105-117
: Add coverage for advanced search queries.
The search_by_value function is tested with a single attribute. If you plan to expand search functionalities (OR, AND combos, partial matches, etc.), add relevant tests to confirm correct behavior.
121-138
: Validate that Redis mocking covers all relevant Redis commands.
Here, you test set() in mock_redis but ensure other commands (like hset, get, or pipeline usage) are also properly tested if used in the actual code.
140-151
: Check for Redis key collisions.
When adding an item with a particular key, you might want to test how the code handles collisions. If a key already exists in Redis, do you overwrite or error out? A short test for that scenario may help.
168-184
: Extend get_clients_by_name tests for partial matches or case sensitivity.
With the current test, the exact name is used. If you eventually handle partial matches or case-insensitive searches, expand coverage to confirm complete functionality.README.md (4)
1-5
: Use shorter expressions to improve readability.
Your phrase “in order to connect” at line ~56 can be shortened to “to connect.” This minor edit clarifies style.
63-100
: Consider removing dollar signs before commands.
Markdownlint warns you about using “$ command” lines without showing the actual output. If you don’t show command output in the snippet, remove the dollar sign for consistency.🧰 Tools
🪛 Markdownlint (0.37.0)
63-63: null
Dollar signs used before commands without showing output(MD014, commands-show-output)
100-100: null
Dollar signs used before commands without showing output(MD014, commands-show-output)
220-224
: Clarify distributed environment instructions.
The brief mention of using --ovos_bus_address and --ovos_bus_port is helpful, but you might want to note typical pitfalls, such as firewall or NAT configurations.
231-241
: Consider specifying DB usage limits and caveats.
The table helps users pick a backend, but mention capacity or concurrency constraints (especially for JSON or SQLite) to guide them more effectively.hivemind_core/service.py (5)
102-102
: Expose DB handling to external classes or logs.
You’ve added db: Optional[ClientDatabase]. Consider providing brief logs or docstrings indicating how external classes can rely on this field’s lifecycle.
138-144
: Improve error message for missing DB.
You currently raise a RuntimeError with a generic message. Provide details (like “Ensure the --db-backend is set properly and that the DB is accessible.”) to help developers fix the issue more easily.
145-151
: Add a unit test verifying that invalid API keys close the connection.
You log “Client provided an invalid api key” and close the connection. Ensure you have a dedicated test verifying the service’s behavior in this exact scenario.
Line range hint
206-221
: Inject database dependency.
You pass db as an optional parameter to HiveMindService. This is a neat approach to dependency injection. However, consider ensuring the system gracefully handles the scenario if a null DB is injected.
268-268
: Extend handshake coverage in automated tests.
You call protocol.bind(...) with self.db. You might want an integration test confirming that handshake logic works with a real DB instance (especially with the handshake disabled scenario).hivemind_core/database.py (2)
39-45
: Optimize multiple isinstance checksThe multiple
isinstance
checks can be merged for better readability and performance.- if isinstance(ret, str) or isinstance(ret, dict): + if isinstance(ret, (str, dict)): return Client.deserialize(ret)🧰 Tools
🪛 Ruff (0.8.2)
41-41: Multiple
isinstance
calls forret
, merge into a single callMerge
isinstance
calls forret
(SIM101)
663-665
: Move crypto_key truncation to Client classThe crypto_key truncation logic should be encapsulated within the Client class for better maintainability.
# In Client class @property def crypto_key(self) -> Optional[str]: return self._crypto_key @crypto_key.setter def crypto_key(self, value: Optional[str]): self._crypto_key = value[:16] if value else None
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
README.md
(1 hunks)hivemind_core/database.py
(1 hunks)hivemind_core/scripts.py
(14 hunks)hivemind_core/service.py
(5 hunks)test/unittests/test_db.py
(1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md
[style] ~56-~56: Consider a shorter alternative to avoid wordiness.
Context: ...provide this info on the client devices in order to connect ### Running the Server Start ...
(IN_ORDER_TO_PREMIUM)
🪛 Markdownlint (0.37.0)
README.md
63-63: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
100-100: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
114-114: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
128-128: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
142-142: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
157-157: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
171-171: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
184-184: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
198-198: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
211-211: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
🪛 Gitleaks (8.21.2)
README.md
51-51: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 Ruff (0.8.2)
hivemind_core/database.py
41-41: Multiple isinstance
calls for ret
, merge into a single call
Merge isinstance
calls for ret
(SIM101)
149-152: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
151-151: Do not use bare except
(E722)
357-357: Local variable e
is assigned to but never used
Remove assignment to unused variable e
(F841)
591-591: f-string without any placeholders
Remove extraneous f
prefix
(F541)
614-614: f-string without any placeholders
Remove extraneous f
prefix
(F541)
🔇 Additional comments (9)
hivemind_core/scripts.py (4)
68-69
: Check client naming fallback logic.
When the user does not provide a name, you generate a default name based on the total number of clients. If multiple operations run concurrently, there is a small possibility of a race condition. This is a minor concern, but you could mitigate it by re-checking or using an auto-increment approach.
115-122
: Be mindful of partial iteration for large datasets.
Iterating and building big tables might be slow if there are many clients. It might be worth implementing pagination or a search approach if you anticipate large amounts of data.
[performance]
Line range hint 185-203
: Consider thread safety when listing clients.
If the database can be accessed concurrently, ensure that the underlying DB code is thread-safe or locked properly to avoid inconsistent reads.
Line range hint 295-349
: Prevent partial skill blacklist updates on script termination.
When processing a skill blacklist, if the script is interrupted after partial operations, some blacklists might remain uncommitted. Check if your DB concurrency model or close methods are robust enough to handle partial updates or transactions.
test/unittests/test_db.py (2)
91-103
: Confirm item deletion success criteria.
The test checks self.db.delete_item(client) and asserts True. Ensure that you also confirm the item is indeed gone from the DB to minimize false positives (e.g., a scenario where the method returns True but item remains in DB).
153-166
: Improve clarity of client_db mocking.
You apply a MagicMock in place of the real database. Make sure your test verifies the correct calls and that the logic aligns with actual usage. For instance, ensure that the code calls “delete_item” with the correct arguments.
README.md (1)
51-51
:
Mask example access keys when showing command output.
Static analysis flagged a “generic-api-key” at line 51. Replace it with a placeholder like <ACCESS_KEY> to avoid confusion and possible accidental credential exposure.
-Access Key: 42caf3d2405075fb9e7a4e1ff44e4c4f
+Access Key: <ACCESS_KEY>
Likely invalid or redundant comment.
🧰 Tools
🪛 Gitleaks (8.21.2)
51-51: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
hivemind_core/service.py (1)
152-158
: Validate attribute assignment vs. storage.
You map multiple user attributes (crypto_key, message_blacklist, etc.) from the retrieved DB object to the client
. Confirm your DB model aligns with the client
object’s expected fields.
hivemind_core/database.py (1)
149-152
: 🛠️ Refactor suggestion
Replace bare except with specific exception handling
The bare except clause can mask important errors and make debugging difficult.
- try:
- other = cast2client(other)
- except:
- pass
+ try:
+ other = cast2client(other)
+ except (TypeError, ValueError):
+ pass
Likely invalid or redundant comment.
🧰 Tools
🪛 Ruff (0.8.2)
149-152: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
151-151: Do not use bare except
(E722)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
README.md (2)
7-7
: Fix preposition in YouTube link text.Change "in youtube" to "on YouTube" for correct English usage.
-Demo videos in [youtube](https://www.youtube.com/channel/UCYoV5kxp2zrH6pnoqVZpKSA/) +Demo videos on [YouTube](https://www.youtube.com/channel/UCYoV5kxp2zrH6pnoqVZpKSA/)🧰 Tools
🪛 LanguageTool
[uncategorized] ~7-~7: The preposition “on” seems more likely in this position than the preposition “in”.
Context: ... can't typically run OVOS. Demo videos in [youtube](https://www.youtube.com/chann...(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_IN_ON)
270-270
: Fix abbreviation format.The abbreviation "eg." should be "e.g." (with periods).
-run a [persona](https://github.com/OpenVoiceOS/ovos-persona) (eg. LLM). *text* input only +run a [persona](https://github.com/OpenVoiceOS/ovos-persona) (e.g., LLM). *text* input only🧰 Tools
🪛 LanguageTool
[uncategorized] ~270-~270: The abbreviation “e.g.” (= for example) requires two periods.
Context: ...//github.com/OpenVoiceOS/ovos-persona) (eg. LLM). text input only ### Client Lib...(E_G)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
README.md
(1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md
[uncategorized] ~7-~7: The preposition “on” seems more likely in this position than the preposition “in”.
Context: ... can't typically run OVOS. Demo videos in [youtube](https://www.youtube.com/chann...
(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_IN_ON)
[style] ~56-~56: Consider a shorter alternative to avoid wordiness.
Context: ...provide this info on the client devices in order to connect ### Running the Server Start ...
(IN_ORDER_TO_PREMIUM)
[uncategorized] ~270-~270: The abbreviation “e.g.” (= for example) requires two periods.
Context: ...//github.com/OpenVoiceOS/ovos-persona) (eg. LLM). text input only ### Client Lib...
(E_G)
🪛 Markdownlint (0.37.0)
README.md
63-63: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
100-100: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
114-114: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
128-128: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
142-142: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
157-157: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
171-171: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
184-184: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
198-198: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
211-211: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
🪛 Gitleaks (8.21.2)
README.md
51-51: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (2)
README.md (2)
43-53
: Replace actual credentials with placeholders in example output.
The example output shows actual API keys and encryption keys. Use placeholder values instead.
Node ID: 3
Friendly Name: HiveMind-Node-2
-Access Key: 42caf3d2405075fb9e7a4e1ff44e4c4f
-Password: 5ae486f7f1c26bd4645bd052e4af3ea3
-Encryption Key: f46351c54f61a715
+Access Key: <access_key>
+Password: <password>
+Encryption Key: <encryption_key>
🧰 Tools
🪛 Gitleaks (8.21.2)
51-51: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
255-255
: Enhance protocol compatibility documentation.
The note about protocol support should include more detailed information.
Consider adding:
- Timeline for Protocol v1 support in HiveMind-JS
- Compatibility matrix for all client libraries
- Migration guide from v0 to v1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (6)
test/unittests/test_db.py (2)
70-117
: Consider expanding test coverage for JsonDB.While the basic CRUD operations are covered, consider adding these test cases:
- Verify all fields are correctly persisted in
test_add_item
- Verify the "revoked" state after deletion in
test_delete_item
- Test searching by different field types in
test_search_by_value
153-184
: Enhance TestClientDatabase coverage.Consider adding tests for:
- Database backend initialization with different configurations
- Switching between different backends
- Error handling for invalid backend configurations
hivemind_core/database.py (4)
11-19
: Add type hints to optional dependency imports.Consider adding type hints to clarify the types when dependencies are not available:
try: import redis except ImportError: - redis = None + redis: Optional[Any] = None try: import sqlite3 except ImportError: - sqlite3 = None + sqlite3: Optional[Any] = None
41-42
: Optimize isinstance checks.Merge the isinstance checks for better readability and performance:
-if isinstance(ret, str) or isinstance(ret, dict): +if isinstance(ret, (str, dict)): return Client.deserialize(ret)🧰 Tools
🪛 Ruff (0.8.2)
41-41: Multiple
isinstance
calls forret
, merge into a single callMerge
isinstance
calls forret
(SIM101)
344-349
: Fix error handling in JsonDB.commit.Use the exception variable in the error message:
try: self._db.store() return True except Exception as e: - LOG.error(f"Failed to save {self._db.path}") + LOG.error(f"Failed to save {self._db.path}: {e}") return False🧰 Tools
🪛 Ruff (0.8.2)
347-347: Local variable
e
is assigned to but never usedRemove assignment to unused variable
e
(F841)
598-608
: Improve error messages for invalid backends.Consider providing more helpful error messages:
-raise NotImplementedError(f"{backend} not supported, choose one of {self.valid_backends}") +raise NotImplementedError( + f"Backend '{backend}' is not supported. " + f"Valid options are: {', '.join(self.valid_backends)}" +)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
hivemind_core/database.py
(1 hunks)test/unittests/test_db.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
hivemind_core/database.py
41-41: Multiple isinstance
calls for ret
, merge into a single call
Merge isinstance
calls for ret
(SIM101)
153-156: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
155-155: Do not use bare except
(E722)
347-347: Local variable e
is assigned to but never used
Remove assignment to unused variable e
(F841)
556-556: f-string without any placeholders
Remove extraneous f
prefix
(F541)
581-581: f-string without any placeholders
Remove extraneous f
prefix
(F541)
🔇 Additional comments (6)
test/unittests/test_db.py (2)
9-67
: Well-structured test coverage for Client class.
The test methods comprehensively cover the essential functionality of the Client class, including object creation, serialization, deserialization, and type casting.
186-230
: Comprehensive negative test cases.
The test class effectively covers important error conditions and edge cases, ensuring robust error handling in the Client class.
hivemind_core/database.py (4)
171-274
: Well-designed abstract base class.
The AbstractDB class provides a solid foundation with:
- Clear interface definition
- Appropriate use of abstract methods
- Helpful default implementations
439-446
:
Fix SQL injection vulnerability.
The current implementation is vulnerable to SQL injection:
-cur = self.conn.execute(f"SELECT * FROM clients WHERE {key} = ?", (val,))
+cur = self.conn.execute("SELECT * FROM clients WHERE ? = ?", (key, val))
Likely invalid or redundant comment.
153-156
:
Fix bare except clause.
The bare except clause can mask important errors. Use specific exception handling:
-try:
- other = cast2client(other)
-except:
- pass
+from contextlib import suppress
+with suppress(TypeError, ValueError):
+ other = cast2client(other)
Likely invalid or redundant comment.
🧰 Tools
🪛 Ruff (0.8.2)
153-156: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
155-155: Do not use bare except
(E722)
684-688
: 🛠️ Refactor suggestion
Improve context manager error handling.
The current implementation swallows all exceptions. Consider:
def __exit__(self, _type, value, traceback):
try:
self.db.commit()
- except Exception as e:
- LOG.error(e)
+ except Exception as e:
+ LOG.error(f"Failed to commit database changes: {e}")
+ return False # Re-raise the exception
+ return True
Likely invalid or redundant comment.
closes #3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
hivemind_core/database.py (1)
439-446
:⚠️ Potential issueCritical: SQL injection vulnerability in search_by_value method.
The current implementation is vulnerable to SQL injection attacks by directly interpolating the key parameter into the SQL query.
This issue was previously identified and tracked in issue #104. The fix needs to be implemented:
- cur = self.conn.execute(f"SELECT * FROM clients WHERE {key} = ?", (val,)) + cur = self.conn.execute("SELECT * FROM clients WHERE ? = ?", (key, val))
🧹 Nitpick comments (3)
hivemind_core/database.py (3)
41-42
: Optimize isinstance checks for better performance.Multiple
isinstance
checks can be merged into a single call for better readability and performance.-if isinstance(ret, str) or isinstance(ret, dict): +if isinstance(ret, (str, dict)): return Client.deserialize(ret)🧰 Tools
🪛 Ruff (0.8.2)
41-41: Multiple
isinstance
calls forret
, merge into a single callMerge
isinstance
calls forret
(SIM101)
510-510
: Implement proper search index support.The TODO comment indicates that proper search index support is missing. This could impact search performance at scale.
Would you like me to help implement a proper search index solution using RediSearch?
638-671
: Separate update and create operations in add_client method.The method currently handles both creating new clients and updating existing ones, making it complex and harder to maintain.
Consider splitting this into two separate methods:
def update_client(self, key: str, **updates) -> bool: user = self.get_client_by_api_key(key) if not user: return False for attr, value in updates.items(): if value is not None: setattr(user, attr, value) return self.db.update_item(user) def create_client(self, key: str, name: str, **kwargs) -> bool: if self.get_client_by_api_key(key): return False user = Client( api_key=key, name=name, client_id=self.total_clients() + 1, **kwargs ) return self.db.add_item(user)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
hivemind_core/database.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
hivemind_core/database.py
41-41: Multiple isinstance
calls for ret
, merge into a single call
Merge isinstance
calls for ret
(SIM101)
153-156: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
155-155: Do not use bare except
(E722)
347-347: Local variable e
is assigned to but never used
Remove assignment to unused variable e
(F841)
556-556: f-string without any placeholders
Remove extraneous f
prefix
(F541)
581-581: f-string without any placeholders
Remove extraneous f
prefix
(F541)
🔇 Additional comments (1)
hivemind_core/database.py (1)
153-156
: 🛠️ Refactor suggestion
Improve error handling in equality comparison.
The bare except clause can mask important errors. Use contextlib.suppress
or catch specific exceptions.
+from contextlib import suppress
+
- try:
- other = cast2client(other)
- except:
- pass
+ with suppress(TypeError, ValueError):
+ other = cast2client(other)
Likely invalid or redundant comment.
🧰 Tools
🪛 Ruff (0.8.2)
153-156: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
155-155: Do not use bare except
(E722)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
hivemind_core/database.py (1)
440-447
:⚠️ Potential issueSQL injection vulnerability in search_by_value.
The current implementation is vulnerable to SQL injection attacks by directly interpolating the key parameter into the SQL query.
This issue has been tracked in #104. Please prioritize fixing this security vulnerability.
🧹 Nitpick comments (5)
test/unittests/test_db.py (2)
11-67
: Consider adding edge cases to client tests.The client tests cover basic functionality well, but could benefit from additional edge cases:
- Unicode characters in name/description
- Empty strings for optional fields
- Maximum length validation for fields
188-230
: Add test for concurrent type validation.The negative test cases should include scenarios where multiple invalid fields are provided simultaneously:
def test_multiple_invalid_fields(self): client_data = { "client_id": "invalid_id", "api_key": 123, # should be string "is_admin": "not_boolean" } with self.assertRaises(ValueError): Client(**client_data)hivemind_core/service.py (1)
137-142
: Improve error message for database initialization failure.The error message could be more specific about the configuration requirements:
-LOG.error("Database connection not initialized. Please ensure database configuration is correct.") +LOG.error("Database connection not initialized. Verify database backend configuration (--db-backend, --db-name) and connection parameters.")README.md (1)
270-270
: Fix abbreviation format.Correct the abbreviation format:
-run a [persona](https://github.com/OpenVoiceOS/ovos-persona) (eg. LLM) +run a [persona](https://github.com/OpenVoiceOS/ovos-persona) (e.g., LLM)🧰 Tools
🪛 LanguageTool
[uncategorized] ~270-~270: The abbreviation “e.g.” (= for example) requires two periods.
Context: ...//github.com/OpenVoiceOS/ovos-persona) (eg. LLM). text input only ### Client Lib...(E_G)
hivemind_core/database.py (1)
39-45
: Optimize multiple isinstance checks.Multiple
isinstance
checks can be combined for better readability and performance.Apply this diff:
- if ret is None or isinstance(ret, Client): - return ret - if isinstance(ret, str) or isinstance(ret, dict): + if ret is None or isinstance(ret, Client): + return ret + if isinstance(ret, (str, dict)):🧰 Tools
🪛 Ruff (0.8.2)
41-41: Multiple
isinstance
calls forret
, merge into a single callMerge
isinstance
calls forret
(SIM101)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
README.md
(1 hunks)hivemind_core/database.py
(1 hunks)hivemind_core/protocol.py
(3 hunks)hivemind_core/service.py
(4 hunks)test/unittests/test_db.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
hivemind_core/database.py
41-41: Multiple isinstance
calls for ret
, merge into a single call
Merge isinstance
calls for ret
(SIM101)
153-156: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
155-155: Do not use bare except
(E722)
348-348: Local variable e
is assigned to but never used
Remove assignment to unused variable e
(F841)
557-557: f-string without any placeholders
Remove extraneous f
prefix
(F541)
582-582: f-string without any placeholders
Remove extraneous f
prefix
(F541)
🪛 LanguageTool
README.md
[style] ~56-~56: Consider a shorter alternative to avoid wordiness.
Context: ...provide this info on the client devices in order to connect ### Running the Server Start ...
(IN_ORDER_TO_PREMIUM)
[uncategorized] ~270-~270: The abbreviation “e.g.” (= for example) requires two periods.
Context: ...//github.com/OpenVoiceOS/ovos-persona) (eg. LLM). text input only ### Client Lib...
(E_G)
🪛 Markdownlint (0.37.0)
README.md
63-63: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
100-100: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
114-114: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
128-128: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
142-142: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
157-157: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
171-171: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
184-184: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
198-198: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
211-211: null
Dollar signs used before commands without showing output
(MD014, commands-show-output)
🪛 Gitleaks (8.21.2)
README.md
51-51: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (6)
README.md (1)
48-53
:
Replace sensitive credentials with placeholders.
The example output shows actual API keys and encryption keys. Use placeholders instead:
-Node ID: 3
-Friendly Name: HiveMind-Node-2
-Access Key: 42caf3d2405075fb9e7a4e1ff44e4c4f
-Password: 5ae486f7f1c26bd4645bd052e4af3ea3
-Encryption Key: f46351c54f61a715
+Node ID: <id>
+Friendly Name: <name>
+Access Key: <access_key>
+Password: <password>
+Encryption Key: <encryption_key>
Likely invalid or redundant comment.
🧰 Tools
🪛 Gitleaks (8.21.2)
51-51: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
hivemind_core/database.py (3)
153-156
: Fix bare except clause.
The bare except clause can mask important errors and make debugging difficult.
Apply this diff to catch specific exceptions:
- try:
- other = cast2client(other)
- except:
- pass
+ from contextlib import suppress
+ with suppress(TypeError, ValueError):
+ other = cast2client(other)
🧰 Tools
🪛 Ruff (0.8.2)
153-156: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
155-155: Do not use bare except
(E722)
557-564
: Optimize Redis search performance.
The current implementation performs a full scan of keys which can be inefficient for large datasets.
Consider using Redis sets for indexing additional fields beyond just name and api_key.
🧰 Tools
🪛 Ruff (0.8.2)
557-557: f-string without any placeholders
Remove extraneous f
prefix
(F541)
573-573
: Improve reliability of client count calculation.
The current implementation assumes a fixed ratio of index entries, which is brittle and could lead to incorrect counts if the indexing strategy changes.
This issue has been tracked in #105.
hivemind_core/protocol.py (2)
266-268
: 🛠️ Refactor suggestion
Add error handling for database binding.
The bind method should validate the database parameter and handle potential connection issues.
Apply this diff:
- def bind(self, websocket, bus, identity, db: ClientDatabase):
+ def bind(self, websocket, bus, identity, db: ClientDatabase) -> None:
+ """
+ Bind the protocol to required components.
+
+ Args:
+ websocket: The websocket handler
+ bus: The message bus client
+ identity: The node identity
+ db: The client database instance
+
+ Raises:
+ ValueError: If database instance is None
+ """
+ if db is None:
+ raise ValueError("Database instance cannot be None")
self.identity = identity
self.db = db
websocket.protocol = self
Likely invalid or redundant comment.
760-763
: 🛠️ Refactor suggestion
Add error handling for database queries.
The blacklist update logic should handle potential database query failures gracefully.
Apply this diff:
- user = self.db.get_client_by_api_key(client.key)
- client.skill_blacklist = user.skill_blacklist or []
- client.intent_blacklist = user.intent_blacklist or []
- client.msg_blacklist = user.message_blacklist or []
+ try:
+ user = self.db.get_client_by_api_key(client.key)
+ if user is None:
+ LOG.error(f"Failed to retrieve user data for client {client.peer}")
+ return message
+ client.skill_blacklist = user.skill_blacklist or []
+ client.intent_blacklist = user.intent_blacklist or []
+ client.msg_blacklist = user.message_blacklist or []
+ except Exception as e:
+ LOG.error(f"Database error while updating blacklist for client {client.peer}: {e}")
+ return message
Likely invalid or redundant comment.
@patch('hivemind_core.database.redis.StrictRedis') | ||
def setUp(self, MockRedis): | ||
self.mock_redis = MagicMock() | ||
MockRedis.return_value = self.mock_redis | ||
self.db = RedisDB() | ||
|
||
def test_add_item(self): | ||
client_data = { | ||
"client_id": 1, | ||
"api_key": "test_api_key", | ||
"name": "Test Client", | ||
"description": "A test client", | ||
"is_admin": False | ||
} | ||
client = Client(**client_data) | ||
self.db.add_item(client) | ||
self.mock_redis.set.assert_called_once() | ||
|
||
def test_delete_item(self): | ||
client_data = { | ||
"client_id": 1, | ||
"api_key": "test_api_key", | ||
"name": "Test Client", | ||
"description": "A test client", | ||
"is_admin": False | ||
} | ||
client = Client(**client_data) | ||
self.db.add_item(client) | ||
result = self.db.delete_item(client) | ||
self.assertTrue(result) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance Redis mock assertions.
The Redis tests could be more thorough in verifying the exact Redis operations:
def test_add_item(self):
client_data = {
"client_id": 1,
"api_key": "test_api_key",
"name": "Test Client",
"description": "A test client",
"is_admin": False
}
client = Client(**client_data)
self.db.add_item(client)
- self.mock_redis.set.assert_called_once()
+ self.mock_redis.set.assert_called_once_with(
+ f"client:{client.client_id}",
+ client.serialize()
+ )
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
@patch('hivemind_core.database.redis.StrictRedis') | |
def setUp(self, MockRedis): | |
self.mock_redis = MagicMock() | |
MockRedis.return_value = self.mock_redis | |
self.db = RedisDB() | |
def test_add_item(self): | |
client_data = { | |
"client_id": 1, | |
"api_key": "test_api_key", | |
"name": "Test Client", | |
"description": "A test client", | |
"is_admin": False | |
} | |
client = Client(**client_data) | |
self.db.add_item(client) | |
self.mock_redis.set.assert_called_once() | |
def test_delete_item(self): | |
client_data = { | |
"client_id": 1, | |
"api_key": "test_api_key", | |
"name": "Test Client", | |
"description": "A test client", | |
"is_admin": False | |
} | |
client = Client(**client_data) | |
self.db.add_item(client) | |
result = self.db.delete_item(client) | |
self.assertTrue(result) | |
@patch('hivemind_core.database.redis.StrictRedis') | |
def setUp(self, MockRedis): | |
self.mock_redis = MagicMock() | |
MockRedis.return_value = self.mock_redis | |
self.db = RedisDB() | |
def test_add_item(self): | |
client_data = { | |
"client_id": 1, | |
"api_key": "test_api_key", | |
"name": "Test Client", | |
"description": "A test client", | |
"is_admin": False | |
} | |
client = Client(**client_data) | |
self.db.add_item(client) | |
self.mock_redis.set.assert_called_once_with( | |
f"client:{client.client_id}", | |
client.serialize() | |
) | |
def test_delete_item(self): | |
client_data = { | |
"client_id": 1, | |
"api_key": "test_api_key", | |
"name": "Test Client", | |
"description": "A test client", | |
"is_admin": False | |
} | |
client = Client(**client_data) | |
self.db.add_item(client) | |
result = self.db.delete_item(client) | |
self.assertTrue(result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (3)
hivemind_core/scripts.py (1)
13-28
: Enhance docstring and add parameter validation.The function is well-structured, but could benefit from:
- Detailed parameter descriptions in the docstring
- Validation for the redis_password parameter
def get_db_kwargs(db_backend: str, db_name: str, db_folder: str, redis_host: str, redis_port: int, redis_password: Optional[str]) -> dict: - """Get database configuration kwargs based on backend type.""" + """Get database configuration kwargs based on backend type. + + Args: + db_backend: The database backend type ('redis', 'json', or 'sqlite') + db_name: The name of the database file for json/sqlite backends + db_folder: The folder where database files are stored for json/sqlite backends + redis_host: The Redis server host + redis_port: The Redis server port + redis_password: Optional Redis server password + + Returns: + dict: Configuration kwargs for the selected database backend + """ kwargs = {"backend": db_backend} if db_backend == "redis": + if redis_password is not None and not isinstance(redis_password, str): + raise ValueError("redis_password must be None or a string") kwargs.update({ "host": redis_host, "port": redis_port, "password": redis_password })hivemind_core/database.py (2)
107-107
: Address TODO comment about filtering kwargs.The TODO comment about filtering kwargs with inspect should be implemented to prevent invalid attribute assignments.
Would you like me to help implement the kwargs filtering using
inspect.signature()
to ensure only valid attributes are passed to the Client constructor?
41-41
: Address static analysis issues.Several code quality issues were identified:
- Multiple
isinstance
calls can be merged- Unused exception variable
- f-strings without placeholders
- if isinstance(ret, str) or isinstance(ret, dict): + if isinstance(ret, (str, dict)): - except Exception as e: + except Exception: - for client_id in self.redis.scan_iter(f"client:*"): + for client_id in self.redis.scan_iter("client:*"): - for client_id in self.redis.scan_iter(f"client:*"): + for client_id in self.redis.scan_iter("client:*"):Also applies to: 348-348, 559-559, 584-584
🧰 Tools
🪛 Ruff (0.8.2)
41-41: Multiple
isinstance
calls forret
, merge into a single callMerge
isinstance
calls forret
(SIM101)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
hivemind_core/database.py
(1 hunks)hivemind_core/scripts.py
(14 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
hivemind_core/database.py
41-41: Multiple isinstance
calls for ret
, merge into a single call
Merge isinstance
calls for ret
(SIM101)
153-156: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
155-155: Do not use bare except
(E722)
348-348: Local variable e
is assigned to but never used
Remove assignment to unused variable e
(F841)
559-559: f-string without any placeholders
Remove extraneous f
prefix
(F541)
584-584: f-string without any placeholders
Remove extraneous f
prefix
(F541)
🔇 Additional comments (3)
hivemind_core/database.py (3)
575-575
:
Improve reliability of client count calculation.
The current implementation assumes a fixed ratio of index entries (dividing by 3), which is brittle and could lead to incorrect counts if the indexing strategy changes.
- return int(len(self.redis.keys("client:*")) / 3) # because of index entries for name/key fastsearch
+ return len([k for k in self.redis.scan_iter("client:*") if not k.startswith("client:index:")])
Likely invalid or redundant comment.
153-156
:
Replace bare except with specific exception handling.
Using a bare except clause can mask important errors and make debugging harder.
try:
other = cast2client(other)
- except:
+ except (TypeError, ValueError):
pass
Likely invalid or redundant comment.
🧰 Tools
🪛 Ruff (0.8.2)
153-156: Use contextlib.suppress(Exception)
instead of try
-except
-pass
Replace with contextlib.suppress(Exception)
(SIM105)
155-155: Do not use bare except
(E722)
442-442
:
Fix SQL injection vulnerability in search_by_value.
The current implementation is vulnerable to SQL injection attacks by directly interpolating the key parameter into the SQL query.
- cur = self.conn.execute(f"SELECT * FROM clients WHERE {key} = ?", (val,))
+ cur = self.conn.execute("SELECT * FROM clients WHERE ? = ?", (key, val))
Likely invalid or redundant comment.
hivemind_core/scripts.py
Outdated
@click.option("--db-backend", type=click.Choice(['redis', 'json', 'sqlite'], case_sensitive=False), default='json', | ||
help="Select the database backend to use. Options: redis, sqlite, json.") | ||
@click.option("--db-name", type=str, default="clients", | ||
help="[json/sqlite] The name for the database file. ~/.cache/hivemind-core/{name}") | ||
@click.option("--db-folder", type=str, default="hivemind-core", | ||
help="[json/sqlite] The subfolder where database files are stored. ~/.cache/{db_folder}}") | ||
@click.option("--redis-host", default="localhost", help="[redis] Host for Redis. Default is localhost.") | ||
@click.option("--redis-port", default=6379, help="[redis] Port for Redis. Default is 6379.") | ||
@click.option("--redis-password", required=FileNotFoundError, help="[redis] Password for Redis. Default None") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Reduce code duplication by extracting database options into a shared decorator.
The database configuration options are repeated across all commands. This violates the DRY principle and makes maintenance harder.
Create a decorator to share these options:
def add_db_options(f):
"""Add database configuration options to a click command."""
options = [
click.option("--db-backend",
type=click.Choice(['redis', 'json', 'sqlite'], case_sensitive=False),
default='json',
help="Select the database backend to use. Options: redis, sqlite, json."),
click.option("--db-name",
type=str,
default="clients",
help="[json/sqlite] The name for the database file. ~/.cache/hivemind-core/{name}"),
click.option("--db-folder",
type=str,
default="hivemind-core",
help="[json/sqlite] The subfolder where database files are stored. ~/.cache/{db_folder}}"),
click.option("--redis-host",
default="localhost",
help="[redis] Host for Redis. Default is localhost."),
click.option("--redis-port",
default=6379,
help="[redis] Port for Redis. Default is 6379."),
click.option("--redis-password",
required=False,
help="[redis] Password for Redis. Default None")
]
for option in reversed(options):
f = option(f)
return f
Then use it on commands:
@hmcore_cmds.command(help="add credentials for a client", name="add-client")
@add_db_options
def add_client(...):
Also applies to: 100-108, 162-170, 191-199, 254-262, 293-301, 355-363, 416-424, 477-485
print(f"Blacklisted '{skill_id}' for {client['name']}") | ||
client.skill_blacklist.remove(skill_id) | ||
db.update_item(client) | ||
print(f"Blacklisted '{skill_id}' for {client.name}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incorrect success message in unblacklist_intent.
The success message incorrectly states that the intent was blacklisted when it was actually unblacklisted.
- print(f"Blacklisted '{skill_id}' for {client.name}")
+ print(f"Unblacklisted '{skill_id}' for {client.name}")
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
print(f"Blacklisted '{skill_id}' for {client.name}") | |
print(f"Unblacklisted '{skill_id}' for {client.name}") |
def delete_client(node_id, db_name, db_folder, | ||
db_backend, redis_host, redis_port, redis_password): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix parameter order in delete_client function signature.
The parameters are in the wrong order compared to their usage in the function call to get_db_kwargs
.
-def delete_client(node_id, db_name, db_folder,
- db_backend, redis_host, redis_port, redis_password):
+def delete_client(node_id, db_backend, db_name, db_folder,
+ redis_host, redis_port, redis_password):
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
def delete_client(node_id, db_name, db_folder, | |
db_backend, redis_host, redis_port, redis_password): | |
def delete_client(node_id, db_backend, db_name, db_folder, | |
redis_host, redis_port, redis_password): |
allow several database backends
Summary by CodeRabbit
New Features
hivemind-core
.Bug Fixes
Refactor
Documentation
Tests