Skip to content

Commit 96bd7e9

Browse files
authored
Merge pull request #37 from maralzar/master
test(main): Add unit tests for Helm template generation and IaC workf…
2 parents 81732c7 + 81ffe5c commit 96bd7e9

8 files changed

Lines changed: 338 additions & 3 deletions

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ execute `sh run.sh` in your terminal
5353
If you want to run and use this chatbot app within your Kubernetes cluster, you can easily install it using the Helm chart provided in this repository
5454

5555
helm install [RELEASE_NAME] helm/ -f helm/values.yaml
56+
# Test
5657

58+
Run tests:
59+
```
60+
cd app
61+
pytest tests/
62+
```
5763
# Contributing
5864
please read the [Contribution guide](https://github.com/abolfazl8131/devops-gpt/blob/master/CONTRIBUTING.md)
5965
# Maintenance

app/prompt_generators.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def IaC_bugfix_generator(input : IaCBugfixInput) -> str:
1919
Write a clear answer to debug {input.service}
2020
focusing on the version {input.version} of {input.service} and based on this bug:{input.bug_description},
2121
generate a correct code that help us to solve this bug.
22-
minimun length of answer is {input.min_tokens} and maximum length is {input.max_tokens}
22+
minimum length of answer is {input.min_tokens} and maximum length is {input.max_tokens}
2323
2424
"""
2525
return prompt
@@ -28,8 +28,8 @@ def IaC_bugfix_generator(input : IaCBugfixInput) -> str:
2828
def IaC_installation_generator(input : IaCInstallationInput) -> str:
2929

3030
prompt = f"""
31-
generate a clear shell acript about installation {input.service} in {input.os} based on {input.service} document.
32-
without any additional note. just script for installation. please consider new lines with out any additional comment.
31+
generate a clear shell script about installation {input.service} in {input.os} based on {input.service} document.
32+
without any additional note. just script for installation. please consider new lines without any additional comment.
3333
3434
"""
3535
return prompt

app/tests/__init__.py

Whitespace-only changes.

app/tests/test_helm_template.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import pytest
2+
from unittest.mock import patch
3+
from fastapi.testclient import TestClient
4+
from app.main import app
5+
from app.models import HelmTemplateGeneration, Pod, Persistance, Ingress, Environment, Output
6+
7+
client = TestClient(app)
8+
9+
@pytest.fixture
10+
def sample_helm_template_input():
11+
return HelmTemplateGeneration(
12+
api_version=3,
13+
pods=[
14+
Pod(
15+
name="nginx",
16+
image="nginx:latest",
17+
target_port=80,
18+
replicas=2,
19+
persistance=Persistance(enabled=False),
20+
environment=[Environment(name="DEBUG", value="true")],
21+
stateless=True,
22+
ingress=Ingress(enabled=True)
23+
),
24+
Pod(
25+
name="redis",
26+
image="redis:latest",
27+
target_port=6379,
28+
replicas=1,
29+
persistance=Persistance(enabled=True),
30+
environment=[],
31+
stateless=False,
32+
ingress=Ingress(enabled=False)
33+
)
34+
]
35+
)
36+
37+
@pytest.fixture
38+
def invalid_input():
39+
return {
40+
"api_version": 0, # Invalid api_version
41+
"pods": [
42+
{
43+
"name": "", # Invalid: empty name
44+
"image": "nginx:latest",
45+
"target_port": 70000, # Invalid: exceeds 65535
46+
"replicas": 0, # Invalid: less than 1
47+
"persistance": {"size": "invalid_size", "accessModes": "InvalidMode"}, # Invalid size and accessModes
48+
"environment": [{"name": "", "value": ""}], # Invalid: empty name and value
49+
"stateless": True,
50+
"ingress": {"enabled": True, "host": ""} # Invalid: empty host
51+
}
52+
]
53+
}
54+
55+
@patch("app.main.execute_pythonfile")
56+
@patch("app.main.edit_directory_generator")
57+
@patch("app.main.gpt_service")
58+
def test_helm_template_generation(
59+
mock_gpt_service,
60+
mock_edit_directory_generator,
61+
mock_execute_pythonfile,
62+
sample_helm_template_input
63+
):
64+
mock_gpt_service.return_value = "Generated Python Code"
65+
66+
response = client.post("/Helm-template/", json=sample_helm_template_input.model_dump())
67+
68+
assert response.status_code == 200
69+
assert response.json()["output"] == "output"
70+
71+
mock_gpt_service.assert_called_once()
72+
mock_edit_directory_generator.assert_called_once_with("helm_generator", "Generated Python Code")
73+
mock_execute_pythonfile.assert_called_once_with("MyHelm", "helm_generator")
74+
75+
@patch("app.main.execute_pythonfile")
76+
@patch("app.main.edit_directory_generator")
77+
@patch("app.main.gpt_service")
78+
def test_helm_invalid_api(
79+
mock_gpt_service,
80+
mock_edit_directory_generator,
81+
mock_execute_pythonfile,
82+
invalid_input
83+
):
84+
invalid_input = invalid_input.copy()
85+
invalid_input["api_version"] = 0
86+
response = client.post("/Helm-template/", json=invalid_input)
87+
assert response.status_code == 422
88+
assert "detail" in response.json()
89+
errors = response.json()["detail"]
90+
assert any(
91+
error["loc"] == ["body", "api_version"] and "API version must be a positive integer." in error["msg"]
92+
for error in errors
93+
)
94+
mock_gpt_service.assert_not_called()
95+
mock_edit_directory_generator.assert_not_called()
96+
mock_execute_pythonfile.assert_not_called()
97+
98+
@patch("app.main.execute_pythonfile")
99+
@patch("app.main.edit_directory_generator")
100+
@patch("app.main.gpt_service")
101+
def test_helm_invalid_port(
102+
mock_gpt_service,
103+
mock_edit_directory_generator,
104+
mock_execute_pythonfile,
105+
invalid_input):
106+
107+
invalid_input = invalid_input.copy()
108+
invalid_input["pods"][0]["target_port"] = 70000 # Invalid target_port
109+
response = client.post("/Helm-template/", json=invalid_input)
110+
assert response.status_code == 422
111+
assert "detail" in response.json()
112+
errors = response.json()["detail"]
113+
assert any(
114+
error["loc"] == ["body", "pods", 0, "target_port"] and "Target port must be between 1 and 65535." in error["msg"]
115+
for error in errors
116+
)
117+
mock_gpt_service.assert_not_called()
118+
mock_edit_directory_generator.assert_not_called()
119+
mock_execute_pythonfile.assert_not_called()

app/tests/test_iac_basic.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from unittest.mock import patch
4+
from app.main import app
5+
from app.models import IaCBasicInput
6+
7+
client = TestClient(app)
8+
mocked_gpt_response = "Mocked GPT response for IaC-basic"
9+
10+
@pytest.fixture
11+
def valid_iac_basic_data():
12+
return IaCBasicInput(
13+
input="How do I manage state effectively in Terraform?",
14+
service="terraform",
15+
min_tokens=100,
16+
max_tokens=500
17+
)
18+
19+
@patch('app.main.gpt_service', return_value=mocked_gpt_response)
20+
def test_iac_basic_generation(mock_gpt_service, valid_iac_basic_data):
21+
"""
22+
Test the /IaC-basic/ endpoint with valid input data to ensure correct output.
23+
"""
24+
response = client.post("/IaC-basic/", json=valid_iac_basic_data.model_dump())
25+
assert response.status_code == 200
26+
assert response.json() == {"output": mocked_gpt_response}
27+
28+
@patch('app.main.gpt_service')
29+
def test_basic_generation(mock_gpt_service):
30+
"""
31+
Test the /IaC-basic/ endpoint with an invalid service to ensure proper validation.
32+
"""
33+
invalid_input = {
34+
"input": "Create a basic configuration",
35+
"service": "invalid_service", # Unsupported service (invalid)
36+
"min_tokens": 100,
37+
"max_tokens": 500
38+
}
39+
40+
response = client.post("/IaC-basic/", json=invalid_input)
41+
assert response.status_code == 422, f"Expected status code 422, got {response.status_code}"
42+
assert "detail" in response.json(), "Response JSON does not contain 'detail'"
43+
errors = response.json()["detail"]
44+
expected_error_loc = ["body", "service"]
45+
expected_error_msg = "Service must be one of ['terraform']."
46+
assert any(
47+
error["loc"] == expected_error_loc and error_msg in error["msg"]
48+
for error in errors
49+
for error_msg in [expected_error_msg]
50+
), f"Expected error message '{expected_error_msg}' at location {expected_error_loc}, but not found."
51+
52+
mock_gpt_service.assert_not_called()

app/tests/test_iac_bugfix.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from unittest.mock import patch
4+
from app.main import app
5+
from app.models import IaCBugfixInput
6+
7+
client = TestClient(app)
8+
mocked_gpt_response = "Mocked GPT response for IaC-bugfix"
9+
10+
@pytest.fixture
11+
def valid_bugfix_data():
12+
return IaCBugfixInput(
13+
bug_description="Application fails to start on version latest",
14+
version="latest",
15+
service="terraform",
16+
min_tokens=100,
17+
max_tokens=500
18+
)
19+
20+
@patch('app.main.gpt_service', return_value=mocked_gpt_response)
21+
def test_bugfix(mock_gpt_service, valid_bugfix_data):
22+
"""
23+
Test the /IaC-bugfix/ endpoint with valid input data to ensure correct output.
24+
"""
25+
response = client.post("/IaC-bugfix/", json=valid_bugfix_data.model_dump())
26+
assert response.status_code == 200
27+
assert response.json() == {"output": mocked_gpt_response}
28+
29+
@patch('app.main.gpt_service')
30+
def test_bugfix_invalid(mock_gpt_service):
31+
"""
32+
Test the /IaC-bugfix/ endpoint with a single invalid input to ensure proper validation.
33+
"""
34+
invalid_input = {
35+
"bug_description": "", # Emptydescription
36+
"version": "latest",
37+
"service": "terraform",
38+
"min_tokens": 100,
39+
"max_tokens": 500
40+
}
41+
response = client.post("/IaC-bugfix/", json=invalid_input)
42+
assert response.status_code == 422, f"Expected status code 422, got {response.status_code}"
43+
assert "detail" in response.json(), "Response JSON does not contain 'detail'"
44+
errors = response.json()["detail"]
45+
expected_error_loc = ["body", "bug_description"]
46+
expected_error_msg = "Bug description cannot be empty."
47+
assert any(
48+
error["loc"] == expected_error_loc and expected_error_msg in error["msg"]
49+
for error in errors
50+
), f"Expected error message '{expected_error_msg}' at location {expected_error_loc}, but not found."
51+
mock_gpt_service.assert_not_called()

app/tests/test_iac_install.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from unittest.mock import patch
4+
from app.main import app
5+
from app.models import IaCInstallationInput
6+
7+
client = TestClient(app)
8+
mocked_gpt_response = "Mocked shell script for installing terraform on Ubuntu."
9+
10+
@pytest.fixture
11+
def valid_installation_data():
12+
return IaCInstallationInput(
13+
os="ubuntu",
14+
service="terraform",
15+
min_tokens=100,
16+
max_tokens=500
17+
)
18+
@patch('app.main.gpt_service', return_value=mocked_gpt_response)
19+
def test_install(mock_gpt_service, valid_installation_data):
20+
"""
21+
Test the /IaC-install/ endpoint with valid input data to ensure correct output.
22+
"""
23+
response = client.post("/IaC-install/", json=valid_installation_data.model_dump())
24+
assert response.status_code == 200
25+
assert response.json() == {"output": mocked_gpt_response}
26+
27+
@patch('app.main.gpt_service')
28+
def test_install_invalid(mock_gpt_service):
29+
"""
30+
Test the /IaC-install/ endpoint with an invalid 'os' value to ensure proper validation.
31+
"""
32+
invalid_input = {
33+
"os": "Kali",
34+
"service": "terraform"
35+
}
36+
37+
response = client.post("/IaC-install/", json=invalid_input)
38+
39+
assert response.status_code == 422, f"Expected status code 422, got {response.status_code}"
40+
assert "detail" in response.json(), "Response JSON does not contain 'detail'"
41+
errors = response.json()["detail"]
42+
43+
expected_error_loc = ["body", "os"]
44+
expected_error_msg = "OS must be one of ['ubuntu', 'centos', 'debian']."
45+
46+
assert any(
47+
error["loc"] == expected_error_loc and error_msg in error["msg"]
48+
for error in errors
49+
for error_msg in [expected_error_msg]
50+
), f"Expected error message '{expected_error_msg}' at location {expected_error_loc}, but not found."
51+
mock_gpt_service.assert_not_called()

app/tests/test_iac_template.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import pytest
2+
from unittest.mock import patch
3+
from fastapi.testclient import TestClient
4+
from app.main import app
5+
from app.models import IaCTemplateGeneration
6+
7+
client = TestClient(app)
8+
9+
@pytest.fixture
10+
def sample_iac_template_input():
11+
return IaCTemplateGeneration(
12+
service="terraform",
13+
CI_integration=True,
14+
base_config="ec2"
15+
)
16+
17+
@patch("app.main.gpt_service")
18+
@patch("app.main.edit_directory_generator")
19+
@patch("app.main.execute_pythonfile")
20+
def test_template(mock_execute_pythonfile, mock_edit_directory_generator, mock_gpt_service, sample_iac_template_input):
21+
mock_gpt_service.return_value = "Generated Python Code"
22+
23+
response = client.post("/IaC-template/", json=sample_iac_template_input.model_dump())
24+
25+
assert response.status_code == 200
26+
assert response.json()["output"] == "output"
27+
28+
mock_gpt_service.assert_called_once()
29+
mock_edit_directory_generator.assert_called_once_with("terraform_generator", "Generated Python Code")
30+
mock_execute_pythonfile.assert_called_once_with("MyTerraform", "terraform_generator")
31+
32+
@patch("app.main.gpt_service")
33+
@patch("app.main.edit_directory_generator")
34+
@patch("app.main.execute_pythonfile")
35+
def test_template_invalid(mock_execute_pythonfile, mock_edit_directory_generator, mock_gpt_service):
36+
"""
37+
Test the /IaC-template/ endpoint with an invalid 'base_config' to ensure proper validation.
38+
"""
39+
invalid_input = {
40+
"CI_integration": True,
41+
"base_config": "k8s",
42+
"service": "terraform"
43+
}
44+
response = client.post("/IaC-template/", json=invalid_input)
45+
assert response.status_code == 422, f"Expected status code 422, got {response.status_code}"
46+
assert "detail" in response.json(), "Response JSON does not contain 'detail'"
47+
errors = response.json()["detail"]
48+
expected_error_loc = ["body", "base_config"]
49+
expected_error_msg = "Base config must be one of ['ec2', 's3', 'rds']."
50+
assert any(
51+
error["loc"] == expected_error_loc and expected_error_msg in error["msg"]
52+
for error in errors
53+
), f"Expected error message '{expected_error_msg}' at location {expected_error_loc}, but not found."
54+
mock_gpt_service.assert_not_called()
55+
mock_edit_directory_generator.assert_not_called()
56+
mock_execute_pythonfile.assert_not_called()

0 commit comments

Comments
 (0)