HarshitaSuri commited on
Commit
4e6b2a1
Β·
verified Β·
1 Parent(s): a0d9e71

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +36 -69
app.py CHANGED
@@ -8,17 +8,15 @@ import numpy as np
8
  from typing import Dict, Tuple
9
  import io
10
 
11
- # Load semantic similarity model
12
  model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
13
 
14
  def calculate_semantic_similarity(text1: str, text2: str) -> float:
15
- """Calculate semantic similarity between two texts using AI embeddings"""
16
  embeddings = model.encode([text1, text2])
17
  similarity = cosine_similarity([embeddings[0]], [embeddings[1]])[0][0]
18
  return float(similarity)
19
 
20
  def is_empty_response(response):
21
- """Check if response is empty or null"""
22
  if response is None or response == '':
23
  return True
24
  if isinstance(response, str):
@@ -26,21 +24,16 @@ def is_empty_response(response):
26
  return False
27
 
28
  def validate_json_response(expected, actual) -> Tuple[bool, str]:
29
- """Validate if JSON responses match"""
30
  try:
31
  expected_json = json.loads(expected) if isinstance(expected, str) else expected
32
  actual_json = json.loads(actual) if isinstance(actual, str) else actual
33
-
34
- if expected_json == actual_json:
35
- return True, "Exact match"
36
- return False, "JSON structure mismatch"
37
  except Exception as e:
38
  return False, f"Invalid JSON: {str(e)}"
39
 
40
  def run_single_test(endpoint: str, method: str, bearer_token: str,
41
  expected_status: int, expected_body: str,
42
  payload: str = None, test_name: str = None) -> Dict:
43
- """Run a single API test with AI-powered semantic validation"""
44
  result = {
45
  'TestName': test_name,
46
  'Endpoint': endpoint,
@@ -60,16 +53,19 @@ def run_single_test(endpoint: str, method: str, bearer_token: str,
60
  headers['Authorization'] = f'Bearer {bearer_token.strip()}'
61
 
62
  data = None
 
63
  if payload and isinstance(payload, str) and payload.strip():
64
- data = payload
 
 
 
65
 
66
- # Make API call
67
  if method.upper() == 'GET':
68
  response = requests.get(endpoint, headers=headers, timeout=10)
69
  elif method.upper() == 'POST':
70
- response = requests.post(endpoint, headers=headers, data=data, timeout=10)
71
  elif method.upper() == 'PUT':
72
- response = requests.put(endpoint, headers=headers, data=data, timeout=10)
73
  elif method.upper() == 'DELETE':
74
  response = requests.delete(endpoint, headers=headers, timeout=10)
75
  else:
@@ -80,13 +76,11 @@ def run_single_test(endpoint: str, method: str, bearer_token: str,
80
  actual_body = response.text
81
  result['Actual Body'] = actual_body[:100] + '...' if len(actual_body) > 100 else actual_body
82
 
83
- # Status code check
84
  if response.status_code != expected_status:
85
  result['Outcome'] = 'FAIL'
86
  result['Notes'] = f'Status mismatch: expected {expected_status}, got {response.status_code}'
87
  return result
88
 
89
- # Body validation
90
  if not expected_body or is_empty_response(expected_body):
91
  result['Outcome'] = 'PASS'
92
  result['Notes'] = 'Status matches, no body validation'
@@ -98,7 +92,6 @@ def run_single_test(endpoint: str, method: str, bearer_token: str,
98
  result['Notes'] = 'Response is empty (manual review needed)'
99
  return result
100
 
101
- # JSON validation
102
  json_match, _ = validate_json_response(expected_body, actual_body)
103
  if json_match:
104
  result['Outcome'] = 'PASS'
@@ -106,20 +99,18 @@ def run_single_test(endpoint: str, method: str, bearer_token: str,
106
  result['Notes'] = 'Exact JSON match'
107
  return result
108
 
109
- # Semantic similarity
110
  try:
111
  similarity = calculate_semantic_similarity(expected_body, actual_body)
112
  result['Similarity'] = round(similarity, 3)
113
-
114
  if similarity >= 0.75:
115
  result['Outcome'] = 'PASS'
116
  result['Notes'] = f'High semantic similarity: {similarity:.1%}'
117
  elif similarity >= 0.5:
118
  result['Outcome'] = 'UNCERTAIN'
119
- result['Notes'] = f'Medium semantic similarity: {similarity:.1%} (manual review needed)'
120
  else:
121
  result['Outcome'] = 'UNCERTAIN'
122
- result['Notes'] = f'Low semantic similarity: {similarity:.1%} (manual review needed)'
123
  except Exception as sim_error:
124
  result['Outcome'] = 'UNCERTAIN'
125
  result['Notes'] = f'Cannot compute similarity: {str(sim_error)}'
@@ -136,14 +127,15 @@ def run_single_test(endpoint: str, method: str, bearer_token: str,
136
  result['Notes'] = f'Error: {str(e)}'
137
  return result
138
 
139
- def process_excel_file(file, bearer_token: str) -> Tuple[pd.DataFrame, str]:
140
- """Process uploaded Excel file and run all tests"""
141
  try:
142
- df = pd.read_excel(file)
 
 
 
143
 
144
  required_columns = ['Endpoint', 'Method', 'ExpectedStatus', 'ExpectedBody']
145
  missing_columns = [col for col in required_columns if col not in df.columns]
146
-
147
  if missing_columns:
148
  return None, f"❌ Missing columns: {', '.join(missing_columns)}"
149
 
@@ -161,81 +153,56 @@ def process_excel_file(file, bearer_token: str) -> Tuple[pd.DataFrame, str]:
161
  results.append(result)
162
 
163
  results_df = pd.DataFrame(results)
164
-
165
  total = len(results_df)
166
  passed = len(results_df[results_df['Outcome'] == 'PASS'])
167
  failed = len(results_df[results_df['Outcome'] == 'FAIL'])
168
  uncertain = len(results_df[results_df['Outcome'] == 'UNCERTAIN'])
169
 
170
  summary = f"""
171
- πŸ“Š **Test Summary**
172
  - Total Tests: {total}
173
  - βœ… Passed: {passed}
174
  - ❌ Failed: {failed}
175
  - ⚠️ Uncertain: {uncertain}
176
  """
177
-
178
  return results_df, summary
179
 
180
  except Exception as e:
181
  return None, f"❌ Error processing file: {str(e)}"
182
 
183
  def download_results(df):
184
- """Convert DataFrame to Excel for download"""
185
  if df is None or df.empty:
186
  return None
187
-
188
  output = io.BytesIO()
189
  with pd.ExcelWriter(output, engine='openpyxl') as writer:
190
  df.to_excel(writer, index=False, sheet_name='Test Results')
191
-
192
  output.seek(0)
193
  return output
194
 
195
  # Gradio UI
196
- with gr.Blocks(title="API Test Runner with AI", theme=gr.themes.Soft()) as app:
197
- gr.Markdown("""
198
- # πŸš€ API Test Runner with AI-Powered Validation
199
-
200
- Upload an Excel file with your API test cases and get automated validation with semantic similarity analysis.
201
- """)
202
-
203
- with gr.Row():
204
- with gr.Column(scale=2):
205
- file_input = gr.File(label="Upload Excel File (.xlsx)", file_types=['.xlsx'])
206
- token_input = gr.Textbox(
207
- label="Bearer Token (Optional)",
208
- placeholder="Enter your API bearer token if required",
209
- type="password"
210
- )
211
- run_button = gr.Button("πŸ” Run Tests", variant="primary", size="lg")
212
-
213
- with gr.Column(scale=1):
214
- summary_output = gr.Markdown(label="Summary")
215
-
216
- with gr.Row():
217
  results_output = gr.Dataframe(
218
- label="Test Results",
219
- headers=['TestName','Endpoint','Method','Expected Status','Actual Status',
220
- 'Outcome','Similarity','Notes'],
221
  interactive=False
222
  )
223
-
224
- with gr.Row():
225
  download_button = gr.Button("πŸ’Ύ Download Results")
226
- download_output = gr.File(label="Download Excel Report")
227
-
228
- run_button.click(
229
- fn=process_excel_file,
230
- inputs=[file_input, token_input],
231
- outputs=[results_output, summary_output]
232
- )
233
-
234
- download_button.click(
235
- fn=download_results,
236
- inputs=[results_output],
237
- outputs=[download_output]
238
- )
239
 
240
  if __name__ == "__main__":
241
  app.launch()
 
8
  from typing import Dict, Tuple
9
  import io
10
 
11
+ # Load semantic similarity model once
12
  model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
13
 
14
  def calculate_semantic_similarity(text1: str, text2: str) -> float:
 
15
  embeddings = model.encode([text1, text2])
16
  similarity = cosine_similarity([embeddings[0]], [embeddings[1]])[0][0]
17
  return float(similarity)
18
 
19
  def is_empty_response(response):
 
20
  if response is None or response == '':
21
  return True
22
  if isinstance(response, str):
 
24
  return False
25
 
26
  def validate_json_response(expected, actual) -> Tuple[bool, str]:
 
27
  try:
28
  expected_json = json.loads(expected) if isinstance(expected, str) else expected
29
  actual_json = json.loads(actual) if isinstance(actual, str) else actual
30
+ return expected_json == actual_json, "Exact match" if expected_json == actual_json else "Mismatch"
 
 
 
31
  except Exception as e:
32
  return False, f"Invalid JSON: {str(e)}"
33
 
34
  def run_single_test(endpoint: str, method: str, bearer_token: str,
35
  expected_status: int, expected_body: str,
36
  payload: str = None, test_name: str = None) -> Dict:
 
37
  result = {
38
  'TestName': test_name,
39
  'Endpoint': endpoint,
 
53
  headers['Authorization'] = f'Bearer {bearer_token.strip()}'
54
 
55
  data = None
56
+ json_payload = None
57
  if payload and isinstance(payload, str) and payload.strip():
58
+ try:
59
+ json_payload = json.loads(payload)
60
+ except Exception:
61
+ data = payload
62
 
 
63
  if method.upper() == 'GET':
64
  response = requests.get(endpoint, headers=headers, timeout=10)
65
  elif method.upper() == 'POST':
66
+ response = requests.post(endpoint, headers=headers, data=data, json=json_payload, timeout=10)
67
  elif method.upper() == 'PUT':
68
+ response = requests.put(endpoint, headers=headers, data=data, json=json_payload, timeout=10)
69
  elif method.upper() == 'DELETE':
70
  response = requests.delete(endpoint, headers=headers, timeout=10)
71
  else:
 
76
  actual_body = response.text
77
  result['Actual Body'] = actual_body[:100] + '...' if len(actual_body) > 100 else actual_body
78
 
 
79
  if response.status_code != expected_status:
80
  result['Outcome'] = 'FAIL'
81
  result['Notes'] = f'Status mismatch: expected {expected_status}, got {response.status_code}'
82
  return result
83
 
 
84
  if not expected_body or is_empty_response(expected_body):
85
  result['Outcome'] = 'PASS'
86
  result['Notes'] = 'Status matches, no body validation'
 
92
  result['Notes'] = 'Response is empty (manual review needed)'
93
  return result
94
 
 
95
  json_match, _ = validate_json_response(expected_body, actual_body)
96
  if json_match:
97
  result['Outcome'] = 'PASS'
 
99
  result['Notes'] = 'Exact JSON match'
100
  return result
101
 
 
102
  try:
103
  similarity = calculate_semantic_similarity(expected_body, actual_body)
104
  result['Similarity'] = round(similarity, 3)
 
105
  if similarity >= 0.75:
106
  result['Outcome'] = 'PASS'
107
  result['Notes'] = f'High semantic similarity: {similarity:.1%}'
108
  elif similarity >= 0.5:
109
  result['Outcome'] = 'UNCERTAIN'
110
+ result['Notes'] = f'Medium similarity: {similarity:.1%} (review needed)'
111
  else:
112
  result['Outcome'] = 'UNCERTAIN'
113
+ result['Notes'] = f'Low similarity: {similarity:.1%} (review needed)'
114
  except Exception as sim_error:
115
  result['Outcome'] = 'UNCERTAIN'
116
  result['Notes'] = f'Cannot compute similarity: {str(sim_error)}'
 
127
  result['Notes'] = f'Error: {str(e)}'
128
  return result
129
 
130
+ def process_excel_file(file, bearer_token: str):
 
131
  try:
132
+ if hasattr(file, "name"):
133
+ df = pd.read_excel(file.name)
134
+ else:
135
+ df = pd.read_excel(file)
136
 
137
  required_columns = ['Endpoint', 'Method', 'ExpectedStatus', 'ExpectedBody']
138
  missing_columns = [col for col in required_columns if col not in df.columns]
 
139
  if missing_columns:
140
  return None, f"❌ Missing columns: {', '.join(missing_columns)}"
141
 
 
153
  results.append(result)
154
 
155
  results_df = pd.DataFrame(results)
 
156
  total = len(results_df)
157
  passed = len(results_df[results_df['Outcome'] == 'PASS'])
158
  failed = len(results_df[results_df['Outcome'] == 'FAIL'])
159
  uncertain = len(results_df[results_df['Outcome'] == 'UNCERTAIN'])
160
 
161
  summary = f"""
162
+ ### πŸ“Š Test Summary
163
  - Total Tests: {total}
164
  - βœ… Passed: {passed}
165
  - ❌ Failed: {failed}
166
  - ⚠️ Uncertain: {uncertain}
167
  """
 
168
  return results_df, summary
169
 
170
  except Exception as e:
171
  return None, f"❌ Error processing file: {str(e)}"
172
 
173
  def download_results(df):
 
174
  if df is None or df.empty:
175
  return None
 
176
  output = io.BytesIO()
177
  with pd.ExcelWriter(output, engine='openpyxl') as writer:
178
  df.to_excel(writer, index=False, sheet_name='Test Results')
 
179
  output.seek(0)
180
  return output
181
 
182
  # Gradio UI
183
+ with gr.Blocks(theme=gr.themes.Soft()) as app:
184
+ gr.Markdown("## πŸš€ API Test Runner with AI-Powered Validation")
185
+
186
+ token_state = gr.State("")
187
+
188
+ with gr.Tab("Run Tests"):
189
+ with gr.Group():
190
+ file_input = gr.File(label="πŸ“‚ Upload Excel (.xlsx)", file_types=['.xlsx'])
191
+ token_input = gr.Textbox(label="πŸ”‘ Bearer Token", type="password")
192
+ run_button = gr.Button("▢️ Run Tests", variant="primary")
193
+
194
+ with gr.Tab("Results"):
195
+ summary_output = gr.Markdown()
 
 
 
 
 
 
 
 
196
  results_output = gr.Dataframe(
197
+ headers=['TestName','Endpoint','Method','Expected Status','Actual Status','Outcome','Similarity','Notes'],
 
 
198
  interactive=False
199
  )
 
 
200
  download_button = gr.Button("πŸ’Ύ Download Results")
201
+ download_output = gr.File()
202
+
203
+ token_input.change(fn=lambda t: t, inputs=token_input, outputs=token_state)
204
+ run_button.click(fn=process_excel_file, inputs=[file_input, token_state], outputs=[results_output, summary_output])
205
+ download_button.click(fn=download_results, inputs=[results_output], outputs=[download_output])
 
 
 
 
 
 
 
 
206
 
207
  if __name__ == "__main__":
208
  app.launch()