166 lines
6.4 KiB
Python
166 lines
6.4 KiB
Python
import importlib.util
|
|
import json
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def load_gpt_img_node():
|
|
spec = importlib.util.spec_from_file_location("gpt_img_node", ROOT / "gpt_img_node.py")
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
class FakeResponse:
|
|
headers = {"content-type": "application/json"}
|
|
|
|
def __init__(self, payload):
|
|
self.payload = payload
|
|
|
|
def read(self, _size=-1):
|
|
return json.dumps(self.payload).encode("utf-8")
|
|
|
|
|
|
class LLMNodeTests(unittest.TestCase):
|
|
def test_parse_json_text_extracts_output_text_from_response(self):
|
|
module = load_gpt_img_node()
|
|
response = FakeResponse(
|
|
{
|
|
"output": [
|
|
{"type": "reasoning", "summary": []},
|
|
{
|
|
"type": "message",
|
|
"content": [
|
|
{"type": "output_text", "text": "Hello"},
|
|
{"type": "output_text", "text": " world"},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
)
|
|
|
|
self.assertEqual(module._parse_json_text(response, "provider"), "Hello world")
|
|
|
|
def test_oauth_llm_node_sends_reasoning_effort_and_returns_text_and_raw_json(self):
|
|
module = load_gpt_img_node()
|
|
captured = {}
|
|
|
|
def fake_post(auth_value, payload, timeout_sec):
|
|
captured["auth_value"] = auth_value
|
|
captured["payload"] = payload
|
|
captured["timeout_sec"] = timeout_sec
|
|
return "answer", {"id": "resp_123", "output": []}
|
|
|
|
with mock.patch.object(module, "_ensure_oauth") as ensure_oauth:
|
|
with mock.patch.object(module, "_post_llm_oauth", side_effect=fake_post):
|
|
text, raw_json = module.GPTImgOAuthLLM().chat(
|
|
system_prompt="Be terse.",
|
|
prompt="Say hi.",
|
|
model="gpt-5.4",
|
|
reasoning_effort="high",
|
|
max_output_tokens=256,
|
|
oauth_port=10531,
|
|
auto_start_oauth=True,
|
|
timeout_sec=90,
|
|
)
|
|
|
|
ensure_oauth.assert_called_once_with(10531, True)
|
|
self.assertEqual(text, "answer")
|
|
self.assertEqual(json.loads(raw_json)["id"], "resp_123")
|
|
self.assertEqual(captured["auth_value"], 10531)
|
|
self.assertEqual(captured["timeout_sec"], 90)
|
|
self.assertEqual(captured["payload"]["reasoning"], {"effort": "high"})
|
|
self.assertEqual(captured["payload"]["max_output_tokens"], 256)
|
|
self.assertEqual(captured["payload"]["input"][0]["role"], "developer")
|
|
self.assertEqual(captured["payload"]["input"][1]["role"], "user")
|
|
|
|
def test_api_llm_node_contract_includes_api_key_and_reasoning_effort(self):
|
|
module = load_gpt_img_node()
|
|
inputs = module.GPTImgAPILLM.INPUT_TYPES()
|
|
required = inputs["required"]
|
|
optional = inputs["optional"]
|
|
|
|
self.assertIn("api_key", required)
|
|
self.assertIn("reasoning_effort", required)
|
|
self.assertIn("gpt-5.5-pro", required["model"][0])
|
|
self.assertIn("system_prompt_input", optional)
|
|
self.assertIn("user_prompt_input", optional)
|
|
self.assertTrue(optional["system_prompt_input"][1]["forceInput"])
|
|
self.assertTrue(optional["user_prompt_input"][1]["forceInput"])
|
|
self.assertEqual(required["reasoning_effort"][1]["default"], "medium")
|
|
self.assertEqual(module.GPTImgAPILLM.RETURN_TYPES, ("STRING", "STRING"))
|
|
self.assertEqual(module.GPTImgAPILLM.RETURN_NAMES, ("text", "raw_response_json"))
|
|
|
|
def test_oauth_llm_node_contract_includes_prompt_input_sockets(self):
|
|
module = load_gpt_img_node()
|
|
inputs = module.GPTImgOAuthLLM.INPUT_TYPES()
|
|
optional = inputs["optional"]
|
|
|
|
self.assertIn("gpt-5.5-pro", inputs["required"]["model"][0])
|
|
self.assertIn("system_prompt_input", optional)
|
|
self.assertIn("user_prompt_input", optional)
|
|
self.assertTrue(optional["system_prompt_input"][1]["forceInput"])
|
|
self.assertTrue(optional["user_prompt_input"][1]["forceInput"])
|
|
|
|
def test_api_llm_prefers_connected_prompt_inputs_over_widgets(self):
|
|
module = load_gpt_img_node()
|
|
captured = {}
|
|
|
|
def fake_post(auth_value, payload, timeout_sec):
|
|
captured["auth_value"] = auth_value
|
|
captured["payload"] = payload
|
|
captured["timeout_sec"] = timeout_sec
|
|
return "answer", {"id": "resp_456", "output": []}
|
|
|
|
with mock.patch.object(module, "_post_llm_api", side_effect=fake_post):
|
|
text, raw_json = module.GPTImgAPILLM().chat(
|
|
system_prompt="widget system",
|
|
prompt="widget user",
|
|
api_key="key",
|
|
model="gpt-5.5",
|
|
reasoning_effort="medium",
|
|
max_output_tokens=512,
|
|
timeout_sec=120,
|
|
system_prompt_input="socket system",
|
|
user_prompt_input="socket user",
|
|
)
|
|
|
|
self.assertEqual(text, "answer")
|
|
self.assertEqual(json.loads(raw_json)["id"], "resp_456")
|
|
self.assertEqual(captured["payload"]["input"][0]["content"], "socket system")
|
|
self.assertEqual(captured["payload"]["input"][1]["content"], "socket user")
|
|
|
|
def test_api_llm_falls_back_to_widget_prompts_when_inputs_unconnected(self):
|
|
module = load_gpt_img_node()
|
|
captured = {}
|
|
|
|
def fake_post(auth_value, payload, timeout_sec):
|
|
captured["payload"] = payload
|
|
return "answer", {"id": "resp_789", "output": []}
|
|
|
|
with mock.patch.object(module, "_post_llm_api", side_effect=fake_post):
|
|
module.GPTImgAPILLM().chat(
|
|
system_prompt="widget system",
|
|
prompt="widget user",
|
|
api_key="key",
|
|
model="gpt-5.5",
|
|
reasoning_effort="medium",
|
|
max_output_tokens=512,
|
|
timeout_sec=120,
|
|
system_prompt_input=None,
|
|
user_prompt_input=None,
|
|
)
|
|
|
|
self.assertEqual(captured["payload"]["input"][0]["content"], "widget system")
|
|
self.assertEqual(captured["payload"]["input"][1]["content"], "widget user")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|