semantics
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
# [DEF:test_task_persistence:Module]
|
||||
# @RELATION: BELONGS_TO -> SrcRoot
|
||||
# #region test_task_persistence [C:2] [TYPE Module]
|
||||
# @RELATION BINDS_TO -> SrcRoot
|
||||
# @SEMANTICS: test, task, persistence, unit_test
|
||||
# @PURPOSE: Unit tests for TaskPersistenceService.
|
||||
# @LAYER: Test
|
||||
# @LAYER Tests
|
||||
# @TEST_DATA: valid_task -> {"id": "test-uuid-1", "plugin_id": "backup", "status": "PENDING"}
|
||||
|
||||
# [SECTION: IMPORTS]
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -17,105 +16,104 @@ from src.core.task_manager.persistence import TaskPersistenceService
|
||||
from src.models.mapping import Base, Environment
|
||||
from src.models.task import TaskRecord
|
||||
|
||||
# [/SECTION]
|
||||
|
||||
|
||||
# [DEF:TestTaskPersistenceHelpers:Class]
|
||||
# @RELATION: BINDS_TO -> test_task_persistence
|
||||
# #region TestTaskPersistenceHelpers [C:2] [TYPE Class]
|
||||
# @RELATION BINDS_TO -> test_task_persistence
|
||||
# @PURPOSE: Test suite for TaskPersistenceService static helper methods.
|
||||
class TestTaskPersistenceHelpers:
|
||||
|
||||
# [DEF:test_json_load_if_needed_none:Function]
|
||||
# #region test_json_load_if_needed_none [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _json_load_if_needed with None input.
|
||||
def test_json_load_if_needed_none(self):
|
||||
assert TaskPersistenceService._json_load_if_needed(None) is None
|
||||
# [/DEF:test_json_load_if_needed_none:Function]
|
||||
# #endregion test_json_load_if_needed_none
|
||||
|
||||
# [DEF:test_json_load_if_needed_dict:Function]
|
||||
# #region test_json_load_if_needed_dict [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _json_load_if_needed with dict input.
|
||||
def test_json_load_if_needed_dict(self):
|
||||
data = {"key": "value"}
|
||||
assert TaskPersistenceService._json_load_if_needed(data) == data
|
||||
# [/DEF:test_json_load_if_needed_dict:Function]
|
||||
# #endregion test_json_load_if_needed_dict
|
||||
|
||||
# [DEF:test_json_load_if_needed_list:Function]
|
||||
# #region test_json_load_if_needed_list [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _json_load_if_needed with list input.
|
||||
def test_json_load_if_needed_list(self):
|
||||
data = [1, 2, 3]
|
||||
assert TaskPersistenceService._json_load_if_needed(data) == data
|
||||
# [/DEF:test_json_load_if_needed_list:Function]
|
||||
# #endregion test_json_load_if_needed_list
|
||||
|
||||
# [DEF:test_json_load_if_needed_json_string:Function]
|
||||
# #region test_json_load_if_needed_json_string [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _json_load_if_needed with JSON string.
|
||||
def test_json_load_if_needed_json_string(self):
|
||||
result = TaskPersistenceService._json_load_if_needed('{"key": "value"}')
|
||||
assert result == {"key": "value"}
|
||||
# [/DEF:test_json_load_if_needed_json_string:Function]
|
||||
# #endregion test_json_load_if_needed_json_string
|
||||
|
||||
# [DEF:test_json_load_if_needed_empty_string:Function]
|
||||
# #region test_json_load_if_needed_empty_string [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _json_load_if_needed with empty/null strings.
|
||||
def test_json_load_if_needed_empty_string(self):
|
||||
assert TaskPersistenceService._json_load_if_needed("") is None
|
||||
assert TaskPersistenceService._json_load_if_needed("null") is None
|
||||
assert TaskPersistenceService._json_load_if_needed(" null ") is None
|
||||
# [/DEF:test_json_load_if_needed_empty_string:Function]
|
||||
# #endregion test_json_load_if_needed_empty_string
|
||||
|
||||
# [DEF:test_json_load_if_needed_plain_string:Function]
|
||||
# #region test_json_load_if_needed_plain_string [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _json_load_if_needed with non-JSON string.
|
||||
def test_json_load_if_needed_plain_string(self):
|
||||
result = TaskPersistenceService._json_load_if_needed("not json")
|
||||
assert result == "not json"
|
||||
# [/DEF:test_json_load_if_needed_plain_string:Function]
|
||||
# #endregion test_json_load_if_needed_plain_string
|
||||
|
||||
# [DEF:test_json_load_if_needed_integer:Function]
|
||||
# #region test_json_load_if_needed_integer [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _json_load_if_needed with integer.
|
||||
def test_json_load_if_needed_integer(self):
|
||||
assert TaskPersistenceService._json_load_if_needed(42) == 42
|
||||
# [/DEF:test_json_load_if_needed_integer:Function]
|
||||
# #endregion test_json_load_if_needed_integer
|
||||
|
||||
# [DEF:test_parse_datetime_none:Function]
|
||||
# #region test_parse_datetime_none [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _parse_datetime with None.
|
||||
def test_parse_datetime_none(self):
|
||||
assert TaskPersistenceService._parse_datetime(None) is None
|
||||
# [/DEF:test_parse_datetime_none:Function]
|
||||
# #endregion test_parse_datetime_none
|
||||
|
||||
# [DEF:test_parse_datetime_datetime_object:Function]
|
||||
# #region test_parse_datetime_datetime_object [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _parse_datetime with datetime object.
|
||||
def test_parse_datetime_datetime_object(self):
|
||||
dt = datetime(2024, 1, 1, 12, 0, 0)
|
||||
assert TaskPersistenceService._parse_datetime(dt) == dt
|
||||
# [/DEF:test_parse_datetime_datetime_object:Function]
|
||||
# #endregion test_parse_datetime_datetime_object
|
||||
|
||||
# [DEF:test_parse_datetime_iso_string:Function]
|
||||
# #region test_parse_datetime_iso_string [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _parse_datetime with ISO string.
|
||||
def test_parse_datetime_iso_string(self):
|
||||
result = TaskPersistenceService._parse_datetime("2024-01-01T12:00:00")
|
||||
assert isinstance(result, datetime)
|
||||
assert result.year == 2024
|
||||
# [/DEF:test_parse_datetime_iso_string:Function]
|
||||
# #endregion test_parse_datetime_iso_string
|
||||
|
||||
# [DEF:test_parse_datetime_invalid_string:Function]
|
||||
# #region test_parse_datetime_invalid_string [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _parse_datetime with invalid string.
|
||||
def test_parse_datetime_invalid_string(self):
|
||||
assert TaskPersistenceService._parse_datetime("not-a-date") is None
|
||||
# [/DEF:test_parse_datetime_invalid_string:Function]
|
||||
# #endregion test_parse_datetime_invalid_string
|
||||
|
||||
# [DEF:test_parse_datetime_integer:Function]
|
||||
# #region test_parse_datetime_integer [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test _parse_datetime with non-string, non-datetime.
|
||||
def test_parse_datetime_integer(self):
|
||||
assert TaskPersistenceService._parse_datetime(12345) is None
|
||||
# [/DEF:test_parse_datetime_integer:Function]
|
||||
# #endregion test_parse_datetime_integer
|
||||
|
||||
# [/DEF:TestTaskPersistenceHelpers:Class]
|
||||
# #endregion TestTaskPersistenceHelpers
|
||||
|
||||
|
||||
# [DEF:TestTaskPersistenceService:Class]
|
||||
# @RELATION: BINDS_TO -> test_task_persistence
|
||||
# #region TestTaskPersistenceService [C:2] [TYPE Class]
|
||||
# @RELATION BINDS_TO -> test_task_persistence
|
||||
# @PURPOSE: Test suite for TaskPersistenceService CRUD operations.
|
||||
# @TEST_DATA: valid_task -> {"id": "test-uuid-1", "plugin_id": "backup", "status": "PENDING"}
|
||||
class TestTaskPersistenceService:
|
||||
|
||||
# [DEF:setup_class:Function]
|
||||
# #region setup_class [C:2] [TYPE Function]
|
||||
# @PURPOSE: Setup in-memory test database.
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
@@ -124,16 +122,16 @@ class TestTaskPersistenceService:
|
||||
Base.metadata.create_all(bind=cls.engine)
|
||||
cls.TestSessionLocal = sessionmaker(bind=cls.engine)
|
||||
cls.service = TaskPersistenceService()
|
||||
# [/DEF:setup_class:Function]
|
||||
# #endregion setup_class
|
||||
|
||||
# [DEF:teardown_class:Function]
|
||||
# #region teardown_class [C:2] [TYPE Function]
|
||||
# @PURPOSE: Dispose of test database.
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
cls.engine.dispose()
|
||||
# [/DEF:teardown_class:Function]
|
||||
# #endregion teardown_class
|
||||
|
||||
# [DEF:setup_method:Function]
|
||||
# #region setup_method [C:2] [TYPE Function]
|
||||
# @PURPOSE: Clean task_records table before each test.
|
||||
def setup_method(self):
|
||||
session = self.TestSessionLocal()
|
||||
@@ -141,7 +139,7 @@ class TestTaskPersistenceService:
|
||||
session.query(Environment).delete()
|
||||
session.commit()
|
||||
session.close()
|
||||
# [/DEF:setup_method:Function]
|
||||
# #endregion setup_method
|
||||
|
||||
def _patched(self):
|
||||
"""Helper: returns a patch context for TasksSessionLocal."""
|
||||
@@ -161,7 +159,7 @@ class TestTaskPersistenceService:
|
||||
defaults.update(kwargs)
|
||||
return Task(**defaults)
|
||||
|
||||
# [DEF:test_persist_task_new:Function]
|
||||
# #region test_persist_task_new [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test persisting a new task creates a record.
|
||||
# @PRE: Empty database.
|
||||
# @POST: TaskRecord exists in database.
|
||||
@@ -179,9 +177,9 @@ class TestTaskPersistenceService:
|
||||
assert record is not None
|
||||
assert record.type == "backup"
|
||||
assert record.status == "PENDING"
|
||||
# [/DEF:test_persist_task_new:Function]
|
||||
# #endregion test_persist_task_new
|
||||
|
||||
# [DEF:test_persist_task_update:Function]
|
||||
# #region test_persist_task_update [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test updating an existing task.
|
||||
# @PRE: Task already persisted.
|
||||
# @POST: Task record updated with new status.
|
||||
@@ -205,9 +203,9 @@ class TestTaskPersistenceService:
|
||||
|
||||
assert record.status == "RUNNING"
|
||||
assert record.started_at is not None
|
||||
# [/DEF:test_persist_task_update:Function]
|
||||
# #endregion test_persist_task_update
|
||||
|
||||
# [DEF:test_persist_task_with_logs:Function]
|
||||
# #region test_persist_task_with_logs [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test persisting a task with log entries.
|
||||
# @PRE: Task has logs attached.
|
||||
# @POST: Logs serialized as JSON in task record.
|
||||
@@ -228,9 +226,9 @@ class TestTaskPersistenceService:
|
||||
|
||||
assert record.logs is not None
|
||||
assert len(record.logs) == 2
|
||||
# [/DEF:test_persist_task_with_logs:Function]
|
||||
# #endregion test_persist_task_with_logs
|
||||
|
||||
# [DEF:test_persist_task_failed_extracts_error:Function]
|
||||
# #region test_persist_task_failed_extracts_error [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test that FAILED task extracts last error message.
|
||||
# @PRE: Task has FAILED status with ERROR logs.
|
||||
# @POST: record.error contains last error message.
|
||||
@@ -252,9 +250,9 @@ class TestTaskPersistenceService:
|
||||
session.close()
|
||||
|
||||
assert record.error == "Fatal: timeout"
|
||||
# [/DEF:test_persist_task_failed_extracts_error:Function]
|
||||
# #endregion test_persist_task_failed_extracts_error
|
||||
|
||||
# [DEF:test_persist_tasks_batch:Function]
|
||||
# #region test_persist_tasks_batch [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test persisting multiple tasks.
|
||||
# @PRE: Empty database.
|
||||
# @POST: All task records created.
|
||||
@@ -273,9 +271,9 @@ class TestTaskPersistenceService:
|
||||
session.close()
|
||||
|
||||
assert count == 3
|
||||
# [/DEF:test_persist_tasks_batch:Function]
|
||||
# #endregion test_persist_tasks_batch
|
||||
|
||||
# [DEF:test_load_tasks:Function]
|
||||
# #region test_load_tasks [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test loading tasks from database.
|
||||
# @PRE: Tasks persisted.
|
||||
# @POST: Returns list of Task objects with correct data.
|
||||
@@ -301,9 +299,9 @@ class TestTaskPersistenceService:
|
||||
assert loaded[0].plugin_id == "backup"
|
||||
assert loaded[0].status == TaskStatus.SUCCESS
|
||||
assert loaded[0].params == {"key": "value"}
|
||||
# [/DEF:test_load_tasks:Function]
|
||||
# #endregion test_load_tasks
|
||||
|
||||
# [DEF:test_load_tasks_with_status_filter:Function]
|
||||
# #region test_load_tasks_with_status_filter [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test loading tasks filtered by status.
|
||||
# @PRE: Tasks with different statuses persisted.
|
||||
# @POST: Returns only tasks matching status filter.
|
||||
@@ -324,9 +322,9 @@ class TestTaskPersistenceService:
|
||||
assert len(failed_tasks) == 1
|
||||
assert failed_tasks[0].id == "s2"
|
||||
assert failed_tasks[0].status == TaskStatus.FAILED
|
||||
# [/DEF:test_load_tasks_with_status_filter:Function]
|
||||
# #endregion test_load_tasks_with_status_filter
|
||||
|
||||
# [DEF:test_load_tasks_with_limit:Function]
|
||||
# #region test_load_tasks_with_limit [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test loading tasks with limit.
|
||||
# @PRE: Multiple tasks persisted.
|
||||
# @POST: Returns at most `limit` tasks.
|
||||
@@ -344,9 +342,9 @@ class TestTaskPersistenceService:
|
||||
loaded = self.service.load_tasks(limit=3)
|
||||
|
||||
assert len(loaded) == 3
|
||||
# [/DEF:test_load_tasks_with_limit:Function]
|
||||
# #endregion test_load_tasks_with_limit
|
||||
|
||||
# [DEF:test_delete_tasks:Function]
|
||||
# #region test_delete_tasks [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test deleting tasks by ID list.
|
||||
# @PRE: Tasks persisted.
|
||||
# @POST: Specified tasks deleted, others remain.
|
||||
@@ -370,9 +368,9 @@ class TestTaskPersistenceService:
|
||||
|
||||
assert len(remaining) == 1
|
||||
assert remaining[0].id == "keep-1"
|
||||
# [/DEF:test_delete_tasks:Function]
|
||||
# #endregion test_delete_tasks
|
||||
|
||||
# [DEF:test_delete_tasks_empty_list:Function]
|
||||
# #region test_delete_tasks_empty_list [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test deleting with empty list (no-op).
|
||||
# @PRE: None.
|
||||
# @POST: No error, no changes.
|
||||
@@ -380,9 +378,9 @@ class TestTaskPersistenceService:
|
||||
"""Test deleting with empty list is a no-op."""
|
||||
with self._patched():
|
||||
self.service.delete_tasks([]) # Should not raise
|
||||
# [/DEF:test_delete_tasks_empty_list:Function]
|
||||
# #endregion test_delete_tasks_empty_list
|
||||
|
||||
# [DEF:test_persist_task_with_datetime_in_params:Function]
|
||||
# #region test_persist_task_with_datetime_in_params [C:2] [TYPE Function]
|
||||
# @PURPOSE: Test json_serializable handles datetime in params.
|
||||
# @PRE: Task params contain datetime values.
|
||||
# @POST: Params serialized correctly.
|
||||
@@ -401,9 +399,9 @@ class TestTaskPersistenceService:
|
||||
assert record.params is not None
|
||||
assert record.params["timestamp"] == "2024-06-15T10:30:00"
|
||||
assert record.params["name"] == "test"
|
||||
# [/DEF:test_persist_task_with_datetime_in_params:Function]
|
||||
# #endregion test_persist_task_with_datetime_in_params
|
||||
|
||||
# [DEF:test_persist_task_resolves_environment_slug_to_existing_id:Function]
|
||||
# #region test_persist_task_resolves_environment_slug_to_existing_id [C:2] [TYPE Function]
|
||||
# @PURPOSE: Ensure slug-like environment token resolves to environments.id before persisting task.
|
||||
# @PRE: environments table contains env with name convertible to provided slug token.
|
||||
# @POST: task_records.environment_id stores actual environments.id and does not violate FK.
|
||||
@@ -425,7 +423,7 @@ class TestTaskPersistenceService:
|
||||
|
||||
assert record is not None
|
||||
assert record.environment_id == "env-uuid-1"
|
||||
# [/DEF:test_persist_task_resolves_environment_slug_to_existing_id:Function]
|
||||
# #endregion test_persist_task_resolves_environment_slug_to_existing_id
|
||||
|
||||
# [/DEF:TestTaskPersistenceService:Class]
|
||||
# [/DEF:test_task_persistence:Module]
|
||||
# #endregion TestTaskPersistenceService
|
||||
# #endregion test_task_persistence
|
||||
|
||||
Reference in New Issue
Block a user