@hybridlabor-api/bdb-hardware-pcb 0.1.0

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.
Files changed (192) hide show
  1. package/README.md +116 -0
  2. package/config/mcp/antigravity.json +22 -0
  3. package/config/mcp/claude.json +22 -0
  4. package/config/mcp/codex.toml +25 -0
  5. package/docs/adr/ADR-001-KICAD-OPENSCAD-MCP-STRATEGY.md +334 -0
  6. package/docs/review_documentation.md +121 -0
  7. package/installer.js +358 -0
  8. package/mcp_servers/kicad-mcp-server/.env.example +22 -0
  9. package/mcp_servers/kicad-mcp-server/.github/workflows/ci.yml +36 -0
  10. package/mcp_servers/kicad-mcp-server/CLAUDE.md +487 -0
  11. package/mcp_servers/kicad-mcp-server/README.md +316 -0
  12. package/mcp_servers/kicad-mcp-server/docs/DEVICE_TREE.md +416 -0
  13. package/mcp_servers/kicad-mcp-server/docs/INSTALLATION.md +332 -0
  14. package/mcp_servers/kicad-mcp-server/docs/PIN_ANALYSIS.md +332 -0
  15. package/mcp_servers/kicad-mcp-server/docs/README.md +240 -0
  16. package/mcp_servers/kicad-mcp-server/docs/TESTING.md +613 -0
  17. package/mcp_servers/kicad-mcp-server/docs/VALIDATION.md +268 -0
  18. package/mcp_servers/kicad-mcp-server/pyproject.toml +96 -0
  19. package/mcp_servers/kicad-mcp-server/requirements-dev.txt +16 -0
  20. package/mcp_servers/kicad-mcp-server/requirements-test.txt +24 -0
  21. package/mcp_servers/kicad-mcp-server/requirements.txt +13 -0
  22. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__init__.py +3 -0
  23. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__main__.py +17 -0
  24. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/config.py +46 -0
  25. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/__init__.py +1 -0
  26. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/types.py +87 -0
  27. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/__init__.py +1 -0
  28. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/netlist_parser.py +234 -0
  29. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser.py +375 -0
  30. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser_kicad.py +327 -0
  31. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/schematic_parser.py +902 -0
  32. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/server.py +71 -0
  33. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/__init__.py +1 -0
  34. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/arduino/connectivity_test.cpp.j2 +189 -0
  35. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/atmega.dts.j2 +77 -0
  36. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/esp32.dts.j2 +77 -0
  37. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/nrf52.dts.j2 +77 -0
  38. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/stm32f4.dts.j2 +89 -0
  39. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/esp_idf/test_suite.c.j2 +340 -0
  40. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/pytest/test_connectivity.py.j2 +147 -0
  41. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/st_hal/hal_test.c.j2 +313 -0
  42. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_gpio_test.py.j2 +99 -0
  43. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_i2c_test.py.j2 +117 -0
  44. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_pinmux_test.py.j2 +43 -0
  45. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_spi_test.py.j2 +94 -0
  46. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_gpio_test.c.j2 +113 -0
  47. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_i2c_test.c.j2 +101 -0
  48. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_spi_test.c.j2 +94 -0
  49. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/unittest/test_schematic.py.j2 +172 -0
  50. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/__init__.py +36 -0
  51. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/device_tree.py +1187 -0
  52. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/hierarchical_analysis.py +211 -0
  53. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/netlist.py +320 -0
  54. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/parts_registry.py +142 -0
  55. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb.py +955 -0
  56. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb_layout.py +308 -0
  57. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pin_analysis.py +765 -0
  58. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/project.py +196 -0
  59. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic.py +319 -0
  60. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_editor.py +674 -0
  61. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_search.py +158 -0
  62. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/validation.py +866 -0
  63. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/visualization.py +225 -0
  64. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/__init__.py +1 -0
  65. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/file_handlers.py +65 -0
  66. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_cli.py +103 -0
  67. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_version.py +103 -0
  68. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/parts_registry.py +197 -0
  69. package/mcp_servers/kicad-mcp-server/tests/__init__.py +1 -0
  70. package/mcp_servers/kicad-mcp-server/tests/examples/ESP32S3_TEST.md +219 -0
  71. package/mcp_servers/kicad-mcp-server/tests/fixtures/README.md +65 -0
  72. package/mcp_servers/kicad-mcp-server/tests/fixtures/__init__.py +1 -0
  73. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_pcb.kicad_pcb +177 -0
  74. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_schematic.kicad_sch +145 -0
  75. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/child.kicad_sch +24 -0
  76. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/root.kicad_sch +38 -0
  77. package/mcp_servers/kicad-mcp-server/tests/test_tools/__init__.py +1 -0
  78. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_hierarchical_labels.py +195 -0
  79. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_kicad_cli.py +116 -0
  80. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_netlist_cache_path.py +29 -0
  81. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic.py +189 -0
  82. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic_hierarchy.py +69 -0
  83. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_visualization.py +136 -0
  84. package/mcp_servers/kicad-mcp-server/uv.lock +2873 -0
  85. package/mcp_servers/openscad-mcp-server/.dockerignore +9 -0
  86. package/mcp_servers/openscad-mcp-server/.github/workflows/test.yml +40 -0
  87. package/mcp_servers/openscad-mcp-server/Dockerfile +29 -0
  88. package/mcp_servers/openscad-mcp-server/LICENSE +21 -0
  89. package/mcp_servers/openscad-mcp-server/README.md +154 -0
  90. package/mcp_servers/openscad-mcp-server/docs/audit.md +56 -0
  91. package/mcp_servers/openscad-mcp-server/docs/docker.md +66 -0
  92. package/mcp_servers/openscad-mcp-server/docs/issue-followup.md +19 -0
  93. package/mcp_servers/openscad-mcp-server/docs/jetson.md +17 -0
  94. package/mcp_servers/openscad-mcp-server/glama.json +4 -0
  95. package/mcp_servers/openscad-mcp-server/legacy/README.md +15 -0
  96. package/mcp_servers/openscad-mcp-server/legacy/README.original.md +294 -0
  97. package/mcp_servers/openscad-mcp-server/legacy/implementation_plan.md +100 -0
  98. package/mcp_servers/openscad-mcp-server/legacy/old/download_sam2_checkpoint.py +115 -0
  99. package/mcp_servers/openscad-mcp-server/legacy/old/src/ai/sam_segmentation.py +209 -0
  100. package/mcp_servers/openscad-mcp-server/legacy/old/src/models/threestudio_generator.py +231 -0
  101. package/mcp_servers/openscad-mcp-server/legacy/old/src/workflow/image_to_model_pipeline.py +260 -0
  102. package/mcp_servers/openscad-mcp-server/legacy/old/test_sam2_segmentation.py +96 -0
  103. package/mcp_servers/openscad-mcp-server/legacy/requirements.txt +57 -0
  104. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/README.md +39 -0
  105. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/ai-driven-code-generation.md +122 -0
  106. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/export-formats.md +76 -0
  107. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/ai/ai_service.py.md +51 -0
  108. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/main.py.md +63 -0
  109. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/models/code_generator.py.md +63 -0
  110. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/nlp/parameter_extractor.py.md +63 -0
  111. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/ai/natural-language-processing.md +78 -0
  112. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/nlp/parameter-extraction.md +173 -0
  113. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/export-formats.md +91 -0
  114. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/openscad-basics.md +66 -0
  115. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/primitive-testing.md +79 -0
  116. package/mcp_servers/openscad-mcp-server/legacy/src/__init__.py +0 -0
  117. package/mcp_servers/openscad-mcp-server/legacy/src/ai/ai_service.py +257 -0
  118. package/mcp_servers/openscad-mcp-server/legacy/src/ai/gemini_api.py +161 -0
  119. package/mcp_servers/openscad-mcp-server/legacy/src/ai/venice_api.py +203 -0
  120. package/mcp_servers/openscad-mcp-server/legacy/src/config.py +121 -0
  121. package/mcp_servers/openscad-mcp-server/legacy/src/main.py +1456 -0
  122. package/mcp_servers/openscad-mcp-server/legacy/src/main.py.new +404 -0
  123. package/mcp_servers/openscad-mcp-server/legacy/src/main_remote.py +401 -0
  124. package/mcp_servers/openscad-mcp-server/legacy/src/models/__init__.py +0 -0
  125. package/mcp_servers/openscad-mcp-server/legacy/src/models/code_generator.py +321 -0
  126. package/mcp_servers/openscad-mcp-server/legacy/src/models/cuda_mvs.py +209 -0
  127. package/mcp_servers/openscad-mcp-server/legacy/src/models/scad_templates/basic_shapes.scad +144 -0
  128. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/__init__.py +0 -0
  129. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/parameter_extractor.py +388 -0
  130. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/__init__.py +0 -0
  131. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/wrapper.py +418 -0
  132. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/__init__.py +1 -0
  133. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/printer_discovery.py +471 -0
  134. package/mcp_servers/openscad-mcp-server/legacy/src/remote/connection_manager.py +537 -0
  135. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_client.py +435 -0
  136. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_server.py +787 -0
  137. package/mcp_servers/openscad-mcp-server/legacy/src/remote/error_handling.py +415 -0
  138. package/mcp_servers/openscad-mcp-server/legacy/src/testing/__init__.py +0 -0
  139. package/mcp_servers/openscad-mcp-server/legacy/src/testing/primitive_tester.py +203 -0
  140. package/mcp_servers/openscad-mcp-server/legacy/src/testing/test_primitives.py +98 -0
  141. package/mcp_servers/openscad-mcp-server/legacy/src/utils/__init__.py +1 -0
  142. package/mcp_servers/openscad-mcp-server/legacy/src/utils/cad_exporter.py +241 -0
  143. package/mcp_servers/openscad-mcp-server/legacy/src/utils/format_validator.py +206 -0
  144. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_exporter.py +140 -0
  145. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_repair.py +91 -0
  146. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_validator.py +123 -0
  147. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/__init__.py +0 -0
  148. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/headless_renderer.py +52 -0
  149. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/renderer.py +177 -0
  150. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/web_interface.py +639 -0
  151. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/image_approval.py +148 -0
  152. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/multi_view_to_model_pipeline.py +338 -0
  153. package/mcp_servers/openscad-mcp-server/legacy/test_complete_workflow.py +374 -0
  154. package/mcp_servers/openscad-mcp-server/legacy/test_cuda_mvs.py +191 -0
  155. package/mcp_servers/openscad-mcp-server/legacy/test_gemini_api.py +168 -0
  156. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval.py +192 -0
  157. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval_workflow.py +251 -0
  158. package/mcp_servers/openscad-mcp-server/legacy/test_image_to_model_pipeline.py +145 -0
  159. package/mcp_servers/openscad-mcp-server/legacy/test_model_selection.py +41 -0
  160. package/mcp_servers/openscad-mcp-server/legacy/test_multi_view_pipeline.py +290 -0
  161. package/mcp_servers/openscad-mcp-server/legacy/test_primitives.sh +13 -0
  162. package/mcp_servers/openscad-mcp-server/legacy/test_rabbit_direct.py +71 -0
  163. package/mcp_servers/openscad-mcp-server/legacy/test_remote_cuda_mvs.py +283 -0
  164. package/mcp_servers/openscad-mcp-server/legacy/test_venice_example.py +69 -0
  165. package/mcp_servers/openscad-mcp-server/pyproject.toml +33 -0
  166. package/mcp_servers/openscad-mcp-server/requirements.txt +2 -0
  167. package/mcp_servers/openscad-mcp-server/scad/simple_cube.scad +2 -0
  168. package/mcp_servers/openscad-mcp-server/scripts/test_docker.py +220 -0
  169. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__init__.py +3 -0
  170. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__main__.py +3 -0
  171. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/engine.py +94 -0
  172. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/geometry.py +242 -0
  173. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/server.py +245 -0
  174. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/service.py +190 -0
  175. package/mcp_servers/openscad-mcp-server/tests/conftest.py +17 -0
  176. package/mcp_servers/openscad-mcp-server/tests/test_engine.py +37 -0
  177. package/mcp_servers/openscad-mcp-server/tests/test_geometry.py +106 -0
  178. package/mcp_servers/openscad-mcp-server/tests/test_integration.py +172 -0
  179. package/mcp_servers/openscad-mcp-server/tests/test_transports.py +314 -0
  180. package/mcp_servers/openscad-mcp-server/uv.lock +1123 -0
  181. package/package.json +44 -0
  182. package/scripts/install_mcps.sh +86 -0
  183. package/scripts/openscad_wrapper.sh +62 -0
  184. package/scripts/run_kicad_mcp.sh +25 -0
  185. package/scripts/run_openscad_mcp.sh +41 -0
  186. package/scripts/test_mcp_connection.py +626 -0
  187. package/scripts/test_mcp_connection.sh +168 -0
  188. package/skills/code-first-hardware-design/SKILL.md +260 -0
  189. package/skills/pcb-constraint-definition/SKILL.md +236 -0
  190. package/skills/pcb-layout-routing-automation/SKILL.md +153 -0
  191. package/skills/pcb-validation-dfm-signoff/SKILL.md +194 -0
  192. package/skills/schematic-datasheet-analysis/SKILL.md +202 -0
@@ -0,0 +1,168 @@
1
+ """
2
+ Test script for Google Gemini API integration.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import logging
8
+ import unittest
9
+ from unittest.mock import patch, MagicMock
10
+ from pathlib import Path
11
+
12
+ # Add the src directory to the path
13
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
14
+
15
+ from src.ai.gemini_api import GeminiImageGenerator
16
+
17
+ # Configure logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ class TestGeminiAPI(unittest.TestCase):
22
+ """
23
+ Test cases for Google Gemini API integration.
24
+ """
25
+
26
+ def setUp(self):
27
+ """
28
+ Set up test environment.
29
+ """
30
+ # Create a test output directory
31
+ self.test_output_dir = "output/test_gemini"
32
+ os.makedirs(self.test_output_dir, exist_ok=True)
33
+
34
+ # Mock API key
35
+ self.api_key = "test_api_key"
36
+
37
+ # Create the generator with the mock API key
38
+ self.gemini_generator = GeminiImageGenerator(
39
+ api_key=self.api_key,
40
+ output_dir=self.test_output_dir
41
+ )
42
+
43
+ @patch('requests.post')
44
+ def test_generate_image(self, mock_post):
45
+ """
46
+ Test generating a single image with Gemini API.
47
+ """
48
+ # Mock response
49
+ mock_response = MagicMock()
50
+ mock_response.status_code = 200
51
+ mock_response.json.return_value = {
52
+ "candidates": [
53
+ {
54
+ "content": {
55
+ "parts": [
56
+ {
57
+ "text": "Generated image description"
58
+ },
59
+ {
60
+ "inlineData": {
61
+ "mimeType": "image/png",
62
+ "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
63
+ }
64
+ }
65
+ ]
66
+ }
67
+ }
68
+ ]
69
+ }
70
+ mock_post.return_value = mock_response
71
+
72
+ # Test parameters
73
+ prompt = "A low-poly rabbit with black background"
74
+ model = "gemini-2.0-flash-exp-image-generation"
75
+
76
+ # Call the method
77
+ result = self.gemini_generator.generate_image(prompt, model)
78
+
79
+ # Verify the result
80
+ self.assertIsNotNone(result)
81
+ self.assertEqual(result["prompt"], prompt)
82
+ self.assertEqual(result["model"], model)
83
+ self.assertTrue("local_path" in result)
84
+ self.assertTrue(os.path.exists(result["local_path"]))
85
+
86
+ # Verify the API call
87
+ mock_post.assert_called_once()
88
+ args, kwargs = mock_post.call_args
89
+ self.assertTrue("generativelanguage.googleapis.com" in args[0])
90
+ self.assertEqual(kwargs["headers"]["x-goog-api-key"], self.api_key)
91
+ self.assertTrue("prompt" in str(kwargs["json"]))
92
+
93
+ @patch('requests.post')
94
+ def test_generate_multiple_views(self, mock_post):
95
+ """
96
+ Test generating multiple views of an object with Gemini API.
97
+ """
98
+ # Mock response
99
+ mock_response = MagicMock()
100
+ mock_response.status_code = 200
101
+ mock_response.json.return_value = {
102
+ "candidates": [
103
+ {
104
+ "content": {
105
+ "parts": [
106
+ {
107
+ "text": "Generated image description"
108
+ },
109
+ {
110
+ "inlineData": {
111
+ "mimeType": "image/png",
112
+ "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
113
+ }
114
+ }
115
+ ]
116
+ }
117
+ }
118
+ ]
119
+ }
120
+ mock_post.return_value = mock_response
121
+
122
+ # Test parameters
123
+ prompt = "A low-poly rabbit"
124
+ num_views = 3
125
+
126
+ # Call the method
127
+ results = self.gemini_generator.generate_multiple_views(prompt, num_views)
128
+
129
+ # Verify the results
130
+ self.assertEqual(len(results), num_views)
131
+ for i, result in enumerate(results):
132
+ self.assertTrue("view_direction" in result)
133
+ self.assertEqual(result["view_index"], i + 1)
134
+ self.assertTrue("local_path" in result)
135
+ self.assertTrue(os.path.exists(result["local_path"]))
136
+
137
+ # Verify the API calls
138
+ self.assertEqual(mock_post.call_count, num_views)
139
+
140
+ @patch('requests.post')
141
+ def test_error_handling(self, mock_post):
142
+ """
143
+ Test error handling in the Gemini API client.
144
+ """
145
+ # Mock error response
146
+ mock_response = MagicMock()
147
+ mock_response.status_code = 400
148
+ mock_response.raise_for_status.side_effect = Exception("API Error")
149
+ mock_post.return_value = mock_response
150
+
151
+ # Test parameters
152
+ prompt = "A low-poly rabbit"
153
+
154
+ # Call the method and expect an exception
155
+ with self.assertRaises(Exception):
156
+ self.gemini_generator.generate_image(prompt)
157
+
158
+ def tearDown(self):
159
+ """
160
+ Clean up after tests.
161
+ """
162
+ # Clean up test output directory
163
+ import shutil
164
+ if os.path.exists(self.test_output_dir):
165
+ shutil.rmtree(self.test_output_dir)
166
+
167
+ if __name__ == "__main__":
168
+ unittest.main()
@@ -0,0 +1,192 @@
1
+ """
2
+ Test script for image approval tool.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import logging
8
+ import unittest
9
+ from unittest.mock import patch, MagicMock
10
+ from pathlib import Path
11
+
12
+ # Add the src directory to the path
13
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
14
+
15
+ from src.workflow.image_approval import ImageApprovalTool
16
+
17
+ # Configure logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ class TestImageApproval(unittest.TestCase):
22
+ """
23
+ Test cases for image approval tool.
24
+ """
25
+
26
+ def setUp(self):
27
+ """
28
+ Set up test environment.
29
+ """
30
+ # Create a test output directory
31
+ self.test_output_dir = "output/test_approval"
32
+ os.makedirs(self.test_output_dir, exist_ok=True)
33
+
34
+ # Create test images directory
35
+ self.test_images_dir = "output/test_approval/images"
36
+ os.makedirs(self.test_images_dir, exist_ok=True)
37
+
38
+ # Create test images
39
+ self.test_images = []
40
+ for i in range(3):
41
+ image_path = os.path.join(self.test_images_dir, f"view_{i}.png")
42
+ with open(image_path, "w") as f:
43
+ f.write(f"Mock image {i}")
44
+ self.test_images.append(image_path)
45
+
46
+ # Create the approval tool
47
+ self.approval_tool = ImageApprovalTool(
48
+ output_dir=os.path.join(self.test_output_dir, "approved")
49
+ )
50
+
51
+ def test_present_image_for_approval(self):
52
+ """
53
+ Test presenting an image for approval.
54
+ """
55
+ # Test parameters
56
+ image_path = self.test_images[0]
57
+ metadata = {
58
+ "prompt": "A test image",
59
+ "view_direction": "front view",
60
+ "view_index": 1
61
+ }
62
+
63
+ # Call the method
64
+ result = self.approval_tool.present_image_for_approval(image_path, metadata)
65
+
66
+ # Verify the result
67
+ self.assertIsNotNone(result)
68
+ self.assertTrue("approval_id" in result)
69
+ self.assertEqual(result["image_path"], image_path)
70
+ self.assertTrue("image_url" in result)
71
+ self.assertEqual(result["metadata"], metadata)
72
+
73
+ def test_process_approval_approved(self):
74
+ """
75
+ Test processing an approved image.
76
+ """
77
+ # Test parameters
78
+ image_path = self.test_images[0]
79
+ approval_id = "test_approval_1"
80
+
81
+ # Call the method
82
+ result = self.approval_tool.process_approval(approval_id, True, image_path)
83
+
84
+ # Verify the result
85
+ self.assertIsNotNone(result)
86
+ self.assertEqual(result["approval_id"], approval_id)
87
+ self.assertTrue(result["approved"])
88
+ self.assertEqual(result["original_path"], image_path)
89
+ self.assertTrue("approved_path" in result)
90
+
91
+ # Verify the file was copied
92
+ self.assertTrue(os.path.exists(result["approved_path"]))
93
+
94
+ def test_process_approval_denied(self):
95
+ """
96
+ Test processing a denied image.
97
+ """
98
+ # Test parameters
99
+ image_path = self.test_images[1]
100
+ approval_id = "test_approval_2"
101
+
102
+ # Call the method
103
+ result = self.approval_tool.process_approval(approval_id, False, image_path)
104
+
105
+ # Verify the result
106
+ self.assertIsNotNone(result)
107
+ self.assertEqual(result["approval_id"], approval_id)
108
+ self.assertFalse(result["approved"])
109
+ self.assertEqual(result["original_path"], image_path)
110
+ self.assertFalse("approved_path" in result)
111
+
112
+ def test_get_approved_images(self):
113
+ """
114
+ Test getting approved images.
115
+ """
116
+ # Approve some images
117
+ for i, image_path in enumerate(self.test_images):
118
+ self.approval_tool.process_approval(f"test_approval_{i}", True, image_path)
119
+
120
+ # Call the method
121
+ approved_images = self.approval_tool.get_approved_images()
122
+
123
+ # Verify the result
124
+ self.assertEqual(len(approved_images), len(self.test_images))
125
+
126
+ def test_get_approval_status(self):
127
+ """
128
+ Test getting approval status.
129
+ """
130
+ # Approve an image
131
+ approval_id = "test_approval_status"
132
+ self.approval_tool.process_approval(approval_id, True, self.test_images[0])
133
+
134
+ # Call the method
135
+ status = self.approval_tool.get_approval_status(approval_id)
136
+
137
+ # Verify the result
138
+ self.assertIsNotNone(status)
139
+ self.assertEqual(status["approval_id"], approval_id)
140
+ self.assertTrue(status["approved"])
141
+ self.assertTrue("approved_path" in status)
142
+
143
+ # Test with non-existent approval ID
144
+ status = self.approval_tool.get_approval_status("non_existent")
145
+ self.assertIsNotNone(status)
146
+ self.assertEqual(status["approval_id"], "non_existent")
147
+ self.assertFalse(status["approved"])
148
+
149
+ def test_batch_process_approvals(self):
150
+ """
151
+ Test batch processing of approvals.
152
+ """
153
+ # Test parameters
154
+ approvals = [
155
+ {
156
+ "approval_id": "batch_1",
157
+ "approved": True,
158
+ "image_path": self.test_images[0]
159
+ },
160
+ {
161
+ "approval_id": "batch_2",
162
+ "approved": False,
163
+ "image_path": self.test_images[1]
164
+ },
165
+ {
166
+ "approval_id": "batch_3",
167
+ "approved": True,
168
+ "image_path": self.test_images[2]
169
+ }
170
+ ]
171
+
172
+ # Call the method
173
+ results = self.approval_tool.batch_process_approvals(approvals)
174
+
175
+ # Verify the results
176
+ self.assertEqual(len(results), len(approvals))
177
+
178
+ # Check approved images
179
+ approved_images = self.approval_tool.get_approved_images()
180
+ self.assertEqual(len(approved_images), 2) # Two images were approved
181
+
182
+ def tearDown(self):
183
+ """
184
+ Clean up after tests.
185
+ """
186
+ # Clean up test output directory
187
+ import shutil
188
+ if os.path.exists(self.test_output_dir):
189
+ shutil.rmtree(self.test_output_dir)
190
+
191
+ if __name__ == "__main__":
192
+ unittest.main()
@@ -0,0 +1,251 @@
1
+ """
2
+ Test script for the image approval workflow.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import json
8
+ import logging
9
+ import unittest
10
+ from unittest.mock import patch, MagicMock
11
+ from pathlib import Path
12
+
13
+ # Add the src directory to the path
14
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
15
+
16
+ from src.workflow.image_approval import ImageApprovalManager
17
+ from src.config import IMAGE_APPROVAL
18
+
19
+ # Configure logging
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+
23
+ class TestImageApprovalWorkflow(unittest.TestCase):
24
+ """
25
+ Test cases for the image approval workflow.
26
+ """
27
+
28
+ def setUp(self):
29
+ """
30
+ Set up test environment.
31
+ """
32
+ # Create test output directories
33
+ self.test_output_dir = "output/test_image_approval"
34
+ self.test_approved_dir = os.path.join(self.test_output_dir, "approved")
35
+ os.makedirs(self.test_output_dir, exist_ok=True)
36
+ os.makedirs(self.test_approved_dir, exist_ok=True)
37
+
38
+ # Create the approval manager
39
+ self.approval_manager = ImageApprovalManager(
40
+ output_dir=self.test_output_dir,
41
+ approved_dir=self.test_approved_dir,
42
+ min_approved_images=3,
43
+ auto_approve=False
44
+ )
45
+
46
+ # Create test images
47
+ self.test_images = []
48
+ for i in range(5):
49
+ image_path = os.path.join(self.test_output_dir, f"test_image_{i}.png")
50
+ with open(image_path, "w") as f:
51
+ f.write(f"test image data {i}")
52
+ self.test_images.append({
53
+ "id": f"image_{i}",
54
+ "local_path": image_path,
55
+ "view_index": i,
56
+ "view_direction": f"view_{i}"
57
+ })
58
+
59
+ def test_add_images(self):
60
+ """
61
+ Test adding images to the approval manager.
62
+ """
63
+ # Add images
64
+ self.approval_manager.add_images(self.test_images)
65
+
66
+ # Verify images were added
67
+ self.assertEqual(len(self.approval_manager.images), 5)
68
+ self.assertEqual(len(self.approval_manager.pending_images), 5)
69
+ self.assertEqual(len(self.approval_manager.approved_images), 0)
70
+ self.assertEqual(len(self.approval_manager.rejected_images), 0)
71
+
72
+ def test_approve_image(self):
73
+ """
74
+ Test approving an image.
75
+ """
76
+ # Add images
77
+ self.approval_manager.add_images(self.test_images)
78
+
79
+ # Approve an image
80
+ result = self.approval_manager.approve_image("image_0")
81
+
82
+ # Verify the result
83
+ self.assertTrue(result["success"])
84
+ self.assertEqual(result["image_id"], "image_0")
85
+ self.assertEqual(result["status"], "approved")
86
+
87
+ # Verify the image was moved to approved
88
+ self.assertEqual(len(self.approval_manager.pending_images), 4)
89
+ self.assertEqual(len(self.approval_manager.approved_images), 1)
90
+ self.assertEqual(len(self.approval_manager.rejected_images), 0)
91
+
92
+ # Verify the image was copied to the approved directory
93
+ approved_path = os.path.join(self.test_approved_dir, "image_0.png")
94
+ self.assertTrue(os.path.exists(approved_path))
95
+
96
+ def test_reject_image(self):
97
+ """
98
+ Test rejecting an image.
99
+ """
100
+ # Add images
101
+ self.approval_manager.add_images(self.test_images)
102
+
103
+ # Reject an image
104
+ result = self.approval_manager.reject_image("image_1")
105
+
106
+ # Verify the result
107
+ self.assertTrue(result["success"])
108
+ self.assertEqual(result["image_id"], "image_1")
109
+ self.assertEqual(result["status"], "rejected")
110
+
111
+ # Verify the image was moved to rejected
112
+ self.assertEqual(len(self.approval_manager.pending_images), 4)
113
+ self.assertEqual(len(self.approval_manager.approved_images), 0)
114
+ self.assertEqual(len(self.approval_manager.rejected_images), 1)
115
+
116
+ def test_get_approval_status(self):
117
+ """
118
+ Test getting the approval status.
119
+ """
120
+ # Add images
121
+ self.approval_manager.add_images(self.test_images)
122
+
123
+ # Approve some images
124
+ self.approval_manager.approve_image("image_0")
125
+ self.approval_manager.approve_image("image_1")
126
+ self.approval_manager.approve_image("image_2")
127
+
128
+ # Reject an image
129
+ self.approval_manager.reject_image("image_3")
130
+
131
+ # Get the status
132
+ status = self.approval_manager.get_status()
133
+
134
+ # Verify the status
135
+ self.assertEqual(status["total_images"], 5)
136
+ self.assertEqual(status["pending_count"], 1)
137
+ self.assertEqual(status["approved_count"], 3)
138
+ self.assertEqual(status["rejected_count"], 1)
139
+ self.assertTrue(status["has_minimum_approved"])
140
+ self.assertEqual(len(status["approved_images"]), 3)
141
+ self.assertEqual(len(status["pending_images"]), 1)
142
+ self.assertEqual(len(status["rejected_images"]), 1)
143
+
144
+ def test_get_approved_images(self):
145
+ """
146
+ Test getting approved images.
147
+ """
148
+ # Add images
149
+ self.approval_manager.add_images(self.test_images)
150
+
151
+ # Approve some images
152
+ self.approval_manager.approve_image("image_0")
153
+ self.approval_manager.approve_image("image_2")
154
+ self.approval_manager.approve_image("image_4")
155
+
156
+ # Get approved images
157
+ approved = self.approval_manager.get_approved_images()
158
+
159
+ # Verify approved images
160
+ self.assertEqual(len(approved), 3)
161
+ self.assertEqual(approved[0]["id"], "image_0")
162
+ self.assertEqual(approved[1]["id"], "image_2")
163
+ self.assertEqual(approved[2]["id"], "image_4")
164
+
165
+ def test_auto_approve(self):
166
+ """
167
+ Test auto-approval mode.
168
+ """
169
+ # Create an auto-approve manager
170
+ auto_manager = ImageApprovalManager(
171
+ output_dir=self.test_output_dir,
172
+ approved_dir=self.test_approved_dir,
173
+ min_approved_images=3,
174
+ auto_approve=True
175
+ )
176
+
177
+ # Add images
178
+ auto_manager.add_images(self.test_images)
179
+
180
+ # Verify all images were auto-approved
181
+ self.assertEqual(len(auto_manager.pending_images), 0)
182
+ self.assertEqual(len(auto_manager.approved_images), 5)
183
+ self.assertEqual(len(auto_manager.rejected_images), 0)
184
+
185
+ def test_has_minimum_approved(self):
186
+ """
187
+ Test checking if minimum approved images are met.
188
+ """
189
+ # Add images
190
+ self.approval_manager.add_images(self.test_images)
191
+
192
+ # Initially should not have minimum
193
+ self.assertFalse(self.approval_manager.has_minimum_approved())
194
+
195
+ # Approve two images
196
+ self.approval_manager.approve_image("image_0")
197
+ self.approval_manager.approve_image("image_1")
198
+
199
+ # Still should not have minimum
200
+ self.assertFalse(self.approval_manager.has_minimum_approved())
201
+
202
+ # Approve one more image
203
+ self.approval_manager.approve_image("image_2")
204
+
205
+ # Now should have minimum
206
+ self.assertTrue(self.approval_manager.has_minimum_approved())
207
+
208
+ def test_save_and_load_state(self):
209
+ """
210
+ Test saving and loading the approval state.
211
+ """
212
+ # Add images
213
+ self.approval_manager.add_images(self.test_images)
214
+
215
+ # Approve and reject some images
216
+ self.approval_manager.approve_image("image_0")
217
+ self.approval_manager.approve_image("image_2")
218
+ self.approval_manager.reject_image("image_3")
219
+
220
+ # Save the state
221
+ state_file = os.path.join(self.test_output_dir, "approval_state.json")
222
+ self.approval_manager.save_state(state_file)
223
+
224
+ # Create a new manager
225
+ new_manager = ImageApprovalManager(
226
+ output_dir=self.test_output_dir,
227
+ approved_dir=self.test_approved_dir,
228
+ min_approved_images=3,
229
+ auto_approve=False
230
+ )
231
+
232
+ # Load the state
233
+ new_manager.load_state(state_file)
234
+
235
+ # Verify the state was loaded correctly
236
+ self.assertEqual(len(new_manager.images), 5)
237
+ self.assertEqual(len(new_manager.pending_images), 2)
238
+ self.assertEqual(len(new_manager.approved_images), 2)
239
+ self.assertEqual(len(new_manager.rejected_images), 1)
240
+
241
+ def tearDown(self):
242
+ """
243
+ Clean up after tests.
244
+ """
245
+ # Clean up test output directory
246
+ import shutil
247
+ if os.path.exists(self.test_output_dir):
248
+ shutil.rmtree(self.test_output_dir)
249
+
250
+ if __name__ == "__main__":
251
+ unittest.main()