mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-28 19:35:27 +08:00
test(core): strengthen config and task behavior oracles
This commit is contained in:
@@ -50,8 +50,7 @@ class TestCheckDeps:
|
|||||||
|
|
||||||
result = asyncio.run(check_deps())
|
result = asyncio.run(check_deps())
|
||||||
|
|
||||||
assert 'requests' in result
|
assert result == ['requests', 'openai']
|
||||||
assert 'openai' in result
|
|
||||||
|
|
||||||
def test_check_deps_all_missing(self):
|
def test_check_deps_all_missing(self):
|
||||||
"""check_deps returns all deps when none present."""
|
"""check_deps returns all deps when none present."""
|
||||||
@@ -66,8 +65,7 @@ class TestCheckDeps:
|
|||||||
|
|
||||||
result = asyncio.run(check_deps())
|
result = asyncio.run(check_deps())
|
||||||
|
|
||||||
# Should include all required_deps keys
|
assert result == list(required_deps)
|
||||||
assert len(result) == len(required_deps)
|
|
||||||
|
|
||||||
def test_required_deps_dict_exists(self):
|
def test_required_deps_dict_exists(self):
|
||||||
"""required_deps dictionary is defined."""
|
"""required_deps dictionary is defined."""
|
||||||
|
|||||||
@@ -119,12 +119,11 @@ class TestApplyEnvOverridesToConfig:
|
|||||||
"""Test that '1', 'yes', 'on' are treated as true."""
|
"""Test that '1', 'yes', 'on' are treated as true."""
|
||||||
load_config = get_load_config_module()
|
load_config = get_load_config_module()
|
||||||
|
|
||||||
cfg = {'system': {'flag': False}}
|
|
||||||
|
|
||||||
for true_val in ['1', 'yes', 'on', 'TRUE']:
|
for true_val in ['1', 'yes', 'on', 'TRUE']:
|
||||||
|
cfg = {'system': {'flag': False}}
|
||||||
env = {'SYSTEM__FLAG': true_val}
|
env = {'SYSTEM__FLAG': true_val}
|
||||||
with patch.dict(os.environ, env, clear=True):
|
with patch.dict(os.environ, env, clear=True):
|
||||||
result = load_config._apply_env_overrides_to_config(cfg.copy())
|
result = load_config._apply_env_overrides_to_config(cfg)
|
||||||
assert result['system']['flag'] is True
|
assert result['system']['flag'] is True
|
||||||
|
|
||||||
def test_override_float_value(self):
|
def test_override_float_value(self):
|
||||||
@@ -237,7 +236,7 @@ class TestApplyEnvOverridesToConfig:
|
|||||||
with patch.dict(os.environ, env, clear=True):
|
with patch.dict(os.environ, env, clear=True):
|
||||||
result = load_config._apply_env_overrides_to_config(cfg)
|
result = load_config._apply_env_overrides_to_config(cfg)
|
||||||
|
|
||||||
assert result['system']['name'] == 'default'
|
assert result == {'system': {'name': 'default'}}
|
||||||
|
|
||||||
def test_skip_env_vars_with_empty_path_segments(self, capsys):
|
def test_skip_env_vars_with_empty_path_segments(self, capsys):
|
||||||
"""Platform variables such as __CF_USER_TEXT_ENCODING are not config."""
|
"""Platform variables such as __CF_USER_TEXT_ENCODING are not config."""
|
||||||
@@ -249,7 +248,7 @@ class TestApplyEnvOverridesToConfig:
|
|||||||
with patch.dict(os.environ, env, clear=True):
|
with patch.dict(os.environ, env, clear=True):
|
||||||
result = load_config._apply_env_overrides_to_config(cfg)
|
result = load_config._apply_env_overrides_to_config(cfg)
|
||||||
|
|
||||||
assert result == cfg
|
assert result == {'system': {'name': 'default'}}
|
||||||
assert capsys.readouterr().out == ''
|
assert capsys.readouterr().out == ''
|
||||||
|
|
||||||
def test_nested_config_path(self):
|
def test_nested_config_path(self):
|
||||||
@@ -299,7 +298,7 @@ class TestApplyEnvOverridesToConfig:
|
|||||||
with patch.dict(os.environ, env, clear=True):
|
with patch.dict(os.environ, env, clear=True):
|
||||||
result = load_config._apply_env_overrides_to_config(cfg)
|
result = load_config._apply_env_overrides_to_config(cfg)
|
||||||
|
|
||||||
assert result == cfg
|
assert result == {'system': {'name': 'default'}}
|
||||||
|
|
||||||
def test_multiple_env_vars_override(self):
|
def test_multiple_env_vars_override(self):
|
||||||
"""Test multiple env vars applied in order."""
|
"""Test multiple env vars applied in order."""
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import asyncio
|
|||||||
import contextvars
|
import contextvars
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
from unittest.mock import Mock, MagicMock
|
from unittest.mock import Mock, MagicMock, patch
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
@@ -181,12 +181,14 @@ class TestTaskContext:
|
|||||||
ctx.set_current_action('test_action')
|
ctx.set_current_action('test_action')
|
||||||
ctx.trace('Test message')
|
ctx.trace('Test message')
|
||||||
ctx.metadata['key'] = 'value'
|
ctx.metadata['key'] = 'value'
|
||||||
|
ctx.metadata['count'] = 5
|
||||||
|
ctx.metadata['items'] = ['a', 'b', 'c']
|
||||||
|
|
||||||
result = ctx.to_dict()
|
result = ctx.to_dict()
|
||||||
|
|
||||||
assert result['current_action'] == 'test_action'
|
assert result['current_action'] == 'test_action'
|
||||||
assert 'Test message' in result['log']
|
assert 'Test message' in result['log']
|
||||||
assert result['metadata'] == {'key': 'value'}
|
assert result['metadata'] == {'key': 'value', 'count': 5, 'items': ['a', 'b', 'c']}
|
||||||
|
|
||||||
def test_static_new_factory(self):
|
def test_static_new_factory(self):
|
||||||
"""Test TaskContext.new() factory method."""
|
"""Test TaskContext.new() factory method."""
|
||||||
@@ -209,19 +211,9 @@ class TestTaskContext:
|
|||||||
ctx1 = TaskContext.placeholder()
|
ctx1 = TaskContext.placeholder()
|
||||||
ctx2 = TaskContext.placeholder()
|
ctx2 = TaskContext.placeholder()
|
||||||
|
|
||||||
|
assert isinstance(ctx1, TaskContext)
|
||||||
assert ctx1 is ctx2
|
assert ctx1 is ctx2
|
||||||
|
|
||||||
def test_metadata_is_mutable_dict(self):
|
|
||||||
"""Test that metadata is a mutable dict."""
|
|
||||||
TaskContext, _, _ = get_taskmgr_classes()
|
|
||||||
ctx = TaskContext()
|
|
||||||
|
|
||||||
ctx.metadata['count'] = 5
|
|
||||||
ctx.metadata['items'] = ['a', 'b', 'c']
|
|
||||||
|
|
||||||
assert ctx.metadata['count'] == 5
|
|
||||||
assert len(ctx.metadata['items']) == 3
|
|
||||||
|
|
||||||
|
|
||||||
class TestTaskWrapper:
|
class TestTaskWrapper:
|
||||||
"""Tests for TaskWrapper class."""
|
"""Tests for TaskWrapper class."""
|
||||||
@@ -346,7 +338,8 @@ class TestTaskWrapper:
|
|||||||
async def successful_coro():
|
async def successful_coro():
|
||||||
return {'file_id': 'file-a'}
|
return {'file_id': 'file-a'}
|
||||||
|
|
||||||
wrapper = TaskWrapper(mock_app, successful_coro(), kind='knowledge_base.store')
|
with patch('time.time', return_value=1720000000.25):
|
||||||
|
wrapper = TaskWrapper(mock_app, successful_coro(), kind='knowledge_base.store')
|
||||||
await wrapper.task
|
await wrapper.task
|
||||||
|
|
||||||
result = wrapper.to_public_dict()
|
result = wrapper.to_public_dict()
|
||||||
@@ -358,7 +351,7 @@ class TestTaskWrapper:
|
|||||||
'status': 'succeeded',
|
'status': 'succeeded',
|
||||||
'error': None,
|
'error': None,
|
||||||
'result': {'file_id': 'file-a'},
|
'result': {'file_id': 'file-a'},
|
||||||
'created_at': result['created_at'],
|
'created_at': 1720000000.25,
|
||||||
}
|
}
|
||||||
assert 'runtime' not in result
|
assert 'runtime' not in result
|
||||||
assert 'traceback' not in str(result).lower()
|
assert 'traceback' not in str(result).lower()
|
||||||
@@ -429,6 +422,29 @@ class TestTaskWrapper:
|
|||||||
class TestAsyncTaskManager:
|
class TestAsyncTaskManager:
|
||||||
"""Tests for AsyncTaskManager class."""
|
"""Tests for AsyncTaskManager class."""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_completed_retention_keeps_newest_tasks_and_running_task(self):
|
||||||
|
_, _, AsyncTaskManager = get_taskmgr_classes()
|
||||||
|
mock_app = create_mock_app()
|
||||||
|
mock_app.instance_config.data['system']['task_retention']['completed_limit'] = 2
|
||||||
|
manager = AsyncTaskManager(mock_app)
|
||||||
|
running = manager.create_task(asyncio.Event().wait())
|
||||||
|
|
||||||
|
async def immediate_coro():
|
||||||
|
return 'done'
|
||||||
|
|
||||||
|
try:
|
||||||
|
completed = [manager.create_task(immediate_coro()) for _ in range(2)]
|
||||||
|
await asyncio.gather(*(wrapper.task for wrapper in completed))
|
||||||
|
assert manager.tasks == [running, *completed]
|
||||||
|
|
||||||
|
newest = manager.create_task(immediate_coro())
|
||||||
|
await newest.task
|
||||||
|
assert manager.tasks == [running, completed[1], newest]
|
||||||
|
finally:
|
||||||
|
running.cancel()
|
||||||
|
await asyncio.gather(running.task, return_exceptions=True)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_task_adds_to_list(self):
|
async def test_create_task_adds_to_list(self):
|
||||||
"""Test that create_task adds task to tasks list."""
|
"""Test that create_task adds task to tasks list."""
|
||||||
|
|||||||
Reference in New Issue
Block a user