diff --git a/config/config.yaml b/config/config.yaml index 28a312a9e..6dff55b4e 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -14,7 +14,6 @@ OPENAI_BASE_URL: "https://api.openai.com/v1" OPENAI_API_MODEL: "gpt-4-1106-preview" MAX_TOKENS: 4096 RPM: 10 -LLM_TYPE: OpenAI # Except for these three major models – OpenAI, MetaGPT LLM, and Azure – other large models can be distinguished based on the validity of the key. TIMEOUT: 60 # Timeout for llm invocation #DEFAULT_PROVIDER: openai diff --git a/docs/.well-known/openapi.yaml b/docs/.well-known/openapi.yaml index bc291b7db..47ca04b23 100644 --- a/docs/.well-known/openapi.yaml +++ b/docs/.well-known/openapi.yaml @@ -11,7 +11,7 @@ paths: post: summary: Generate greeting description: Generates a greeting message. - operationId: hello.post_greeting + operationId: openapi_v3_hello.post_greeting responses: 200: description: greeting response diff --git a/metagpt/actions/prepare_documents.py b/metagpt/actions/prepare_documents.py index a936ea655..5c5798d95 100644 --- a/metagpt/actions/prepare_documents.py +++ b/metagpt/actions/prepare_documents.py @@ -35,7 +35,6 @@ class PrepareDocuments(Action): if path.exists() and not CONFIG.inc: shutil.rmtree(path) CONFIG.project_path = path - CONFIG.project_name = path.name CONFIG.git_repo = GitRepository(local_path=path, auto_init=True) async def run(self, with_messages, **kwargs): diff --git a/metagpt/actions/write_code.py b/metagpt/actions/write_code.py index 25c4912c3..7377442b5 100644 --- a/metagpt/actions/write_code.py +++ b/metagpt/actions/write_code.py @@ -130,7 +130,7 @@ class WriteCode(Action): if not coding_context.code_doc: # avoid root_path pydantic ValidationError if use WriteCode alone root_path = CONFIG.src_workspace if CONFIG.src_workspace else "" - coding_context.code_doc = Document(filename=coding_context.filename, root_path=root_path) + coding_context.code_doc = Document(filename=coding_context.filename, root_path=str(root_path)) coding_context.code_doc.content = code return coding_context diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index d51c0a7be..073d8c076 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -14,6 +14,7 @@ from __future__ import annotations import json +import uuid from pathlib import Path from typing import Optional @@ -117,7 +118,7 @@ class WritePRD(Action): # if sas.result: # logger.info(sas.result) # logger.info(rsp) - project_name = CONFIG.project_name if CONFIG.project_name else "" + project_name = CONFIG.project_name or "" context = CONTEXT_TEMPLATE.format(requirements=requirements, project_name=project_name) exclude = [PROJECT_NAME.key] if project_name else [] node = await WRITE_PRD_NODE.fill(context=context, llm=self.llm, exclude=exclude) # schema=schema @@ -183,6 +184,8 @@ class WritePRD(Action): ws_name = CodeParser.parse_str(block="Project Name", text=prd) if ws_name: CONFIG.project_name = ws_name + if not CONFIG.project_name: # The LLM failed to provide a project name, and the user didn't provide one either. + CONFIG.project_name = "app" + uuid.uuid4().hex[:16] CONFIG.git_repo.rename_root(CONFIG.project_name) async def _is_bugfix(self, context) -> bool: diff --git a/metagpt/tools/metagpt_oas3_api_svc.py b/metagpt/tools/metagpt_oas3_api_svc.py index 319e7efb2..8e9f4a0da 100644 --- a/metagpt/tools/metagpt_oas3_api_svc.py +++ b/metagpt/tools/metagpt_oas3_api_svc.py @@ -5,6 +5,12 @@ @Author : mashenquan @File : metagpt_oas3_api_svc.py @Desc : MetaGPT OpenAPI Specification 3.0 REST API service + + curl -X 'POST' \ + 'http://localhost:8080/openapi/greeting/dave' \ + -H 'accept: text/plain' \ + -H 'Content-Type: application/json' \ + -d '{}' """ from pathlib import Path @@ -15,7 +21,7 @@ import connexion def oas_http_svc(): """Start the OAS 3.0 OpenAPI HTTP service""" print("http://localhost:8080/oas3/ui/") - specification_dir = Path(__file__).parent.parent.parent / ".well-known" + specification_dir = Path(__file__).parent.parent.parent / "docs/.well-known" app = connexion.AsyncApp(__name__, specification_dir=str(specification_dir)) app.add_api("metagpt_oas3_api.yaml") app.add_api("openapi.yaml") diff --git a/metagpt/tools/openapi_v3_hello.py b/metagpt/tools/openapi_v3_hello.py index c8f5de42d..d1c83eac2 100644 --- a/metagpt/tools/openapi_v3_hello.py +++ b/metagpt/tools/openapi_v3_hello.py @@ -23,7 +23,7 @@ async def post_greeting(name: str) -> str: if __name__ == "__main__": - specification_dir = Path(__file__).parent.parent.parent / ".well-known" + specification_dir = Path(__file__).parent.parent.parent / "docs/.well-known" app = connexion.AsyncApp(__name__, specification_dir=str(specification_dir)) app.add_api("openapi.yaml", arguments={"title": "Hello World Example"}) app.run(port=8082) diff --git a/metagpt/utils/file_repository.py b/metagpt/utils/file_repository.py index ff750fbbb..0ddca414d 100644 --- a/metagpt/utils/file_repository.py +++ b/metagpt/utils/file_repository.py @@ -138,6 +138,8 @@ class FileRepository: files = self._git_repo.changed_files relative_files = {} for p, ct in files.items(): + if ct.value == "D": # deleted + continue try: rf = Path(p).relative_to(self._relative_path) except ValueError: diff --git a/setup.py b/setup.py index 17fe8815e..94e9a35c2 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ extras_require["test"] = [ "chromadb==0.4.14", "gradio==3.0.0", "grpcio-status==1.48.2", + "mock==5.1.0", ] extras_require["pyppeteer"] = [ diff --git a/tests/conftest.py b/tests/conftest.py index 1f4a73030..419ac7d0c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,7 @@ import json import logging import os import re +import uuid from typing import Optional import pytest @@ -151,9 +152,9 @@ def loguru_caplog(caplog): # init & dispose git repo -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(scope="function", autouse=True) def setup_and_teardown_git_repo(request): - CONFIG.git_repo = GitRepository(local_path=DEFAULT_WORKSPACE_ROOT / "unittest") + CONFIG.git_repo = GitRepository(local_path=DEFAULT_WORKSPACE_ROOT / f"unittest/{uuid.uuid4().hex}") CONFIG.git_reinit = True # Destroy git repo at the end of the test session. diff --git a/tests/metagpt/tools/test_hello.py b/tests/metagpt/tools/test_openapi_v3_hello.py similarity index 65% rename from tests/metagpt/tools/test_hello.py rename to tests/metagpt/tools/test_openapi_v3_hello.py index 7e61532ab..5726cf8e0 100644 --- a/tests/metagpt/tools/test_hello.py +++ b/tests/metagpt/tools/test_openapi_v3_hello.py @@ -3,7 +3,7 @@ """ @Time : 2023/12/26 @Author : mashenquan -@File : test_hello.py +@File : test_openapi_v3_hello.py """ import asyncio import subprocess @@ -24,13 +24,14 @@ async def test_hello(): process = subprocess.Popen(["python", str(script_pathname)], cwd=workdir, env=env) await asyncio.sleep(5) - url = "http://localhost:8082/openapi/greeting/dave" - headers = {"accept": "text/plain", "Content-Type": "application/json"} - data = {} - response = requests.post(url, headers=headers, json=data) - assert response.text == "Hello dave\n" - - process.terminate() + try: + url = "http://localhost:8082/openapi/greeting/dave" + headers = {"accept": "text/plain", "Content-Type": "application/json"} + data = {} + response = requests.post(url, headers=headers, json=data) + assert response.text == "Hello dave\n" + finally: + process.terminate() if __name__ == "__main__": diff --git a/tests/metagpt/utils/test_redis.py b/tests/metagpt/utils/test_redis.py index b93ff0cdb..d499418ac 100644 --- a/tests/metagpt/utils/test_redis.py +++ b/tests/metagpt/utils/test_redis.py @@ -6,20 +6,34 @@ @File : test_redis.py """ +import mock import pytest from metagpt.config import CONFIG from metagpt.utils.redis import Redis +async def async_mock_from_url(*args, **kwargs): + mock_client = mock.AsyncMock() + mock_client.set.return_value = None + mock_client.get.side_effect = [b"test", b""] + return mock_client + + @pytest.mark.asyncio -async def test_redis(): +@mock.patch("aioredis.from_url", return_value=async_mock_from_url()) +async def test_redis(mock_from_url): + # Mock + # mock_client = mock.AsyncMock() + # mock_client.set.return_value=None + # mock_client.get.side_effect = [b'test', b''] + # mock_from_url.return_value = mock_client + # Prerequisites - assert CONFIG.REDIS_HOST and CONFIG.REDIS_HOST != "YOUR_REDIS_HOST" - assert CONFIG.REDIS_PORT and CONFIG.REDIS_PORT != "YOUR_REDIS_PORT" - # assert CONFIG.REDIS_USER - assert CONFIG.REDIS_PASSWORD is not None and CONFIG.REDIS_PASSWORD != "YOUR_REDIS_PASSWORD" - assert CONFIG.REDIS_DB is not None and CONFIG.REDIS_DB != "YOUR_REDIS_DB_INDEX, str, 0-based" + CONFIG.REDIS_HOST = "MOCK_REDIS_HOST" + CONFIG.REDIS_PORT = "MOCK_REDIS_PORT" + CONFIG.REDIS_PASSWORD = "MOCK_REDIS_PASSWORD" + CONFIG.REDIS_DB = 0 conn = Redis() assert not conn.is_valid diff --git a/tests/metagpt/utils/test_s3.py b/tests/metagpt/utils/test_s3.py index f74e7b52a..132aa0635 100644 --- a/tests/metagpt/utils/test_s3.py +++ b/tests/metagpt/utils/test_s3.py @@ -9,20 +9,36 @@ import uuid from pathlib import Path import aiofiles +import mock import pytest from metagpt.config import CONFIG +from metagpt.utils.common import aread from metagpt.utils.s3 import S3 @pytest.mark.asyncio -async def test_s3(): +@mock.patch("aioboto3.Session") +async def test_s3(mock_session_class): + # Set up the mock response + data = await aread(__file__, "utf-8") + mock_session_object = mock.Mock() + reader_mock = mock.AsyncMock() + reader_mock.read.side_effect = [data.encode("utf-8"), b"", data.encode("utf-8")] + type(reader_mock).url = mock.PropertyMock(return_value="https://mock") + mock_client = mock.AsyncMock() + mock_client.put_object.return_value = None + mock_client.get_object.return_value = {"Body": reader_mock} + mock_client.__aenter__.return_value = mock_client + mock_client.__aexit__.return_value = None + mock_session_object.client.return_value = mock_client + mock_session_class.return_value = mock_session_object + # Prerequisites - assert CONFIG.S3_ACCESS_KEY and CONFIG.S3_ACCESS_KEY != "YOUR_S3_ACCESS_KEY" - assert CONFIG.S3_SECRET_KEY and CONFIG.S3_SECRET_KEY != "YOUR_S3_SECRET_KEY" - assert CONFIG.S3_ENDPOINT_URL and CONFIG.S3_ENDPOINT_URL != "YOUR_S3_ENDPOINT_URL" - # assert CONFIG.S3_SECURE: true # true/false - assert CONFIG.S3_BUCKET and CONFIG.S3_BUCKET != "YOUR_S3_BUCKET" + # assert CONFIG.S3_ACCESS_KEY and CONFIG.S3_ACCESS_KEY != "YOUR_S3_ACCESS_KEY" + # assert CONFIG.S3_SECRET_KEY and CONFIG.S3_SECRET_KEY != "YOUR_S3_SECRET_KEY" + # assert CONFIG.S3_ENDPOINT_URL and CONFIG.S3_ENDPOINT_URL != "YOUR_S3_ENDPOINT_URL" + # assert CONFIG.S3_BUCKET and CONFIG.S3_BUCKET != "YOUR_S3_BUCKET" conn = S3() assert conn.is_valid @@ -42,6 +58,7 @@ async def test_s3(): assert "http" in res # Mock session env + type(reader_mock).url = mock.PropertyMock(return_value="") old_options = CONFIG.options.copy() new_options = old_options.copy() new_options["S3_ACCESS_KEY"] = "YOUR_S3_ACCESS_KEY" @@ -54,6 +71,8 @@ async def test_s3(): finally: CONFIG.set_context(old_options) + await reader.close() + if __name__ == "__main__": pytest.main([__file__, "-s"])