npcpy 1.3.15__tar.gz → 1.3.16__tar.gz
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.
- {npcpy-1.3.15/npcpy.egg-info → npcpy-1.3.16}/PKG-INFO +1 -1
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/ft/rl.py +27 -14
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/serve.py +3 -3
- {npcpy-1.3.15 → npcpy-1.3.16/npcpy.egg-info}/PKG-INFO +1 -1
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy.egg-info/SOURCES.txt +13 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/setup.py +1 -1
- npcpy-1.3.16/tests/test_browser.py +233 -0
- npcpy-1.3.16/tests/test_build_funcs.py +274 -0
- npcpy-1.3.16/tests/test_data_models.py +251 -0
- npcpy-1.3.16/tests/test_diff.py +360 -0
- npcpy-1.3.16/tests/test_genetic_evolver.py +447 -0
- npcpy-1.3.16/tests/test_memory_processor.py +268 -0
- npcpy-1.3.16/tests/test_ml_funcs.py +288 -0
- npcpy-1.3.16/tests/test_model_runner.py +329 -0
- npcpy-1.3.16/tests/test_npc_sysenv.py +173 -0
- npcpy-1.3.16/tests/test_sql_adapters.py +171 -0
- npcpy-1.3.16/tests/test_sql_compiler.py +287 -0
- npcpy-1.3.16/tests/test_sql_functions.py +221 -0
- npcpy-1.3.16/tests/test_video.py +133 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/LICENSE +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/MANIFEST.in +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/README.md +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/build_funcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/data/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/data/audio.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/data/data_models.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/data/image.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/data/load.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/data/text.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/data/video.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/data/web.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/ft/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/ft/diff.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/ft/ge.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/ft/memory_trainer.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/ft/model_ensembler.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/ft/sft.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/ft/usft.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/gen/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/gen/audio_gen.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/gen/embeddings.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/gen/image_gen.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/gen/ocr.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/gen/response.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/gen/video_gen.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/gen/world_gen.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/llm_funcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/main.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/memory/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/memory/command_history.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/memory/kg_vis.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/memory/knowledge_graph.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/memory/memory_processor.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/memory/search.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/mix/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/mix/debate.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/ml_funcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/npc_array.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/npc_compiler.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/npc_sysenv.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/npcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/sql/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/sql/ai_function_tools.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/sql/database_ai_adapters.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/sql/database_ai_functions.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/sql/model_runner.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/sql/npcsql.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/sql/sql_model_compiler.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/tools.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/work/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/work/browser.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/work/desktop.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/work/plan.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy/work/trigger.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy.egg-info/dependency_links.txt +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy.egg-info/requires.txt +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/npcpy.egg-info/top_level.txt +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/setup.cfg +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_audio.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_command_history.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_documentation_examples.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_image.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_llm_funcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_load.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_npc_array.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_npc_compiler.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_npcsql.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_response.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_serve.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_text.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_tools.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.16}/tests/test_web.py +0 -0
|
@@ -6,27 +6,40 @@ import glob
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
import pandas as pd
|
|
9
|
+
# Core imports that should always work
|
|
9
10
|
try:
|
|
10
|
-
from datasets import Dataset
|
|
11
|
-
|
|
12
|
-
from peft import LoraConfig, PeftModel
|
|
13
11
|
import torch
|
|
14
|
-
|
|
15
|
-
AutoModelForCausalLM,
|
|
16
|
-
AutoTokenizer,
|
|
17
|
-
BitsAndBytesConfig
|
|
18
|
-
)
|
|
19
|
-
from trl import DPOTrainer, DPOConfig
|
|
20
|
-
except:
|
|
21
|
-
Dataset = None
|
|
22
|
-
PeftModel = None
|
|
23
|
-
DPOConfig = None
|
|
24
|
-
DPOTrainer = None
|
|
12
|
+
except ImportError:
|
|
25
13
|
torch = None
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
17
|
+
except ImportError:
|
|
26
18
|
AutoModelForCausalLM = None
|
|
27
19
|
AutoTokenizer = None
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from transformers import BitsAndBytesConfig
|
|
23
|
+
except ImportError:
|
|
28
24
|
BitsAndBytesConfig = None
|
|
29
25
|
|
|
26
|
+
try:
|
|
27
|
+
from datasets import Dataset
|
|
28
|
+
except ImportError:
|
|
29
|
+
Dataset = None
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
from peft import LoraConfig, PeftModel
|
|
33
|
+
except ImportError:
|
|
34
|
+
LoraConfig = None
|
|
35
|
+
PeftModel = None
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
from trl import DPOTrainer, DPOConfig
|
|
39
|
+
except ImportError:
|
|
40
|
+
DPOTrainer = None
|
|
41
|
+
DPOConfig = None
|
|
42
|
+
|
|
30
43
|
|
|
31
44
|
import random
|
|
32
45
|
from typing import List, Dict, Any, Optional, Callable
|
|
@@ -5273,7 +5273,7 @@ def openai_chat_completions():
|
|
|
5273
5273
|
current_path = request.headers.get("X-Current-Path", os.getcwd())
|
|
5274
5274
|
|
|
5275
5275
|
# Load team and NPC
|
|
5276
|
-
db_path = app.config.get('DB_PATH') or os.path.expanduser("
|
|
5276
|
+
db_path = app.config.get('DB_PATH') or os.path.expanduser("~/npcsh_history.db")
|
|
5277
5277
|
db_conn = create_engine(f'sqlite:///{db_path}')
|
|
5278
5278
|
|
|
5279
5279
|
npc = None
|
|
@@ -6110,8 +6110,8 @@ if __name__ == "__main__":
|
|
|
6110
6110
|
|
|
6111
6111
|
SETTINGS_FILE = Path(os.path.expanduser("~/.npcshrc"))
|
|
6112
6112
|
|
|
6113
|
-
# Use
|
|
6114
|
-
db_path = os.path.expanduser("
|
|
6113
|
+
# Use environment variable for DB path, or fall back to home directory path (matching Electron app)
|
|
6114
|
+
db_path = os.environ.get('INCOGNIDE_DB_PATH', os.path.expanduser("~/npcsh_history.db"))
|
|
6115
6115
|
user_npc_directory = os.path.expanduser("~/.npcsh/npc_team")
|
|
6116
6116
|
|
|
6117
6117
|
# Ensure directories exist
|
|
@@ -63,16 +63,29 @@ npcpy/work/desktop.py
|
|
|
63
63
|
npcpy/work/plan.py
|
|
64
64
|
npcpy/work/trigger.py
|
|
65
65
|
tests/test_audio.py
|
|
66
|
+
tests/test_browser.py
|
|
67
|
+
tests/test_build_funcs.py
|
|
66
68
|
tests/test_command_history.py
|
|
69
|
+
tests/test_data_models.py
|
|
70
|
+
tests/test_diff.py
|
|
67
71
|
tests/test_documentation_examples.py
|
|
72
|
+
tests/test_genetic_evolver.py
|
|
68
73
|
tests/test_image.py
|
|
69
74
|
tests/test_llm_funcs.py
|
|
70
75
|
tests/test_load.py
|
|
76
|
+
tests/test_memory_processor.py
|
|
77
|
+
tests/test_ml_funcs.py
|
|
78
|
+
tests/test_model_runner.py
|
|
71
79
|
tests/test_npc_array.py
|
|
72
80
|
tests/test_npc_compiler.py
|
|
81
|
+
tests/test_npc_sysenv.py
|
|
73
82
|
tests/test_npcsql.py
|
|
74
83
|
tests/test_response.py
|
|
75
84
|
tests/test_serve.py
|
|
85
|
+
tests/test_sql_adapters.py
|
|
86
|
+
tests/test_sql_compiler.py
|
|
87
|
+
tests/test_sql_functions.py
|
|
76
88
|
tests/test_text.py
|
|
77
89
|
tests/test_tools.py
|
|
90
|
+
tests/test_video.py
|
|
78
91
|
tests/test_web.py
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Test suite for browser session storage module."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestBrowserSessions:
|
|
7
|
+
"""Test browser session storage functions."""
|
|
8
|
+
|
|
9
|
+
def test_get_sessions_returns_dict(self):
|
|
10
|
+
"""Test get_sessions returns a dictionary"""
|
|
11
|
+
from npcpy.work import browser
|
|
12
|
+
|
|
13
|
+
sessions = browser.get_sessions()
|
|
14
|
+
|
|
15
|
+
assert isinstance(sessions, dict)
|
|
16
|
+
|
|
17
|
+
def test_get_current_driver_none_initially(self):
|
|
18
|
+
"""Test get_current_driver returns None when no session set"""
|
|
19
|
+
from npcpy.work import browser
|
|
20
|
+
|
|
21
|
+
# Clear any existing sessions
|
|
22
|
+
browser._sessions.clear()
|
|
23
|
+
|
|
24
|
+
driver = browser.get_current_driver()
|
|
25
|
+
|
|
26
|
+
assert driver is None
|
|
27
|
+
|
|
28
|
+
def test_set_driver_stores_session(self):
|
|
29
|
+
"""Test set_driver stores the driver"""
|
|
30
|
+
from npcpy.work import browser
|
|
31
|
+
|
|
32
|
+
# Clear any existing sessions
|
|
33
|
+
browser._sessions.clear()
|
|
34
|
+
|
|
35
|
+
# Create a mock driver object
|
|
36
|
+
class MockDriver:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
mock_driver = MockDriver()
|
|
40
|
+
|
|
41
|
+
browser.set_driver("session-1", mock_driver)
|
|
42
|
+
|
|
43
|
+
assert "session-1" in browser._sessions
|
|
44
|
+
assert browser._sessions["session-1"] is mock_driver
|
|
45
|
+
assert browser._sessions["current"] == "session-1"
|
|
46
|
+
|
|
47
|
+
def test_set_driver_updates_current(self):
|
|
48
|
+
"""Test set_driver updates current session"""
|
|
49
|
+
from npcpy.work import browser
|
|
50
|
+
|
|
51
|
+
browser._sessions.clear()
|
|
52
|
+
|
|
53
|
+
class MockDriver:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
browser.set_driver("session-1", MockDriver())
|
|
57
|
+
assert browser._sessions["current"] == "session-1"
|
|
58
|
+
|
|
59
|
+
browser.set_driver("session-2", MockDriver())
|
|
60
|
+
assert browser._sessions["current"] == "session-2"
|
|
61
|
+
|
|
62
|
+
def test_get_current_driver_returns_active_session(self):
|
|
63
|
+
"""Test get_current_driver returns the current driver"""
|
|
64
|
+
from npcpy.work import browser
|
|
65
|
+
|
|
66
|
+
browser._sessions.clear()
|
|
67
|
+
|
|
68
|
+
class MockDriver:
|
|
69
|
+
def __init__(self, name):
|
|
70
|
+
self.name = name
|
|
71
|
+
|
|
72
|
+
driver1 = MockDriver("driver1")
|
|
73
|
+
driver2 = MockDriver("driver2")
|
|
74
|
+
|
|
75
|
+
browser.set_driver("session-1", driver1)
|
|
76
|
+
browser.set_driver("session-2", driver2)
|
|
77
|
+
|
|
78
|
+
current = browser.get_current_driver()
|
|
79
|
+
|
|
80
|
+
assert current is driver2
|
|
81
|
+
assert current.name == "driver2"
|
|
82
|
+
|
|
83
|
+
def test_close_current_removes_session(self):
|
|
84
|
+
"""Test close_current removes the current session"""
|
|
85
|
+
from npcpy.work import browser
|
|
86
|
+
|
|
87
|
+
browser._sessions.clear()
|
|
88
|
+
|
|
89
|
+
class MockDriver:
|
|
90
|
+
def __init__(self):
|
|
91
|
+
self.quit_called = False
|
|
92
|
+
|
|
93
|
+
def quit(self):
|
|
94
|
+
self.quit_called = True
|
|
95
|
+
|
|
96
|
+
mock_driver = MockDriver()
|
|
97
|
+
browser.set_driver("session-1", mock_driver)
|
|
98
|
+
|
|
99
|
+
result = browser.close_current()
|
|
100
|
+
|
|
101
|
+
assert result is True
|
|
102
|
+
assert "session-1" not in browser._sessions
|
|
103
|
+
assert browser._sessions.get("current") is None
|
|
104
|
+
|
|
105
|
+
def test_close_current_calls_quit(self):
|
|
106
|
+
"""Test close_current calls quit on driver"""
|
|
107
|
+
from npcpy.work import browser
|
|
108
|
+
|
|
109
|
+
browser._sessions.clear()
|
|
110
|
+
|
|
111
|
+
quit_called = {"value": False}
|
|
112
|
+
|
|
113
|
+
class MockDriver:
|
|
114
|
+
def quit(self):
|
|
115
|
+
quit_called["value"] = True
|
|
116
|
+
|
|
117
|
+
browser.set_driver("session-1", MockDriver())
|
|
118
|
+
browser.close_current()
|
|
119
|
+
|
|
120
|
+
assert quit_called["value"] is True
|
|
121
|
+
|
|
122
|
+
def test_close_current_handles_quit_error(self):
|
|
123
|
+
"""Test close_current handles quit errors gracefully"""
|
|
124
|
+
from npcpy.work import browser
|
|
125
|
+
|
|
126
|
+
browser._sessions.clear()
|
|
127
|
+
|
|
128
|
+
class MockDriver:
|
|
129
|
+
def quit(self):
|
|
130
|
+
raise Exception("Browser already closed")
|
|
131
|
+
|
|
132
|
+
browser.set_driver("session-1", MockDriver())
|
|
133
|
+
|
|
134
|
+
# Should not raise, should return True
|
|
135
|
+
result = browser.close_current()
|
|
136
|
+
|
|
137
|
+
assert result is True
|
|
138
|
+
|
|
139
|
+
def test_close_current_no_session(self):
|
|
140
|
+
"""Test close_current returns False when no session"""
|
|
141
|
+
from npcpy.work import browser
|
|
142
|
+
|
|
143
|
+
browser._sessions.clear()
|
|
144
|
+
|
|
145
|
+
result = browser.close_current()
|
|
146
|
+
|
|
147
|
+
assert result is False
|
|
148
|
+
|
|
149
|
+
def test_multiple_sessions_stored(self):
|
|
150
|
+
"""Test multiple sessions can be stored"""
|
|
151
|
+
from npcpy.work import browser
|
|
152
|
+
|
|
153
|
+
browser._sessions.clear()
|
|
154
|
+
|
|
155
|
+
class MockDriver:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
browser.set_driver("session-1", MockDriver())
|
|
159
|
+
browser.set_driver("session-2", MockDriver())
|
|
160
|
+
browser.set_driver("session-3", MockDriver())
|
|
161
|
+
|
|
162
|
+
sessions = browser.get_sessions()
|
|
163
|
+
|
|
164
|
+
assert "session-1" in sessions
|
|
165
|
+
assert "session-2" in sessions
|
|
166
|
+
assert "session-3" in sessions
|
|
167
|
+
assert sessions["current"] == "session-3"
|
|
168
|
+
|
|
169
|
+
def test_get_current_driver_invalid_current(self):
|
|
170
|
+
"""Test get_current_driver handles invalid current reference"""
|
|
171
|
+
from npcpy.work import browser
|
|
172
|
+
|
|
173
|
+
browser._sessions.clear()
|
|
174
|
+
|
|
175
|
+
# Set current to non-existent session
|
|
176
|
+
browser._sessions["current"] = "nonexistent"
|
|
177
|
+
|
|
178
|
+
driver = browser.get_current_driver()
|
|
179
|
+
|
|
180
|
+
assert driver is None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class TestBrowserModuleImports:
|
|
184
|
+
"""Test browser module imports correctly."""
|
|
185
|
+
|
|
186
|
+
def test_module_imports(self):
|
|
187
|
+
"""Test browser module can be imported"""
|
|
188
|
+
from npcpy.work import browser
|
|
189
|
+
|
|
190
|
+
assert hasattr(browser, "get_sessions")
|
|
191
|
+
assert hasattr(browser, "get_current_driver")
|
|
192
|
+
assert hasattr(browser, "set_driver")
|
|
193
|
+
assert hasattr(browser, "close_current")
|
|
194
|
+
|
|
195
|
+
def test_sessions_is_module_level(self):
|
|
196
|
+
"""Test _sessions is module-level dict"""
|
|
197
|
+
from npcpy.work import browser
|
|
198
|
+
|
|
199
|
+
assert hasattr(browser, "_sessions")
|
|
200
|
+
assert isinstance(browser._sessions, dict)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class TestSessionIsolation:
|
|
204
|
+
"""Test session isolation between tests."""
|
|
205
|
+
|
|
206
|
+
def test_sessions_can_be_cleared(self):
|
|
207
|
+
"""Test sessions dict can be cleared"""
|
|
208
|
+
from npcpy.work import browser
|
|
209
|
+
|
|
210
|
+
browser._sessions["test"] = "value"
|
|
211
|
+
browser._sessions.clear()
|
|
212
|
+
|
|
213
|
+
assert len(browser._sessions) == 0
|
|
214
|
+
|
|
215
|
+
def test_sessions_persist_in_module(self):
|
|
216
|
+
"""Test sessions persist across function calls"""
|
|
217
|
+
from npcpy.work import browser
|
|
218
|
+
|
|
219
|
+
browser._sessions.clear()
|
|
220
|
+
|
|
221
|
+
class MockDriver:
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
browser.set_driver("persistent", MockDriver())
|
|
225
|
+
|
|
226
|
+
# Get sessions in separate call
|
|
227
|
+
sessions = browser.get_sessions()
|
|
228
|
+
|
|
229
|
+
assert "persistent" in sessions
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
if __name__ == "__main__":
|
|
233
|
+
pytest.main([__file__, "-v"])
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Test suite for build_funcs module."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import shutil
|
|
6
|
+
import pytest
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestGetTeamName:
|
|
11
|
+
"""Test get_team_name function."""
|
|
12
|
+
|
|
13
|
+
def test_get_team_name_from_ctx_file(self):
|
|
14
|
+
"""Test extracting team name from .ctx file"""
|
|
15
|
+
from npcpy.build_funcs import get_team_name
|
|
16
|
+
|
|
17
|
+
temp_dir = tempfile.mkdtemp()
|
|
18
|
+
try:
|
|
19
|
+
# Create a .ctx file with team name
|
|
20
|
+
ctx_content = {"name": "My Awesome Team", "version": "1.0"}
|
|
21
|
+
ctx_path = os.path.join(temp_dir, "team.ctx")
|
|
22
|
+
with open(ctx_path, "w") as f:
|
|
23
|
+
yaml.dump(ctx_content, f)
|
|
24
|
+
|
|
25
|
+
name = get_team_name(temp_dir)
|
|
26
|
+
assert name == "My Awesome Team"
|
|
27
|
+
finally:
|
|
28
|
+
shutil.rmtree(temp_dir)
|
|
29
|
+
|
|
30
|
+
def test_get_team_name_from_folder(self):
|
|
31
|
+
"""Test getting team name from folder when no .ctx file"""
|
|
32
|
+
from npcpy.build_funcs import get_team_name
|
|
33
|
+
|
|
34
|
+
temp_dir = tempfile.mkdtemp()
|
|
35
|
+
try:
|
|
36
|
+
# Create a subfolder to test
|
|
37
|
+
team_dir = os.path.join(temp_dir, "my_team")
|
|
38
|
+
os.makedirs(team_dir)
|
|
39
|
+
|
|
40
|
+
name = get_team_name(team_dir)
|
|
41
|
+
assert name == "my_team"
|
|
42
|
+
finally:
|
|
43
|
+
shutil.rmtree(temp_dir)
|
|
44
|
+
|
|
45
|
+
def test_get_team_name_npc_team_fallback(self):
|
|
46
|
+
"""Test fallback when folder is named npc_team"""
|
|
47
|
+
from npcpy.build_funcs import get_team_name
|
|
48
|
+
|
|
49
|
+
temp_dir = tempfile.mkdtemp()
|
|
50
|
+
try:
|
|
51
|
+
npc_team_dir = os.path.join(temp_dir, "npc_team")
|
|
52
|
+
os.makedirs(npc_team_dir)
|
|
53
|
+
|
|
54
|
+
name = get_team_name(npc_team_dir)
|
|
55
|
+
# Should use parent folder name
|
|
56
|
+
assert name == os.path.basename(temp_dir)
|
|
57
|
+
finally:
|
|
58
|
+
shutil.rmtree(temp_dir)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TestBuildDockerfile:
|
|
62
|
+
"""Test build_dockerfile function."""
|
|
63
|
+
|
|
64
|
+
def test_build_dockerfile_default(self):
|
|
65
|
+
"""Test Dockerfile generation with defaults"""
|
|
66
|
+
from npcpy.build_funcs import build_dockerfile
|
|
67
|
+
|
|
68
|
+
temp_dir = tempfile.mkdtemp()
|
|
69
|
+
try:
|
|
70
|
+
team_dir = os.path.join(temp_dir, "test_team")
|
|
71
|
+
os.makedirs(team_dir)
|
|
72
|
+
|
|
73
|
+
config = {"team_path": team_dir}
|
|
74
|
+
dockerfile = build_dockerfile(config)
|
|
75
|
+
|
|
76
|
+
assert "FROM python:3.11-slim" in dockerfile
|
|
77
|
+
assert "pip install --no-cache-dir npcsh" in dockerfile
|
|
78
|
+
assert "EXPOSE 5337" in dockerfile
|
|
79
|
+
assert "test_team" in dockerfile
|
|
80
|
+
finally:
|
|
81
|
+
shutil.rmtree(temp_dir)
|
|
82
|
+
|
|
83
|
+
def test_build_dockerfile_custom_port(self):
|
|
84
|
+
"""Test Dockerfile with custom port"""
|
|
85
|
+
from npcpy.build_funcs import build_dockerfile
|
|
86
|
+
|
|
87
|
+
temp_dir = tempfile.mkdtemp()
|
|
88
|
+
try:
|
|
89
|
+
team_dir = os.path.join(temp_dir, "my_team")
|
|
90
|
+
os.makedirs(team_dir)
|
|
91
|
+
|
|
92
|
+
config = {"team_path": team_dir, "port": 8080}
|
|
93
|
+
dockerfile = build_dockerfile(config)
|
|
94
|
+
|
|
95
|
+
assert "EXPOSE 8080" in dockerfile
|
|
96
|
+
assert '"8080"' in dockerfile
|
|
97
|
+
finally:
|
|
98
|
+
shutil.rmtree(temp_dir)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class TestBuildDockerCompose:
|
|
102
|
+
"""Test build_docker_compose function."""
|
|
103
|
+
|
|
104
|
+
def test_build_docker_compose_creates_files(self):
|
|
105
|
+
"""Test docker-compose build creates all expected files"""
|
|
106
|
+
from npcpy.build_funcs import build_docker_compose
|
|
107
|
+
|
|
108
|
+
temp_dir = tempfile.mkdtemp()
|
|
109
|
+
try:
|
|
110
|
+
team_dir = os.path.join(temp_dir, "test_team")
|
|
111
|
+
os.makedirs(team_dir)
|
|
112
|
+
# Create a dummy file in team dir
|
|
113
|
+
with open(os.path.join(team_dir, "test.npc"), "w") as f:
|
|
114
|
+
f.write("name: test\n")
|
|
115
|
+
|
|
116
|
+
output_dir = os.path.join(temp_dir, "build")
|
|
117
|
+
config = {"team_path": team_dir, "output_dir": output_dir}
|
|
118
|
+
|
|
119
|
+
result = build_docker_compose(config)
|
|
120
|
+
|
|
121
|
+
# Check files were created
|
|
122
|
+
assert os.path.exists(os.path.join(output_dir, "Dockerfile"))
|
|
123
|
+
assert os.path.exists(os.path.join(output_dir, "docker-compose.yml"))
|
|
124
|
+
assert os.path.exists(os.path.join(output_dir, ".env.example"))
|
|
125
|
+
assert os.path.exists(os.path.join(output_dir, "test_team"))
|
|
126
|
+
|
|
127
|
+
# Check output message
|
|
128
|
+
assert "output" in result
|
|
129
|
+
assert "Docker deployment created" in result["output"]
|
|
130
|
+
finally:
|
|
131
|
+
shutil.rmtree(temp_dir)
|
|
132
|
+
|
|
133
|
+
def test_build_docker_compose_with_cors(self):
|
|
134
|
+
"""Test docker-compose with CORS origins"""
|
|
135
|
+
from npcpy.build_funcs import build_docker_compose
|
|
136
|
+
|
|
137
|
+
temp_dir = tempfile.mkdtemp()
|
|
138
|
+
try:
|
|
139
|
+
team_dir = os.path.join(temp_dir, "test_team")
|
|
140
|
+
os.makedirs(team_dir)
|
|
141
|
+
|
|
142
|
+
output_dir = os.path.join(temp_dir, "build")
|
|
143
|
+
config = {
|
|
144
|
+
"team_path": team_dir,
|
|
145
|
+
"output_dir": output_dir,
|
|
146
|
+
"cors_origins": ["http://localhost:3000", "https://example.com"],
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
build_docker_compose(config)
|
|
150
|
+
|
|
151
|
+
# Check docker-compose.yml contains CORS
|
|
152
|
+
compose_path = os.path.join(output_dir, "docker-compose.yml")
|
|
153
|
+
with open(compose_path, "r") as f:
|
|
154
|
+
content = f.read()
|
|
155
|
+
|
|
156
|
+
assert "CORS_ORIGINS" in content
|
|
157
|
+
assert "localhost:3000" in content
|
|
158
|
+
finally:
|
|
159
|
+
shutil.rmtree(temp_dir)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestBuildFlaskServer:
|
|
163
|
+
"""Test build_flask_server function."""
|
|
164
|
+
|
|
165
|
+
def test_build_flask_server_creates_script(self):
|
|
166
|
+
"""Test Flask server script generation"""
|
|
167
|
+
from npcpy.build_funcs import build_flask_server
|
|
168
|
+
|
|
169
|
+
temp_dir = tempfile.mkdtemp()
|
|
170
|
+
try:
|
|
171
|
+
team_dir = os.path.join(temp_dir, "test_team")
|
|
172
|
+
os.makedirs(team_dir)
|
|
173
|
+
|
|
174
|
+
output_dir = os.path.join(temp_dir, "build")
|
|
175
|
+
config = {"team_path": team_dir, "output_dir": output_dir, "port": 5000}
|
|
176
|
+
|
|
177
|
+
result = build_flask_server(config)
|
|
178
|
+
|
|
179
|
+
# Check server.py was created
|
|
180
|
+
server_path = os.path.join(output_dir, "server.py")
|
|
181
|
+
assert os.path.exists(server_path)
|
|
182
|
+
|
|
183
|
+
# Check content
|
|
184
|
+
with open(server_path, "r") as f:
|
|
185
|
+
content = f.read()
|
|
186
|
+
|
|
187
|
+
assert "from npcpy.serve import start_flask_server" in content
|
|
188
|
+
assert "port=5000" in content
|
|
189
|
+
|
|
190
|
+
# Check output message
|
|
191
|
+
assert "Flask server created" in result["output"]
|
|
192
|
+
finally:
|
|
193
|
+
shutil.rmtree(temp_dir)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class TestBuildCliExecutable:
|
|
197
|
+
"""Test build_cli_executable function."""
|
|
198
|
+
|
|
199
|
+
def test_build_cli_executable_creates_scripts(self):
|
|
200
|
+
"""Test CLI script generation for NPCs"""
|
|
201
|
+
from npcpy.build_funcs import build_cli_executable
|
|
202
|
+
|
|
203
|
+
temp_dir = tempfile.mkdtemp()
|
|
204
|
+
try:
|
|
205
|
+
team_dir = os.path.join(temp_dir, "test_team")
|
|
206
|
+
os.makedirs(team_dir)
|
|
207
|
+
|
|
208
|
+
# Create some .npc files
|
|
209
|
+
for name in ["assistant", "coder", "reviewer"]:
|
|
210
|
+
npc_path = os.path.join(team_dir, f"{name}.npc")
|
|
211
|
+
with open(npc_path, "w") as f:
|
|
212
|
+
f.write(f"name: {name}\n")
|
|
213
|
+
|
|
214
|
+
output_dir = os.path.join(temp_dir, "build")
|
|
215
|
+
config = {"team_path": team_dir, "output_dir": output_dir}
|
|
216
|
+
|
|
217
|
+
result = build_cli_executable(config)
|
|
218
|
+
|
|
219
|
+
# Check scripts were created
|
|
220
|
+
assert os.path.exists(os.path.join(output_dir, "assistant"))
|
|
221
|
+
assert os.path.exists(os.path.join(output_dir, "coder"))
|
|
222
|
+
assert os.path.exists(os.path.join(output_dir, "reviewer"))
|
|
223
|
+
|
|
224
|
+
# Check output message
|
|
225
|
+
assert "CLI scripts created" in result["output"]
|
|
226
|
+
assert "assistant" in result["output"]
|
|
227
|
+
finally:
|
|
228
|
+
shutil.rmtree(temp_dir)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TestBuildStaticSite:
|
|
232
|
+
"""Test build_static_site function."""
|
|
233
|
+
|
|
234
|
+
def test_build_static_site_creates_html(self):
|
|
235
|
+
"""Test static site HTML generation"""
|
|
236
|
+
from npcpy.build_funcs import build_static_site
|
|
237
|
+
|
|
238
|
+
temp_dir = tempfile.mkdtemp()
|
|
239
|
+
try:
|
|
240
|
+
team_dir = os.path.join(temp_dir, "test_team")
|
|
241
|
+
os.makedirs(team_dir)
|
|
242
|
+
|
|
243
|
+
# Create .ctx file with team name
|
|
244
|
+
ctx_content = {"name": "Documentation Team"}
|
|
245
|
+
with open(os.path.join(team_dir, "team.ctx"), "w") as f:
|
|
246
|
+
yaml.dump(ctx_content, f)
|
|
247
|
+
|
|
248
|
+
# Create .npc files
|
|
249
|
+
npc_content = {"name": "writer", "primary_directive": "Write documentation"}
|
|
250
|
+
with open(os.path.join(team_dir, "writer.npc"), "w") as f:
|
|
251
|
+
yaml.dump(npc_content, f)
|
|
252
|
+
|
|
253
|
+
output_dir = os.path.join(temp_dir, "build")
|
|
254
|
+
config = {"team_path": team_dir, "output_dir": output_dir}
|
|
255
|
+
|
|
256
|
+
result = build_static_site(config)
|
|
257
|
+
|
|
258
|
+
# Check index.html was created
|
|
259
|
+
html_path = os.path.join(output_dir, "index.html")
|
|
260
|
+
assert os.path.exists(html_path)
|
|
261
|
+
|
|
262
|
+
# Check content
|
|
263
|
+
with open(html_path, "r") as f:
|
|
264
|
+
content = f.read()
|
|
265
|
+
|
|
266
|
+
assert "Documentation Team" in content
|
|
267
|
+
assert "writer" in content
|
|
268
|
+
assert "Write documentation" in content
|
|
269
|
+
finally:
|
|
270
|
+
shutil.rmtree(temp_dir)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
pytest.main([__file__, "-v"])
|