Download tests/test_modular_sync.py from nanoppa/gmw: direct link, hf CLI and curl.
- Browser
- Download file 16.5 kB
-
https://huggingface.co/spaces/nanoppa/gmw/resolve/main/tests/test_modular_sync.py
- Command line
-
hf download hf://spaces/nanoppa/gmw/tests/test_modular_sync.py
-
curl -L -o test_modular_sync.py https://huggingface.co/spaces/nanoppa/gmw/resolve/main/tests/test_modular_sync.py
16.5 kB
| import http.client | |
| import base64 | |
| import json | |
| import threading | |
| import unittest | |
| from unittest import mock | |
| from urllib.parse import parse_qs | |
| from gemini_web2api.config import CONFIG, DEFAULT_CONFIG | |
| from gemini_web2api.gemini import _build_payload | |
| from gemini_web2api.server import GeminiHandler, ThreadedServer | |
| from gemini_web2api.tools import google_contents_to_prompt, messages_to_prompt | |
| def _decode_payload(payload): | |
| outer = json.loads(parse_qs(payload)["f.req"][0]) | |
| return json.loads(outer[1]) | |
| def _decode_sse(body): | |
| events = [] | |
| for block in body.strip().split("\n\n"): | |
| lines = block.splitlines() | |
| event_type = next( | |
| (line[len("event: "):] for line in lines if line.startswith("event: ")), | |
| None, | |
| ) | |
| data = next( | |
| (line[len("data: "):] for line in lines if line.startswith("data: ")), | |
| None, | |
| ) | |
| if event_type and data: | |
| events.append((event_type, json.loads(data))) | |
| return events | |
| class PayloadPersistenceTests(unittest.TestCase): | |
| def setUp(self): | |
| self.original_config = dict(CONFIG) | |
| def tearDown(self): | |
| CONFIG.clear() | |
| CONFIG.update(self.original_config) | |
| def test_temporary_chats_default_to_disabled(self): | |
| self.assertIs(DEFAULT_CONFIG["temporary_chats"], False) | |
| def test_persistent_chat_payload(self): | |
| CONFIG["temporary_chats"] = False | |
| inner = _decode_payload(_build_payload("hello", 1, 4)) | |
| self.assertEqual(inner[41], [2]) | |
| self.assertIsNone(inner[45]) | |
| def test_temporary_chat_payload(self): | |
| CONFIG["temporary_chats"] = True | |
| inner = _decode_payload(_build_payload("hello", 1, 4)) | |
| self.assertEqual(inner[41], [1]) | |
| self.assertEqual(inner[45], 1) | |
| def test_payload_includes_uploaded_image_refs(self): | |
| inner = _decode_payload(_build_payload("describe", 1, 4, ["/uploaded/image-ref"])) | |
| self.assertEqual(inner[0][0], "describe") | |
| self.assertEqual(inner[0][3], [[None, None, "/uploaded/image-ref"]]) | |
| class MessageParsingTests(unittest.TestCase): | |
| def test_messages_to_prompt_extracts_openai_image_url_data_url(self): | |
| image_data = base64.b64encode(b"fake png").decode() | |
| prompt, images = messages_to_prompt([{ | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": "Describe"}, | |
| {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_data}"}}, | |
| ], | |
| }]) | |
| self.assertEqual(prompt, "Describe [Image attached]") | |
| self.assertEqual(images, [(b"fake png", "image/png")]) | |
| def test_messages_to_prompt_extracts_responses_input_image_url(self): | |
| prompt, images = messages_to_prompt([{ | |
| "role": "user", | |
| "content": [ | |
| {"type": "input_text", "text": "Describe"}, | |
| {"type": "input_image", "image_url": "https://example.com/image.png"}, | |
| ], | |
| }]) | |
| self.assertEqual(prompt, "Describe [Image attached]") | |
| self.assertEqual(images, [("https://example.com/image.png", "image/png")]) | |
| def test_messages_to_prompt_ignores_malformed_image_data_url(self): | |
| prompt, images = messages_to_prompt([{ | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": "Describe"}, | |
| {"type": "image_url", "image_url": {"url": "data:image/png;base64,%%%"}}, | |
| ], | |
| }]) | |
| self.assertEqual(prompt, "Describe") | |
| self.assertEqual(images, []) | |
| def test_google_contents_to_prompt_extracts_inline_image_data(self): | |
| image_data = base64.b64encode(b"fake png").decode() | |
| prompt, images = google_contents_to_prompt({ | |
| "contents": [{ | |
| "role": "user", | |
| "parts": [ | |
| {"text": "Describe"}, | |
| {"inlineData": {"mimeType": "image/png", "data": image_data}}, | |
| ], | |
| }], | |
| }) | |
| self.assertEqual(prompt, "Describe\n[Image attached]") | |
| self.assertEqual(images, [(b"fake png", "image/png")]) | |
| def test_google_contents_to_prompt_ignores_malformed_inline_image_data(self): | |
| prompt, images = google_contents_to_prompt({ | |
| "contents": [{ | |
| "role": "user", | |
| "parts": [ | |
| {"text": "Describe"}, | |
| {"inlineData": {"mimeType": "image/png", "data": "%%%"}}, | |
| ], | |
| }], | |
| }) | |
| self.assertEqual(prompt, "Describe") | |
| self.assertEqual(images, []) | |
| class StreamingEndpointTests(unittest.TestCase): | |
| def setUpClass(cls): | |
| cls.server = ThreadedServer(("127.0.0.1", 0), GeminiHandler) | |
| cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True) | |
| cls.thread.start() | |
| cls.port = cls.server.server_address[1] | |
| def tearDownClass(cls): | |
| cls.server.shutdown() | |
| cls.server.server_close() | |
| cls.thread.join(timeout=5) | |
| def setUp(self): | |
| self.original_config = dict(CONFIG) | |
| CONFIG["api_keys"] = [] | |
| CONFIG["log_requests"] = False | |
| def tearDown(self): | |
| CONFIG.clear() | |
| CONFIG.update(self.original_config) | |
| def post_json(self, path, payload): | |
| connection = http.client.HTTPConnection("127.0.0.1", self.port, timeout=5) | |
| connection.request( | |
| "POST", | |
| path, | |
| body=json.dumps(payload), | |
| headers={"Content-Type": "application/json"}, | |
| ) | |
| response = connection.getresponse() | |
| body = response.read().decode() | |
| headers = dict(response.getheaders()) | |
| connection.close() | |
| return response.status, headers, body | |
| def post_chunked_json(self, path, payload): | |
| connection = http.client.HTTPConnection("127.0.0.1", self.port, timeout=5) | |
| connection.request( | |
| "POST", | |
| path, | |
| body=json.dumps(payload).encode(), | |
| headers={"Content-Type": "application/json"}, | |
| encode_chunked=True, | |
| ) | |
| response = connection.getresponse() | |
| body = response.read().decode() | |
| headers = dict(response.getheaders()) | |
| connection.close() | |
| return response.status, headers, body | |
| def test_chat_stream_starts_with_assistant_role(self, generate_stream): | |
| generate_stream.return_value = iter(["hel", "lo"]) | |
| status, headers, body = self.post_json( | |
| "/v1/chat/completions", | |
| { | |
| "model": "gemini-3.6-flash", | |
| "messages": [{"role": "user", "content": "hello"}], | |
| "stream": True, | |
| }, | |
| ) | |
| self.assertEqual(status, 200) | |
| self.assertEqual(headers["Content-Type"], "text/event-stream") | |
| chunks = [ | |
| json.loads(line[len("data: "):]) | |
| for line in body.splitlines() | |
| if line.startswith("data: {") | |
| ] | |
| self.assertEqual(chunks[0]["choices"][0]["delta"], {"role": "assistant"}) | |
| self.assertEqual(chunks[1]["choices"][0]["delta"], {"content": "hel"}) | |
| self.assertEqual(chunks[2]["choices"][0]["delta"], {"content": "lo"}) | |
| self.assertTrue(body.endswith("data: [DONE]\n\n")) | |
| def test_chat_accepts_chunked_body(self, _generate): | |
| status, _, body = self.post_chunked_json( | |
| "/v1/chat/completions", | |
| { | |
| "model": "gemini-3.6-flash", | |
| "messages": [{"role": "user", "content": "hello"}], | |
| }, | |
| ) | |
| self.assertEqual(status, 200) | |
| self.assertEqual(json.loads(body)["choices"][0]["message"]["content"], "chunked ok") | |
| def test_chat_accepts_openai_image_url_data_url(self, generate, upload_image): | |
| image_data = base64.b64encode(b"fake png").decode() | |
| status, _, body = self.post_json( | |
| "/v1/chat/completions", | |
| { | |
| "model": "gemini-3.6-flash", | |
| "messages": [{ | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": "Describe this image"}, | |
| { | |
| "type": "image_url", | |
| "image_url": { | |
| "url": f"data:image/png;base64,{image_data}" | |
| }, | |
| }, | |
| ], | |
| }], | |
| }, | |
| ) | |
| self.assertEqual(status, 200) | |
| upload_image.assert_called_once_with(b"fake png", "image.png", "image/png") | |
| self.assertEqual(generate.call_args.args[3], ["/uploaded/image-ref"]) | |
| self.assertIn("[Image attached]", generate.call_args.args[0]) | |
| self.assertEqual(json.loads(body)["choices"][0]["message"]["content"], "looks good") | |
| def test_responses_accepts_input_image_url(self, generate, upload_image, fetch_image_bytes): | |
| status, _, _ = self.post_json( | |
| "/v1/responses", | |
| { | |
| "model": "gemini-3.6-flash", | |
| "input": [{ | |
| "role": "user", | |
| "content": [ | |
| {"type": "input_text", "text": "What is shown?"}, | |
| { | |
| "type": "input_image", | |
| "image_url": "https://example.com/image.jpg", | |
| }, | |
| ], | |
| }], | |
| }, | |
| ) | |
| self.assertEqual(status, 200) | |
| fetch_image_bytes.assert_called_once_with("https://example.com/image.jpg") | |
| upload_image.assert_called_once_with(b"\xff\xd8\xffremote jpeg", "image.png", "image/jpeg") | |
| self.assertEqual(generate.call_args.args[3], ["/uploaded/remote-ref"]) | |
| self.assertIn("[Image attached]", generate.call_args.args[0]) | |
| def test_responses_accepts_top_level_input_image(self, generate, upload_image): | |
| image_data = base64.b64encode(b"fake png").decode() | |
| status, _, _ = self.post_json( | |
| "/v1/responses", | |
| { | |
| "model": "gemini-3.6-flash", | |
| "input": [ | |
| {"type": "input_text", "text": "What is shown?"}, | |
| { | |
| "type": "input_image", | |
| "image_url": f"data:image/png;base64,{image_data}", | |
| }, | |
| ], | |
| }, | |
| ) | |
| self.assertEqual(status, 200) | |
| upload_image.assert_called_once_with(b"fake png", "image.png", "image/png") | |
| self.assertEqual(generate.call_args.args[3], ["/uploaded/image-ref"]) | |
| self.assertIn("What is shown?", generate.call_args.args[0]) | |
| self.assertIn("[Image attached]", generate.call_args.args[0]) | |
| def test_google_image_upload_failure_returns_502(self, _upload_image): | |
| image_data = base64.b64encode(b"fake png").decode() | |
| status, _, body = self.post_json( | |
| "/v1beta/models/gemini-3.6-flash:generateContent", | |
| { | |
| "contents": [{ | |
| "role": "user", | |
| "parts": [{ | |
| "inlineData": { | |
| "mimeType": "image/png", | |
| "data": image_data, | |
| }, | |
| }], | |
| }], | |
| }, | |
| ) | |
| self.assertEqual(status, 502) | |
| self.assertIn("image upload failed: upload denied", json.loads(body)["error"]["message"]) | |
| def test_google_stream_generate_content_uses_sse(self, _generate_stream): | |
| status, headers, body = self.post_json( | |
| "/v1beta/models/gemini-3.6-flash:streamGenerateContent", | |
| { | |
| "contents": [{ | |
| "role": "user", | |
| "parts": [{"text": "Stream this"}], | |
| }], | |
| }, | |
| ) | |
| self.assertEqual(status, 200) | |
| self.assertEqual(headers["Content-Type"], "text/event-stream") | |
| self.assertIn('"text": "streamed"', body) | |
| def test_responses_text_stream_has_complete_event_sequence(self, _generate): | |
| status, headers, body = self.post_json( | |
| "/v1/responses", | |
| { | |
| "model": "gemini-3.6-flash", | |
| "input": "hello", | |
| "stream": True, | |
| }, | |
| ) | |
| self.assertEqual(status, 200) | |
| self.assertEqual(headers["Content-Type"], "text/event-stream") | |
| events = _decode_sse(body) | |
| self.assertEqual( | |
| [event_type for event_type, _ in events], | |
| [ | |
| "response.created", | |
| "response.in_progress", | |
| "response.output_item.added", | |
| "response.content_part.added", | |
| "response.output_text.delta", | |
| "response.output_text.done", | |
| "response.content_part.done", | |
| "response.output_item.done", | |
| "response.completed", | |
| ], | |
| ) | |
| self.assertEqual( | |
| [event["sequence_number"] for _, event in events], | |
| list(range(1, len(events) + 1)), | |
| ) | |
| self.assertEqual(events[4][1]["delta"], "hello") | |
| self.assertEqual(events[-1][1]["response"]["status"], "completed") | |
| self.assertEqual(events[-1][1]["response"]["output"][0]["content"][0]["text"], "hello") | |
| def test_responses_function_call_stream_has_complete_event_sequence( | |
| self, _generate, parse_tool_calls | |
| ): | |
| parse_tool_calls.return_value = ( | |
| "", | |
| [ | |
| { | |
| "id": "call_test", | |
| "type": "function", | |
| "function": {"name": "get_weather", "arguments": '{"city":"Shanghai"}'}, | |
| } | |
| ], | |
| ) | |
| status, _, body = self.post_json( | |
| "/v1/responses", | |
| { | |
| "model": "gemini-3.6-flash", | |
| "input": "weather", | |
| "tools": [ | |
| { | |
| "type": "function", | |
| "name": "get_weather", | |
| "description": "Get weather", | |
| "parameters": {"type": "object"}, | |
| } | |
| ], | |
| "stream": True, | |
| }, | |
| ) | |
| self.assertEqual(status, 200) | |
| events = _decode_sse(body) | |
| self.assertEqual( | |
| [event_type for event_type, _ in events], | |
| [ | |
| "response.created", | |
| "response.in_progress", | |
| "response.output_item.added", | |
| "response.function_call_arguments.delta", | |
| "response.function_call_arguments.done", | |
| "response.output_item.done", | |
| "response.completed", | |
| ], | |
| ) | |
| self.assertEqual( | |
| [event["sequence_number"] for _, event in events], | |
| list(range(1, len(events) + 1)), | |
| ) | |
| self.assertEqual(events[2][1]["output_index"], 0) | |
| self.assertEqual(events[3][1]["delta"], '{"city":"Shanghai"}') | |
| self.assertEqual(events[4][1]["arguments"], '{"city":"Shanghai"}') | |
| self.assertEqual(events[-1][1]["response"]["output"][0]["name"], "get_weather") | |
| if __name__ == "__main__": | |
| unittest.main() | |