veris-ai 0.1.1__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of veris-ai might be problematic. Click here for more details.
- veris_ai-0.2.0/.github/workflows/publish.yml +43 -0
- veris_ai-0.2.0/.github/workflows/test.yml +44 -0
- veris_ai-0.2.0/CHANGELOG.md +33 -0
- {veris_ai-0.1.1 → veris_ai-0.2.0}/PKG-INFO +1 -3
- {veris_ai-0.1.1 → veris_ai-0.2.0}/pyproject.toml +34 -4
- {veris_ai-0.1.1 → veris_ai-0.2.0}/src/veris_ai/tool_mock.py +60 -43
- veris_ai-0.2.0/tests/__init__.py +1 -0
- veris_ai-0.2.0/tests/conftest.py +46 -0
- veris_ai-0.2.0/tests/test_tool_mock.py +223 -0
- veris_ai-0.2.0/uv.lock +524 -0
- veris_ai-0.1.1/uv.lock +0 -142
- {veris_ai-0.1.1 → veris_ai-0.2.0}/.gitignore +0 -0
- {veris_ai-0.1.1 → veris_ai-0.2.0}/LICENSE +0 -0
- {veris_ai-0.1.1 → veris_ai-0.2.0}/README.md +0 -0
- {veris_ai-0.1.1 → veris_ai-0.2.0}/src/veris_ai/__init__.py +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
publish:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
permissions:
|
|
10
|
+
contents: write
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0
|
|
15
|
+
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v4
|
|
18
|
+
with:
|
|
19
|
+
python-version: '3.13'
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: |
|
|
23
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
24
|
+
uv sync
|
|
25
|
+
uv pip install python-semantic-release
|
|
26
|
+
|
|
27
|
+
- name: Configure Git
|
|
28
|
+
run: |
|
|
29
|
+
git config --global user.name "GitHub Actions"
|
|
30
|
+
git config --global user.email "actions@github.com"
|
|
31
|
+
|
|
32
|
+
- name: Version and Release
|
|
33
|
+
env:
|
|
34
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
35
|
+
PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
36
|
+
run: |
|
|
37
|
+
uv run semantic-release version
|
|
38
|
+
uv run semantic-release publish
|
|
39
|
+
|
|
40
|
+
- name: Push changes
|
|
41
|
+
run: |
|
|
42
|
+
git push
|
|
43
|
+
git push --tags
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
run: |
|
|
26
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: |
|
|
30
|
+
uv venv
|
|
31
|
+
uv pip install -e .[dev]
|
|
32
|
+
|
|
33
|
+
- name: Run code quality checks with Ruff
|
|
34
|
+
run: |
|
|
35
|
+
uv run ruff check .
|
|
36
|
+
uv run ruff format --check .
|
|
37
|
+
|
|
38
|
+
- name: Run type checks with mypy
|
|
39
|
+
run: |
|
|
40
|
+
uv run mypy src/veris_ai tests
|
|
41
|
+
|
|
42
|
+
- name: Run tests with pytest
|
|
43
|
+
run: |
|
|
44
|
+
uv run pytest tests/ --cov=veris_ai --cov-report=xml --cov-report=term-missing
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## v0.2.0 (2025-04-18)
|
|
5
|
+
|
|
6
|
+
### Chores
|
|
7
|
+
|
|
8
|
+
- Update changelog for 0.1.1
|
|
9
|
+
([`24d7139`](https://github.com/veris-ai/veris-python-sdk/commit/24d713943737abcea6394258e5129ae0a55cb869))
|
|
10
|
+
|
|
11
|
+
- Update semantic release config
|
|
12
|
+
([`6f745e0`](https://github.com/veris-ai/veris-python-sdk/commit/6f745e09263703cfe034ba688b96e4da50759889))
|
|
13
|
+
|
|
14
|
+
### Continuous Integration
|
|
15
|
+
|
|
16
|
+
- Add workflows for release ([#3](https://github.com/veris-ai/veris-python-sdk/pull/3),
|
|
17
|
+
[`3c160e7`](https://github.com/veris-ai/veris-python-sdk/commit/3c160e7e928ed1efb42825b767084df5d451edb5))
|
|
18
|
+
|
|
19
|
+
- Fix publish ci workflow ([#4](https://github.com/veris-ai/veris-python-sdk/pull/4),
|
|
20
|
+
[`47f23e1`](https://github.com/veris-ai/veris-python-sdk/commit/47f23e19cd04b60ab0ae087b6d70b4748350393c))
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
- Remove dependencies and use linter ([#2](https://github.com/veris-ai/veris-python-sdk/pull/2),
|
|
25
|
+
[`4b8c43b`](https://github.com/veris-ai/veris-python-sdk/commit/4b8c43b551265ff9c994f53d29a7ef185b7e3286))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## v0.1.1 (2025-04-17)
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
- Updates to package for publishing ([#1](https://github.com/veris-ai/veris-python-sdk/pull/1),
|
|
33
|
+
[`c6f460e`](https://github.com/veris-ai/veris-python-sdk/commit/c6f460ea6e2f8472c120370a14f67f1d8c28626c))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: veris-ai
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A Python package for Veris AI tools
|
|
5
5
|
Project-URL: Homepage, https://github.com/veris-ai/veris-python-sdk
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/veris-ai/veris-python-sdk/issues
|
|
@@ -9,8 +9,6 @@ License-Expression: MIT
|
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Requires-Dist: httpx>=0.24.0
|
|
12
|
-
Requires-Dist: python-dotenv>=1.0.0
|
|
13
|
-
Requires-Dist: typing-extensions>=4.0.0
|
|
14
12
|
Provides-Extra: dev
|
|
15
13
|
Requires-Dist: black>=23.7.0; extra == 'dev'
|
|
16
14
|
Requires-Dist: mypy>=1.5.1; extra == 'dev'
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "veris-ai"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "A Python package for Veris AI tools"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -13,9 +13,7 @@ authors = [
|
|
|
13
13
|
{ name = "Mehdi Jamei", email = "mehdi@veris.ai" }
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
|
-
"typing-extensions>=4.0.0",
|
|
17
16
|
"httpx>=0.24.0",
|
|
18
|
-
"python-dotenv>=1.0.0",
|
|
19
17
|
]
|
|
20
18
|
|
|
21
19
|
[project.optional-dependencies]
|
|
@@ -97,4 +95,36 @@ known-first-party = ["veris_ai"]
|
|
|
97
95
|
convention = "google"
|
|
98
96
|
|
|
99
97
|
[tool.ruff.per-file-ignores]
|
|
100
|
-
"tests/*" = [
|
|
98
|
+
"tests/*" = [
|
|
99
|
+
"D", # Ignore documentation requirements
|
|
100
|
+
"ANN", # Ignore type annotation requirements
|
|
101
|
+
"S", # Ignore security warnings
|
|
102
|
+
"ARG", # Ignore unused arguments
|
|
103
|
+
"PLR", # Ignore pylint refactor suggestions
|
|
104
|
+
"N", # Ignore naming conventions
|
|
105
|
+
"T201", # Ignore print statements
|
|
106
|
+
"E", # Ignore style errors
|
|
107
|
+
"F", # Ignore pyflakes warnings
|
|
108
|
+
"B", # Ignore bugbear warnings
|
|
109
|
+
"C4", # Ignore comprehension warnings
|
|
110
|
+
"UP", # Ignore upgrade suggestions
|
|
111
|
+
"SLF", # Ignore self warnings
|
|
112
|
+
"ERA", # Ignore eradicate warnings
|
|
113
|
+
"PD", # Ignore pandas warnings
|
|
114
|
+
"PGH", # Ignore pygrep-hooks
|
|
115
|
+
"PL", # Ignore pylint warnings
|
|
116
|
+
"PT" # Ignore pytest warnings
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
[tool.semantic_release]
|
|
120
|
+
version_variables = ["pyproject.toml:version"]
|
|
121
|
+
branch = "master"
|
|
122
|
+
changelog_file = "CHANGELOG.md"
|
|
123
|
+
build_command = "uv build"
|
|
124
|
+
dist_path = "dist/"
|
|
125
|
+
upload_to_repository = true
|
|
126
|
+
repository_url = "https://upload.pypi.org/legacy/"
|
|
127
|
+
commit_message = "chore: release v{version}"
|
|
128
|
+
commit_parser = "angular"
|
|
129
|
+
major_on_zero = false
|
|
130
|
+
tag_format = "v{version}"
|
|
@@ -3,62 +3,81 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
from collections.abc import Callable
|
|
6
|
+
from contextlib import suppress
|
|
6
7
|
from functools import wraps
|
|
7
|
-
from typing import Any, Union, get_type_hints
|
|
8
|
+
from typing import Any, TypeVar, Union, get_args, get_origin, get_type_hints
|
|
8
9
|
|
|
9
10
|
import httpx
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
14
|
+
T = TypeVar("T") # Generic type for return value
|
|
15
|
+
|
|
16
|
+
|
|
13
17
|
class ToolMock:
|
|
14
18
|
"""Class for mocking tool calls."""
|
|
15
|
-
def __init__(self):
|
|
16
|
-
self.endpoint = os.getenv("VERIS_MOCK_ENDPOINT_URL")
|
|
17
|
-
if not self.endpoint:
|
|
18
|
-
raise ValueError("VERIS_MOCK_ENDPOINT_URL environment variable is not set")
|
|
19
|
-
# Default timeout of 30 seconds
|
|
20
|
-
self.timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "30.0"))
|
|
21
19
|
|
|
22
|
-
def
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
"""Initialize the ToolMock class."""
|
|
22
|
+
|
|
23
|
+
def _convert_to_type(self, value: object, target_type: type) -> object:
|
|
23
24
|
"""Convert a value to the specified type."""
|
|
24
|
-
if target_type
|
|
25
|
+
if target_type is Any:
|
|
25
26
|
return value
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
# Handle basic types
|
|
28
29
|
if target_type in (str, int, float, bool):
|
|
29
30
|
return target_type(value)
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
# Handle List types
|
|
32
|
-
|
|
33
|
+
origin = get_origin(target_type)
|
|
34
|
+
if origin is list:
|
|
33
35
|
if not isinstance(value, list):
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
error_msg = f"Expected list but got {type(value)}"
|
|
37
|
+
raise ValueError(error_msg)
|
|
38
|
+
item_type = get_args(target_type)[0]
|
|
36
39
|
return [self._convert_to_type(item, item_type) for item in value]
|
|
37
|
-
|
|
40
|
+
|
|
38
41
|
# Handle Dict types
|
|
39
|
-
if
|
|
42
|
+
if origin is dict:
|
|
40
43
|
if not isinstance(value, dict):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
error_msg = f"Expected dict but got {type(value)}"
|
|
45
|
+
raise ValueError(error_msg)
|
|
46
|
+
key_type, value_type = get_args(target_type)
|
|
47
|
+
return {
|
|
48
|
+
self._convert_to_type(k, key_type): self._convert_to_type(v, value_type)
|
|
49
|
+
for k, v in value.items()
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
# Handle Union types
|
|
47
|
-
if
|
|
48
|
-
|
|
53
|
+
if origin is Union:
|
|
54
|
+
error_msg = (
|
|
55
|
+
f"Could not convert {value} to any of the union types {get_args(target_type)}"
|
|
56
|
+
)
|
|
57
|
+
for possible_type in get_args(target_type):
|
|
49
58
|
try:
|
|
50
59
|
return self._convert_to_type(value, possible_type)
|
|
51
60
|
except (ValueError, TypeError):
|
|
52
61
|
continue
|
|
53
|
-
raise ValueError(
|
|
54
|
-
|
|
62
|
+
raise ValueError(error_msg)
|
|
63
|
+
|
|
55
64
|
# For other types, try direct conversion
|
|
56
65
|
return target_type(value)
|
|
57
66
|
|
|
58
67
|
def mock(self, func: Callable) -> Callable:
|
|
59
68
|
"""Decorator for mocking tool calls."""
|
|
69
|
+
endpoint = os.getenv("VERIS_MOCK_ENDPOINT_URL")
|
|
70
|
+
if not endpoint:
|
|
71
|
+
error_msg = "VERIS_MOCK_ENDPOINT_URL environment variable is not set"
|
|
72
|
+
raise ValueError(error_msg)
|
|
73
|
+
# Default timeout of 30 seconds
|
|
74
|
+
timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "30.0"))
|
|
75
|
+
|
|
60
76
|
@wraps(func)
|
|
61
|
-
async def wrapper(
|
|
77
|
+
async def wrapper(
|
|
78
|
+
*args: tuple[object, ...],
|
|
79
|
+
**kwargs: dict[str, object],
|
|
80
|
+
) -> object:
|
|
62
81
|
# Check if we're in simulation mode
|
|
63
82
|
env_mode = os.getenv("ENV", "").lower()
|
|
64
83
|
if env_mode != "simulation":
|
|
@@ -68,16 +87,16 @@ class ToolMock:
|
|
|
68
87
|
logger.info(f"Simulating function: {func.__name__}")
|
|
69
88
|
sig = inspect.signature(func)
|
|
70
89
|
type_hints = get_type_hints(func)
|
|
71
|
-
|
|
90
|
+
|
|
72
91
|
# Extract return type object (not just the name)
|
|
73
92
|
return_type_obj = type_hints.pop("return", Any)
|
|
74
|
-
|
|
93
|
+
|
|
75
94
|
# Create parameter info
|
|
76
95
|
params_info = {}
|
|
77
96
|
bound_args = sig.bind(*args, **kwargs)
|
|
78
97
|
bound_args.apply_defaults()
|
|
79
|
-
|
|
80
|
-
ctx = bound_args.arguments.pop(
|
|
98
|
+
|
|
99
|
+
ctx = bound_args.arguments.pop("ctx", None)
|
|
81
100
|
session_id = None
|
|
82
101
|
if ctx:
|
|
83
102
|
try:
|
|
@@ -97,31 +116,29 @@ class ToolMock:
|
|
|
97
116
|
payload = {
|
|
98
117
|
"session_id": session_id,
|
|
99
118
|
"tool_call": {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
119
|
+
"function_name": func.__name__,
|
|
120
|
+
"parameters": params_info,
|
|
121
|
+
"return_type": return_type_obj.__name__,
|
|
122
|
+
"docstring": docstring,
|
|
123
|
+
},
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
# Send request to endpoint with timeout
|
|
108
|
-
async with httpx.AsyncClient(timeout=
|
|
109
|
-
response = await client.post(
|
|
127
|
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
128
|
+
response = await client.post(endpoint, json=payload)
|
|
110
129
|
response.raise_for_status()
|
|
111
130
|
mock_result = response.json()["result"]
|
|
112
131
|
logger.info(f"Mock response: {mock_result}")
|
|
113
|
-
|
|
132
|
+
|
|
114
133
|
# Parse the mock result if it's a string
|
|
115
134
|
if isinstance(mock_result, str):
|
|
116
|
-
|
|
135
|
+
with suppress(json.JSONDecodeError):
|
|
117
136
|
mock_result = json.loads(mock_result)
|
|
118
|
-
|
|
119
|
-
# If it's not valid JSON, treat it as a raw string
|
|
120
|
-
pass
|
|
121
|
-
|
|
137
|
+
|
|
122
138
|
# Convert the mock result to the expected return type
|
|
123
139
|
return self._convert_to_type(mock_result, return_type_obj)
|
|
124
140
|
|
|
125
141
|
return wrapper
|
|
126
142
|
|
|
143
|
+
|
|
127
144
|
veris = ToolMock()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests for the veris_ai package."""
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MockContext:
|
|
8
|
+
class RequestContext:
|
|
9
|
+
class LifespanContext:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.session_id = "test-session"
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.lifespan_context = self.LifespanContext()
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.request_context = self.RequestContext()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def mock_context():
|
|
22
|
+
return MockContext()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def simulation_env():
|
|
27
|
+
with patch.dict(
|
|
28
|
+
os.environ,
|
|
29
|
+
{
|
|
30
|
+
"VERIS_MOCK_ENDPOINT_URL": "http://test-endpoint",
|
|
31
|
+
"ENV": "simulation",
|
|
32
|
+
},
|
|
33
|
+
):
|
|
34
|
+
yield
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def production_env():
|
|
39
|
+
with patch.dict(
|
|
40
|
+
os.environ,
|
|
41
|
+
{
|
|
42
|
+
"VERIS_MOCK_ENDPOINT_URL": "http://test-endpoint",
|
|
43
|
+
"ENV": "production",
|
|
44
|
+
},
|
|
45
|
+
):
|
|
46
|
+
yield
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, Union
|
|
3
|
+
from unittest.mock import AsyncMock, Mock, patch
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from veris_ai import veris
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def tool_mock():
|
|
13
|
+
return veris
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Test type conversion
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
"value,target_type,expected",
|
|
19
|
+
[
|
|
20
|
+
("42", int, 42),
|
|
21
|
+
(42, str, "42"),
|
|
22
|
+
("3.14", float, 3.14),
|
|
23
|
+
("true", bool, True),
|
|
24
|
+
([1, 2, 3], list[int], [1, 2, 3]),
|
|
25
|
+
(["1", "2"], list[int], [1, 2]),
|
|
26
|
+
({"a": 1}, dict[str, int], {"a": 1}),
|
|
27
|
+
("42", Union[int, str], 42),
|
|
28
|
+
("abc", Union[int, str], "abc"),
|
|
29
|
+
],
|
|
30
|
+
)
|
|
31
|
+
def test_convert_to_type(tool_mock, value, target_type, expected):
|
|
32
|
+
result = tool_mock._convert_to_type(value, target_type)
|
|
33
|
+
assert result == expected
|
|
34
|
+
assert isinstance(result, type(expected))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_convert_to_type_invalid(tool_mock):
|
|
38
|
+
with pytest.raises(ValueError):
|
|
39
|
+
tool_mock._convert_to_type("not a list", list[int])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_convert_to_type_dict_invalid(tool_mock):
|
|
43
|
+
with pytest.raises(ValueError, match="Expected dict but got <class 'str'>"):
|
|
44
|
+
tool_mock._convert_to_type("not a dict", dict[str, int])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_convert_to_type_union_all_fail(tool_mock):
|
|
48
|
+
with pytest.raises(ValueError, match="Could not convert abc to any of the union types"):
|
|
49
|
+
tool_mock._convert_to_type("abc", Union[int, float])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_convert_to_type_custom_type(tool_mock):
|
|
53
|
+
class CustomType:
|
|
54
|
+
def __init__(self, value):
|
|
55
|
+
self.value = value
|
|
56
|
+
|
|
57
|
+
result = tool_mock._convert_to_type("test", CustomType)
|
|
58
|
+
assert isinstance(result, CustomType)
|
|
59
|
+
assert result.value == "test"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Test mock decorator
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_mock_decorator_simulation_mode(simulation_env):
|
|
65
|
+
@veris.mock
|
|
66
|
+
async def test_func(param1: str, param2: int) -> dict[str, Any]:
|
|
67
|
+
return {"result": "real"}
|
|
68
|
+
|
|
69
|
+
mock_response = {"result": {"mocked": True}}
|
|
70
|
+
|
|
71
|
+
with patch("httpx.AsyncClient") as mock_client:
|
|
72
|
+
mock_response_obj = Mock()
|
|
73
|
+
mock_response_obj.json.return_value = mock_response
|
|
74
|
+
mock_response_obj.raise_for_status.return_value = None
|
|
75
|
+
|
|
76
|
+
mock_client.return_value.__aenter__.return_value.post = AsyncMock(
|
|
77
|
+
return_value=mock_response_obj,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
result = await test_func("test", 42)
|
|
81
|
+
assert result == {"mocked": True}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_mock_decorator_production_mode(production_env):
|
|
86
|
+
@veris.mock
|
|
87
|
+
async def test_func(param1: str, param2: int) -> dict:
|
|
88
|
+
return {"result": "real"}
|
|
89
|
+
|
|
90
|
+
result = await test_func("test", 42)
|
|
91
|
+
assert result == {"result": "real"}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.mark.asyncio
|
|
95
|
+
async def test_mock_with_context(simulation_env, mock_context):
|
|
96
|
+
@veris.mock
|
|
97
|
+
async def test_func(ctx) -> dict:
|
|
98
|
+
return {"result": "real"}
|
|
99
|
+
|
|
100
|
+
mock_response = {"result": {"mocked": True}}
|
|
101
|
+
|
|
102
|
+
with patch("veris_ai.tool_mock.httpx.AsyncClient") as mock_client:
|
|
103
|
+
mock_response_obj = Mock()
|
|
104
|
+
mock_response_obj.json.return_value = mock_response
|
|
105
|
+
mock_response_obj.raise_for_status.return_value = None
|
|
106
|
+
|
|
107
|
+
mock_client.return_value.__aenter__.return_value.post = AsyncMock(
|
|
108
|
+
return_value=mock_response_obj,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
result = await test_func(mock_context)
|
|
112
|
+
first_call = mock_client.return_value.__aenter__.return_value.post.call_args
|
|
113
|
+
assert (
|
|
114
|
+
first_call.kwargs["json"]["session_id"]
|
|
115
|
+
== mock_context.request_context.lifespan_context.session_id
|
|
116
|
+
)
|
|
117
|
+
assert result == {"mocked": True}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.asyncio
|
|
121
|
+
async def test_mock_without_context(simulation_env):
|
|
122
|
+
@veris.mock
|
|
123
|
+
async def test_func() -> dict:
|
|
124
|
+
return {"result": "real"}
|
|
125
|
+
|
|
126
|
+
mock_response = {"result": {"mocked": True}}
|
|
127
|
+
|
|
128
|
+
with patch("veris_ai.tool_mock.httpx.AsyncClient") as mock_client:
|
|
129
|
+
mock_response_obj = Mock()
|
|
130
|
+
mock_response_obj.json.return_value = mock_response
|
|
131
|
+
mock_response_obj.raise_for_status.return_value = None
|
|
132
|
+
|
|
133
|
+
mock_client.return_value.__aenter__.return_value.post = AsyncMock(
|
|
134
|
+
return_value=mock_response_obj,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
result = await test_func()
|
|
138
|
+
first_call = mock_client.return_value.__aenter__.return_value.post.call_args
|
|
139
|
+
assert first_call.kwargs["json"]["session_id"] == None
|
|
140
|
+
assert result == {"mocked": True}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.mark.asyncio
|
|
144
|
+
async def test_mock_with_malformed_context(simulation_env):
|
|
145
|
+
@veris.mock
|
|
146
|
+
async def test_func(ctx) -> dict:
|
|
147
|
+
return {"result": "real"}
|
|
148
|
+
|
|
149
|
+
mock_response = {"result": {"mocked": True}}
|
|
150
|
+
|
|
151
|
+
with patch("veris_ai.tool_mock.httpx.AsyncClient") as mock_client:
|
|
152
|
+
mock_response_obj = Mock()
|
|
153
|
+
mock_response_obj.json.return_value = mock_response
|
|
154
|
+
mock_response_obj.raise_for_status.return_value = None
|
|
155
|
+
|
|
156
|
+
mock_client.return_value.__aenter__.return_value.post = AsyncMock(
|
|
157
|
+
return_value=mock_response_obj,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
result = await test_func({"unknown": "unknown"})
|
|
161
|
+
first_call = mock_client.return_value.__aenter__.return_value.post.call_args
|
|
162
|
+
assert first_call.kwargs["json"]["session_id"] == None
|
|
163
|
+
assert result == {"mocked": True}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Test error handling
|
|
167
|
+
@pytest.mark.asyncio
|
|
168
|
+
async def test_mock_http_error(simulation_env):
|
|
169
|
+
@veris.mock
|
|
170
|
+
async def test_func(param1: str) -> dict:
|
|
171
|
+
return {"result": "real"}
|
|
172
|
+
|
|
173
|
+
with patch("httpx.AsyncClient") as mock_client:
|
|
174
|
+
mock_client.return_value.__aenter__.return_value.post = AsyncMock(
|
|
175
|
+
side_effect=httpx.HTTPError("Mock HTTP Error"),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
with pytest.raises(httpx.HTTPError):
|
|
179
|
+
await test_func("test")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@pytest.mark.asyncio
|
|
183
|
+
async def test_mock_missing_endpoint():
|
|
184
|
+
with (
|
|
185
|
+
patch.dict(os.environ, {"VERIS_MOCK_ENDPOINT_URL": ""}),
|
|
186
|
+
pytest.raises(ValueError, match="VERIS_MOCK_ENDPOINT_URL environment variable is not set"),
|
|
187
|
+
):
|
|
188
|
+
|
|
189
|
+
@veris.mock
|
|
190
|
+
async def test_func():
|
|
191
|
+
return {"result": "real"}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@pytest.mark.asyncio
|
|
195
|
+
async def test_mock_invalid_endpoint(simulation_env):
|
|
196
|
+
with patch.dict(os.environ, {"VERIS_MOCK_ENDPOINT_URL": ""}), pytest.raises(ValueError):
|
|
197
|
+
|
|
198
|
+
@veris.mock
|
|
199
|
+
async def test_func():
|
|
200
|
+
return {"result": "real"}
|
|
201
|
+
|
|
202
|
+
await test_func()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@pytest.mark.asyncio
|
|
206
|
+
async def test_mock_string_json_response(simulation_env):
|
|
207
|
+
@veris.mock
|
|
208
|
+
async def test_func() -> dict:
|
|
209
|
+
return {"result": "real"}
|
|
210
|
+
|
|
211
|
+
mock_response = {"result": '{"key": "value"}'}
|
|
212
|
+
|
|
213
|
+
with patch("httpx.AsyncClient") as mock_client:
|
|
214
|
+
mock_response_obj = Mock()
|
|
215
|
+
mock_response_obj.json.return_value = mock_response
|
|
216
|
+
mock_response_obj.raise_for_status.return_value = None
|
|
217
|
+
|
|
218
|
+
mock_client.return_value.__aenter__.return_value.post = AsyncMock(
|
|
219
|
+
return_value=mock_response_obj,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
result = await test_func()
|
|
223
|
+
assert result == {"key": "value"}
|