python2mobile 1.0.1__py3-none-any.whl
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.
- examples/example_ecommerce_app.py +189 -0
- examples/example_todo_app.py +159 -0
- p2m/__init__.py +31 -0
- p2m/cli.py +470 -0
- p2m/config.py +205 -0
- p2m/core/__init__.py +18 -0
- p2m/core/api.py +191 -0
- p2m/core/ast_walker.py +171 -0
- p2m/core/database.py +192 -0
- p2m/core/events.py +56 -0
- p2m/core/render_engine.py +597 -0
- p2m/core/runtime.py +128 -0
- p2m/core/state.py +51 -0
- p2m/core/validator.py +284 -0
- p2m/devserver/__init__.py +9 -0
- p2m/devserver/server.py +84 -0
- p2m/i18n/__init__.py +7 -0
- p2m/i18n/translator.py +74 -0
- p2m/imagine/__init__.py +35 -0
- p2m/imagine/agent.py +463 -0
- p2m/imagine/legacy.py +217 -0
- p2m/llm/__init__.py +20 -0
- p2m/llm/anthropic_provider.py +78 -0
- p2m/llm/base.py +42 -0
- p2m/llm/compatible_provider.py +120 -0
- p2m/llm/factory.py +72 -0
- p2m/llm/ollama_provider.py +89 -0
- p2m/llm/openai_provider.py +79 -0
- p2m/testing/__init__.py +41 -0
- p2m/ui/__init__.py +43 -0
- p2m/ui/components.py +301 -0
- python2mobile-1.0.1.dist-info/METADATA +238 -0
- python2mobile-1.0.1.dist-info/RECORD +50 -0
- python2mobile-1.0.1.dist-info/WHEEL +5 -0
- python2mobile-1.0.1.dist-info/entry_points.txt +2 -0
- python2mobile-1.0.1.dist-info/top_level.txt +3 -0
- tests/test_basic_engine.py +281 -0
- tests/test_build_generation.py +603 -0
- tests/test_build_test_gate.py +150 -0
- tests/test_carousel_modal.py +84 -0
- tests/test_config_system.py +272 -0
- tests/test_i18n.py +101 -0
- tests/test_ifood_app_integration.py +172 -0
- tests/test_imagine_cli.py +133 -0
- tests/test_imagine_command.py +341 -0
- tests/test_llm_providers.py +321 -0
- tests/test_new_apps_integration.py +588 -0
- tests/test_ollama_functional.py +329 -0
- tests/test_real_world_apps.py +228 -0
- tests/test_run_integration.py +776 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration tests for tests-p2m/ifood_app.
|
|
3
|
+
|
|
4
|
+
Uses the same module-isolation strategy as test_new_apps_integration.py.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import importlib
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
# ── Paths ─────────────────────────────────────────────────────────────────────
|
|
13
|
+
FRAMEWORK_PATH = Path(__file__).parent.parent # .../python2mobile/
|
|
14
|
+
TESTS_P2M_PATH = FRAMEWORK_PATH.parent / "tests-p2m" # .../tests-p2m/
|
|
15
|
+
IFOOD_DIR = TESTS_P2M_PATH / "ifood_app"
|
|
16
|
+
|
|
17
|
+
if str(FRAMEWORK_PATH) not in sys.path:
|
|
18
|
+
sys.path.insert(0, str(FRAMEWORK_PATH))
|
|
19
|
+
|
|
20
|
+
from p2m.core import events, Render
|
|
21
|
+
from p2m.core.render_engine import RenderEngine
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
def _render(create_view) -> str:
|
|
27
|
+
tree = Render.execute(create_view)
|
|
28
|
+
return RenderEngine().render_content(tree)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load_app():
|
|
32
|
+
sys.modules.pop("main", None)
|
|
33
|
+
return importlib.import_module("main")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── Fixture ───────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
@pytest.fixture()
|
|
39
|
+
def ifood_app():
|
|
40
|
+
saved_path = list(sys.path)
|
|
41
|
+
saved_module_keys = set(sys.modules.keys())
|
|
42
|
+
events.clear()
|
|
43
|
+
|
|
44
|
+
# Reset i18n state before each test
|
|
45
|
+
import p2m.i18n.translator as _t
|
|
46
|
+
_t._locale = "en"
|
|
47
|
+
_t._translations = {}
|
|
48
|
+
_t._locales_dir = None
|
|
49
|
+
|
|
50
|
+
sys.path.insert(0, str(IFOOD_DIR))
|
|
51
|
+
mod = _load_app()
|
|
52
|
+
_render(mod.create_view) # initial render registers event handlers
|
|
53
|
+
|
|
54
|
+
from state.store import store
|
|
55
|
+
yield mod, store
|
|
56
|
+
|
|
57
|
+
for key in list(sys.modules.keys()):
|
|
58
|
+
if key not in saved_module_keys:
|
|
59
|
+
del sys.modules[key]
|
|
60
|
+
sys.path[:] = saved_path
|
|
61
|
+
events.clear()
|
|
62
|
+
|
|
63
|
+
# Reset i18n again after test
|
|
64
|
+
_t._locale = "en"
|
|
65
|
+
_t._translations = {}
|
|
66
|
+
_t._locales_dir = None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ── Tests ─────────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
class TestIFoodIntegration:
|
|
72
|
+
def test_initial_render(self, ifood_app):
|
|
73
|
+
mod, _ = ifood_app
|
|
74
|
+
tree = Render.execute(mod.create_view)
|
|
75
|
+
assert tree["type"] == "Column"
|
|
76
|
+
|
|
77
|
+
def test_restaurants_visible_in_html(self, ifood_app):
|
|
78
|
+
mod, _ = ifood_app
|
|
79
|
+
html = _render(mod.create_view)
|
|
80
|
+
assert "Domino" in html
|
|
81
|
+
|
|
82
|
+
def test_all_restaurants_initially(self, ifood_app):
|
|
83
|
+
mod, _ = ifood_app
|
|
84
|
+
html = _render(mod.create_view)
|
|
85
|
+
assert "Sushi Nagoya" in html
|
|
86
|
+
assert "Bob" in html
|
|
87
|
+
assert "Taco Loco" in html
|
|
88
|
+
|
|
89
|
+
def test_category_filter_sushi(self, ifood_app):
|
|
90
|
+
mod, _ = ifood_app
|
|
91
|
+
events.dispatch("select_category", "sushi")
|
|
92
|
+
html = _render(mod.create_view)
|
|
93
|
+
assert "Sushi Nagoya" in html
|
|
94
|
+
assert "Domino" not in html
|
|
95
|
+
|
|
96
|
+
def test_category_filter_pizza(self, ifood_app):
|
|
97
|
+
mod, _ = ifood_app
|
|
98
|
+
events.dispatch("select_category", "pizza")
|
|
99
|
+
html = _render(mod.create_view)
|
|
100
|
+
assert "Domino" in html
|
|
101
|
+
assert "Bob" not in html
|
|
102
|
+
|
|
103
|
+
def test_open_restaurant_sets_modal(self, ifood_app):
|
|
104
|
+
_, store = ifood_app
|
|
105
|
+
events.dispatch("open_restaurant", 1)
|
|
106
|
+
assert store.modal_visible is True
|
|
107
|
+
assert store.selected_restaurant_id == 1
|
|
108
|
+
|
|
109
|
+
def test_close_modal(self, ifood_app):
|
|
110
|
+
_, store = ifood_app
|
|
111
|
+
events.dispatch("open_restaurant", 1)
|
|
112
|
+
events.dispatch("close_modal")
|
|
113
|
+
assert store.modal_visible is False
|
|
114
|
+
assert store.selected_restaurant_id is None
|
|
115
|
+
|
|
116
|
+
def test_modal_content_visible_when_open(self, ifood_app):
|
|
117
|
+
mod, _ = ifood_app
|
|
118
|
+
events.dispatch("open_restaurant", 1)
|
|
119
|
+
html = _render(mod.create_view)
|
|
120
|
+
assert "Pepperoni" in html
|
|
121
|
+
|
|
122
|
+
def test_modal_hidden_initially(self, ifood_app):
|
|
123
|
+
mod, _ = ifood_app
|
|
124
|
+
html = _render(mod.create_view)
|
|
125
|
+
assert "display:none" in html
|
|
126
|
+
|
|
127
|
+
def test_add_to_cart(self, ifood_app):
|
|
128
|
+
_, store = ifood_app
|
|
129
|
+
events.dispatch("add_to_cart", 1)
|
|
130
|
+
assert len(store.cart) == 1
|
|
131
|
+
|
|
132
|
+
def test_cart_qty_increments(self, ifood_app):
|
|
133
|
+
_, store = ifood_app
|
|
134
|
+
events.dispatch("add_to_cart", 1)
|
|
135
|
+
events.dispatch("add_to_cart", 1)
|
|
136
|
+
assert len(store.cart) == 1
|
|
137
|
+
assert store.cart[0]["qty"] == 2
|
|
138
|
+
|
|
139
|
+
def test_add_different_items(self, ifood_app):
|
|
140
|
+
_, store = ifood_app
|
|
141
|
+
events.dispatch("add_to_cart", 1)
|
|
142
|
+
events.dispatch("add_to_cart", 4)
|
|
143
|
+
assert len(store.cart) == 2
|
|
144
|
+
|
|
145
|
+
def test_clear_cart(self, ifood_app):
|
|
146
|
+
_, store = ifood_app
|
|
147
|
+
events.dispatch("add_to_cart", 1)
|
|
148
|
+
events.dispatch("add_to_cart", 4)
|
|
149
|
+
events.dispatch("clear_cart")
|
|
150
|
+
assert store.cart == []
|
|
151
|
+
|
|
152
|
+
def test_locale_switch_to_en(self, ifood_app):
|
|
153
|
+
mod, _ = ifood_app
|
|
154
|
+
events.dispatch("switch_locale", "en")
|
|
155
|
+
html = _render(mod.create_view)
|
|
156
|
+
assert "Search" in html or "Nearby" in html
|
|
157
|
+
|
|
158
|
+
def test_locale_switch_updates_store(self, ifood_app):
|
|
159
|
+
_, store = ifood_app
|
|
160
|
+
events.dispatch("switch_locale", "en")
|
|
161
|
+
assert store.locale == "en"
|
|
162
|
+
|
|
163
|
+
def test_cart_bar_shown_when_items(self, ifood_app):
|
|
164
|
+
mod, _ = ifood_app
|
|
165
|
+
events.dispatch("add_to_cart", 1)
|
|
166
|
+
html = _render(mod.create_view)
|
|
167
|
+
assert "clear_cart" in html
|
|
168
|
+
|
|
169
|
+
def test_cart_bar_hidden_when_empty(self, ifood_app):
|
|
170
|
+
mod, _ = ifood_app
|
|
171
|
+
html = _render(mod.create_view)
|
|
172
|
+
assert "clear_cart" not in html
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test the p2m imagine CLI command
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_imagine_help():
|
|
11
|
+
"""Test p2m imagine --help"""
|
|
12
|
+
print("\n🧪 Test 1: p2m imagine --help")
|
|
13
|
+
|
|
14
|
+
result = subprocess.run(
|
|
15
|
+
["p2m", "imagine", "--help"],
|
|
16
|
+
capture_output=True,
|
|
17
|
+
text=True
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
assert result.returncode == 0, f"Command failed: {result.stderr}"
|
|
21
|
+
assert "P2M project" in result.stdout or "P2M code" in result.stdout or "natural language" in result.stdout
|
|
22
|
+
assert "--provider" in result.stdout
|
|
23
|
+
assert "--model" in result.stdout
|
|
24
|
+
assert "--api-key" in result.stdout
|
|
25
|
+
|
|
26
|
+
print("✅ Help command works correctly")
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_imagine_missing_description():
|
|
31
|
+
"""Test p2m imagine without description"""
|
|
32
|
+
print("\n🧪 Test 2: p2m imagine (missing description)")
|
|
33
|
+
|
|
34
|
+
result = subprocess.run(
|
|
35
|
+
["p2m", "imagine"],
|
|
36
|
+
capture_output=True,
|
|
37
|
+
text=True
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Should fail with missing argument
|
|
41
|
+
assert result.returncode != 0
|
|
42
|
+
print("✅ Correctly requires description argument")
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_imagine_version():
|
|
47
|
+
"""Test p2m --version"""
|
|
48
|
+
print("\n🧪 Test 3: p2m --version")
|
|
49
|
+
|
|
50
|
+
result = subprocess.run(
|
|
51
|
+
["p2m", "--version"],
|
|
52
|
+
capture_output=True,
|
|
53
|
+
text=True
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Version command may not be implemented, that's ok
|
|
57
|
+
if result.returncode == 0:
|
|
58
|
+
print(f"✅ Version: {result.stdout.strip()}")
|
|
59
|
+
else:
|
|
60
|
+
print(f"✅ Version command not implemented (optional)")
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_imagine_list_providers():
|
|
65
|
+
"""Test listing available providers"""
|
|
66
|
+
print("\n🧪 Test 4: List Available Providers")
|
|
67
|
+
|
|
68
|
+
# Try to get help which lists providers
|
|
69
|
+
result = subprocess.run(
|
|
70
|
+
["p2m", "imagine", "--help"],
|
|
71
|
+
capture_output=True,
|
|
72
|
+
text=True
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
assert "openai" in result.stdout.lower()
|
|
76
|
+
assert "anthropic" in result.stdout.lower()
|
|
77
|
+
|
|
78
|
+
print("✅ Providers listed in help:")
|
|
79
|
+
print(" - openai")
|
|
80
|
+
print(" - anthropic")
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def run_all_tests():
|
|
85
|
+
"""Run all CLI tests"""
|
|
86
|
+
print("\n" + "="*60)
|
|
87
|
+
print("🧪 P2M Imagine CLI Test Suite")
|
|
88
|
+
print("="*60)
|
|
89
|
+
|
|
90
|
+
tests = [
|
|
91
|
+
test_imagine_help,
|
|
92
|
+
test_imagine_missing_description,
|
|
93
|
+
test_imagine_version,
|
|
94
|
+
test_imagine_list_providers,
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
results = []
|
|
98
|
+
|
|
99
|
+
for test_func in tests:
|
|
100
|
+
try:
|
|
101
|
+
result = test_func()
|
|
102
|
+
results.append((test_func.__name__, result))
|
|
103
|
+
except Exception as e:
|
|
104
|
+
results.append((test_func.__name__, False))
|
|
105
|
+
print(f"❌ {test_func.__name__} failed: {e}")
|
|
106
|
+
import traceback
|
|
107
|
+
traceback.print_exc()
|
|
108
|
+
|
|
109
|
+
# Summary
|
|
110
|
+
print("\n" + "="*60)
|
|
111
|
+
print("📊 Test Summary")
|
|
112
|
+
print("="*60)
|
|
113
|
+
|
|
114
|
+
passed = sum(1 for _, success in results if success)
|
|
115
|
+
total = len(results)
|
|
116
|
+
|
|
117
|
+
for name, success in results:
|
|
118
|
+
status = "✅ PASS" if success else "❌ FAIL"
|
|
119
|
+
print(f"{status} - {name}")
|
|
120
|
+
|
|
121
|
+
print(f"\n📈 Results: {passed}/{total} tests passed")
|
|
122
|
+
|
|
123
|
+
if passed == total:
|
|
124
|
+
print("\n🎉 All tests passed!")
|
|
125
|
+
else:
|
|
126
|
+
print(f"\n⚠️ {total - passed} test(s) failed")
|
|
127
|
+
|
|
128
|
+
return passed == total
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
success = run_all_tests()
|
|
133
|
+
sys.exit(0 if success else 1)
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test suite for p2m imagine command
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
# Add project to path
|
|
10
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
11
|
+
|
|
12
|
+
from p2m.imagine.legacy import CodeImaginer
|
|
13
|
+
from p2m.imagine import imagine_command
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_config_validation_openai():
|
|
17
|
+
"""Test OpenAI configuration validation"""
|
|
18
|
+
print("\n🧪 Test 1: OpenAI Config Validation")
|
|
19
|
+
|
|
20
|
+
imaginer = CodeImaginer(
|
|
21
|
+
provider="openai",
|
|
22
|
+
model="gpt-4o",
|
|
23
|
+
api_key="sk-test"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
valid, msg = imaginer.validate_config()
|
|
27
|
+
assert valid, f"Should be valid: {msg}"
|
|
28
|
+
print("✅ OpenAI config validation passed")
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_config_validation_anthropic():
|
|
33
|
+
"""Test Anthropic configuration validation"""
|
|
34
|
+
print("\n🧪 Test 2: Anthropic Config Validation")
|
|
35
|
+
|
|
36
|
+
imaginer = CodeImaginer(
|
|
37
|
+
provider="anthropic",
|
|
38
|
+
model="claude-3-opus-20240229",
|
|
39
|
+
api_key="sk-ant-test"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
valid, msg = imaginer.validate_config()
|
|
43
|
+
assert valid, f"Should be valid: {msg}"
|
|
44
|
+
print("✅ Anthropic config validation passed")
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_config_validation_ollama():
|
|
49
|
+
"""Test Ollama configuration validation"""
|
|
50
|
+
print("\n🧪 Test 3: Ollama Config Validation")
|
|
51
|
+
|
|
52
|
+
imaginer = CodeImaginer(
|
|
53
|
+
provider="ollama",
|
|
54
|
+
model="llama2",
|
|
55
|
+
base_url="http://localhost:11434"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
valid, msg = imaginer.validate_config()
|
|
59
|
+
assert valid, f"Should be valid: {msg}"
|
|
60
|
+
assert imaginer.base_url == "http://localhost:11434"
|
|
61
|
+
print("✅ Ollama config validation passed")
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_config_validation_compatible():
|
|
66
|
+
"""Test OpenAI-compatible configuration validation"""
|
|
67
|
+
print("\n🧪 Test 4: OpenAI-Compatible Config Validation")
|
|
68
|
+
|
|
69
|
+
imaginer = CodeImaginer(
|
|
70
|
+
provider="openai-compatible",
|
|
71
|
+
model="custom-model",
|
|
72
|
+
api_key="test-key",
|
|
73
|
+
base_url="https://api.example.com/v1"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
valid, msg = imaginer.validate_config()
|
|
77
|
+
assert valid, f"Should be valid: {msg}"
|
|
78
|
+
print("✅ OpenAI-compatible config validation passed")
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_missing_api_key():
|
|
83
|
+
"""Test missing API key validation"""
|
|
84
|
+
print("\n🧪 Test 5: Missing API Key Validation")
|
|
85
|
+
|
|
86
|
+
imaginer = CodeImaginer(
|
|
87
|
+
provider="openai",
|
|
88
|
+
model="gpt-4o",
|
|
89
|
+
api_key=None # No API key
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Clear environment variable if set
|
|
93
|
+
import os
|
|
94
|
+
old_key = os.environ.pop("OPENAI_API_KEY", None)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
valid, msg = imaginer.validate_config()
|
|
98
|
+
assert not valid, "Should be invalid without API key"
|
|
99
|
+
assert "OPENAI_API_KEY" in msg
|
|
100
|
+
print(f"✅ Correctly detected missing API key: {msg}")
|
|
101
|
+
return True
|
|
102
|
+
finally:
|
|
103
|
+
if old_key:
|
|
104
|
+
os.environ["OPENAI_API_KEY"] = old_key
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_missing_base_url():
|
|
108
|
+
"""Test missing base URL validation"""
|
|
109
|
+
print("\n🧪 Test 6: Missing Base URL Validation")
|
|
110
|
+
|
|
111
|
+
imaginer = CodeImaginer(
|
|
112
|
+
provider="openai-compatible",
|
|
113
|
+
model="custom-model",
|
|
114
|
+
api_key="test-key",
|
|
115
|
+
base_url=None # No base URL
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
valid, msg = imaginer.validate_config()
|
|
119
|
+
assert not valid, "Should be invalid without base_url"
|
|
120
|
+
assert "base-url" in msg.lower()
|
|
121
|
+
print(f"✅ Correctly detected missing base_url: {msg}")
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_code_validation_valid():
|
|
126
|
+
"""Test code validation with valid code"""
|
|
127
|
+
print("\n🧪 Test 7: Code Validation - Valid Code")
|
|
128
|
+
|
|
129
|
+
valid_code = '''from p2m.core import Render
|
|
130
|
+
from p2m.ui import Container, Text
|
|
131
|
+
|
|
132
|
+
def create_view():
|
|
133
|
+
container = Container(class_="bg-white p-4")
|
|
134
|
+
text = Text("Hello World", class_="text-lg")
|
|
135
|
+
container.add(text)
|
|
136
|
+
return container.build()
|
|
137
|
+
|
|
138
|
+
def main():
|
|
139
|
+
Render.execute(create_view)
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
main()
|
|
143
|
+
'''
|
|
144
|
+
|
|
145
|
+
imaginer = CodeImaginer(
|
|
146
|
+
provider="openai",
|
|
147
|
+
model="gpt-4o",
|
|
148
|
+
api_key="test"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
valid, msg = imaginer.validate_code(valid_code)
|
|
152
|
+
assert valid, f"Should be valid: {msg}"
|
|
153
|
+
print("✅ Valid code passed validation")
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_code_validation_syntax_error():
|
|
158
|
+
"""Test code validation with syntax error"""
|
|
159
|
+
print("\n🧪 Test 8: Code Validation - Syntax Error")
|
|
160
|
+
|
|
161
|
+
invalid_code = '''from p2m.core import Render
|
|
162
|
+
from p2m.ui import Container, Text
|
|
163
|
+
|
|
164
|
+
def create_view(
|
|
165
|
+
# Missing closing parenthesis
|
|
166
|
+
container = Container()
|
|
167
|
+
'''
|
|
168
|
+
|
|
169
|
+
imaginer = CodeImaginer(
|
|
170
|
+
provider="openai",
|
|
171
|
+
model="gpt-4o",
|
|
172
|
+
api_key="test"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
valid, msg = imaginer.validate_code(invalid_code)
|
|
176
|
+
assert not valid, "Should be invalid due to syntax error"
|
|
177
|
+
assert "Syntax error" in msg
|
|
178
|
+
print(f"✅ Correctly detected syntax error: {msg}")
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_code_validation_missing_imports():
|
|
183
|
+
"""Test code validation with missing imports"""
|
|
184
|
+
print("\n🧪 Test 9: Code Validation - Missing Imports")
|
|
185
|
+
|
|
186
|
+
invalid_code = '''def create_view():
|
|
187
|
+
container = Container()
|
|
188
|
+
return container.build()
|
|
189
|
+
'''
|
|
190
|
+
|
|
191
|
+
imaginer = CodeImaginer(
|
|
192
|
+
provider="openai",
|
|
193
|
+
model="gpt-4o",
|
|
194
|
+
api_key="test"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
valid, msg = imaginer.validate_code(invalid_code)
|
|
198
|
+
assert not valid, "Should be invalid without p2m imports"
|
|
199
|
+
assert "import from p2m" in msg.lower()
|
|
200
|
+
print(f"✅ Correctly detected missing imports: {msg}")
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_code_validation_missing_create_view():
|
|
205
|
+
"""Test code validation with missing create_view"""
|
|
206
|
+
print("\n🧪 Test 10: Code Validation - Missing create_view")
|
|
207
|
+
|
|
208
|
+
invalid_code = '''from p2m.core import Render
|
|
209
|
+
from p2m.ui import Container, Text
|
|
210
|
+
|
|
211
|
+
def main():
|
|
212
|
+
print("Hello")
|
|
213
|
+
'''
|
|
214
|
+
|
|
215
|
+
imaginer = CodeImaginer(
|
|
216
|
+
provider="openai",
|
|
217
|
+
model="gpt-4o",
|
|
218
|
+
api_key="test"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
valid, msg = imaginer.validate_code(invalid_code)
|
|
222
|
+
assert not valid, "Should be invalid without create_view"
|
|
223
|
+
assert "create_view" in msg
|
|
224
|
+
print(f"✅ Correctly detected missing create_view: {msg}")
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def test_save_code():
|
|
229
|
+
"""Test saving generated code"""
|
|
230
|
+
print("\n🧪 Test 11: Save Generated Code")
|
|
231
|
+
|
|
232
|
+
code = '''from p2m.core import Render
|
|
233
|
+
from p2m.ui import Container, Text
|
|
234
|
+
|
|
235
|
+
def create_view():
|
|
236
|
+
container = Container(class_="bg-white p-4")
|
|
237
|
+
text = Text("Hello World", class_="text-lg")
|
|
238
|
+
container.add(text)
|
|
239
|
+
return container.build()
|
|
240
|
+
'''
|
|
241
|
+
|
|
242
|
+
imaginer = CodeImaginer(
|
|
243
|
+
provider="openai",
|
|
244
|
+
model="gpt-4o",
|
|
245
|
+
api_key="test"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
249
|
+
output_file = Path(tmpdir) / "test_app.py"
|
|
250
|
+
success, filepath = imaginer.save_code(code, str(output_file))
|
|
251
|
+
|
|
252
|
+
assert success, f"Should save successfully: {filepath}"
|
|
253
|
+
assert Path(filepath).exists(), "File should exist"
|
|
254
|
+
|
|
255
|
+
# Verify content
|
|
256
|
+
saved_code = Path(filepath).read_text()
|
|
257
|
+
assert "create_view" in saved_code
|
|
258
|
+
assert "Container" in saved_code
|
|
259
|
+
|
|
260
|
+
print(f"✅ Code saved successfully to {filepath}")
|
|
261
|
+
|
|
262
|
+
return True
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def test_provider_creation():
|
|
266
|
+
"""Test LLM provider creation"""
|
|
267
|
+
print("\n🧪 Test 12: LLM Provider Creation")
|
|
268
|
+
|
|
269
|
+
imaginer = CodeImaginer(
|
|
270
|
+
provider="openai",
|
|
271
|
+
model="gpt-4o",
|
|
272
|
+
api_key="sk-test"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
imaginer.validate_config()
|
|
276
|
+
success, msg = imaginer.create_provider()
|
|
277
|
+
|
|
278
|
+
assert success, f"Should create provider: {msg}"
|
|
279
|
+
assert imaginer.llm is not None
|
|
280
|
+
print(f"✅ Provider created successfully: {msg}")
|
|
281
|
+
return True
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def run_all_tests():
|
|
285
|
+
"""Run all tests"""
|
|
286
|
+
print("\n" + "="*60)
|
|
287
|
+
print("🧪 P2M Imagine Command Test Suite")
|
|
288
|
+
print("="*60)
|
|
289
|
+
|
|
290
|
+
tests = [
|
|
291
|
+
test_config_validation_openai,
|
|
292
|
+
test_config_validation_anthropic,
|
|
293
|
+
test_config_validation_ollama,
|
|
294
|
+
test_config_validation_compatible,
|
|
295
|
+
test_missing_api_key,
|
|
296
|
+
test_missing_base_url,
|
|
297
|
+
test_code_validation_valid,
|
|
298
|
+
test_code_validation_syntax_error,
|
|
299
|
+
test_code_validation_missing_imports,
|
|
300
|
+
test_code_validation_missing_create_view,
|
|
301
|
+
test_save_code,
|
|
302
|
+
test_provider_creation,
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
results = []
|
|
306
|
+
|
|
307
|
+
for test_func in tests:
|
|
308
|
+
try:
|
|
309
|
+
result = test_func()
|
|
310
|
+
results.append((test_func.__name__, result))
|
|
311
|
+
except Exception as e:
|
|
312
|
+
results.append((test_func.__name__, False))
|
|
313
|
+
print(f"❌ {test_func.__name__} failed: {e}")
|
|
314
|
+
import traceback
|
|
315
|
+
traceback.print_exc()
|
|
316
|
+
|
|
317
|
+
# Summary
|
|
318
|
+
print("\n" + "="*60)
|
|
319
|
+
print("📊 Test Summary")
|
|
320
|
+
print("="*60)
|
|
321
|
+
|
|
322
|
+
passed = sum(1 for _, success in results if success)
|
|
323
|
+
total = len(results)
|
|
324
|
+
|
|
325
|
+
for name, success in results:
|
|
326
|
+
status = "✅ PASS" if success else "❌ FAIL"
|
|
327
|
+
print(f"{status} - {name}")
|
|
328
|
+
|
|
329
|
+
print(f"\n📈 Results: {passed}/{total} tests passed")
|
|
330
|
+
|
|
331
|
+
if passed == total:
|
|
332
|
+
print("\n🎉 All tests passed!")
|
|
333
|
+
else:
|
|
334
|
+
print(f"\n⚠️ {total - passed} test(s) failed")
|
|
335
|
+
|
|
336
|
+
return passed == total
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
if __name__ == "__main__":
|
|
340
|
+
success = run_all_tests()
|
|
341
|
+
sys.exit(0 if success else 1)
|