Skip to content
Closed
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
50 changes: 50 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Python Tests

on:
push:
branches:
- '**'
pull_request:
branches:
- '**'

jobs:
test-python-scripts:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- name: Checkout repository
uses: actions/checkout@v4

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

- name: Install chart-visualization dependencies
run: |
cd tests/chart-visualization
pip install -r requirements.txt

- name: Run chart-visualization tests
run: |
cd tests/chart-visualization
python -m unittest test_generate.py -v

- name: Install icon-retrieval dependencies
run: |
cd tests/icon-retrieval
# icon-retrieval has no external dependencies, just verify requirements.txt exists
if [ -f requirements.txt ]; then
echo "requirements.txt found"
fi

- name: Run icon-retrieval tests
run: |
cd tests/icon-retrieval
python -m unittest test_search.py -v
2 changes: 2 additions & 0 deletions tests/chart-visualization/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Python 3.0+ dependencies for chart-visualization skill
requests>=2.31.0
139 changes: 139 additions & 0 deletions tests/chart-visualization/test_generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""
Integration and unit tests for generate.py script

Tests the chart generation functionality including:
- Chart type mapping (unit test)
- Environment variable helpers (unit test)
- Real API integration for chart generation (integration test)
- Command line interface (unit test)
"""

import unittest
import sys
import os
import json
from unittest.mock import patch
from io import StringIO

# Add the scripts directory to the path
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'skills', 'chart-visualization', 'scripts'))
import generate


class TestChartTypeMapping(unittest.TestCase):
"""Test the CHART_TYPE_MAP constant"""

def test_chart_type_map_contains_expected_keys(self):
"""Verify key chart types are in the mapping"""
self.assertIn("generate_line_chart", generate.CHART_TYPE_MAP)
self.assertIn("generate_bar_chart", generate.CHART_TYPE_MAP)
self.assertIn("generate_pie_chart", generate.CHART_TYPE_MAP)

def test_chart_type_map_values_are_strings(self):
"""Verify all mapped values are strings"""
for value in generate.CHART_TYPE_MAP.values():
self.assertIsInstance(value, str)


class TestEnvironmentHelpers(unittest.TestCase):
"""Test environment variable helper functions"""

def test_get_vis_request_server_default(self):
"""Test default VIS_REQUEST_SERVER value"""
with patch.dict(os.environ, {}, clear=True):
result = generate.get_vis_request_server()
self.assertEqual(result, "https://antv-studio.alipay.com/api/gpt-vis")

def test_get_vis_request_server_custom(self):
"""Test custom VIS_REQUEST_SERVER value"""
custom_url = "https://custom.example.com/api"
with patch.dict(os.environ, {"VIS_REQUEST_SERVER": custom_url}):
result = generate.get_vis_request_server()
self.assertEqual(result, custom_url)

def test_get_service_identifier_none(self):
"""Test SERVICE_ID when not set"""
with patch.dict(os.environ, {}, clear=True):
result = generate.get_service_identifier()
self.assertIsNone(result)

def test_get_service_identifier_set(self):
"""Test SERVICE_ID when set"""
service_id = "test-service-123"
with patch.dict(os.environ, {"SERVICE_ID": service_id}):
result = generate.get_service_identifier()
self.assertEqual(result, service_id)


class TestRealAPIIntegration(unittest.TestCase):
"""Integration tests with real API calls"""

def test_generate_chart_url_real_api(self):
"""Test real chart URL generation with API"""
try:
# Test with simple line chart data
result = generate.generate_chart_url("line", {
"data": [
{"year": "2020", "value": 10},
{"year": "2021", "value": 20},
{"year": "2022", "value": 30}
]
})

# Verify we got a URL back
self.assertIsInstance(result, str)
# Most likely the result will be a URL or some identifier
print(f"Generated chart URL: {result}")

except Exception as e:
# If API is down or returns error, we still want to know what happened
print(f"API call failed (this may be expected): {e}")
# Don't fail the test if it's a network issue, just log it
self.skipTest(f"API unavailable or returned error: {e}")


class TestCLIInterface(unittest.TestCase):
"""Test the command line interface"""

def test_main_no_arguments(self):
"""Test main() with no arguments"""
with patch.object(sys, 'argv', ['generate.py']):
with self.assertRaises(SystemExit) as cm:
with patch('builtins.print'):
generate.main()
self.assertEqual(cm.exception.code, 1)

def test_main_with_invalid_json(self):
"""Test main() with invalid JSON input"""
with patch.object(sys, 'argv', ['generate.py', 'invalid json {']):
with self.assertRaises(SystemExit) as cm:
with patch('builtins.print'):
generate.main()
self.assertEqual(cm.exception.code, 1)

def test_main_with_unknown_tool(self):
"""Test main() with unknown tool name"""
spec = json.dumps({"tool": "generate_unknown_chart", "args": {}})

with patch.object(sys, 'argv', ['generate.py', spec]):
with patch('builtins.print') as mock_print:
generate.main()
# Should print error message for unknown tool
printed = [str(call) for call in mock_print.call_args_list]
self.assertTrue(any("Unknown tool" in str(call) for call in printed))

def test_main_with_spec_missing_tool(self):
"""Test main() with spec missing tool field"""
spec = json.dumps({"args": {"data": [1, 2, 3]}})

with patch.object(sys, 'argv', ['generate.py', spec]):
with patch('builtins.print') as mock_print:
generate.main()
# Should print error about missing tool
printed = [str(call) for call in mock_print.call_args_list]
self.assertTrue(any("tool" in str(call).lower() for call in printed))


if __name__ == '__main__':
unittest.main()
3 changes: 3 additions & 0 deletions tests/icon-retrieval/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Python 3.0+ dependencies for icon-retrieval skill
# This script uses only standard library modules (urllib, json, ssl, os, sys)
# No external dependencies required
121 changes: 121 additions & 0 deletions tests/icon-retrieval/test_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""
Integration and unit tests for search.py script

Tests the icon search functionality including:
- Real API integration for icon search (integration test)
- Command line argument parsing (unit test)
- SSL configuration (unit test)
"""

import unittest
import sys
import os
import json
from unittest.mock import patch
from io import StringIO
import ssl

# Add the scripts directory to the path
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'skills', 'icon-retrieval', 'scripts'))
import search


class TestRealAPIIntegration(unittest.TestCase):
"""Integration tests with real API calls"""

def test_search_icons_real_api(self):
"""Test real icon search with API"""
try:
# Search for a common icon with small topK
results = search.search_icons('document', 2)

# Verify we got results back
self.assertIsInstance(results, list)

# If we got results, verify the structure
if len(results) > 0:
self.assertIn('url', results[0])
self.assertIn('svg', results[0])
self.assertIsInstance(results[0]['url'], str)
self.assertIsInstance(results[0]['svg'], str)
# SVG should contain SVG tag
self.assertIn('svg', results[0]['svg'].lower())
print(f"Successfully retrieved {len(results)} icon(s)")
else:
print("No icons found (may be expected)")

except Exception as e:
# If API is down or returns error, we still want to know what happened
print(f"API call failed (this may be expected): {e}")
# Don't fail the test if it's a network issue, just log it
self.skipTest(f"API unavailable or returned error: {e}")

def test_search_icons_custom_top_k(self):
"""Test custom topK parameter with real API"""
try:
# Test with different topK value
results = search.search_icons('arrow', 3)

self.assertIsInstance(results, list)
# Should return at most 3 results
self.assertLessEqual(len(results), 3)
print(f"Retrieved {len(results)} icon(s) with topK=3")

except Exception as e:
print(f"API call failed: {e}")
self.skipTest(f"API unavailable: {e}")


class TestCLIInterface(unittest.TestCase):
"""Test the command line interface"""

def test_main_no_arguments(self):
"""Test main() with no arguments"""
with patch.object(sys, 'argv', ['search.py']):
with self.assertRaises(SystemExit) as cm:
with patch('sys.stderr', new_callable=StringIO):
search.main()
self.assertEqual(cm.exception.code, 1)

def test_main_with_invalid_topk(self):
"""Test main() with invalid topK value"""
with patch.object(sys, 'argv', ['search.py', 'document', '0']):
with self.assertRaises(SystemExit) as cm:
with patch('sys.stderr', new_callable=StringIO):
search.main()
self.assertEqual(cm.exception.code, 1)

def test_main_with_negative_topk(self):
"""Test main() with negative topK value"""
with patch.object(sys, 'argv', ['search.py', 'document', '-5']):
with self.assertRaises(SystemExit) as cm:
with patch('sys.stderr', new_callable=StringIO):
search.main()
self.assertEqual(cm.exception.code, 1)


class TestSSLConfiguration(unittest.TestCase):
"""Test SSL context configuration"""

@patch.dict(os.environ, {}, clear=True)
def test_default_ssl_verification_enabled(self):
"""Test SSL verification is enabled by default"""
# This test verifies the behavior described in the code comments
# In actual usage, SSL verification should be enabled by default
self.assertNotIn('PYTHONHTTPSVERIFY', os.environ)
self.assertNotIn('SSL_VERIFY', os.environ)

@patch.dict(os.environ, {'PYTHONHTTPSVERIFY': '1'})
def test_ssl_verification_explicitly_enabled(self):
"""Test SSL verification when explicitly enabled"""
self.assertEqual(os.environ.get('PYTHONHTTPSVERIFY'), '1')

@patch.dict(os.environ, {'SSL_VERIFY': 'true'})
def test_ssl_verify_true(self):
"""Test SSL_VERIFY=true environment variable"""
self.assertEqual(os.environ.get('SSL_VERIFY'), 'true')


if __name__ == '__main__':
unittest.main()