Skip to content

Commit

Permalink
testing: add unit testing (#11)
Browse files Browse the repository at this point in the history
Co-authored-by: Roberto Pastor Muela <[email protected]>
  • Loading branch information
laurasgkadri98 and RobPasMue authored Aug 7, 2024
1 parent be03927 commit 71a037c
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 3 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,45 @@ jobs:
- name: "Run PyAnsys code style checks"
uses: ansys/actions/code-style@v6

tests:
name: "Tests"
runs-on: ${{ matrix.os }}
needs: [code-style]
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ['3.9', '3.12']
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Testing
uses: ansys/actions/tests-pytest@v6
timeout-minutes: 12
with:
checkout: false
skip-install: true
pytest-extra-args: "--cov=ansys.allie.flowkit.python --cov-report=term --cov-report=html:.cov/html --cov-report=xml:.cov/coverage.xml"

- name: Upload coverage results (HTML)
uses: actions/upload-artifact@v4
if: (matrix.python-version == env.MAIN_PYTHON_VERSION) && (runner.os == 'Linux')
with:
name: coverage-html
path: .cov/html
retention-days: 7

release:
name: "Release project"
if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
Expand Down
6 changes: 3 additions & 3 deletions app/fastapi_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ def get_parameters_info(params):
if param.annotation == bytes:
param_info = ParameterInfo(name=param.name, type="bytes")
parameters_info.append(param_info)
elif hasattr(param.annotation, "schema"):
schema = param.annotation.schema()
elif hasattr(param.annotation, "model_json_schema"):
schema = param.annotation.model_json_schema()
param_info = extract_fields_from_schema(schema)
parameters_info.extend(param_info)
else:
Expand All @@ -101,7 +101,7 @@ def get_return_type_info(return_type: Type[BaseModel]):
A list of ParameterInfo objects representing the return type fields.
"""
if hasattr(return_type, "schema"):
if hasattr(return_type, "model_json_schema"):
schema = return_type.model_json_schema()
return extract_fields_from_schema(schema)
return [ParameterInfo(name="return", type=str(return_type.__name__))]
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
fastapi==0.112.0
httpx==0.27.0
langchain==0.2.12
pydantic==2.8.2
pymupdf==1.24.9
pytest==8.3.2
pytest-cov==5.0.0
python_pptx==1.0.1
PyYAML==6.0.1
7 changes: 7 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from setuptools import find_packages, setup

setup(
name="ansys-allie-flowkit-python",
version="0.1.0",
packages=find_packages(include=["app", "docker", "configs"]),
)
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests module."""
13 changes: 13 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from unittest.mock import patch

import pytest

# Mock API key for testing
MOCK_API_KEY = "test_api_key"


@pytest.fixture(autouse=True)
def mock_api_key():
"""Mock the API key for testing."""
with patch("app.config.CONFIG.flowkit_python_api_key", MOCK_API_KEY):
yield
116 changes: 116 additions & 0 deletions tests/test_endpoints_splitter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import base64

from app.app import app
from app.endpoints.splitter import validate_request
from app.models.splitter import SplitterRequest
from fastapi import HTTPException
from fastapi.testclient import TestClient
import pytest

from tests.conftest import MOCK_API_KEY

# Create a test client
client = TestClient(app)


def encode_file_to_base64(file_path):
"""Encode a file to base64 string."""
with open(file_path, "rb") as file:
return base64.b64encode(file.read()).decode("utf-8")


@pytest.mark.asyncio
async def test_split_ppt():
"""Test splitting text in a PowerPoint document into chunks."""
ppt_content_base64 = encode_file_to_base64("./tests/test_files/test_presentation.pptx")
request_payload = {
"document_content": ppt_content_base64,
"chunk_size": 100,
"chunk_overlap": 10,
}
response = client.post("/splitter/ppt", json=request_payload, headers={"api-key": MOCK_API_KEY})
if response.status_code != 200:
print(f"Response status code: {response.status_code}")
print(f"Response content: {response.json()}")
assert response.status_code == 200
assert "chunks" in response.json()


@pytest.mark.asyncio
async def test_split_py():
"""Test splitting Python code into chunks."""
python_code = """
def hello_world():
print("Hello, world!")
"""
python_code_base64 = base64.b64encode(python_code.encode()).decode("utf-8")
request_payload = {"document_content": python_code_base64, "chunk_size": 50, "chunk_overlap": 5}
response = client.post("/splitter/py", json=request_payload, headers={"api-key": MOCK_API_KEY})
assert response.status_code == 200
assert "chunks" in response.json()


@pytest.mark.asyncio
async def test_split_pdf():
"""Test splitting text in a PDF document into chunks."""
pdf_content_base64 = encode_file_to_base64("./tests/test_files/test_document.pdf")
request_payload = {
"document_content": pdf_content_base64,
"chunk_size": 200,
"chunk_overlap": 20,
}
response = client.post("/splitter/pdf", json=request_payload, headers={"api-key": MOCK_API_KEY})
assert response.status_code == 200
assert "chunks" in response.json()


# Define test cases for validate_request()
validate_request_test_cases = [
# Test case 1: valid request
(
SplitterRequest(
document_content="dGVzdA==", chunk_size=100, chunk_overlap=10 # base64 for "test"
),
MOCK_API_KEY,
None,
),
# Test case: invalid API key
(
SplitterRequest(document_content="dGVzdA==", chunk_size=100, chunk_overlap=10),
"invalid_api_key",
HTTPException(status_code=401, detail="Invalid API key"),
),
# Test case 2: missing document content
(
SplitterRequest(document_content="", chunk_size=100, chunk_overlap=10),
MOCK_API_KEY,
HTTPException(status_code=400, detail="No document content provided"),
),
# Test case 4: invalid chunk size
(
SplitterRequest(document_content="dGVzdA==", chunk_size=0, chunk_overlap=10),
MOCK_API_KEY,
HTTPException(status_code=400, detail="No chunk size provided"),
),
# Test case 5: invalid chunk overlap
(
SplitterRequest(document_content="dGVzdA==", chunk_size=100, chunk_overlap=-1),
MOCK_API_KEY,
HTTPException(status_code=400, detail="Chunk overlap must be greater than or equal to 0"),
),
]


@pytest.mark.parametrize("api_request, api_key, expected_exception", validate_request_test_cases)
def test_validate_request(api_request, api_key, expected_exception):
"""Test the validate_request function with various scenarios."""
if expected_exception:
with pytest.raises(HTTPException) as exc_info:
validate_request(api_request, api_key)
assert exc_info.value.status_code == expected_exception.status_code
assert exc_info.value.detail == expected_exception.detail
else:
try:
validate_request(api_request, api_key)
except HTTPException:
pytest.fail("validate_request() raised HTTPException unexpectedly!")
Binary file added tests/test_files/test_document.pdf
Binary file not shown.
Binary file added tests/test_files/test_presentation.pptx
Binary file not shown.
58 changes: 58 additions & 0 deletions tests/test_list_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from app.app import app
from fastapi.testclient import TestClient
import pytest

# Initialize the test client
client = TestClient(app)


@pytest.mark.asyncio
async def test_list_functions():
"""Test listing available functions."""
# Test splitter results
response = client.get("/", headers={"api-key": "test_api_key"})
assert response.status_code == 200
response_data = response.json()

expected_response_start = [
{
"name": "split_ppt",
"path": "/splitter/ppt",
"inputs": [
{"name": "document_content", "type": "string(binary)"},
{"name": "chunk_size", "type": "integer"},
{"name": "chunk_overlap", "type": "integer"},
],
"outputs": [{"name": "chunks", "type": "array<string>"}],
"definitions": {},
},
{
"name": "split_py",
"path": "/splitter/py",
"inputs": [
{"name": "document_content", "type": "string(binary)"},
{"name": "chunk_size", "type": "integer"},
{"name": "chunk_overlap", "type": "integer"},
],
"outputs": [{"name": "chunks", "type": "array<string>"}],
"definitions": {},
},
{
"name": "split_pdf",
"path": "/splitter/pdf",
"inputs": [
{"name": "document_content", "type": "string(binary)"},
{"name": "chunk_size", "type": "integer"},
{"name": "chunk_overlap", "type": "integer"},
],
"outputs": [{"name": "chunks", "type": "array<string>"}],
"definitions": {},
},
]

assert response_data[:3] == expected_response_start

# Test invalid API key
response = client.get("/", headers={"api-key": "invalid_api_key"})
assert response.status_code == 401
assert response.json() == {"detail": "Invalid API key"}

0 comments on commit 71a037c

Please sign in to comment.