Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ Thumbs.db
*.conv
debug/

# Claude Code settings
.claude/*

# Testing and coverage
.pytest_cache/
.coverage
htmlcov/
coverage.xml
.tox/
.nox/

# IDE files
.idea/
.vscode/
*.swp
*.swo
*~

# Package manager files (keep lock files)
# poetry.lock - keep this file
# uv.lock - keep this file



# Android
Expand Down
1,013 changes: 1,013 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

101 changes: 101 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "dialogue-engine"
version = "0.1.0"
description = "A Python AIML 2.0 chatbot framework"
authors = ["Your Name <[email protected]>"]
readme = "README.md"
packages = [{include = "programy", from = "dialogue-engine/src"}]

[tool.poetry.dependencies]
python = "^3.8"
# Minimal core dependencies for testing infrastructure
PyYAML = "*"
requests = "*"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.11.1"
toml = "^0.10.2"
pylint = "^2.17.0"
coverage = "^7.2.0"
packaging = "^23.0"

# NLU dependencies (can be installed separately if needed)
# [tool.poetry.group.nlu.dependencies]
# camphr = "0.5.23"
# tqdm = "4.62.3"
# spacy = "2.2.4"
# Cython = "0.29.28"

# Scripts will be run directly with `poetry run pytest` and `poetry run pytest` (both work)

[tool.pytest.ini_options]
minversion = "7.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*", "*Tests"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"--verbose",
"--cov=dialogue-engine/src/programy",
"--cov=nlu",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-report=term-missing",
"--cov-fail-under=80",
"--tb=short"
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests"
]
filterwarnings = [
"ignore::DeprecationWarning",
"ignore::PendingDeprecationWarning"
]

[tool.coverage.run]
source = ["dialogue-engine/src/programy", "nlu"]
omit = [
"*/tests/*",
"*/test/*",
"*/__pycache__/*",
"*/migrations/*",
"*/venv/*",
"*/.venv/*",
"*/node_modules/*",
"*/site-packages/*",
"*/conftest.py"
]
branch = true

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod"
]
show_missing = true
precision = 2
fail_under = 80

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"
Empty file added tests/__init__.py
Empty file.
240 changes: 240 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
"""
Shared pytest fixtures for the dialogue-engine testing infrastructure.
"""
import os
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, patch
import pytest
import yaml


@pytest.fixture
def temp_dir():
"""Create a temporary directory for tests."""
temp_dir = tempfile.mkdtemp()
yield temp_dir
shutil.rmtree(temp_dir)


@pytest.fixture
def temp_file():
"""Create a temporary file for tests."""
temp_file = tempfile.NamedTemporaryFile(delete=False)
temp_file.close()
yield temp_file.name
if os.path.exists(temp_file.name):
os.unlink(temp_file.name)


@pytest.fixture
def sample_config():
"""Provide a sample configuration dictionary."""
return {
'version': '1.0',
'bot': {
'name': 'test-bot',
'prompt': 'Hello!',
'default_response': 'I do not understand.',
},
'brain': {
'files': {
'aiml': {
'files': '*.aiml'
}
}
},
'storage': {
'entities': {
'categories': 'file',
'errors': 'file',
'duplicates': 'file',
'learnf': 'file',
'conversations': 'file'
}
}
}


@pytest.fixture
def sample_config_file(temp_dir, sample_config):
"""Create a sample configuration file."""
config_path = os.path.join(temp_dir, 'config.yaml')
with open(config_path, 'w') as f:
yaml.dump(sample_config, f)
return config_path


@pytest.fixture
def mock_client():
"""Mock client for testing."""
client = Mock()
client.userid = 'testuser'
client.clientid = 'testclient'
return client


@pytest.fixture
def mock_context():
"""Mock context for testing."""
context = Mock()
context.userid = 'testuser'
context.clientid = 'testclient'
context.brain = Mock()
context.bot = Mock()
return context


@pytest.fixture
def mock_bot():
"""Mock bot instance for testing."""
bot = Mock()
bot.configuration = Mock()
bot.brain = Mock()
bot.license_keys = Mock()
return bot


@pytest.fixture
def mock_brain():
"""Mock brain instance for testing."""
brain = Mock()
brain.configuration = Mock()
brain.aiml_parser = Mock()
brain.denormalize = Mock(return_value="normalized")
brain.normalize = Mock(return_value="denormalized")
return brain


@pytest.fixture
def sample_aiml_content():
"""Provide sample AIML content for testing."""
return """<?xml version="1.0" encoding="UTF-8"?>
<aiml version="2.0">
<category>
<pattern>HELLO</pattern>
<template>Hi there!</template>
</category>
<category>
<pattern>HOW ARE YOU</pattern>
<template>I'm doing well, thank you!</template>
</category>
</aiml>"""


@pytest.fixture
def sample_aiml_file(temp_dir, sample_aiml_content):
"""Create a sample AIML file."""
aiml_path = os.path.join(temp_dir, 'sample.aiml')
with open(aiml_path, 'w') as f:
f.write(sample_aiml_content)
return aiml_path


@pytest.fixture
def mock_logger():
"""Mock logger for testing."""
with patch('programy.utils.logging.ylogger.YLogger') as mock:
yield mock


@pytest.fixture
def mock_redis():
"""Mock Redis connection for testing."""
with patch('redis.Redis') as mock:
yield mock


@pytest.fixture
def mock_mongodb():
"""Mock MongoDB connection for testing."""
with patch('pymongo.MongoClient') as mock:
yield mock


@pytest.fixture
def mock_sql():
"""Mock SQL connection for testing."""
with patch('sqlalchemy.create_engine') as mock:
yield mock


@pytest.fixture
def mock_requests():
"""Mock requests module for testing HTTP calls."""
with patch('requests.get') as mock_get, \
patch('requests.post') as mock_post:
yield {'get': mock_get, 'post': mock_post}


@pytest.fixture
def environment_variables():
"""Set up test environment variables."""
original_env = os.environ.copy()
test_env = {
'PYTHONPATH': os.pathsep.join([
'/workspace/dialogue-engine/src',
'/workspace/nlu'
])
}
os.environ.update(test_env)
yield test_env
os.environ.clear()
os.environ.update(original_env)


@pytest.fixture
def capture_logs(caplog):
"""Capture and provide log messages for testing."""
return caplog


@pytest.fixture(scope="session", autouse=True)
def setup_test_environment():
"""Set up the test environment once per session."""
# Add source directories to Python path
import sys
src_paths = [
'/workspace/dialogue-engine/src',
'/workspace/nlu',
'/workspace'
]
for path in src_paths:
if path not in sys.path:
sys.path.insert(0, path)

yield

# Cleanup if needed
pass


@pytest.fixture
def mock_file_system(temp_dir):
"""Mock file system operations."""
original_cwd = os.getcwd()
os.chdir(temp_dir)

# Create some standard directories
os.makedirs('config', exist_ok=True)
os.makedirs('storage', exist_ok=True)
os.makedirs('logs', exist_ok=True)

yield temp_dir

os.chdir(original_cwd)


@pytest.fixture
def performance_timer():
"""Timer fixture for performance testing."""
import time
start_time = time.time()
yield lambda: time.time() - start_time


# Markers for test categorization
pytest.mark.unit = pytest.mark.unit
pytest.mark.integration = pytest.mark.integration
pytest.mark.slow = pytest.mark.slow
Empty file added tests/integration/__init__.py
Empty file.
Loading