Skip to content

Commit 71a037c

Browse files
testing: add unit testing (#11)
Co-authored-by: Roberto Pastor Muela <[email protected]>
1 parent be03927 commit 71a037c

File tree

10 files changed

+240
-3
lines changed

10 files changed

+240
-3
lines changed

.github/workflows/ci_cd.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,45 @@ jobs:
2626
- name: "Run PyAnsys code style checks"
2727
uses: ansys/actions/code-style@v6
2828

29+
tests:
30+
name: "Tests"
31+
runs-on: ${{ matrix.os }}
32+
needs: [code-style]
33+
strategy:
34+
matrix:
35+
os: [ubuntu-latest, windows-latest]
36+
python-version: ['3.9', '3.12']
37+
fail-fast: false
38+
steps:
39+
- name: Checkout code
40+
uses: actions/checkout@v4
41+
42+
- name: Set up Python ${{ matrix.python-version }}
43+
uses: actions/setup-python@v5
44+
with:
45+
python-version: ${{ matrix.python-version }}
46+
47+
- name: Install dependencies
48+
run: |
49+
python -m pip install --upgrade pip
50+
pip install -r requirements.txt
51+
52+
- name: Testing
53+
uses: ansys/actions/tests-pytest@v6
54+
timeout-minutes: 12
55+
with:
56+
checkout: false
57+
skip-install: true
58+
pytest-extra-args: "--cov=ansys.allie.flowkit.python --cov-report=term --cov-report=html:.cov/html --cov-report=xml:.cov/coverage.xml"
59+
60+
- name: Upload coverage results (HTML)
61+
uses: actions/upload-artifact@v4
62+
if: (matrix.python-version == env.MAIN_PYTHON_VERSION) && (runner.os == 'Linux')
63+
with:
64+
name: coverage-html
65+
path: .cov/html
66+
retention-days: 7
67+
2968
release:
3069
name: "Release project"
3170
if: github.event_name == 'push' && contains(github.ref, 'refs/tags')

app/fastapi_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ def get_parameters_info(params):
7777
if param.annotation == bytes:
7878
param_info = ParameterInfo(name=param.name, type="bytes")
7979
parameters_info.append(param_info)
80-
elif hasattr(param.annotation, "schema"):
81-
schema = param.annotation.schema()
80+
elif hasattr(param.annotation, "model_json_schema"):
81+
schema = param.annotation.model_json_schema()
8282
param_info = extract_fields_from_schema(schema)
8383
parameters_info.extend(param_info)
8484
else:
@@ -101,7 +101,7 @@ def get_return_type_info(return_type: Type[BaseModel]):
101101
A list of ParameterInfo objects representing the return type fields.
102102
103103
"""
104-
if hasattr(return_type, "schema"):
104+
if hasattr(return_type, "model_json_schema"):
105105
schema = return_type.model_json_schema()
106106
return extract_fields_from_schema(schema)
107107
return [ParameterInfo(name="return", type=str(return_type.__name__))]

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
fastapi==0.112.0
2+
httpx==0.27.0
23
langchain==0.2.12
34
pydantic==2.8.2
45
pymupdf==1.24.9
6+
pytest==8.3.2
7+
pytest-cov==5.0.0
58
python_pptx==1.0.1
69
PyYAML==6.0.1

setup.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from setuptools import find_packages, setup
2+
3+
setup(
4+
name="ansys-allie-flowkit-python",
5+
version="0.1.0",
6+
packages=find_packages(include=["app", "docker", "configs"]),
7+
)

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests module."""

tests/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from unittest.mock import patch
2+
3+
import pytest
4+
5+
# Mock API key for testing
6+
MOCK_API_KEY = "test_api_key"
7+
8+
9+
@pytest.fixture(autouse=True)
10+
def mock_api_key():
11+
"""Mock the API key for testing."""
12+
with patch("app.config.CONFIG.flowkit_python_api_key", MOCK_API_KEY):
13+
yield

tests/test_endpoints_splitter.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import base64
2+
3+
from app.app import app
4+
from app.endpoints.splitter import validate_request
5+
from app.models.splitter import SplitterRequest
6+
from fastapi import HTTPException
7+
from fastapi.testclient import TestClient
8+
import pytest
9+
10+
from tests.conftest import MOCK_API_KEY
11+
12+
# Create a test client
13+
client = TestClient(app)
14+
15+
16+
def encode_file_to_base64(file_path):
17+
"""Encode a file to base64 string."""
18+
with open(file_path, "rb") as file:
19+
return base64.b64encode(file.read()).decode("utf-8")
20+
21+
22+
@pytest.mark.asyncio
23+
async def test_split_ppt():
24+
"""Test splitting text in a PowerPoint document into chunks."""
25+
ppt_content_base64 = encode_file_to_base64("./tests/test_files/test_presentation.pptx")
26+
request_payload = {
27+
"document_content": ppt_content_base64,
28+
"chunk_size": 100,
29+
"chunk_overlap": 10,
30+
}
31+
response = client.post("/splitter/ppt", json=request_payload, headers={"api-key": MOCK_API_KEY})
32+
if response.status_code != 200:
33+
print(f"Response status code: {response.status_code}")
34+
print(f"Response content: {response.json()}")
35+
assert response.status_code == 200
36+
assert "chunks" in response.json()
37+
38+
39+
@pytest.mark.asyncio
40+
async def test_split_py():
41+
"""Test splitting Python code into chunks."""
42+
python_code = """
43+
def hello_world():
44+
print("Hello, world!")
45+
"""
46+
python_code_base64 = base64.b64encode(python_code.encode()).decode("utf-8")
47+
request_payload = {"document_content": python_code_base64, "chunk_size": 50, "chunk_overlap": 5}
48+
response = client.post("/splitter/py", json=request_payload, headers={"api-key": MOCK_API_KEY})
49+
assert response.status_code == 200
50+
assert "chunks" in response.json()
51+
52+
53+
@pytest.mark.asyncio
54+
async def test_split_pdf():
55+
"""Test splitting text in a PDF document into chunks."""
56+
pdf_content_base64 = encode_file_to_base64("./tests/test_files/test_document.pdf")
57+
request_payload = {
58+
"document_content": pdf_content_base64,
59+
"chunk_size": 200,
60+
"chunk_overlap": 20,
61+
}
62+
response = client.post("/splitter/pdf", json=request_payload, headers={"api-key": MOCK_API_KEY})
63+
assert response.status_code == 200
64+
assert "chunks" in response.json()
65+
66+
67+
# Define test cases for validate_request()
68+
validate_request_test_cases = [
69+
# Test case 1: valid request
70+
(
71+
SplitterRequest(
72+
document_content="dGVzdA==", chunk_size=100, chunk_overlap=10 # base64 for "test"
73+
),
74+
MOCK_API_KEY,
75+
None,
76+
),
77+
# Test case: invalid API key
78+
(
79+
SplitterRequest(document_content="dGVzdA==", chunk_size=100, chunk_overlap=10),
80+
"invalid_api_key",
81+
HTTPException(status_code=401, detail="Invalid API key"),
82+
),
83+
# Test case 2: missing document content
84+
(
85+
SplitterRequest(document_content="", chunk_size=100, chunk_overlap=10),
86+
MOCK_API_KEY,
87+
HTTPException(status_code=400, detail="No document content provided"),
88+
),
89+
# Test case 4: invalid chunk size
90+
(
91+
SplitterRequest(document_content="dGVzdA==", chunk_size=0, chunk_overlap=10),
92+
MOCK_API_KEY,
93+
HTTPException(status_code=400, detail="No chunk size provided"),
94+
),
95+
# Test case 5: invalid chunk overlap
96+
(
97+
SplitterRequest(document_content="dGVzdA==", chunk_size=100, chunk_overlap=-1),
98+
MOCK_API_KEY,
99+
HTTPException(status_code=400, detail="Chunk overlap must be greater than or equal to 0"),
100+
),
101+
]
102+
103+
104+
@pytest.mark.parametrize("api_request, api_key, expected_exception", validate_request_test_cases)
105+
def test_validate_request(api_request, api_key, expected_exception):
106+
"""Test the validate_request function with various scenarios."""
107+
if expected_exception:
108+
with pytest.raises(HTTPException) as exc_info:
109+
validate_request(api_request, api_key)
110+
assert exc_info.value.status_code == expected_exception.status_code
111+
assert exc_info.value.detail == expected_exception.detail
112+
else:
113+
try:
114+
validate_request(api_request, api_key)
115+
except HTTPException:
116+
pytest.fail("validate_request() raised HTTPException unexpectedly!")

tests/test_files/test_document.pdf

36.2 KB
Binary file not shown.
36.5 KB
Binary file not shown.

tests/test_list_functions.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from app.app import app
2+
from fastapi.testclient import TestClient
3+
import pytest
4+
5+
# Initialize the test client
6+
client = TestClient(app)
7+
8+
9+
@pytest.mark.asyncio
10+
async def test_list_functions():
11+
"""Test listing available functions."""
12+
# Test splitter results
13+
response = client.get("/", headers={"api-key": "test_api_key"})
14+
assert response.status_code == 200
15+
response_data = response.json()
16+
17+
expected_response_start = [
18+
{
19+
"name": "split_ppt",
20+
"path": "/splitter/ppt",
21+
"inputs": [
22+
{"name": "document_content", "type": "string(binary)"},
23+
{"name": "chunk_size", "type": "integer"},
24+
{"name": "chunk_overlap", "type": "integer"},
25+
],
26+
"outputs": [{"name": "chunks", "type": "array<string>"}],
27+
"definitions": {},
28+
},
29+
{
30+
"name": "split_py",
31+
"path": "/splitter/py",
32+
"inputs": [
33+
{"name": "document_content", "type": "string(binary)"},
34+
{"name": "chunk_size", "type": "integer"},
35+
{"name": "chunk_overlap", "type": "integer"},
36+
],
37+
"outputs": [{"name": "chunks", "type": "array<string>"}],
38+
"definitions": {},
39+
},
40+
{
41+
"name": "split_pdf",
42+
"path": "/splitter/pdf",
43+
"inputs": [
44+
{"name": "document_content", "type": "string(binary)"},
45+
{"name": "chunk_size", "type": "integer"},
46+
{"name": "chunk_overlap", "type": "integer"},
47+
],
48+
"outputs": [{"name": "chunks", "type": "array<string>"}],
49+
"definitions": {},
50+
},
51+
]
52+
53+
assert response_data[:3] == expected_response_start
54+
55+
# Test invalid API key
56+
response = client.get("/", headers={"api-key": "invalid_api_key"})
57+
assert response.status_code == 401
58+
assert response.json() == {"detail": "Invalid API key"}

0 commit comments

Comments
 (0)