Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
47 changes: 47 additions & 0 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,53 @@ 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@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Testing (Linux)
uses: ansys/actions/tests-pytest@v6
timeout-minutes: 12
if: runner.os == 'Linux'
with:
checkout: false
pytest-extra-args: "--cov=ansys.allie.flowkit.python --cov-report=term --cov-report=html:.cov/html --cov-report=xml:.cov/coverage.xml"

- name: Testing (Windows)
uses: ansys/actions/tests-pytest@v6
timeout-minutes: 12
if: runner.os == 'Windows'
with:
checkout: false
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"}
Loading