Skip to content

Commit 8636fdb

Browse files
committed
Fix CI workflow and code quality issues
- Fix safety CLI command in workflow (use --output json --save-json) - Update Codecov upload to use 'files' parameter instead of 'file' - Update Allure CLI to latest version (2.27.0) in workflow - Fix all flake8 code quality issues: * Remove unused imports in conftest.py * Fix blank line spacing issues across test files * Fix long lines in test assertions * Remove unnecessary f-string placeholders * Ensure all files end with newlines - Maintain proper PEP8 compliance across codebase
1 parent b60fd10 commit 8636fdb

8 files changed

Lines changed: 144 additions & 73 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
- name: Upload coverage to Codecov
5959
uses: codecov/codecov-action@v5
6060
with:
61-
file: ./coverage.xml
61+
files: ./coverage.xml
6262
flags: unittests
6363
name: codecov-umbrella
6464

@@ -73,9 +73,9 @@ jobs:
7373
run: |
7474
sudo apt-get update
7575
sudo apt-get install -y default-jre
76-
curl -o allure-2.13.8.tgz -Ls https://github.com/allure-framework/allure2/releases/download/2.13.8/allure-2.13.8.tgz
77-
sudo tar -zxvf allure-2.13.8.tgz -C /opt/
78-
sudo ln -s /opt/allure-2.13.8/bin/allure /usr/bin/allure
76+
curl -o allure-2.27.0.tgz -Ls https://github.com/allure-framework/allure2/releases/download/2.27.0/allure-2.27.0.tgz
77+
sudo tar -zxvf allure-2.27.0.tgz -C /opt/
78+
sudo ln -s /opt/allure-2.27.0/bin/allure /usr/bin/allure
7979
allure --version
8080
8181
- name: Generate Allure HTML report
@@ -123,7 +123,7 @@ jobs:
123123
run: bandit -r . -f json -o bandit-report.json || true
124124

125125
- name: Run Safety check
126-
run: safety check --json --output safety-report.json || true
126+
run: safety check --output json --save-json safety-report.json || true
127127

128128
- name: Upload security reports
129129
uses: actions/upload-artifact@v4
@@ -183,9 +183,9 @@ jobs:
183183
run: |
184184
sudo apt-get update
185185
sudo apt-get install -y default-jre
186-
curl -o allure-2.13.8.tgz -Ls https://github.com/allure-framework/allure2/releases/download/2.13.8/allure-2.13.8.tgz
187-
sudo tar -zxvf allure-2.13.8.tgz -C /opt/
188-
sudo ln -s /opt/allure-2.13.8/bin/allure /usr/bin/allure
186+
curl -o allure-2.27.0.tgz -Ls https://github.com/allure-framework/allure2/releases/download/2.27.0/allure-2.27.0.tgz
187+
sudo tar -zxvf allure-2.27.0.tgz -C /opt/
188+
sudo ln -s /opt/allure-2.27.0/bin/allure /usr/bin/allure
189189
allure --version
190190
191191
- name: Generate test documentation

conftest.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
import allure
3-
import requests
4-
from utils.api_utils import load_config, log_request_response
3+
from utils.api_utils import load_config
4+
55

66
@pytest.fixture(scope="session")
77
def config():
@@ -10,6 +10,7 @@ def config():
1010
"""
1111
return load_config()
1212

13+
1314
@pytest.fixture(scope="session")
1415
def base_url(config):
1516
"""
@@ -18,6 +19,7 @@ def base_url(config):
1819
env = config["env"]
1920
return config["environments"][env]["base_url"]
2021

22+
2123
@pytest.fixture(scope="function")
2224
def driver():
2325
"""
@@ -29,6 +31,7 @@ def driver():
2931
# We keep this fixture placeholder in case future tests integrate with UI.
3032
yield None
3133

34+
3235
@pytest.fixture(autouse=True)
3336
def attach_allure_logs(request):
3437
"""
@@ -51,6 +54,7 @@ def attach_allure_logs(request):
5154
attachment_type=allure.attachment_type.TEXT
5255
)
5356

57+
5458
def pytest_configure(config):
5559
"""
5660
Pytest hook to customize test configuration.
@@ -65,9 +69,9 @@ def pytest_configure(config):
6569
# Create reports directory if it doesn't exist
6670
import os
6771
os.makedirs("reports", exist_ok=True)
68-
72+
6973
# Create Allure environment properties file for reporting context
7074
with open("reports/environment.properties", "w") as f:
7175
f.write(f"BaseURL={load_config()['environments'][load_config()['env']]['base_url']}\n")
7276
f.write("Framework=API Test Automation\n")
73-
f.write("Language=Python\n")
77+
f.write("Language=Python\n")

monitor_workflow.sh

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/bin/bash
2+
3+
# GitHub Actions workflow monitoring script
4+
echo "🔍 Monitoring GitHub Actions workflow status..."
5+
6+
# Function to get latest workflow run status
7+
get_workflow_status() {
8+
curl -s -H "Accept: application/vnd.github.v3+json" \
9+
https://api.github.com/repos/SaahilParmar/API-Test-Automation-Framework/actions/runs?per_page=1 | \
10+
python -c "
11+
import sys, json
12+
try:
13+
data = json.load(sys.stdin)
14+
if data['workflow_runs']:
15+
run = data['workflow_runs'][0]
16+
print(f'📊 Run #{run[\"run_number\"]} - {run[\"display_title\"]}')
17+
print(f'📅 Status: {run[\"status\"]}')
18+
if run.get('conclusion'):
19+
conclusion = run['conclusion']
20+
emoji = '✅' if conclusion == 'success' else '❌' if conclusion == 'failure' else '⚠️'
21+
print(f'{emoji} Conclusion: {conclusion}')
22+
print(f'🔗 URL: {run[\"html_url\"]}')
23+
print(f'⏰ Started: {run[\"run_started_at\"]}')
24+
print(f'📝 Commit: {run[\"head_sha\"][:8]}')
25+
else:
26+
print('❌ No workflow runs found')
27+
except Exception as e:
28+
print(f'❌ Error: {e}')
29+
"
30+
}
31+
32+
# Monitor for up to 5 minutes with 30-second intervals
33+
echo "⏱️ Monitoring for up to 5 minutes (30-second intervals)..."
34+
echo ""
35+
36+
for i in {1..10}; do
37+
echo "🔄 Check $i/10:"
38+
get_workflow_status
39+
echo ""
40+
41+
# Check if workflow is complete
42+
status=$(curl -s -H "Accept: application/vnd.github.v3+json" \
43+
https://api.github.com/repos/SaahilParmar/API-Test-Automation-Framework/actions/runs?per_page=1 | \
44+
python -c "import sys, json; data=json.load(sys.stdin); print(data['workflow_runs'][0]['status'] if data['workflow_runs'] else 'unknown')")
45+
46+
if [ "$status" = "completed" ]; then
47+
echo "🎉 Workflow completed!"
48+
break
49+
fi
50+
51+
if [ $i -lt 10 ]; then
52+
echo "⏳ Waiting 30 seconds before next check..."
53+
sleep 30
54+
fi
55+
done
56+
57+
echo ""
58+
echo "📋 Monitoring complete. Check the GitHub Actions tab for detailed logs:"
59+
echo " https://github.com/SaahilParmar/API-Test-Automation-Framework/actions"

tests/test_api_validation.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,26 @@ def test_headers_validation():
3030
with allure.step("Send GET request to users endpoint"):
3131
url = f"{BASE_URL}/users?page=1"
3232
response = requests.get(url, headers=get_headers())
33-
33+
3434
with allure.step("Verify response status is 200"):
3535
assert response.status_code == 200
36-
36+
3737
with allure.step("Validate Content-Type header"):
3838
assert response.headers["Content-Type"].startswith("application/json")
39-
39+
4040
with allure.step("Validate response headers are present"):
4141
# Check for Content-Type (always present)
4242
assert "Content-Type" in response.headers
43-
43+
4444
# Check for either Content-Length or Transfer-Encoding (both are valid)
4545
has_content_length = "Content-Length" in response.headers
4646
has_transfer_encoding = "Transfer-Encoding" in response.headers
47-
assert has_content_length or has_transfer_encoding, "Either Content-Length or Transfer-Encoding should be present"
48-
47+
assert has_content_length or has_transfer_encoding, \
48+
"Either Content-Length or Transfer-Encoding should be present"
49+
4950
# Validate Date header is present
5051
assert "Date" in response.headers
51-
52+
5253
with allure.step("Attach headers to report"):
5354
headers_info = dict(response.headers)
5455
allure.attach(str(headers_info), "Response Headers", allure.attachment_type.TEXT)
@@ -68,23 +69,23 @@ def test_user_list_schema_validation():
6869
with allure.step("Send GET request to user list endpoint"):
6970
url = f"{BASE_URL}/users?page=1"
7071
response = requests.get(url, headers=get_headers())
71-
72+
7273
with allure.step("Verify response status is 200"):
7374
assert response.status_code == 200
74-
75+
7576
with allure.step("Load and apply schema validation"):
7677
data = response.json()
7778
schema = load_schema("user_list_schema.json")
7879
validate(instance=data, schema=schema)
79-
80+
8081
with allure.step("Validate specific data structure"):
8182
assert "data" in data
8283
assert "page" in data
8384
assert "per_page" in data
8485
assert "total" in data
8586
assert "total_pages" in data
8687
assert isinstance(data["data"], list)
87-
88+
8889
with allure.step("Validate user objects in data array"):
8990
for user in data["data"]:
9091
assert "id" in user
@@ -95,7 +96,7 @@ def test_user_list_schema_validation():
9596
assert isinstance(user["id"], int)
9697
assert isinstance(user["email"], str)
9798
assert "@" in user["email"] # Basic email validation
98-
99+
99100
with allure.step("Attach response for verification"):
100101
allure.attach(response.text, "User List Response", allure.attachment_type.JSON)
101102

@@ -114,15 +115,15 @@ def test_single_user_schema_validation():
114115
with allure.step("Send GET request to single user endpoint"):
115116
url = f"{BASE_URL}/users/2"
116117
response = requests.get(url, headers=get_headers())
117-
118+
118119
with allure.step("Verify response status is 200"):
119120
assert response.status_code == 200
120-
121+
121122
with allure.step("Load and apply schema validation"):
122123
data = response.json()
123124
schema = load_schema("single_user_schema.json")
124125
validate(instance=data, schema=schema)
125-
126+
126127
with allure.step("Validate user data structure"):
127128
assert "data" in data
128129
assert "support" in data
@@ -132,12 +133,12 @@ def test_single_user_schema_validation():
132133
assert "first_name" in user_data
133134
assert "last_name" in user_data
134135
assert "avatar" in user_data
135-
136+
136137
with allure.step("Validate support information"):
137138
support = data["support"]
138139
assert "url" in support
139140
assert "text" in support
140-
141+
141142
with allure.step("Attach response for verification"):
142143
allure.attach(response.text, "Single User Response", allure.attachment_type.JSON)
143144

@@ -157,15 +158,16 @@ def test_response_time_validation(endpoint):
157158
with allure.step(f"Send GET request to {endpoint}"):
158159
url = f"{BASE_URL}{endpoint}"
159160
response = requests.get(url, headers=get_headers())
160-
161+
161162
with allure.step("Verify response status is 200"):
162163
assert response.status_code == 200
163-
164+
164165
with allure.step("Validate response time is acceptable"):
165166
response_time = response.elapsed.total_seconds()
166167
# Expecting response within 5 seconds (configurable)
167168
max_response_time = config.get("timeout_seconds", 5)
168-
assert response_time < max_response_time, f"Response time {response_time}s exceeded limit of {max_response_time}s"
169-
169+
assert response_time < max_response_time, \
170+
f"Response time {response_time}s exceeded limit of {max_response_time}s"
171+
170172
with allure.step("Attach performance metrics"):
171173
allure.attach(f"Response Time: {response_time:.3f} seconds", "Performance Metrics", allure.attachment_type.TEXT)

tests/test_error_scenarios.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ def test_get_user_not_found():
2929
with allure.step("Send GET request for non-existent user"):
3030
url = f"{BASE_URL}/users/9999"
3131
response = requests.get(url, headers=get_headers())
32-
32+
3333
with allure.step("Verify response status is 404"):
3434
assert response.status_code == 404
35-
35+
3636
with allure.step("Verify response body is empty or contains error info"):
3737
# ReqRes API returns empty object for 404
3838
data = response.json()
3939
assert data == {}
40-
40+
4141
with allure.step("Attach response details to report"):
4242
allure.attach(response.text, "404 Response Body", allure.attachment_type.JSON)
4343

@@ -57,11 +57,11 @@ def test_get_user_invalid_id(invalid_id):
5757
with allure.step(f"Send GET request with invalid ID: {invalid_id}"):
5858
url = f"{BASE_URL}/users/{invalid_id}"
5959
response = requests.get(url, headers=get_headers())
60-
60+
6161
with allure.step("Verify response indicates error or not found"):
6262
# API might return 404 or other error codes for invalid IDs
6363
assert response.status_code in [400, 404, 422]
64-
64+
6565
with allure.step("Attach response details to report"):
6666
allure.attach(response.text, f"Invalid ID Response: {invalid_id}", allure.attachment_type.JSON)
6767

@@ -80,7 +80,7 @@ def test_create_user_empty_payload():
8080
with allure.step("Send POST request with empty payload"):
8181
url = f"{BASE_URL}/users"
8282
response = requests.post(url, json={}, headers=get_headers())
83-
83+
8484
with allure.step("Verify response (API may accept empty payload)"):
8585
# ReqRes API might accept empty payloads, so we check what actually happens
8686
if response.status_code == 201:
@@ -90,7 +90,7 @@ def test_create_user_empty_payload():
9090
else:
9191
# If API rejects empty payload, status should indicate error
9292
assert response.status_code in [400, 422]
93-
93+
9494
with allure.step("Attach response details to report"):
9595
allure.attach(response.text, "Empty Payload Response", allure.attachment_type.JSON)
9696

@@ -111,11 +111,11 @@ def test_create_user_invalid_json():
111111
headers = get_headers()
112112
# Send malformed JSON as string
113113
response = requests.post(url, data='{"name": "test", "job":}', headers=headers)
114-
114+
115115
with allure.step("Verify response indicates bad request"):
116116
# Should return 400 Bad Request for malformed JSON
117117
assert response.status_code == 400
118-
118+
119119
with allure.step("Attach response details to report"):
120120
allure.attach(response.text, "Invalid JSON Response", allure.attachment_type.JSON)
121121

@@ -134,22 +134,22 @@ def test_invalid_endpoint():
134134
with allure.step("Send GET request to invalid endpoint"):
135135
url = f"{BASE_URL}/invalid-endpoint"
136136
response = requests.get(url, headers=get_headers())
137-
137+
138138
with allure.step("Document API behavior for invalid endpoints"):
139139
# ReqRes API might return 200 with some default content for invalid endpoints
140140
# This is not uncommon for some APIs that have catch-all routes
141141
allure.attach(f"Status Code: {response.status_code}", "Response Status", allure.attachment_type.TEXT)
142142
allure.attach(response.text[:500], "Response Body (first 500 chars)", allure.attachment_type.TEXT)
143-
143+
144144
# The test documents behavior rather than enforcing a specific status
145145
# This is valuable for API contract understanding
146146
if response.status_code == 200:
147-
print(f"INFO: API returns 200 for invalid endpoint (common for some APIs)")
147+
print("INFO: API returns 200 for invalid endpoint (common for some APIs)")
148148
elif response.status_code == 404:
149-
print(f"INFO: API properly returns 404 for invalid endpoint")
149+
print("INFO: API properly returns 404 for invalid endpoint")
150150
else:
151151
print(f"INFO: API returns {response.status_code} for invalid endpoint")
152-
152+
153153
# Always pass - this test is for documentation/exploration
154154
assert True, "Test passes - documents API behavior for invalid endpoints"
155155

@@ -169,15 +169,15 @@ def test_malformed_url():
169169
# Use a very high user ID that definitely doesn't exist
170170
url = f"{BASE_URL}/users/999999"
171171
response = requests.get(url, headers=get_headers())
172-
172+
173173
with allure.step("Verify response indicates not found"):
174174
# ReqRes API returns 404 for non-existent user IDs
175175
assert response.status_code == 404
176-
176+
177177
with allure.step("Verify response body indicates not found"):
178178
# Should return empty object for 404
179179
data = response.json()
180180
assert data == {}
181-
181+
182182
with allure.step("Attach response details to report"):
183183
allure.attach(response.text, "Non-existent User Response", allure.attachment_type.JSON)

0 commit comments

Comments
 (0)