Skip to content

Commit d42070e

Browse files
committed
test(main) Add unit tests for Helm template generation and IaC workflows (basic, bugfix, install, template) with pytest and mocker
1 parent 0fc7ad0 commit d42070e

5 files changed

Lines changed: 226 additions & 34 deletions

File tree

app/tests/test_helm_template.py

Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,118 @@
22
from unittest.mock import patch
33
from fastapi.testclient import TestClient
44
from app.main import app
5-
from app.models import HelmTemplateGeneration, Pod, Persistance, Ingress
5+
from app.models import HelmTemplateGeneration, Pod, Persistance, Ingress, Environment, Output
66

77
client = TestClient(app)
88

99
@pytest.fixture
1010
def sample_helm_template_input():
1111
return HelmTemplateGeneration(
12-
api_version="3",
12+
api_version=3,
1313
pods=[
14-
Pod(name="nginx", image="nginx:latest", target_port=80, replicas=2, persistance=Persistance(enabled=False), environment=[{"name": "DEBUG", "value": "true"}], stateless=True, ingress=Ingress(enabled=True)),
15-
Pod(name="redis", image="redis:latest", target_port=6379, replicas=1, persistance=Persistance(enabled=True), environment=[], stateless=False, ingress=Ingress(enabled=False))
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+
)
1634
]
1735
)
1836

19-
@patch("app.main.gpt_service")
20-
@patch("app.main.edit_directory_generator")
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+
2155
@patch("app.main.execute_pythonfile")
22-
def test_helm_template_generation(mock_execute_pythonfile, mock_edit_directory_generator, mock_gpt_service, sample_helm_template_input):
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+
):
2364
mock_gpt_service.return_value = "Generated Python Code"
2465

25-
response = client.post("/Helm-template/", json=sample_helm_template_input.dict())
66+
response = client.post("/Helm-template/", json=sample_helm_template_input.model_dump())
2667

2768
assert response.status_code == 200
2869
assert response.json()["output"] == "output"
2970

3071
mock_gpt_service.assert_called_once()
3172
mock_edit_directory_generator.assert_called_once_with("helm_generator", "Generated Python Code")
3273
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: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,53 @@
88
mocked_gpt_response = "Mocked GPT response for IaC-basic"
99

1010
@pytest.fixture
11-
def valid_basic_data():
11+
def valid_iac_basic_data():
1212
return IaCBasicInput(
13-
input="Create a basic configuration",
13+
input="How do I manage state effectively in Terraform?",
1414
service="terraform",
1515
min_tokens=100,
1616
max_tokens=500
1717
)
1818

1919
@patch('app.main.gpt_service', return_value=mocked_gpt_response)
20-
def test_iac_basic_generation(mock_gpt_service, valid_basic_data):
20+
def test_iac_basic_generation(mock_gpt_service, valid_iac_basic_data):
2121
"""
22-
Test the /IaC-basic/ endpoint with valid input data.
22+
Test the /IaC-basic/ endpoint with valid input data to ensure correct output.
2323
"""
24-
response = client.post("/IaC-basic/", json=valid_basic_data.model_dump())
24+
response = client.post("/IaC-basic/", json=valid_iac_basic_data.model_dump())
2525
assert response.status_code == 200
2626
assert response.json() == {"output": mocked_gpt_response}
2727

2828
expected_prompt = """
29-
Write a robust answer about terraform,
30-
focusing on the latest update of terraform and based on this question:Create a basic configuration,
31-
minimun length of answer is 100 and maximum length is 500
29+
Write a robust answer about terraform, focusing on the latest update of terraform and based on this question:How do I manage state effectively in Terraform?,
30+
minimun length of answer is 100 and maximum length is 500
3231
"""
3332
actual_prompt = " ".join(mock_gpt_service.call_args[0][0].split())
3433
normalized_expected_prompt = " ".join(expected_prompt.split())
3534
assert actual_prompt == normalized_expected_prompt
35+
36+
@patch('app.main.gpt_service')
37+
def test_basic_generation(mock_gpt_service):
38+
"""
39+
Test the /IaC-basic/ endpoint with an invalid service to ensure proper validation.
40+
"""
41+
invalid_input = {
42+
"input": "Create a basic configuration",
43+
"service": "invalid_service", # Unsupported service (invalid)
44+
"min_tokens": 100,
45+
"max_tokens": 500
46+
}
47+
48+
response = client.post("/IaC-basic/", json=invalid_input)
49+
assert response.status_code == 422, f"Expected status code 422, got {response.status_code}"
50+
assert "detail" in response.json(), "Response JSON does not contain 'detail'"
51+
errors = response.json()["detail"]
52+
expected_error_loc = ["body", "service"]
53+
expected_error_msg = "Service must be one of ['terraform']."
54+
assert any(
55+
error["loc"] == expected_error_loc and error_msg in error["msg"]
56+
for error in errors
57+
for error_msg in [expected_error_msg]
58+
), f"Expected error message '{expected_error_msg}' at location {expected_error_loc}, but not found."
59+
60+
mock_gpt_service.assert_not_called()

app/tests/test_iac_bugfix.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,15 @@
1010
@pytest.fixture
1111
def valid_bugfix_data():
1212
return IaCBugfixInput(
13-
input="Fix the deployment issue",
14-
bug_description="Application fails to start on version 1.2.3",
15-
version="1.2.3",
13+
bug_description="Application fails to start on version latest",
14+
version="latest",
1615
service="terraform",
1716
min_tokens=100,
1817
max_tokens=500
1918
)
2019

2120
@patch('app.main.gpt_service', return_value=mocked_gpt_response)
22-
def test_iac_bugfix_generation(mock_gpt_service, valid_bugfix_data):
21+
def test_bugfix(mock_gpt_service, valid_bugfix_data):
2322
"""
2423
Test the /IaC-bugfix/ endpoint with valid input data to ensure correct output.
2524
"""
@@ -28,11 +27,38 @@ def test_iac_bugfix_generation(mock_gpt_service, valid_bugfix_data):
2827
assert response.json() == {"output": mocked_gpt_response}
2928

3029
expected_prompt = """
31-
Write a clear answer to debug terraform
32-
focusing on the version 1.2.3 of terraform and based on this bug:Application fails to start on version 1.2.3,
33-
generate a correct code that help us to solve this bug.
34-
minimun length of answer is 100 and maximum length is 500
30+
Write a clear answer to debug terraform
31+
focusing on the version latest of terraform and based on this bug:Application fails to start on version latest,
32+
generate a correct code that help us to solve this bug.
33+
minimum length of answer is 100 and maximum length is 500
3534
"""
35+
36+
3637
actual_prompt = " ".join(mock_gpt_service.call_args[0][0].split())
3738
normalized_expected_prompt = " ".join(expected_prompt.split())
3839
assert actual_prompt == normalized_expected_prompt
40+
41+
42+
@patch('app.main.gpt_service')
43+
def test_bugfix_invalid(mock_gpt_service):
44+
"""
45+
Test the /IaC-bugfix/ endpoint with a single invalid input to ensure proper validation.
46+
"""
47+
invalid_input = {
48+
"bug_description": "", # Emptydescription
49+
"version": "latest",
50+
"service": "terraform",
51+
"min_tokens": 100,
52+
"max_tokens": 500
53+
}
54+
response = client.post("/IaC-bugfix/", json=invalid_input)
55+
assert response.status_code == 422, f"Expected status code 422, got {response.status_code}"
56+
assert "detail" in response.json(), "Response JSON does not contain 'detail'"
57+
errors = response.json()["detail"]
58+
expected_error_loc = ["body", "bug_description"]
59+
expected_error_msg = "Bug description cannot be empty."
60+
assert any(
61+
error["loc"] == expected_error_loc and expected_error_msg in error["msg"]
62+
for error in errors
63+
), f"Expected error message '{expected_error_msg}' at location {expected_error_loc}, but not found."
64+
mock_gpt_service.assert_not_called()

app/tests/test_iac_install.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,48 @@ def valid_installation_data():
1515
min_tokens=100,
1616
max_tokens=500
1717
)
18-
1918
@patch('app.main.gpt_service', return_value=mocked_gpt_response)
20-
def test_iac_install_generation(mock_gpt_service, valid_installation_data):
19+
def test_install(mock_gpt_service, valid_installation_data):
2120
"""
2221
Test the /IaC-install/ endpoint with valid input data to ensure correct output.
2322
"""
2423
response = client.post("/IaC-install/", json=valid_installation_data.model_dump())
2524
assert response.status_code == 200
2625
assert response.json() == {"output": mocked_gpt_response}
27-
2826
expected_prompt = """
2927
generate a clear shell script about installation terraform in ubuntu based on terraform document.
30-
without any additional note. just script for installation.
28+
without any additional note. just script for installation. please consider new lines without any additional comment.
3129
"""
32-
actual_prompt = " ".join(mock_gpt_service.call_args[0][0].split())
33-
normalized_expected_prompt = " ".join(expected_prompt.split())
30+
actual_prompt = " ".join(mock_gpt_service.call_args[0][0].split()).strip()
31+
normalized_expected_prompt = " ".join(expected_prompt.split()).strip()
32+
if actual_prompt != normalized_expected_prompt:
33+
print("Expected Prompt:", repr(normalized_expected_prompt))
34+
print("Actual Prompt:", repr(actual_prompt))
35+
3436
assert actual_prompt == normalized_expected_prompt
37+
38+
@patch('app.main.gpt_service')
39+
def test_install_invalid(mock_gpt_service):
40+
"""
41+
Test the /IaC-install/ endpoint with an invalid 'os' value to ensure proper validation.
42+
"""
43+
invalid_input = {
44+
"os": "Kali",
45+
"service": "terraform"
46+
}
47+
48+
response = client.post("/IaC-install/", json=invalid_input)
49+
50+
assert response.status_code == 422, f"Expected status code 422, got {response.status_code}"
51+
assert "detail" in response.json(), "Response JSON does not contain 'detail'"
52+
errors = response.json()["detail"]
53+
54+
expected_error_loc = ["body", "os"]
55+
expected_error_msg = "OS must be one of ['ubuntu', 'centos', 'debian']."
56+
57+
assert any(
58+
error["loc"] == expected_error_loc and error_msg in error["msg"]
59+
for error in errors
60+
for error_msg in [expected_error_msg]
61+
), f"Expected error message '{expected_error_msg}' at location {expected_error_loc}, but not found."
62+
mock_gpt_service.assert_not_called()

app/tests/test_iac_template.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,48 @@
99
@pytest.fixture
1010
def sample_iac_template_input():
1111
return IaCTemplateGeneration(
12-
service="MyService",
12+
service="terraform",
1313
CI_integration=True,
14-
base_config="BasicConfig"
14+
base_config="ec2"
1515
)
1616

1717
@patch("app.main.gpt_service")
1818
@patch("app.main.edit_directory_generator")
1919
@patch("app.main.execute_pythonfile")
20-
def test_iac_template_generation(mock_execute_pythonfile, mock_edit_directory_generator, mock_gpt_service, sample_iac_template_input):
20+
def test_template(mock_execute_pythonfile, mock_edit_directory_generator, mock_gpt_service, sample_iac_template_input):
2121
mock_gpt_service.return_value = "Generated Python Code"
2222

23-
response = client.post("/IaC-template/", json=sample_iac_template_input.dict())
23+
response = client.post("/IaC-template/", json=sample_iac_template_input.model_dump())
2424

2525
assert response.status_code == 200
2626
assert response.json()["output"] == "output"
2727

2828
mock_gpt_service.assert_called_once()
2929
mock_edit_directory_generator.assert_called_once_with("terraform_generator", "Generated Python Code")
3030
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)