From 58981b33b16ab22a0dfedfaf9cd1f405c747fbd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Gonz=C3=A1lez-Santander=20de=20la=20Cruz?= Date: Mon, 16 Dec 2024 12:47:35 +0100 Subject: [PATCH] =?UTF-8?q?Enhance=20test=20cases=20in=20custom=5Ftest=5Fc?= =?UTF-8?q?ase.py=20with=20detailed=20docstrings=20fo=E2=80=A6=20(#585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enhance test cases in custom_test_case.py with detailed docstrings for better clarity and maintainability. Added comprehensive descriptions for methods related to user authentication, API interactions, and database operations. Improved structure and documentation of test classes for better organization and understanding of test functionalities. * Update GitHub Actions workflow to support Python 3.12 and upgrade action versions * Enhance unit tests across multiple modules with detailed docstrings for improved clarity and maintainability. Updated test descriptions for actions, alarms, API views, cases, CLI commands, DAGs, and data checks to include functionality, expected outcomes, and validation criteria. This refactoring aims to provide better organization and understanding of test functionalities, ensuring comprehensive coverage of various scenarios. * Small typos --------- Co-authored-by: AlejandraGalan --- .github/workflows/test_cornflow_server.yml | 4 +- .../cornflow/tests/custom_test_case.py | 342 ++++++++++++ .../cornflow/tests/unit/test_actions.py | 47 +- .../cornflow/tests/unit/test_alarms.py | 66 ++- .../cornflow/tests/unit/test_apiview.py | 46 +- .../cornflow/tests/unit/test_cases.py | 488 +++++++++++++++++- .../cornflow/tests/unit/test_cli.py | 233 +++++++++ .../cornflow/tests/unit/test_commands.py | 232 ++++++++- .../cornflow/tests/unit/test_dags.py | 150 +++++- .../cornflow/tests/unit/test_data_checks.py | 136 ++++- 10 files changed, 1711 insertions(+), 33 deletions(-) diff --git a/.github/workflows/test_cornflow_server.yml b/.github/workflows/test_cornflow_server.yml index a8976bc3a..67323c089 100644 --- a/.github/workflows/test_cornflow_server.yml +++ b/.github/workflows/test_cornflow_server.yml @@ -48,9 +48,9 @@ jobs: - 5432:5432 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Copy DAG files diff --git a/cornflow-server/cornflow/tests/custom_test_case.py b/cornflow-server/cornflow/tests/custom_test_case.py index 14fb47d93..f2c3d2222 100644 --- a/cornflow-server/cornflow/tests/custom_test_case.py +++ b/cornflow-server/cornflow/tests/custom_test_case.py @@ -1,5 +1,17 @@ """ This file contains the different custom test classes used to generalize the unit testing of cornflow. +It provides base test cases and utilities for testing authentication, API endpoints, and database operations. + +Classes +------- +CustomTestCase + Base test case class with common testing utilities +BaseTestCases + Container for common test case scenarios +CheckTokenTestCase + Test cases for token validation +LoginTestCases + Test cases for login functionality """ # Import from libraries @@ -39,12 +51,34 @@ def date_from_str(_string): class CustomTestCase(TestCase): + """ + Base test case class that provides common utilities for testing Cornflow applications. + + This class sets up a test environment with a test database, user authentication, + and common test methods for CRUD operations. + """ + def create_app(self): + """ + Creates and configures a Flask application for testing. + + :returns: A configured Flask application instance + :rtype: Flask + """ app = create_app("testing") return app @staticmethod def load_file(_file, fk=None, fk_id=None): + """ + Loads and optionally modifies a JSON file. + + :param str _file: Path to the JSON file to load + :param str fk: Foreign key field name to modify (optional) + :param int fk_id: Foreign key ID value to set (optional) + :returns: The loaded and potentially modified JSON data + :rtype: dict + """ with open(_file) as f: temp = json.load(f) if fk is not None and fk_id is not None: @@ -52,6 +86,11 @@ def load_file(_file, fk=None, fk_id=None): return temp def setUp(self): + """ + Sets up the test environment before each test. + + Creates database tables, initializes access controls, and creates a test user. + """ log.root.setLevel(current_app.config["LOG_LEVEL"]) db.create_all() access_init_command(verbose=False) @@ -91,9 +130,23 @@ def setUp(self): @staticmethod def get_header_with_auth(token): + """ + Creates HTTP headers with authentication token. + + :param str token: JWT authentication token + :returns: Headers dictionary with content type and authorization + :rtype: dict + """ return {"Content-Type": "application/json", "Authorization": "Bearer " + token} def create_user(self, data): + """ + Creates a new user through the API. + + :param dict data: Dictionary containing user data (username, email, password) + :returns: API response from user creation + :rtype: Response + """ return self.client.post( SIGNUP_URL, data=json.dumps(data), @@ -103,6 +156,14 @@ def create_user(self, data): @staticmethod def assign_role(user_id, role_id): + """ + Assigns a role to a user in the database. + + :param int user_id: ID of the user + :param int role_id: ID of the role to assign + :returns: The created or existing user role association + :rtype: UserRoleModel + """ if UserRoleModel.check_if_role_assigned(user_id, role_id): user_role = UserRoleModel.query.filter_by( user_id=user_id, role_id=role_id @@ -113,6 +174,15 @@ def assign_role(user_id, role_id): return user_role def create_role_endpoint(self, user_id, role_id, token): + """ + Creates a role assignment through the API endpoint. + + :param int user_id: ID of the user + :param int role_id: ID of the role to assign + :param str token: Authentication token + :returns: API response from role assignment + :rtype: Response + """ return self.client.post( USER_ROLE_URL, data=json.dumps({"user_id": user_id, "role_id": role_id}), @@ -121,6 +191,13 @@ def create_role_endpoint(self, user_id, role_id, token): ) def create_user_with_role(self, role_id): + """ + Creates a new user and assigns them a specific role. + + :param int role_id: ID of the role to assign + :returns: Authentication token for the created user + :rtype: str + """ data = { "username": "testuser" + str(role_id), "email": "testemail" + str(role_id) + "@test.org", @@ -138,21 +215,54 @@ def create_user_with_role(self, role_id): ).json["token"] def create_service_user(self): + """ + Creates a new user with service role. + + :returns: Authentication token for the service user + :rtype: str + """ return self.create_user_with_role(SERVICE_ROLE) def create_admin(self): + """ + Creates a new user with admin role. + + :returns: Authentication token for the admin user + :rtype: str + """ return self.create_user_with_role(ADMIN_ROLE) def create_planner(self): + """ + Creates a new user with planner role. + + :returns: Authentication token for the planner user + :rtype: str + """ return self.create_user_with_role(PLANNER_ROLE) def tearDown(self): + """ + Cleans up the test environment after each test. + """ db.session.remove() db.drop_all() def create_new_row( self, url, model, payload, expected_status=201, check_payload=True, token=None ): + """ + Creates a new database row through the API. + + :param str url: API endpoint URL + :param class model: Database model class + :param dict payload: Data to create the row + :param int expected_status: Expected HTTP status code (default: 201) + :param bool check_payload: Whether to verify the created data (default: True) + :param str token: Authentication token (optional) + :returns: ID of the created row + :rtype: int + """ token = token or self.token response = self.client.post( @@ -177,6 +287,17 @@ def create_new_row( def get_rows( self, url, data, token=None, check_data=True, keys_to_check: List[str] = None ): + """ + Retrieves multiple rows through the API and verifies their contents. + + :param str url: API endpoint URL + :param list data: List of data dictionaries to create and verify + :param str token: Authentication token (optional) + :param bool check_data: Whether to verify the retrieved data (default: True) + :param list keys_to_check: Specific keys to verify in the response (optional) + :returns: API response containing the rows + :rtype: Response + """ token = token or self.token codes = [ @@ -200,6 +321,13 @@ def get_rows( return rows def get_keys_to_check(self, payload): + """ + Determines which keys should be checked in API responses. + + :param dict payload: Data dictionary containing keys + :returns: List of keys to check + :rtype: list + """ if len(self.items_to_check): return self.items_to_check return payload.keys() @@ -213,6 +341,18 @@ def get_one_row( token=None, keys_to_check: List[str] = None, ): + """ + Retrieves a single row through the API and verifies its contents. + + :param str url: API endpoint URL + :param dict payload: Expected data dictionary + :param int expected_status: Expected HTTP status code (default: 200) + :param bool check_payload: Whether to verify the retrieved data (default: True) + :param str token: Authentication token (optional) + :param list keys_to_check: Specific keys to verify in the response (optional) + :returns: API response data + :rtype: dict + """ token = token or self.token row = self.client.get( @@ -232,6 +372,14 @@ def get_one_row( return row.json def get_no_rows(self, url, token=None): + """ + Verifies that no rows are returned from the API endpoint. + + :param str url: API endpoint URL + :param str token: Authentication token (optional) + :returns: Empty list from API response + :rtype: list + """ token = token or self.token rows = self.client.get( url, follow_redirects=True, headers=self.get_header_with_auth(token) @@ -249,6 +397,18 @@ def update_row( check_payload=True, token=None, ): + """ + Updates a row through the API and verifies the changes. + + :param str url: API endpoint URL + :param dict change: Dictionary of changes to apply + :param dict payload_to_check: Expected data after update + :param int expected_status: Expected HTTP status code (default: 200) + :param bool check_payload: Whether to verify the updated data (default: True) + :param str token: Authentication token (optional) + :returns: Updated row data + :rtype: dict + """ token = token or self.token response = self.client.put( @@ -280,6 +440,15 @@ def update_row( def patch_row( self, url, json_patch, payload_to_check, expected_status=200, check_payload=True ): + """ + Patches a row through the API and verifies the changes. + + :param str url: API endpoint URL + :param dict json_patch: JSON patch operations to apply + :param dict payload_to_check: Expected data after patch + :param int expected_status: Expected HTTP status code (default: 200) + :param bool check_payload: Whether to verify the patched data (default: True) + """ response = self.client.patch( url, data=json.dumps(json_patch), @@ -301,6 +470,13 @@ def patch_row( self.assertEqual(payload_to_check["solution"], row.json["solution"]) def delete_row(self, url): + """ + Deletes a row through the API and verifies its removal. + + :param str url: API endpoint URL + :returns: API response from the delete operation + :rtype: Response + """ response = self.client.delete( url, follow_redirects=True, headers=self.get_header_with_auth(self.token) ) @@ -314,6 +490,13 @@ def delete_row(self, url): return response def apply_filter(self, url, _filter, result): + """ + Tests API filtering functionality. + + :param str url: API endpoint URL + :param dict _filter: Filter parameters to apply + :param list result: Expected filtered results + """ # we take out the potential query (e.g., ?param=1) arguments inside the url get_with_opts = lambda data: self.client.get( url.split("?")[0], @@ -328,16 +511,39 @@ def apply_filter(self, url, _filter, result): return def repr_method(self, idx, representation): + """ + Tests the string representation of a model instance. + + :param int idx: ID of the model instance + :param str representation: Expected string representation + """ row = self.model.query.get(idx) self.assertEqual(repr(row), representation) def str_method(self, idx, string: str): + """ + Tests the string conversion of a model instance. + + :param int idx: ID of the model instance + :param str string: Expected string value + """ row = self.model.query.get(idx) self.assertEqual(str(row), string) def cascade_delete( self, url, model, payload, url_2, model_2, payload_2, parent_key ): + """ + Tests cascade deletion functionality between related models. + + :param str url: Parent model API endpoint + :param class model: Parent model class + :param dict payload: Parent model data + :param str url_2: Child model API endpoint + :param class model_2: Child model class + :param dict payload_2: Child model data + :param str parent_key: Foreign key field linking child to parent + """ parent_object_idx = self.create_new_row(url, model, payload) payload_2[parent_key] = parent_object_idx child_object_idx = self.create_new_row(url_2, model_2, payload_2) @@ -356,24 +562,44 @@ def cascade_delete( class BaseTestCases: + """ + Container class for common test case scenarios. + """ + class ListFilters(CustomTestCase): + """ + Test cases for list endpoint filtering functionality. + """ + def setUp(self): + """ + Sets up the test environment for filter tests. + """ super().setUp() self.payload = None def test_opt_filters_limit(self): + """ + Tests the limit filter option. + """ # we create 4 instances data_many = [self.payload for _ in range(4)] allrows = self.get_rows(self.url, data_many) self.apply_filter(self.url, dict(limit=1), [allrows.json[0]]) def test_opt_filters_offset(self): + """ + Tests the offset filter option. + """ # we create 4 instances data_many = [self.payload for _ in range(4)] allrows = self.get_rows(self.url, data_many) self.apply_filter(self.url, dict(offset=1, limit=2), allrows.json[1:3]) def test_opt_filters_schema(self): + """ + Tests the schema filter option. + """ # (we patch the request to airflow to check if the schema is valid) # we create 4 instances data_many = [self.payload for _ in range(4)] @@ -382,6 +608,9 @@ def test_opt_filters_schema(self): self.apply_filter(self.url, dict(schema="timer"), allrows.json[:1]) def test_opt_filters_date_lte(self): + """ + Tests the less than or equal to date filter. + """ # we create 4 instances data_many = [self.payload for _ in range(4)] allrows = self.get_rows(self.url, data_many) @@ -397,6 +626,9 @@ def test_opt_filters_date_lte(self): ) def test_opt_filters_date_gte(self): + """ + Tests the greater than or equal to date filter. + """ # we create 4 instances data_many = [self.payload for _ in range(4)] allrows = self.get_rows(self.url, data_many) @@ -413,13 +645,26 @@ def test_opt_filters_date_gte(self): return class DetailEndpoint(CustomTestCase): + """ + Test cases for detail endpoint functionality. + """ + def setUp(self): + """ + Sets up the test environment for detail endpoint tests. + """ super().setUp() self.payload = None self.response_items = None self.query_arguments = None def url_with_query_arguments(self): + """ + Constructs URL with query arguments. + + :returns: URL with query parameters + :rtype: str + """ if self.query_arguments is None: return self.url else: @@ -430,6 +675,9 @@ def url_with_query_arguments(self): ) def test_get_one_row(self): + """ + Tests retrieving a single row. + """ idx = self.create_new_row( self.url_with_query_arguments(), self.model, self.payload ) @@ -440,6 +688,9 @@ def test_get_one_row(self): self.assertEqual(len(diff), 0) def test_get_one_row_superadmin(self): + """ + Tests retrieving a single row as superadmin. + """ idx = self.create_new_row( self.url_with_query_arguments(), self.model, self.payload ) @@ -449,11 +700,17 @@ def test_get_one_row_superadmin(self): ) def test_get_nonexistent_row(self): + """ + Tests attempting to retrieve a non-existent row. + """ self.get_one_row( self.url + "500" + "/", {}, expected_status=404, check_payload=False ) def test_update_one_row(self): + """ + Tests updating a single row. + """ idx = self.create_new_row( self.url_with_query_arguments(), self.model, self.payload ) @@ -465,6 +722,9 @@ def test_update_one_row(self): ) def test_update_one_row_bad_format(self): + """ + Tests updating a row with invalid format. + """ idx = self.create_new_row( self.url_with_query_arguments(), self.model, self.payload ) @@ -485,6 +745,9 @@ def test_update_one_row_bad_format(self): ) def test_delete_one_row(self): + """ + Tests deleting a single row. + """ idx = self.create_new_row( self.url_with_query_arguments(), self.model, self.payload ) @@ -492,6 +755,9 @@ def test_delete_one_row(self): # TODO: move to base endpoint custom class def test_incomplete_payload(self): + """ + Tests creating a row with incomplete payload. + """ payload = {"description": "arg"} self.create_new_row( self.url_with_query_arguments(), @@ -503,6 +769,9 @@ def test_incomplete_payload(self): # TODO: move to base endpoint custom class def test_payload_bad_format(self): + """ + Tests creating a row with invalid payload format. + """ payload = {"name": 1} self.create_new_row( self.url_with_query_arguments(), @@ -514,22 +783,45 @@ def test_payload_bad_format(self): class CheckTokenTestCase: + """ + Container class for token validation test cases. + """ + class TokenEndpoint(TestCase): + """ + Test cases for token endpoint functionality. + """ + def create_app(self): + """ + Creates test application instance. + + :returns: Test Flask application + :rtype: Flask + """ app = create_app("testing") return app def setUp(self): + """ + Sets up test environment for token tests. + """ db.create_all() self.data = None self.token = None self.response = None def tearDown(self): + """ + Cleans up test environment after token tests. + """ db.session.remove() db.drop_all() def get_check_token(self): + """ + Tests token validation endpoint. + """ if self.token: self.response = self.client.get( TOKEN_URL, @@ -550,22 +842,45 @@ def get_check_token(self): class LoginTestCases: + """ + Container class for login-related test cases. + """ + class LoginEndpoint(TestCase): + """ + Test cases for login endpoint functionality. + """ + def create_app(self): + """ + Creates test application instance. + + :returns: Test Flask application + :rtype: Flask + """ app = create_app("testing") return app def setUp(self): + """ + Sets up test environment for login tests. + """ log.root.setLevel(current_app.config["LOG_LEVEL"]) db.create_all() self.data = None self.response = None def tearDown(self): + """ + Cleans up test environment after login tests. + """ db.session.remove() db.drop_all() def test_successful_log_in(self): + """ + Tests successful login attempt. + """ payload = self.data self.response = self.client.post( @@ -579,6 +894,9 @@ def test_successful_log_in(self): self.assertEqual(str, type(self.response.json["token"])) def test_validation_error(self): + """ + Tests login with invalid data. + """ payload = self.data payload["email"] = "test" @@ -593,6 +911,9 @@ def test_validation_error(self): self.assertEqual(str, type(response.json["error"])) def test_missing_username(self): + """ + Tests login with missing username. + """ payload = self.data payload.pop("username", None) response = self.client.post( @@ -606,6 +927,9 @@ def test_missing_username(self): self.assertEqual(str, type(response.json["error"])) def test_missing_password(self): + """ + Tests login with missing password. + """ payload = self.data payload.pop("password", None) response = self.client.post( @@ -619,6 +943,9 @@ def test_missing_password(self): self.assertEqual(str, type(response.json["error"])) def test_invalid_username(self): + """ + Tests login with invalid username. + """ payload = self.data payload["username"] = "invalid_username" @@ -634,6 +961,9 @@ def test_invalid_username(self): self.assertEqual("Invalid credentials", response.json["error"]) def test_invalid_password(self): + """ + Tests login with invalid password. + """ payload = self.data payload["password"] = "testpassword_2" @@ -649,6 +979,9 @@ def test_invalid_password(self): self.assertEqual("Invalid credentials", response.json["error"]) def test_old_token(self): + """ + Tests using an expired token. + """ token = ( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTA1MzYwNjUsImlhdCI6MTYxMDQ0OTY2NSwic3ViIjoxfQ" ".QEfmO-hh55PjtecnJ1RJT3aW2brGLadkg5ClH9yrRnc " @@ -680,6 +1013,9 @@ def test_old_token(self): ) def test_bad_format_token(self): + """ + Tests using a malformed token. + """ response = self.client.post( LOGIN_URL, data=json.dumps(self.data), @@ -701,6 +1037,9 @@ def test_bad_format_token(self): self.assertEqual(400, response.status_code) def test_invalid_token(self): + """ + Tests using an invalid token. + """ token = ( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTA1Mzk5NTMsImlhdCI6MTYxMDQ1MzU1Mywic3ViIjoxfQ" ".g3Gh7k7twXZ4K2MnQpgpSr76Sl9VX6TkDWusX5YzImo" @@ -732,6 +1071,9 @@ def test_invalid_token(self): ) def test_token(self): + """ + Tests token generation and validation. + """ payload = self.data self.response = self.client.post( diff --git a/cornflow-server/cornflow/tests/unit/test_actions.py b/cornflow-server/cornflow/tests/unit/test_actions.py index 5733a1470..2d956a72f 100644 --- a/cornflow-server/cornflow/tests/unit/test_actions.py +++ b/cornflow-server/cornflow/tests/unit/test_actions.py @@ -1,5 +1,15 @@ """ -Unit test for the actions endpoint +Unit tests for the actions endpoint. + +This module contains tests for the actions API endpoint functionality, including: +- Authorization checks for different user roles +- Validation of action listings +- Access control verification + +Classes +------- +TestActionsListEndpoint + Tests for the actions list endpoint functionality """ # Import from libraries @@ -10,7 +20,24 @@ class TestActionsListEndpoint(CustomTestCase): + """ + Test cases for the actions list endpoint. + + This class tests the functionality of listing available actions, including: + - Authorization checks for different user roles + - Validation of returned action data + - Access control for authorized and unauthorized roles + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes test data including: + - Base test case setup + - Roles with access permissions + - Test payload with action data + """ super().setUp() self.roles_with_access = ActionListEndpoint.ROLES_WITH_ACCESS self.payload = [ @@ -19,9 +46,20 @@ def setUp(self): ] def tearDown(self): + """ + Clean up test environment after each test. + """ super().tearDown() def test_get_actions_authorized(self): + """ + Test that authorized roles can access the actions list. + + Verifies that users with proper roles can: + - Successfully access the actions endpoint + - Receive the correct list of actions + - Get properly formatted action data + """ for role in self.roles_with_access: self.token = self.create_user_with_role(role) response = self.client.get( @@ -36,6 +74,13 @@ def test_get_actions_authorized(self): self.assertCountEqual(self.payload, response.json) def test_get_actions_not_authorized(self): + """ + Test that unauthorized roles cannot access the actions list. + + Verifies that users without proper roles: + - Are denied access to the actions endpoint + - Receive appropriate error responses + """ for role in ROLES_MAP: if role not in self.roles_with_access: self.token = self.create_user_with_role(role) diff --git a/cornflow-server/cornflow/tests/unit/test_alarms.py b/cornflow-server/cornflow/tests/unit/test_alarms.py index d0c317a67..959f4feda 100644 --- a/cornflow-server/cornflow/tests/unit/test_alarms.py +++ b/cornflow-server/cornflow/tests/unit/test_alarms.py @@ -1,6 +1,17 @@ """ +Unit tests for the alarms endpoint. +This module contains tests for the alarms API endpoint functionality, including: +- Creating new alarms +- Retrieving alarm listings +- Validating alarm data and properties + +Classes +------- +TestAlarmsEndpoint + Tests for the alarms endpoint functionality """ + # Imports from internal modules from cornflow.models import AlarmsModel from cornflow.tests.const import ALARMS_URL @@ -8,7 +19,25 @@ class TestAlarmsEndpoint(CustomTestCase): + """ + Test cases for the alarms endpoint. + + This class tests the functionality of managing alarms, including: + - Creating new alarms with various properties + - Retrieving and validating alarm data + - Checking alarm schema and criticality levels + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes test data including: + - Base test case setup + - URL endpoint configuration + - Model and response field definitions + - Test items to check + """ super().setUp() self.url = ALARMS_URL self.model = AlarmsModel @@ -16,24 +45,43 @@ def setUp(self): self.items_to_check = ["name", "description", "schema", "criticality"] def test_post_alarm(self): - payload = {"name": "Alarm 1", "description": "Description Alarm 1", "criticality": 1} + """ + Test creating a new alarm. + + Verifies that an alarm can be created with: + - A name + - A description + - A criticality level + """ + payload = { + "name": "Alarm 1", + "description": "Description Alarm 1", + "criticality": 1, + } self.create_new_row(self.url, self.model, payload) def test_get_alarms(self): + """ + Test retrieving multiple alarms. + + Verifies: + - Retrieval of multiple alarms with different properties + - Proper handling of alarms with and without schema + - Correct validation of alarm data fields + """ data = [ {"name": "Alarm 1", "description": "Description Alarm 1", "criticality": 1}, - {"name": "Alarm 2", "description": "Description Alarm 2", "criticality": 2, "schema": "solve_model_dag"}, + { + "name": "Alarm 2", + "description": "Description Alarm 2", + "criticality": 2, + "schema": "solve_model_dag", + }, ] - rows = self.get_rows( - self.url, - data, - check_data=False - ) + rows = self.get_rows(self.url, data, check_data=False) rows_data = list(rows.json) for i in range(len(data)): for key in self.get_keys_to_check(data[i]): self.assertIn(key, rows_data[i]) if key in data[i]: self.assertEqual(rows_data[i][key], data[i][key]) - - diff --git a/cornflow-server/cornflow/tests/unit/test_apiview.py b/cornflow-server/cornflow/tests/unit/test_apiview.py index 677a0a753..f18e011c7 100644 --- a/cornflow-server/cornflow/tests/unit/test_apiview.py +++ b/cornflow-server/cornflow/tests/unit/test_apiview.py @@ -1,5 +1,15 @@ """ -Unit test for the api views endpoint +Unit tests for the API views endpoint. + +This module contains tests for the API views endpoint functionality, including: +- Authorization checks for different user roles +- Validation of API view listings +- Access control verification for endpoints + +Classes +------- +TestApiViewListEndpoint + Tests for the API views list endpoint functionality """ # Import from internal modules @@ -10,7 +20,25 @@ class TestApiViewListEndpoint(CustomTestCase): + """ + Test cases for the API views list endpoint. + + This class tests the functionality of listing available API views, including: + - Authorization checks for different user roles + - Validation of returned API view data + - Access control for authorized and unauthorized roles + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes test data including: + - Base test case setup + - Roles with access permissions + - Test payload with API view data + - Items to check in responses + """ super().setUp() self.roles_with_access = ApiViewListEndpoint.ROLES_WITH_ACCESS self.payload = [ @@ -24,9 +52,18 @@ def setUp(self): self.items_to_check = ["name", "description", "url_rule"] def tearDown(self): + """Clean up test environment after each test.""" super().tearDown() def test_get_api_view_authorized(self): + """ + Test that authorized roles can access the API views list. + + Verifies that users with proper roles can: + - Successfully access the API views endpoint + - Receive the correct list of views + - Get properly formatted view data with all required fields + """ for role in self.roles_with_access: self.token = self.create_user_with_role(role) response = self.client.get( @@ -45,6 +82,13 @@ def test_get_api_view_authorized(self): ) def test_get_api_view_not_authorized(self): + """ + Test that unauthorized roles cannot access the API views list. + + Verifies that users without proper roles: + - Are denied access to the API views endpoint + - Receive appropriate error responses with 403 status code + """ for role in ROLES_MAP: if role not in self.roles_with_access: self.token = self.create_user_with_role(role) diff --git a/cornflow-server/cornflow/tests/unit/test_cases.py b/cornflow-server/cornflow/tests/unit/test_cases.py index 8f4b22a51..25109b930 100644 --- a/cornflow-server/cornflow/tests/unit/test_cases.py +++ b/cornflow-server/cornflow/tests/unit/test_cases.py @@ -1,5 +1,34 @@ """ -Unit test for the cases models and endpoints +Unit tests for the cases models and endpoints. + +This module contains tests for the cases functionality, including: +- Case model operations and relationships +- Case API endpoints +- Case data manipulation and validation +- Case tree structure management + +Classes +------- +TestCasesModels + Tests for the case model functionality and relationships +TestCasesFromInstanceExecutionEndpoint + Tests for creating cases from instances and executions +TestCasesRawDataEndpoint + Tests for handling raw case data +TestCaseCopyEndpoint + Tests for case copying functionality +TestCaseListEndpoint + Tests for case listing functionality +TestCaseDetailEndpoint + Tests for case detail operations +TestCaseToInstanceEndpoint + Tests for converting cases to instances +TestCaseJsonPatch + Tests for JSON patch operations on cases +TestCaseDataEndpoint + Tests for case data operations +TestCaseCompare + Tests for case comparison functionality """ # Import from libraries @@ -33,7 +62,24 @@ class TestCasesModels(CustomTestCase): + """ + Test cases for the case model functionality. + + This class tests the core case model operations including: + - Case creation and relationships + - Case tree structure management + - Case deletion and cascading effects + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes test data including: + - Base test case setup + - Test case data and relationships + - Case tree structure + """ super().setUp() def load_file(_file): @@ -53,6 +99,13 @@ def load_file(_file): node.save() def test_new_case(self): + """ + Test creating new cases with parent-child relationships. + + Verifies: + - Correct path generation for cases + - Proper parent-child relationships + """ user = UserModel.get_one_user(self.user) case = CaseModel.get_one_object(user=user, idx=6) self.assertEqual(case.path, "1/3/") @@ -60,6 +113,13 @@ def test_new_case(self): self.assertEqual(case.path, "1/7/") def test_move_case(self): + """ + Test moving cases within the case tree. + + Verifies: + - Cases can be moved to new parents + - Path updates correctly after move + """ user = UserModel.get_one_user(self.user) case6 = CaseModel.get_one_object(user=user, idx=6) case11 = CaseModel.get_one_object(user=user, idx=11) @@ -67,6 +127,14 @@ def test_move_case(self): self.assertEqual(case6.path, "1/7/11/") def test_move_case2(self): + """ + Test complex case movement scenarios. + + Verifies: + - Multiple case movements + - Nested path updates + - Path integrity after moves + """ user = UserModel.get_one_user(self.user) case3 = CaseModel.get_one_object(user=user, idx=3) case11 = CaseModel.get_one_object(user=user, idx=11) @@ -77,6 +145,13 @@ def test_move_case2(self): self.assertEqual(case10.path, "1/7/11/3/9/") def test_delete_case(self): + """ + Test case deletion with cascading effects. + + Verifies: + - Case deletion removes the case + - Child cases are properly handled + """ user = UserModel.get_one_user(self.user) case7 = CaseModel.get_one_object(user=user, idx=7) case7.delete() @@ -84,18 +159,49 @@ def test_delete_case(self): self.assertIsNone(case11) def test_descendants(self): + """ + Test retrieval of case descendants. + + Verifies: + - Correct counting of descendants + - Proper descendant relationships + """ user = UserModel.get_one_user(self.user) case7 = CaseModel.get_one_object(user=user, idx=7) self.assertEqual(len(case7.descendants), 4) def test_depth(self): + """ + Test case depth calculation. + + Verifies: + - Correct depth calculation in case tree + - Proper nesting level determination + """ user = UserModel.get_one_user(self.user) case10 = CaseModel.get_one_object(user=user, idx=10) self.assertEqual(case10.depth, 4) class TestCasesFromInstanceExecutionEndpoint(CustomTestCase): + """ + Test cases for creating cases from instances and executions. + + This class tests the functionality of: + - Creating cases from existing instances + - Creating cases from executions + - Validating case data from different sources + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + - Test instance and execution data + - API endpoints and models + - Response validation parameters + """ super().setUp() payload = self.load_file(INSTANCE_PATH) @@ -137,6 +243,14 @@ def setUp(self): ) def test_new_case_execution(self): + """ + Test creating a new case from an execution. + + Verifies: + - Case creation from execution data + - Proper data and solution mapping + - Correct metadata assignment + """ self.payload.pop("instance_id") case_id = self.create_new_row(self.url, self.model, self.payload) @@ -159,6 +273,14 @@ def test_new_case_execution(self): self.assertEqual(self.payload[key], created_case.json[key]) def test_new_case_instance(self): + """ + Test creating a new case from an instance. + + Verifies: + - Case creation from instance data + - Proper data mapping + - Correct handling of missing solution + """ self.payload.pop("execution_id") case_id = self.create_new_row(self.url, self.model, self.payload) @@ -179,13 +301,35 @@ def test_new_case_instance(self): self.assertEqual(self.payload[key], created_case.json[key]) def test_case_not_created(self): + """ + Test case creation failure scenarios. + + Verifies proper error handling when case creation fails. + """ self.create_new_row( self.url, self.model, self.payload, expected_status=400, check_payload=False ) class TestCasesRawDataEndpoint(CustomTestCase): + """ + Test cases for handling raw case data operations. + + This class tests the functionality of: + - Creating cases with raw data + - Handling cases with and without solutions + - Managing case parent-child relationships + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + - Test case data + - API endpoints + - Test model configuration + """ super().setUp() self.payload = self.load_file(CASE_PATH) self.url = CASE_URL @@ -193,11 +337,26 @@ def setUp(self): self.items_to_check = ["name", "description", "schema"] def test_new_case(self): + """ + Test creating a new case with raw data. + + Verifies: + - Case creation with complete data + - Solution data handling + """ self.items_to_check = ["name", "description", "schema", "data", "solution"] self.payload["solution"] = self.payload["data"] self.create_new_row(self.url, self.model, self.payload) def test_new_case_without_solution(self): + """ + Test creating a case without solution data. + + Verifies: + - Case creation without solution + - Proper handling of missing solution fields + - Correct response structure + """ self.payload.pop("solution") self.items_to_check = ["name", "description", "schema", "data"] _id = self.create_new_row(self.url, self.model, self.payload) @@ -228,6 +387,14 @@ def test_new_case_without_solution(self): self.assertIsNone(data["solution"]) def test_case_with_parent(self): + """ + Test creating cases with parent-child relationships. + + Verifies: + - Case creation with parent reference + - Proper path generation + - Correct relationship establishment + """ payload = dict(self.payload) payload.pop("data") case_id = self.create_new_row(self.url, self.model, payload) @@ -244,6 +411,11 @@ def test_case_with_parent(self): self.assertEqual(len(diff), 0) def test_case_with_bad_parent(self): + """ + Test case creation with invalid parent reference. + + Verifies proper error handling for non-existent parent cases. + """ payload = dict(self.payload) payload["parent_id"] = 1 self.create_new_row( @@ -251,6 +423,11 @@ def test_case_with_bad_parent(self): ) def test_case_with_case_parent(self): + """ + Test case creation with invalid parent type. + + Verifies proper error handling when using invalid parent references. + """ case_id = self.create_new_row(self.url, self.model, self.payload) payload = dict(self.payload) payload["parent_id"] = case_id @@ -260,7 +437,24 @@ def test_case_with_case_parent(self): class TestCaseCopyEndpoint(CustomTestCase): + """ + Test cases for case copying functionality. + + This class tests the functionality of: + - Copying existing cases + - Validating copied case data + - Handling metadata in copied cases + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + - Source case data + - Copy operation parameters + - Validation fields + """ super().setUp() payload = self.load_file(CASE_PATH) self.model = CaseModel @@ -283,6 +477,14 @@ def setUp(self): self.new_items = ["created_at", "updated_at"] def test_copy_case(self): + """ + Test copying a case. + + Verifies: + - Successful case duplication + - Correct copying of case attributes + - Proper handling of modified and new attributes + """ new_case = self.create_new_row( self.url + str(self.case_id) + "/copy/", self.model, {}, check_payload=False ) @@ -305,7 +507,24 @@ def test_copy_case(self): class TestCaseListEndpoint(BaseTestCases.ListFilters): + """ + Test cases for case listing functionality. + + This class tests the functionality of: + - Retrieving case listings + - Applying filters to case lists + - Validating case list responses + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + - Test case data + - List operation parameters + - Response validation fields + """ super().setUp() self.payload = self.load_file(CASE_PATH) self.payloads = [self.load_file(f) for f in CASES_LIST] @@ -314,6 +533,14 @@ def setUp(self): self.url = CASE_URL def test_get_rows(self): + """ + Test retrieving multiple cases. + + Verifies: + - Successful retrieval of case listings + - Proper response structure + - Correct field validation + """ keys_to_check = [ "data_hash", "created_at", @@ -332,7 +559,24 @@ def test_get_rows(self): class TestCaseDetailEndpoint(BaseTestCases.DetailEndpoint): + """ + Test cases for case detail operations. + + This class tests the functionality of: + - Retrieving individual case details + - Updating case information + - Handling case deletion + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + - Test case data + - Detail operation parameters + - Response validation fields + """ super().setUp() self.payload = self.load_file(CASE_PATH) self.model = CaseModel @@ -360,6 +604,14 @@ def setUp(self): self.url = CASE_URL def test_delete_children(self): + """ + Test case deletion with child cases. + + Verifies: + - Successful deletion of parent case + - Proper handling of child cases + - Cascade deletion behavior + """ payload = dict(self.payload) payload.pop("data") case_id = self.create_new_row(self.url, self.model, payload) @@ -376,7 +628,24 @@ def test_delete_children(self): class TestCaseToInstanceEndpoint(CustomTestCase): + """ + Test cases for converting cases to instances. + + This class tests the functionality of: + - Converting cases to instances + - Validating converted instance data + - Handling conversion errors + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + - Test case data + - Conversion parameters + - Response validation fields + """ super().setUp() self.payload = self.load_file(CASE_PATH) self.model = CaseModel @@ -393,6 +662,14 @@ def setUp(self): } def test_case_to_new_instance(self): + """ + Test converting a case to a new instance. + + Verifies: + - Successful case to instance conversion + - Proper data mapping + - Correct response structure + """ response = self.client.post( CASE_URL + str(self.case_id) + "/instance/", follow_redirects=True, @@ -413,8 +690,8 @@ def test_case_to_new_instance(self): result = self.get_one_row( INSTANCE_URL + payload["id"] + "/", payload, keys_to_check=keys_to_check ) - dif = self.response_items.symmetric_difference(result.keys()) - self.assertEqual(len(dif), 0) + diff = self.response_items.symmetric_difference(result.keys()) + self.assertEqual(len(diff), 0) self.items_to_check = [ "id", @@ -450,6 +727,11 @@ def test_case_to_new_instance(self): self.assertEqual(len(dif), 0) def test_case_does_not_exist(self): + """ + Test conversion of non-existent case. + + Verifies proper error handling when converting non-existent cases. + """ response = self.client.post( CASE_URL + str(2) + "/instance/", follow_redirects=True, @@ -460,7 +742,24 @@ def test_case_does_not_exist(self): class TestCaseJsonPatch(CustomTestCase): + """ + Test cases for JSON patch operations on cases. + + This class tests the functionality of: + - Applying JSON patches to cases + - Validating patched case data + - Handling patch errors + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + - Test case data + - Patch operation parameters + - Test payloads + """ super().setUp() self.payload = self.load_file(CASE_PATH) self.model = CaseModel @@ -476,6 +775,14 @@ def setUp(self): self.patch_file = self.load_file(JSON_PATCH_GOOD_PATH) def test_json_patch(self): + """ + Test applying a JSON patch to a case. + + Verifies: + - Successful patch application + - Correct data transformation + - Proper validation of patched data + """ self.patch_row( self.url + str(self.case_id) + "/data/", self.patch, @@ -490,6 +797,14 @@ def test_json_patch(self): self.assertIsNone(row.json["checks"]) def test_json_patch_complete(self): + """ + Test applying a complete JSON patch. + + Verifies: + - Complex patch operations + - Data and solution patching + - Response validation + """ original = self.load_file(FULL_CASE_LIST[0]) original["data"] = get_pulp_jsonschema("../tests/data/gc_input.json") original["solution"] = get_pulp_jsonschema("../tests/data/gc_output.json") @@ -513,6 +828,11 @@ def test_json_patch_complete(self): self.assertIsNone(row.json["solution_checks"]) def test_json_patch_file(self): + """ + Test applying a JSON patch from a file. + + Verifies successful patch application from file source. + """ self.patch_row( self.url + str(self.case_id) + "/data/", self.patch_file, @@ -520,6 +840,11 @@ def test_json_patch_file(self): ) def test_not_valid_json_patch(self): + """ + Test handling of invalid JSON patches. + + Verifies proper error handling for malformed patches. + """ payload = {"patch": "Not a valid patch"} self.patch_row( self.url + str(self.case_id) + "/data/", @@ -530,6 +855,11 @@ def test_not_valid_json_patch(self): ) def test_not_valid_json_patch_2(self): + """ + Test handling of invalid JSON patch structure. + + Verifies proper error handling for patches with invalid structure. + """ payload = {"some_key": "some_value"} self.patch_row( self.url + str(self.case_id) + "/data/", @@ -540,6 +870,11 @@ def test_not_valid_json_patch_2(self): ) def test_not_valid_json_patch_3(self): + """ + Test handling of invalid patch operations. + + Verifies proper error handling for invalid patch operations. + """ patch = { "patch": jsonpatch.make_patch(self.payloads[0], self.payloads[1]).patch } @@ -552,6 +887,11 @@ def test_not_valid_json_patch_3(self): ) def test_not_valid_json_patch_4(self): + """ + Test handling of bad patch file content. + + Verifies proper error handling for invalid patch file content. + """ patch = self.load_file(JSON_PATCH_BAD_PATH) self.patch_row( self.url + str(self.case_id) + "/data/", @@ -562,6 +902,11 @@ def test_not_valid_json_patch_4(self): ) def test_patch_non_existing_case(self): + """ + Test patching non-existent case. + + Verifies proper error handling when patching non-existent cases. + """ self.patch_row( self.url + str(500) + "/data/", self.patch, @@ -571,24 +916,49 @@ def test_patch_non_existing_case(self): ) def test_patch_created_properly(self): + """ + Test proper patch creation. + + Verifies correct patch generation and structure. + """ self.assertEqual( len(self.patch_file["data_patch"]), len(self.patch["data_patch"]) ) def test_patch_not_created_properly(self): - # Compares the number of operations, not the operations themselves + """ + Test improper patch creation scenarios. + + Verifies detection of improperly created patches. + """ self.assertNotEqual( len(self.patch_file["data_patch"]), len(jsonpatch.make_patch(self.payloads[0], self.payloads[1]).patch), ) - # Compares the number of operations, not the operations themselves patch = self.load_file(JSON_PATCH_BAD_PATH) self.assertNotEqual(len(patch["data_patch"]), len(self.patch["data_patch"])) class TestCaseDataEndpoint(CustomTestCase): + """ + Test cases for case data operations. + + This class tests the functionality of: + - Retrieving case data + - Handling compressed data + - Validating data responses + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + - Test case data + - Data operation parameters + - Response validation fields + """ super().setUp() self.payload = self.load_file(CASE_PATH) self.model = CaseModel @@ -602,6 +972,14 @@ def setUp(self): ] def test_get_data(self): + """ + Test retrieving case data. + + Verifies: + - Successful data retrieval + - Proper response structure + - Field validation + """ keys_to_check = [ "data", "solution_checks", @@ -627,6 +1005,11 @@ def test_get_data(self): ) def test_get_no_data(self): + """ + Test retrieving non-existent case data. + + Verifies proper error handling for non-existent cases. + """ self.get_one_row( self.url + str(500) + "/data/", {}, @@ -636,6 +1019,14 @@ def test_get_no_data(self): ) def test_get_compressed_data(self): + """ + Test retrieving compressed case data. + + Verifies: + - Successful compression + - Proper decompression + - Data integrity + """ headers = self.get_header_with_auth(self.token) headers["Accept-Encoding"] = "gzip" @@ -650,7 +1041,24 @@ def test_get_compressed_data(self): class TestCaseCompare(CustomTestCase): + """ + Test cases for case comparison functionality. + + This class tests the functionality of: + - Comparing different cases + - Generating comparison patches + - Handling comparison errors + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + - Test cases for comparison + - Comparison parameters + - Validation fields + """ super().setUp() self.payloads = [self.load_file(f) for f in FULL_CASE_LIST] self.payloads[0]["data"] = get_pulp_jsonschema("../tests/data/gc_input.json") @@ -669,6 +1077,14 @@ def setUp(self): self.items_to_check = ["name", "description", "schema"] def test_get_full_patch(self): + """ + Test generating full comparison patch. + + Verifies: + - Successful patch generation + - Correct patch structure + - Proper response format + """ response = self.client.get( self.url + str(self.cases_id[0]) + "/" + str(self.cases_id[1]) + "/", follow_redirects=True, @@ -680,6 +1096,11 @@ def test_get_full_patch(self): self.assertEqual(200, response.status_code) def test_same_case_error(self): + """ + Test comparing a case with itself. + + Verifies proper error handling for self-comparison. + """ response = self.client.get( self.url + str(self.cases_id[0]) + "/" + str(self.cases_id[0]) + "/", follow_redirects=True, @@ -689,6 +1110,14 @@ def test_same_case_error(self): self.assertEqual(400, response.status_code) def test_get_only_data(self): + """ + Test comparing only case data. + + Verifies: + - Data-only comparison + - Proper exclusion of solution + - Correct response format + """ response = self.client.get( self.url + str(self.cases_id[0]) @@ -705,6 +1134,14 @@ def test_get_only_data(self): self.assertEqual(200, response.status_code) def test_get_only_solution(self): + """ + Test comparing only case solutions. + + Verifies: + - Solution-only comparison + - Proper exclusion of data + - Correct response format + """ response = self.client.get( self.url + str(self.cases_id[0]) + "/" + str(self.cases_id[1]) + "/?data=0", follow_redirects=True, @@ -717,6 +1154,11 @@ def test_get_only_solution(self): self.assertEqual(200, response.status_code) def test_patch_not_symmetric(self): + """ + Test patch asymmetry. + + Verifies that patches are direction-dependent. + """ response = self.client.get( self.url + str(self.cases_id[1]) + "/" + str(self.cases_id[0]) + "/", follow_redirects=True, @@ -728,6 +1170,11 @@ def test_patch_not_symmetric(self): self.assertEqual(200, response.status_code) def test_case_does_not_exist(self): + """ + Test comparing with non-existent case. + + Verifies proper error handling for non-existent cases. + """ response = self.client.get( self.url + str(self.cases_id[0]) + "/" + str(500) + "/", follow_redirects=True, @@ -745,6 +1192,14 @@ def test_case_does_not_exist(self): self.assertEqual(404, response.status_code) def test_get_patch_and_apply(self): + """ + Test generating and applying a patch. + + Verifies: + - Patch generation + - Successful patch application + - Data consistency + """ response = self.client.get( self.url + str(self.cases_id[0]) + "/" + str(self.cases_id[1]) + "/", follow_redirects=True, @@ -764,6 +1219,14 @@ def test_get_patch_and_apply(self): ) def test_case_compare_compression(self): + """ + Test case comparison with compression. + + Verifies: + - Successful compression + - Proper decompression + - Data integrity + """ headers = self.get_header_with_auth(self.token) headers["Accept-Encoding"] = "gzip" response = self.client.get( @@ -780,6 +1243,11 @@ def test_case_compare_compression(self): def modify_data(data): + """ + Modify test case data. + + Helper function to modify case data for testing. + """ data["pairs"][16]["n2"] = 10 data["pairs"][27]["n2"] = 3 data["pairs"][30]["n1"] = 6 @@ -790,6 +1258,11 @@ def modify_data(data): def modify_solution(solution): + """ + Modify test case solution. + + Helper function to modify case solution for testing. + """ solution["assignment"][4]["color"] = 3 solution["assignment"][7]["color"] = 2 solution["assignment"][24]["color"] = 1 @@ -797,6 +1270,11 @@ def modify_solution(solution): def modify_data_solution(data): + """ + Modify both test case data and solution. + + Helper function to modify both case data and solution for testing. + """ modify_data(data["data"]) modify_solution(data["solution"]) return data diff --git a/cornflow-server/cornflow/tests/unit/test_cli.py b/cornflow-server/cornflow/tests/unit/test_cli.py index 47ff47abe..b029a7c16 100644 --- a/cornflow-server/cornflow/tests/unit/test_cli.py +++ b/cornflow-server/cornflow/tests/unit/test_cli.py @@ -1,3 +1,21 @@ +""" +Unit tests for the Cornflow CLI commands. + +This module contains tests for the command-line interface functionality, including: + +- Entry point commands and help messages +- Actions management commands +- Configuration management commands +- Roles management commands +- Views management commands +- Permissions management commands +- Service management commands +- User management commands + +The tests verify both the command structure and the actual functionality +of each command, ensuring proper database operations and state changes. +""" + import configparser import os @@ -18,18 +36,61 @@ class CLITests(TestCase): + """ + Test suite for Cornflow CLI functionality. + + This class tests all CLI commands and their effects on the system, including: + + - Command help messages and documentation + - Actions initialization and management + - Configuration variable handling + - Role management and initialization + - View registration and management + - Permission system setup and validation + - Service initialization + - User creation and management + + Each test method focuses on a specific command or group of related commands, + verifying both the command interface and its actual effects on the system. + """ + def setUp(self): + """ + Set up test environment before each test. + + Creates all database tables required for testing. + """ db.create_all() def tearDown(self): + """ + Clean up test environment after each test. + + Removes database session and drops all tables. + """ db.session.remove() db.drop_all() def create_app(self): + """ + Create and configure the Flask application for testing. + + :return: The configured Flask application instance + :rtype: Flask + """ app = create_app("testing") return app def test_entry_point(self): + """ + Test the main CLI entry point and help command. + + Verifies: + + - Command execution success + - Presence of all main command groups + - Help message content and formatting + """ runner = CliRunner() result = runner.invoke(cli, ["--help"]) self.assertEqual(result.exit_code, 0) @@ -53,6 +114,15 @@ def test_entry_point(self): self.assertIn("Commands to manage the views", result.output) def test_actions_entry_point(self): + """ + Test the actions command group entry point. + + Verifies: + + - Actions command help message + - Presence of init subcommand + - Command description accuracy + """ runner = CliRunner() result = runner.invoke(cli, ["actions", "--help"]) self.assertEqual(result.exit_code, 0) @@ -61,6 +131,15 @@ def test_actions_entry_point(self): self.assertIn("Initialize the actions", result.output) def test_actions(self): + """ + Test the actions initialization command. + + Verifies: + + - Successful action initialization + - Correct number of actions created + - Database state after initialization + """ runner = CliRunner() result = runner.invoke(cli, ["actions", "init", "-v"]) self.assertEqual(result.exit_code, 0) @@ -68,6 +147,15 @@ def test_actions(self): self.assertEqual(len(actions), 5) def test_config_entrypoint(self): + """ + Test the config command group entry point. + + Verifies: + + - Config command help message + - Presence of all config subcommands + - Command descriptions + """ runner = CliRunner() result = runner.invoke(cli, ["config", "--help"]) self.assertEqual(result.exit_code, 0) @@ -80,6 +168,15 @@ def test_config_entrypoint(self): self.assertIn("Save the configuration variables to a file", result.output) def test_config_list(self): + """ + Test the config list command. + + Verifies: + + - Successful listing of configuration variables + - Presence of key configuration items + - Correct values in testing environment + """ runner = CliRunner() result = runner.invoke(cli, ["config", "list"]) self.assertEqual(result.exit_code, 0) @@ -87,12 +184,29 @@ def test_config_list(self): self.assertIn("testing", result.output) def test_config_get(self): + """ + Test the config get command. + + Verifies: + + - Successful retrieval of specific config value + - Correct value returned for ENV variable + """ runner = CliRunner() result = runner.invoke(cli, ["config", "get", "-k", "ENV"]) self.assertEqual(result.exit_code, 0) self.assertIn("testing", result.output) def test_config_save(self): + """ + Test the config save command. + + Verifies: + + - Successful configuration file creation + - Correct content in saved file + - Proper file cleanup after test + """ runner = CliRunner() result = runner.invoke(cli, ["config", "save", "-p", "./"]) self.assertEqual(result.exit_code, 0) @@ -104,6 +218,15 @@ def test_config_save(self): os.remove("config.cfg") def test_roles_entrypoint(self): + """ + Test the roles command group entry point. + + Verifies: + + - Roles command help message + - Presence of init subcommand + - Command description accuracy + """ runner = CliRunner() result = runner.invoke(cli, ["roles", "--help"]) self.assertEqual(result.exit_code, 0) @@ -112,6 +235,15 @@ def test_roles_entrypoint(self): self.assertIn("Initializes the roles with the default roles", result.output) def test_roles_init_command(self): + """ + Test the roles initialization command. + + Verifies: + + - Successful role initialization + - Correct number of default roles created + - Database state after initialization + """ runner = CliRunner() result = runner.invoke(cli, ["roles", "init", "-v"]) self.assertEqual(result.exit_code, 0) @@ -119,6 +251,15 @@ def test_roles_init_command(self): self.assertEqual(len(roles), 4) def test_views_entrypoint(self): + """ + Test the views command group entry point. + + Verifies: + + - Views command help message + - Presence of init subcommand + - Command description accuracy + """ runner = CliRunner() result = runner.invoke(cli, ["views", "--help"]) self.assertEqual(result.exit_code, 0) @@ -127,6 +268,15 @@ def test_views_entrypoint(self): self.assertIn("Initialize the views", result.output) def test_views_init_command(self): + """ + Test the views initialization command. + + Verifies: + + - Successful view initialization + - Correct number of views created + - Database state after initialization + """ runner = CliRunner() result = runner.invoke(cli, ["views", "init", "-v"]) self.assertEqual(result.exit_code, 0) @@ -134,6 +284,15 @@ def test_views_init_command(self): self.assertEqual(len(views), 49) def test_permissions_entrypoint(self): + """ + Test the permissions command group entry point. + + Verifies: + + - Permissions command help message + - Presence of all subcommands + - Command descriptions + """ runner = CliRunner() result = runner.invoke(cli, ["permissions", "--help"]) self.assertEqual(result.exit_code, 0) @@ -146,6 +305,15 @@ def test_permissions_entrypoint(self): self.assertIn("Initialize the base permissions", result.output) def test_permissions_init(self): + """ + Test the permissions initialization command. + + Verifies: + + - Successful initialization of all permission components + - Correct number of actions, roles, views, and permissions + - Database state after initialization + """ runner = CliRunner() result = runner.invoke(cli, ["permissions", "init", "-v"]) self.assertEqual(result.exit_code, 0) @@ -159,6 +327,15 @@ def test_permissions_init(self): self.assertEqual(len(permissions), 546) def test_permissions_base_command(self): + """ + Test the base permissions initialization command. + + Verifies: + + - Successful initialization of base permissions + - Correct setup of all permission components + - Database state consistency + """ runner = CliRunner() runner.invoke(cli, ["actions", "init", "-v"]) runner.invoke(cli, ["roles", "init", "-v"]) @@ -175,6 +352,15 @@ def test_permissions_base_command(self): self.assertEqual(len(permissions), 546) def test_service_entrypoint(self): + """ + Test the service command group entry point. + + Verifies: + + - Service command help message + - Presence of init subcommand + - Command description accuracy + """ runner = CliRunner() result = runner.invoke(cli, ["service", "--help"]) self.assertEqual(result.exit_code, 0) @@ -183,6 +369,15 @@ def test_service_entrypoint(self): self.assertIn("Initialize the service", result.output) def test_users_entrypoint(self): + """ + Test the users command group entry point. + + Verifies: + + - Users command help message + - Presence of create subcommand + - Command description accuracy + """ runner = CliRunner() result = runner.invoke(cli, ["users", "--help"]) self.assertEqual(result.exit_code, 0) @@ -191,6 +386,15 @@ def test_users_entrypoint(self): self.assertIn("Create a user", result.output) def test_users_create_entrypoint(self): + """ + Test the users create command entry point. + + Verifies: + + - Create command help message + - Presence of service subcommand + - Command description accuracy + """ runner = CliRunner() result = runner.invoke(cli, ["users", "create", "--help"]) self.assertEqual(result.exit_code, 0) @@ -198,6 +402,15 @@ def test_users_create_entrypoint(self): self.assertIn("Create a service user", result.output) def test_service_user_help(self): + """ + Test the service user creation help command. + + Verifies: + + - Help message content + - Required parameter descriptions + - Parameter documentation accuracy + """ runner = CliRunner() result = runner.invoke(cli, ["users", "create", "service", "--help"]) self.assertEqual(result.exit_code, 0) @@ -209,6 +422,16 @@ def test_service_user_help(self): self.assertIn("email", result.output) def test_service_user_command(self): + """ + Test service user creation command. + + Verifies: + + - Successful service user creation + - Correct user attributes + - Service role assignment + - Service user status verification + """ runner = CliRunner() self.test_roles_init_command() result = runner.invoke( @@ -233,6 +456,16 @@ def test_service_user_command(self): self.assertTrue(user.is_service_user()) def test_viewer_user_command(self): + """ + Test viewer user creation command. + + Verifies: + + - Successful viewer user creation + - Correct user attributes + - Viewer role assignment + - Service user status check + """ runner = CliRunner() self.test_roles_init_command() result = runner.invoke( diff --git a/cornflow-server/cornflow/tests/unit/test_commands.py b/cornflow-server/cornflow/tests/unit/test_commands.py index 223bc9766..f516c2145 100644 --- a/cornflow-server/cornflow/tests/unit/test_commands.py +++ b/cornflow-server/cornflow/tests/unit/test_commands.py @@ -1,3 +1,20 @@ +""" +Unit tests for Cornflow command functionality. + +This module contains tests for various command operations, including: + +- User management commands (service, admin, base users) +- Action registration and management +- View registration and management +- Role management +- Permission assignment and validation +- DAG deployment and permissions +- Command argument validation + +The tests verify both successful operations and error handling +for various command scenarios and configurations. +""" + import json from flask_testing import TestCase @@ -37,12 +54,45 @@ class TestCommands(TestCase): + """ + Test suite for Cornflow command functionality. + + This class tests various command operations and their effects on the system: + + - User creation and management commands + - Action and view registration + - Role initialization and management + - Permission system configuration + - DAG deployment and permissions + - Command argument validation and error handling + + Each test method focuses on a specific command or related set of commands, + verifying both successful execution and proper error handling. + """ + def create_app(self): + """ + Create and configure the Flask application for testing. + + :return: The configured Flask application instance with testing configuration + :rtype: Flask + """ app = create_app("testing") app.config["OPEN_DEPLOYMENT"] = 1 return app def setUp(self): + """ + Set up test environment before each test. + + Initializes: + + - Database tables + - Test user credentials + - API resources + - CLI runner + - Base roles + """ db.create_all() self.payload = { "email": "testemail@test.org", @@ -53,10 +103,32 @@ def setUp(self): self.runner.invoke(register_roles, ["-v"]) def tearDown(self): + """ + Clean up test environment after each test. + + Removes database session and drops all tables. + """ db.session.remove() db.drop_all() def user_command(self, command, username, email): + """ + Helper method to test user creation commands. + + :param command: The user creation command to test + :type command: function + :param username: Username for the new user + :type username: str + :param email: Email for the new user + :type email: str + :return: The created user object + :rtype: UserModel + + Verifies: + + - User creation success + - Correct user attributes + """ self.runner.invoke( command, ["-u", username, "-e", email, "-p", self.payload["password"], "-v"], @@ -69,6 +141,18 @@ def user_command(self, command, username, email): return user def user_missing_arguments(self, command): + """ + Test user creation with missing required arguments. + + :param command: The user creation command to test + :type command: function + + Verifies: + + - Proper error handling for missing username + - Proper error handling for missing email + - Proper error handling for missing password + """ result = self.runner.invoke( command, [ @@ -106,9 +190,27 @@ def user_missing_arguments(self, command): self.assertIn("Missing option '-p' / '--password'", result.output) def test_service_user_command(self): + """ + Test service user creation command. + + Verifies: + + - Successful service user creation + - Correct user attributes + - Service role assignment + """ return self.user_command(create_service_user, "cornflow", self.payload["email"]) def test_service_user_existing_admin(self): + """ + Test service user creation when admin user exists. + + Verifies: + + - Successful service user creation with existing admin + - Correct user attributes + - Proper role assignments + """ self.test_admin_user_command() self.runner.invoke( create_service_user, @@ -127,22 +229,56 @@ def test_service_user_existing_admin(self): self.assertNotEqual(None, user) self.assertEqual(self.payload["email"], user.email) self.assertEqual("cornflow", user.username) - # TODO: check the user has both roles def test_service_user_existing_service(self): + """ + Test service user creation when service user exists. + + Verifies: + + - Proper handling of existing service user + - Correct user attributes + - Role consistency + """ self.test_service_user_command() user = self.test_service_user_command() self.assertEqual("cornflow", user.username) - # TODO: check the user has the role def test_admin_user_command(self): + """ + Test admin user creation command. + + Verifies: + + - Successful admin user creation + - Correct user attributes + - Admin role assignment + """ return self.user_command(create_admin_user, "admin", "admin@test.org") def test_base_user_command(self): + """ + Test base user creation command. + + Verifies: + + - Successful base user creation + - Correct user attributes + - Base role assignment + """ return self.user_command(create_base_user, "base", "base@test.org") def test_register_actions(self): + """ + Test action registration command. + + Verifies: + + - Successful action registration + - Correct action names and mappings + - Database state after registration + """ self.runner.invoke(register_actions) actions = ActionModel.query.all() @@ -151,6 +287,15 @@ def test_register_actions(self): self.assertEqual(ACTIONS_MAP[a.id], a.name) def test_register_views(self): + """ + Test view registration command. + + Verifies: + + - Successful view registration + - Correct view endpoints + - Proper mapping to resources + """ self.runner.invoke(register_views) views = ViewModel.query.all() @@ -162,11 +307,29 @@ def test_register_views(self): self.assertCountEqual(views_list, resources_list) def test_register_roles(self): + """ + Test role registration command. + + Verifies: + + - Successful role registration + - Correct role names and mappings + - Database state after registration + """ roles = RoleModel.query.all() for r in roles: self.assertEqual(ROLES_MAP[r.id], r.name) def test_base_permissions_assignation(self): + """ + Test base permission assignment. + + Verifies: + + - Successful permission assignment + - Correct role-view-action mappings + - Proper access control setup + """ self.runner.invoke(access_init) for base in BASE_PERMISSION_ASSIGNATION: @@ -183,12 +346,30 @@ def test_base_permissions_assignation(self): self.assertEqual(True, permission) def test_deployed_dags_test_command(self): + """ + Test DAG deployment command in test mode. + + Verifies: + + - Successful DAG deployment + - Correct DAG registration + - Presence of required DAGs + """ register_deployed_dags_command_test(verbose=True) dags = DeployedDAG.get_all_objects() for dag in ["solve_model_dag", "gc", "timer"]: self.assertIn(dag, [d.id for d in dags]) def test_dag_permissions_command(self): + """ + Test DAG permissions command with open deployment. + + Verifies: + + - Successful permission assignment + - Correct permissions for service and admin users + - Proper access control setup + """ register_deployed_dags_command_test() self.test_service_user_command() self.test_admin_user_command() @@ -204,6 +385,15 @@ def test_dag_permissions_command(self): self.assertEqual(3, len(admin_permissions)) def test_dag_permissions_command_no_open(self): + """ + Test DAG permissions command without open deployment. + + Verifies: + + - Successful permission assignment + - Restricted access for admin users + - Proper service user permissions + """ register_deployed_dags_command_test() self.test_service_user_command() self.test_admin_user_command() @@ -219,6 +409,15 @@ def test_dag_permissions_command_no_open(self): self.assertEqual(0, len(admin_permissions)) def test_argument_parsing_correct(self): + """ + Test correct argument parsing for DAG permissions. + + Verifies: + + - Proper handling of invalid arguments + - Error messages for incorrect input + - No permission changes on error + """ self.test_service_user_command() result = self.runner.invoke(register_dag_permissions, ["-o", "a"]) @@ -230,21 +429,50 @@ def test_argument_parsing_correct(self): self.assertEqual(0, len(service_permissions)) def test_argument_parsing_incorrect(self): + """ + Test incorrect argument parsing for DAG permissions. + + Verifies: + + - Error handling for invalid input types + - Proper error messages + - Command failure behavior + """ self.test_service_user_command() result = self.runner.invoke(register_dag_permissions, ["-o", "a"]) self.assertEqual(2, result.exit_code) self.assertIn("is not a valid integer", result.output) def test_missing_required_argument_service(self): + """ + Test missing arguments for service user creation. + + Verifies proper error handling for missing required arguments. + """ self.user_missing_arguments(create_service_user) def test_missing_required_argument_admin(self): + """ + Test missing arguments for admin user creation. + + Verifies proper error handling for missing required arguments. + """ self.user_missing_arguments(create_admin_user) def test_missing_required_argument_user(self): + """ + Test missing arguments for base user creation. + + Verifies proper error handling for missing required arguments. + """ self.user_missing_arguments(create_base_user) def test_error_no_views(self): + """ + Test error handling when views are not registered. + + Verifies proper error handling when attempting operations without registered views. + """ self.test_service_user_command() token = self.client.post( LOGIN_URL, diff --git a/cornflow-server/cornflow/tests/unit/test_dags.py b/cornflow-server/cornflow/tests/unit/test_dags.py index f00d14320..dad3ee9ea 100644 --- a/cornflow-server/cornflow/tests/unit/test_dags.py +++ b/cornflow-server/cornflow/tests/unit/test_dags.py @@ -1,5 +1,17 @@ """ -Unit test for the DAG endpoints +Unit tests for the DAG endpoints. + +This module contains tests for DAG (Directed Acyclic Graph) functionality, including: + +- DAG execution and state management +- Manual and automated DAG operations +- Service and planner user permissions +- DAG deployment and registration +- Permission cascade deletion +- DAG configuration and data handling + +The tests verify both successful operations and proper error handling +for various DAG-related scenarios. """ # Import from libraries @@ -31,7 +43,28 @@ class TestDagEndpoint(TestExecutionsDetailEndpointMock): + """ + Test suite for DAG endpoint functionality. + + This class tests the DAG endpoints for different user roles and states: + + - Manual DAG operations for service users + - Manual DAG operations for planner users + - DAG state management + - Data validation and processing + """ + def test_manual_dag_service_user(self): + """ + Test manual DAG operations for service users. + + Verifies: + + - Service user can create manual DAGs + - Proper state assignment + - Correct data handling + - Required fields validation + """ with open(CASE_PATH) as f: payload = json.load(f) data = dict( @@ -59,6 +92,16 @@ def test_manual_dag_service_user(self): ) def test_manual_dag_planner_user(self): + """ + Test manual DAG operations for planner users. + + Verifies: + + - Planner user can create manual DAGs + - Proper state assignment + - Correct data handling + - Required fields validation + """ with open(CASE_PATH) as f: payload = json.load(f) data = dict( @@ -87,7 +130,29 @@ def test_manual_dag_planner_user(self): class TestDagDetailEndpoint(TestExecutionsDetailEndpointMock): + """ + Test suite for DAG detail endpoint functionality. + + This class tests detailed DAG operations including: + + - DAG updates and modifications + - Log handling and validation + - Data retrieval and verification + - Error handling for unauthorized access + """ + def test_put_dag(self): + """ + Test updating a DAG. + + Verifies: + + - Successful DAG update + - Log JSON handling + - State transition + - Field validation + - Log field filtering + """ idx = self.create_new_row(EXECUTION_URL_NORUN, self.model, self.payload) with open(CASE_PATH) as f: payload = json.load(f) @@ -104,19 +169,12 @@ def test_put_dag(self): data = dict( data=payload["data"], state=EXEC_STATE_CORRECT, - log_json={ - "time": 10.3, - "solver": "dummy", - "status": "feasible", - "status_code": 2, - "sol_code": 1, - "some_other_key": "this should be excluded", - }, + log_json=log_json, ) payload_to_check = {**self.payload, **data} token = self.create_service_user() self.update_row( - url=DAG_URL + idx + "/", + url=f"{DAG_URL}{idx}/", payload_to_check=payload_to_check, change=data, token=token, @@ -124,7 +182,7 @@ def test_put_dag(self): ) data = self.get_one_row( - url=EXECUTION_URL + idx + "/log/", + url=f"{EXECUTION_URL}{idx}/log/", token=token, check_payload=False, payload=self.payload, @@ -137,6 +195,16 @@ def test_put_dag(self): self.assertNotIn("some_other_key", data["log"].keys()) def test_get_dag(self): + """ + Test retrieving a DAG. + + Verifies: + + - Successful DAG retrieval + - Correct data structure + - Instance data consistency + - Configuration validation + """ idx = self.create_new_row(EXECUTION_URL_NORUN, self.model, self.payload) token = self.create_service_user() keys_to_check = ["id", "data", "solution_data", "config"] @@ -169,6 +237,15 @@ def test_get_dag(self): return def test_get_no_dag(self): + """ + Test retrieving a non-existent DAG. + + Verifies: + + - Proper error handling for missing DAGs + - Correct error status code + - Error message validation + """ idx = self.create_new_row(EXECUTION_URL_NORUN, self.model, self.payload) data = self.get_one_row( url=DAG_URL + idx + "/", @@ -181,11 +258,39 @@ def test_get_no_dag(self): class TestDeployedDAG(TestCase): + """ + Test suite for deployed DAG functionality. + + This class tests deployed DAG operations including: + + - DAG deployment and registration + - Permission management + - Cascade deletion + - User role interactions + """ + def create_app(self): + """ + Create and configure the Flask application for testing. + + :return: The configured Flask application instance + :rtype: Flask + """ app = create_app("testing") return app def setUp(self): + """ + Set up test environment before each test. + + Initializes: + + - Database tables + - Access controls + - Test DAGs + - Admin user with roles + - DAG permissions + """ db.create_all() access_init_command(verbose=False) register_deployed_dags_command_test(verbose=False) @@ -226,10 +331,24 @@ def setUp(self): register_dag_permissions_command(verbose=False) def tearDown(self): + """ + Clean up test environment after each test. + + Removes database session and drops all tables. + """ db.session.remove() db.drop_all() def test_permission_cascade_deletion(self): + """ + Test cascade deletion of DAG permissions. + + Verifies: + + - Successful permission deletion on DAG removal + - Proper cascade effect + - Permission count validation + """ before = PermissionsDAG.get_user_dag_permissions(self.admin["id"]) self.assertIsNotNone(before) dag = DeployedDAG.query.get("solve_model_dag") @@ -239,6 +358,15 @@ def test_permission_cascade_deletion(self): self.assertGreater(len(before), len(after)) def test_get_deployed_dags(self): + """ + Test retrieving deployed DAGs. + + Verifies: + + - Successful DAG listing + - Proper authorization + - Response structure + """ response = self.client.get( DEPLOYED_DAG_URL, follow_redirects=True, diff --git a/cornflow-server/cornflow/tests/unit/test_data_checks.py b/cornflow-server/cornflow/tests/unit/test_data_checks.py index 297c548a6..900f80ee3 100644 --- a/cornflow-server/cornflow/tests/unit/test_data_checks.py +++ b/cornflow-server/cornflow/tests/unit/test_data_checks.py @@ -1,5 +1,17 @@ """ -Unit test for the data check endpoint +Unit tests for the data check endpoints. + +This module contains tests for data validation functionality, including: + +- Execution data checks +- Instance data validation +- Case data verification +- Airflow integration testing +- Run and no-run scenarios +- Response validation + +The tests verify both successful validation operations and proper error handling +for various data check scenarios. """ # Import from libraries @@ -11,7 +23,6 @@ from cornflow.tests.const import ( INSTANCE_PATH, EXECUTION_PATH, - EXECUTION_URL, CASE_PATH, EXECUTION_URL_NORUN, DATA_CHECK_EXECUTION_URL, @@ -25,7 +36,28 @@ class TestDataChecksExecutionEndpoint(CustomTestCase): + """ + Test suite for execution data check endpoints. + + This class tests data validation for executions: + + - Direct execution checks + - Run and no-run validation scenarios + - Response structure validation + - Airflow integration testing + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + + - Base test configuration + - Test instance data + - Execution model + - Test payload + """ super().setUp() with open(INSTANCE_PATH) as f: @@ -42,6 +74,16 @@ def load_file_fk(_file): self.payload = load_file_fk(EXECUTION_PATH) def test_check_execution(self): + """ + Test execution data check without running. + + Verifies: + + - Successful data check creation + - Correct response status + - Proper ID assignment + - Data consistency + """ exec_to_check_id = self.create_new_row( EXECUTION_URL_NORUN, self.model, payload=self.payload ) @@ -61,6 +103,16 @@ def test_check_execution(self): @patch("cornflow.endpoints.data_check.Airflow") def test_check_execution_run(self, af_client_class): + """ + Test execution data check with Airflow integration. + + Verifies: + + - Successful data check creation + - Airflow client interaction + - Response validation + - Data consistency + """ patch_af_client(af_client_class) exec_to_check_id = self.create_new_row( @@ -83,7 +135,28 @@ def test_check_execution_run(self, af_client_class): class TestDataChecksInstanceEndpoint(CustomTestCase): + """ + Test suite for instance data check endpoints. + + This class tests data validation for instances: + + - Instance data validation + - Run and no-run scenarios + - Configuration verification + - Response structure validation + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + + - Base test configuration + - Test instance data + - Instance ID reference + - Execution model + """ super().setUp() with open(INSTANCE_PATH) as f: @@ -93,7 +166,16 @@ def setUp(self): self.model = ExecutionModel def test_new_data_check_execution(self): + """ + Test instance data check without running. + + Verifies: + - Successful data check creation + - Correct response status + - Instance ID association + - Configuration settings + """ url = DATA_CHECK_INSTANCE_URL + self.instance_id + "/?run=0" response = self.client.post( url, @@ -112,6 +194,16 @@ def test_new_data_check_execution(self): @patch("cornflow.endpoints.data_check.Airflow") def test_new_data_check_execution_run(self, af_client_class): + """ + Test instance data check with Airflow integration. + + Verifies: + + - Successful data check creation + - Airflow client interaction + - Instance association + - Configuration validation + """ patch_af_client(af_client_class) url = DATA_CHECK_INSTANCE_URL + self.instance_id + "/" @@ -132,7 +224,28 @@ def test_new_data_check_execution_run(self, af_client_class): class TestDataChecksCaseEndpoint(CustomTestCase): + """ + Test suite for case data check endpoints. + + This class tests data validation for cases: + + - Case data validation + - Run and no-run scenarios + - Configuration verification + - Response structure validation + """ + def setUp(self): + """ + Set up test environment before each test. + + Initializes: + + - Base test configuration + - Test case data + - Case ID reference + - Execution model + """ super().setUp() with open(CASE_PATH) as f: @@ -142,7 +255,16 @@ def setUp(self): self.model = ExecutionModel def test_new_data_check_execution(self): + """ + Test case data check without running. + Verifies: + + - Successful data check creation + - Correct response status + - Configuration settings + - Response validation + """ url = DATA_CHECK_CASE_URL + str(self.case_id) + "/?run=0" response = self.client.post( url, @@ -159,6 +281,16 @@ def test_new_data_check_execution(self): @patch("cornflow.endpoints.data_check.Airflow") def test_new_data_check_execution_run(self, af_client_class): + """ + Test case data check with Airflow integration. + + Verifies: + + - Successful data check creation + - Airflow client interaction + - Configuration validation + - Response structure + """ patch_af_client(af_client_class) url = DATA_CHECK_CASE_URL + str(self.case_id) + "/"