Skip to content

Commit e71dcef

Browse files
committed
test code added
CONTRIBUTING.md updated for testing instruction
1 parent f755199 commit e71dcef

File tree

10 files changed

+602
-60
lines changed

10 files changed

+602
-60
lines changed

CONTRIBUTING.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
## To set up automated Ruff linting and formatting with VS Code, Follow the instructions:
1+
# Formatter and Linter
2+
3+
To set up automated Ruff linting and formatting with VS Code, Follow the instructions:
24

35
### 1. Python Extension for VS Code (if not already installed):
46

@@ -22,3 +24,41 @@ poetry run ruff format .
2224
```bash
2325
poetry run ruff check .
2426
```
27+
28+
# Testing
29+
30+
For testing, we use `pytest`.
31+
32+
### install `pytest` if not installed yet.
33+
```bash
34+
poetry add --dev pytest
35+
```
36+
37+
### Running `pytest`
38+
This command will run files named `test_*.py` or `*_test.py`
39+
```bash
40+
poetry run pytest
41+
```
42+
43+
44+
For more detailed test result, you can use `-v` option
45+
```bash
46+
poetry run pytest -v
47+
```
48+
49+
for more detailed test information
50+
```bash
51+
poetry run pytest -vv
52+
```
53+
54+
### Writing TestCode
55+
You should name your test class and method as follows:
56+
57+
#### Class name starts with `Test`
58+
59+
#### Method name starts with `test_`
60+
61+
```python
62+
class TestMyFeature: # class name
63+
def test_feature_functionality: # method name
64+
```

code_mage/api.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ def __init__(self, model, config):
99
self.model = model if model is not None else "openrouter"
1010

1111
if self.model not in self.supported_model:
12-
sys.exit(
13-
f"{self.model} api model is not suppored. Model Supported: {self.supported_model}"
14-
)
12+
sys.exit(f"{self.model} is not suppored. Model Supported: {self.supported_model}")
1513

1614
# default api_url and api_model
1715
self.api_url = "https://openrouter.ai/api/v1"

code_mage/argsParser.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import argparse
2+
3+
4+
def arg_parser(config):
5+
VERSION = "release 0.1"
6+
7+
parser = argparse.ArgumentParser(
8+
description="This Tool translates a source file into another programming language."
9+
)
10+
11+
# Arguments
12+
parser.add_argument("source_files", nargs="*", help="The path to the source file to translate.")
13+
14+
# Options
15+
parser.add_argument(
16+
"--language",
17+
"-l",
18+
help="The language to translate the source files into",
19+
default=config.get("language"),
20+
)
21+
parser.add_argument(
22+
"--output",
23+
"-o",
24+
help="Specify the output file name(without extension)",
25+
default=config.get("output"),
26+
)
27+
parser.add_argument(
28+
"--version",
29+
"-v",
30+
action="version",
31+
version=f"CodeMage {VERSION}",
32+
help="Show program's version number and exit",
33+
)
34+
parser.add_argument(
35+
"--token-usage",
36+
"-t",
37+
action="store_true",
38+
help="Get information about token usage for the prompt and response",
39+
default=config.get("token_usage", False),
40+
)
41+
parser.add_argument(
42+
"--model", "-m", help="Specify the LLM API model name", default=config.get("model")
43+
)
44+
parser.add_argument(
45+
"--stream",
46+
"-s",
47+
action="store_true",
48+
help="Stream out the output into stdout",
49+
default=config.get("stream", False),
50+
)
51+
52+
return parser.parse_args()

code_mage/codeMage.py

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,16 @@
11
#!/usr/bin/env python3
22

3-
import argparse
43
import sys
54
from .translator import Translator
65
from .loadConfig import load_config
6+
from .argsParser import arg_parser
77

88

99
def main():
10-
VERSION = "release 0.1"
11-
1210
# Load config file
1311
config = load_config()
1412

15-
parser = argparse.ArgumentParser(
16-
description="This Tool translates a source file into another programming language."
17-
)
18-
19-
# Arguments
20-
parser.add_argument("source_files", nargs="*", help="The path to the source file to translate.")
21-
22-
# Options
23-
parser.add_argument(
24-
"--language",
25-
"-l",
26-
help="The language to translate the source files into",
27-
default=config.get("language"),
28-
)
29-
parser.add_argument(
30-
"--output",
31-
"-o",
32-
help="Specify the output file name(without extension)",
33-
default=config.get("output"),
34-
)
35-
parser.add_argument(
36-
"--version",
37-
"-v",
38-
action="version",
39-
version=f"CodeMage {VERSION}",
40-
help="Show program's version number and exit",
41-
)
42-
parser.add_argument(
43-
"--token-usage",
44-
"-t",
45-
action="store_true",
46-
help="Get information about token usage for the prompt and response",
47-
default=config.get("token_usage", False),
48-
)
49-
parser.add_argument(
50-
"--model", "-m", help="Specify the LLM API model name", default=config.get("model")
51-
)
52-
parser.add_argument(
53-
"--stream",
54-
"-s",
55-
action="store_true",
56-
help="Stream out the output into stdout",
57-
default=config.get("stream", False),
58-
)
59-
60-
args = parser.parse_args()
13+
args = arg_parser(config)
6114

6215
if not args.source_files:
6316
sys.exit(
@@ -67,6 +20,7 @@ def main():
6720
translator = Translator(args, config)
6821

6922
for index, file in enumerate(args.source_files):
23+
print(file)
7024
translator.translate(file, index + 1)
7125

7226

code_mage/translator.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,17 @@ def __get_output_filename(self, origin_file_name, file_number):
8585
return output_file_name
8686

8787
def __get_source_lang(self, extension):
88+
src_lang = "none"
8889
if extension == ".js":
89-
self.src_lang = "javascript"
90+
src_lang = "javascript"
9091
elif extension == ".py":
91-
self.src_lang = "python"
92+
src_lang = "python"
9293
elif extension == ".cpp":
93-
self.src_lang = "c++"
94+
src_lang = "c++"
9495
elif extension == ".java":
95-
self.src_lang = "java"
96+
src_lang = "java"
9697

97-
return self.src_lang
98+
return src_lang
9899

99100
def __get_output_ext(self, language):
100101
# Decide output file extension.

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ toml = "^0.10.2"
1313

1414
[tool.poetry.group.dev.dependencies]
1515
ruff = "^0.7.1"
16+
pytest = "^8.3.3"
17+
pytest-mock = "^3.14.0"
1618

1719
[tool.ruff]
1820
line-length = 100
19-
exclude = [
20-
"tests/*",
21+
exclude = [
2122
"docs/*",
2223
"*.pyc",
2324
"migrations/*",

tests/test_api.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import pytest
2+
from unittest.mock import patch, MagicMock
3+
import os
4+
from code_mage.api import Api
5+
6+
7+
class TestApiConstructor:
8+
@pytest.fixture
9+
def mock_config(self):
10+
return {"OPENROUTER_API_KEY": "fake_api_key_from_toml"}
11+
12+
def test_constructor_with_openrouter_and_config(self, mock_config, monkeypatch):
13+
# This overwirtes only the specified key-value pair.
14+
monkeypatch.setenv("OPENROUTER_API_KEY", "")
15+
16+
api = Api(model="openrouter", config=mock_config)
17+
18+
assert api.model == "openrouter"
19+
assert api.api_model == "sao10k/l3-euryale-70b"
20+
assert api.api_url == "https://openrouter.ai/api/v1"
21+
assert api.api_key == "fake_api_key_from_toml"
22+
assert api.supported_model == ["groq", "openrouter"]
23+
24+
def test_constructor_with_groq_and_env(self, mock_config, monkeypatch):
25+
# Mock the environment variable for OPENROUTER_API_KEY
26+
monkeypatch.setenv("GROQ_API_KEY", "fake_api_key_from_env")
27+
28+
api = Api(model="groq", config=mock_config)
29+
30+
assert api.model == "groq"
31+
assert api.api_model == "llama3-8b-8192"
32+
assert api.api_url == "https://api.groq.com/openai/v1"
33+
assert api.api_key == "fake_api_key_from_env"
34+
assert api.supported_model == ["groq", "openrouter"]
35+
36+
def test_constructor_with_unsupported_model(self, mock_config, monkeypatch):
37+
monkeypatch.setenv("GROQ_API_KEY", "fake_api_key_from_env")
38+
39+
with pytest.raises(SystemExit) as exc_info:
40+
api = Api(model="unsupported_model", config=mock_config) # noqa
41+
42+
assert (
43+
str(exc_info.value)
44+
== "unsupported_model is not suppored. Model Supported: ['groq', 'openrouter']"
45+
)
46+
47+
48+
class TestApiWithEnv:
49+
@pytest.fixture
50+
def mock_config(self):
51+
return {
52+
"OPENROUTER_API_KEY": "fake_openrouter_api_key_from_toml",
53+
"GROQ_API_KEY": "fake_groq_api_key",
54+
}
55+
56+
# Mock OpenAI response
57+
def mock_api_call(self, *args, **kwargs):
58+
# Return a mock response object with a `choices` attribute
59+
mock_response = MagicMock()
60+
mock_response.choices = [{"message": {"content": "Translated code"}}]
61+
return mock_response
62+
63+
# Test when model is supported
64+
@patch.dict(
65+
os.environ,
66+
{
67+
"OPENROUTER_API_KEY": "fake_api_key_from_env",
68+
"GROQ_API_KEY": "fake_groq_api_key_from_env",
69+
},
70+
)
71+
@patch("code_mage.api.OpenAI")
72+
def test_api_with_openrouter(self, mock_openai_class):
73+
# Mock the OpenAI client and its methods
74+
mock_client = MagicMock()
75+
76+
# When the code create OpenAI class inside call_api(), it's replaced by mock_openai_class
77+
mock_openai_class.return_value = mock_client
78+
mock_client.chat.completions.create = MagicMock(return_value=self.mock_api_call())
79+
80+
# Create an instance of the Api with a supported model
81+
api = Api(model="openrouter", config={})
82+
83+
# Simulate an API call
84+
response = api.call_api(target_lang="python", code="some_code")
85+
86+
# Check if the API call was made with the expected arguments
87+
mock_client.chat.completions.create.assert_called_once_with(
88+
extra_headers={},
89+
model="sao10k/l3-euryale-70b",
90+
messages=[
91+
{"role": "system", "content": "only display the code without any explanation"},
92+
{"role": "user", "content": "translate this to python language: some_code"},
93+
],
94+
stream=False,
95+
)
96+
97+
# Assert the response content
98+
assert response.choices[0]["message"]["content"] == "Translated code"
99+
100+
@patch.dict(
101+
os.environ,
102+
{
103+
"OPENROUTER_API_KEY": "fake_api_key_from_env",
104+
"GROQ_API_KEY": "fake_groq_api_key_from_env",
105+
},
106+
)
107+
@patch("code_mage.api.OpenAI")
108+
def test_api_with_groq(self, mock_openai_class):
109+
# Mock the OpenAI client and its methods
110+
mock_client = MagicMock()
111+
112+
# When the code create OpenAI class inside call_api(), it's replaced by mock_openai_class
113+
mock_openai_class.return_value = mock_client
114+
mock_client.chat.completions.create = MagicMock(return_value=self.mock_api_call())
115+
116+
# Create an instance of the Api with a supported model
117+
api = Api(model="groq", config={})
118+
119+
# Simulate an API call
120+
response = api.call_api(target_lang="python", code="some_code")
121+
122+
# Check if the API call was made with the expected arguments
123+
mock_client.chat.completions.create.assert_called_once_with(
124+
extra_headers={},
125+
model="llama3-8b-8192",
126+
messages=[
127+
{"role": "system", "content": "only display the code without any explanation"},
128+
{"role": "user", "content": "translate this to python language: some_code"},
129+
],
130+
stream=False,
131+
)
132+
133+
assert api.api_url == "https://api.groq.com/openai/v1"
134+
assert api.api_key == "fake_groq_api_key_from_env"
135+
136+
# Assert the response content
137+
assert response.choices[0]["message"]["content"] == "Translated code"

0 commit comments

Comments
 (0)