-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_cli.py
More file actions
320 lines (280 loc) · 14 KB
/
test_cli.py
File metadata and controls
320 lines (280 loc) · 14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
#!/usr/bin/env python3
"""
Test script for GraphDBLite CLI
This script tests the CLI functionality by simulating user commands
"""
import os
import sys
import tempfile
import shutil
from io import StringIO
from typing import Optional
from unittest.mock import patch
from cli.cli import GraphDBLiteCLI
from main import process_command
from utils.constants import save_file_path
class CLITester:
def __init__(self):
self.test_results = []
self.temp_dir = tempfile.mkdtemp()
self.backup_file = None
def setup(self):
"""Setup test environment"""
print("Setting up test environment...")
# Backup existing data file if it exists
if os.path.exists(save_file_path):
self.backup_file = f"{save_file_path}.backup"
shutil.copy2(save_file_path, self.backup_file)
print(f"Backed up existing data to {self.backup_file}")
# Create a fresh CLI instance
self.cli = GraphDBLiteCLI()
print("Test environment ready.")
def teardown(self):
"""Cleanup test environment"""
print("Cleaning up test environment...")
# Restore original data file
if self.backup_file and os.path.exists(self.backup_file):
shutil.move(self.backup_file, save_file_path)
print("Restored original data file")
# Clean up temp directory
shutil.rmtree(self.temp_dir, ignore_errors=True)
print("Test environment cleaned up.")
def test_command(self, command: str, expected_success: bool = True, expected_output_contains: Optional[str] = None):
"""Test a single command"""
print(f"\nTesting command: {command}")
# Capture output
with patch('sys.stdout', new=StringIO()) as fake_out:
success = process_command(self.cli, command)
output = fake_out.getvalue()
# Check success/failure
if success == expected_success:
print(f"✓ Success/failure check passed")
else:
print(f"✗ Success/failure check failed. Expected: {expected_success}, Got: {success}")
self.test_results.append(False)
return False
# Check output content if specified
if expected_output_contains is not None:
if expected_output_contains in output:
print(f"✓ Output contains expected text: '{expected_output_contains}'")
else:
print(f"✗ Output does not contain expected text: '{expected_output_contains}'")
print(f" Actual output: {output.strip()}")
self.test_results.append(False)
return False
print(f"✓ Command test passed")
self.test_results.append(True)
return True
def run_tests(self):
"""Run all tests"""
print("=" * 60)
print("GraphDBLite CLI Test Suite")
print("=" * 60)
try:
self.setup()
# Test 1: Create a graph
print("\n" + "="*40)
print("Test 1: Graph Creation")
print("="*40)
self.test_command("CREATE GRAPH g1 DIRECTED WEIGHTED", True, "Created graph 'g1'")
# Test 2: Create another graph
self.test_command("CREATE GRAPH g2", True, "Created graph 'g2'")
# Test 3: Try to create duplicate graph (should fail)
self.test_command("CREATE GRAPH g1", False, "already exists")
# Test 4: Add nodes
print("\n" + "="*40)
print("Test 2: Node Operations")
print("="*40)
self.test_command("ADD NODE g1 alice", True, "Added node 'alice'")
self.test_command("ADD NODE g1 bob", True, "Added node 'bob'")
self.test_command("ADD NODE g1 charlie", True, "Added node 'charlie'")
self.test_command("ADD NODE g2 dave", True, "Added node 'dave'")
# Test 5: Add edges
print("\n" + "="*40)
print("Test 3: Edge Operations")
print("="*40)
self.test_command("ADD EDGE g1 alice bob 5", True, "Added edge from 'alice' to 'bob'")
self.test_command("ADD EDGE g1 bob charlie 3", True, "Added edge from 'bob' to 'charlie'")
self.test_command("ADD EDGE g2 dave dave 1", True, "Added edge from 'dave' to 'dave'")
# Test 6: List operations
print("\n" + "="*40)
print("Test 4: List Operations")
print("="*40)
self.test_command("LIST GRAPHS", True, "g1")
self.test_command("LIST GRAPHS", True, "g2")
self.test_command("LIST NODES g1", True, "alice")
self.test_command("LIST NODES g1", True, "bob")
self.test_command("LIST NODES g1", True, "charlie")
self.test_command("LIST EDGES g1", True, "alice")
self.test_command("LIST EDGES g1", True, "bob")
# Test 7: Describe graph
print("\n" + "="*40)
print("Test 5: Graph Description")
print("="*40)
self.test_command("DESCRIBE GRAPH g1", True, "Graph: g1")
self.test_command("DESCRIBE GRAPH g1", True, "Directed: True")
self.test_command("DESCRIBE GRAPH g1", True, "Weighted: True")
# Test 8: Delete edge
print("\n" + "="*40)
print("Test 6: Edge Deletion")
print("="*40)
self.test_command("DEL EDGE g1 bob charlie 3", True, "Removed edge from 'bob' to 'charlie'")
# Test 9: Delete node (newly implemented)
print("\n" + "="*40)
print("Test 7: Node Deletion (New Feature)")
print("="*40)
self.test_command("DEL NODE g1 charlie", True, "Removed node 'charlie'")
# Verify node was removed
self.test_command("LIST NODES g1", True, "alice")
self.test_command("LIST NODES g1", True, "bob")
# Should not contain charlie anymore
with patch('sys.stdout', new=StringIO()) as fake_out:
process_command(self.cli, "LIST NODES g1")
output = fake_out.getvalue()
if "charlie" not in output:
print("✓ Node deletion verified - charlie not in node list")
self.test_results.append(True)
else:
print("✗ Node deletion failed - charlie still in node list")
self.test_results.append(False)
# Test 10: Save graph (newly implemented)
print("\n" + "="*40)
print("Test 8: Graph Saving (New Feature)")
print("="*40)
test_file = os.path.join(self.temp_dir, "g1.json")
self.test_command(f"SAVE GRAPH g1 {test_file}", True, "Saved graph 'g1'")
# Verify file was created
if os.path.exists(test_file):
print("✓ Graph save verified - file created")
self.test_results.append(True)
else:
print("✗ Graph save failed - file not created")
self.test_results.append(False)
# Test 11: Load graph
print("\n" + "="*40)
print("Test 9: Graph Loading")
print("="*40)
self.test_command(f"LOAD GRAPH {test_file}", True, "Loaded graph from")
# Test 12: Error handling
print("\n" + "="*40)
print("Test 10: Error Handling")
print("="*40)
self.test_command("CREATE GRAPH", False, "Usage:")
self.test_command("ADD NODE nonexistent alice", False, "does not exist") # Non-existent graph
self.test_command("ADD NODE g1", False, "ERROR: Correct usage:")
self.test_command("DEL NODE g1 nonexistent", False, "does not exist") # Non-existent node
# Test 13: Help command
print("\n" + "="*40)
print("Test 11: Help Command")
print("="*40)
self.test_command("HELP", True, "GraphDBLite - Lightweight Graph Database")
# Test 14: Clear command
print("\n" + "="*40)
print("Test 12: Clear Command")
print("="*40)
self.test_command("CLEAR", True)
# Test 15: Exit command
print("\n" + "="*40)
print("Test 13: Exit Command")
print("="*40)
self.test_command("EXIT", False)
# Test 14: Edge Cases and Robustness
print("\n" + "="*40)
print("Test 14: Edge Cases and Robustness")
print("="*40)
# Add edge between non-existent nodes (should auto-create nodes)
self.test_command("ADD EDGE g1 nonexist1 nonexist2 2", True, "Added edge from 'nonexist1' to 'nonexist2'")
# Add node with invalid characters
self.test_command("ADD NODE g1 alice!", False, "ERROR: Correct usage:")
# Add edge with non-numeric weight in weighted graph
self.test_command("ADD EDGE g1 alice bob notanumber", False, "ERROR: Correct usage:")
# Add duplicate edge
self.test_command("ADD EDGE g1 alice bob 5", False, "already exists")
# Add duplicate node
self.test_command("ADD NODE g1 alice", False, "already exists")
# Add self-loop in undirected graph
self.test_command("CREATE GRAPH g3", True, "Created graph 'g3'")
self.test_command("ADD NODE g3 zed", True, "Added node 'zed'")
self.test_command("ADD EDGE g3 zed zed", True, "Added edge from 'zed' to 'zed'")
# Create graph with only DIRECTED
self.test_command("CREATE GRAPH g4 DIRECTED", True, "Created graph 'g4'")
# Create graph with only WEIGHTED
self.test_command("CREATE GRAPH g5 WEIGHTED", True, "Created graph 'g5'")
# Describe a graph with no nodes/edges
self.test_command("DESCRIBE GRAPH g4", True, "Graph: g4")
# Save, clear, and load a graph
test_file2 = os.path.join(self.temp_dir, "g3.json")
self.test_command(f"SAVE GRAPH g3 {test_file2}", True, "Saved graph 'g3'")
self.test_command("CLEAR", True)
self.test_command(f"LOAD GRAPH {test_file2}", True, "Loaded graph from")
self.test_command("LIST NODES g3", True, "zed")
# Load a non-existent file
self.test_command("LOAD GRAPH /tmp/nonexistentfile.json", False, "Failed to load graph")
# Delete a node with multiple edges
self.test_command("CREATE GRAPH g6", True, "Created graph 'g6'")
self.test_command("ADD NODE g6 a", True, "Added node 'a'")
self.test_command("ADD NODE g6 b", True, "Added node 'b'")
self.test_command("ADD NODE g6 c", True, "Added node 'c'")
self.test_command("ADD EDGE g6 a b", True, "Added edge from 'a' to 'b'")
self.test_command("ADD EDGE g6 a c", True, "Added edge from 'a' to 'c'")
self.test_command("DEL NODE g6 a", True, "Removed node 'a'")
self.test_command("LIST NODES g6", True, "b")
self.test_command("LIST NODES g6", True, "c")
# Delete a non-existent edge
self.test_command("DEL EDGE g6 b c", False, "does not exist")
# Delete a node from a graph with only one node
self.test_command("CREATE GRAPH g7", True, "Created graph 'g7'")
self.test_command("ADD NODE g7 solo", True, "Added node 'solo'")
self.test_command("DEL NODE g7 solo", True, "Removed node 'solo'")
# List nodes/edges in an empty graph
self.test_command("LIST NODES g7", True)
self.test_command("LIST EDGES g7", True)
# List graphs when none exist (after clear)
self.test_command("CLEAR", True)
self.test_command("LIST GRAPHS", True)
# Invalid command
self.test_command("FOO BAR", False, "ERROR: Unknown command")
# HELP, CLEAR, EXIT commands
self.test_command("HELP", True, "GraphDBLite - Lightweight Graph Database")
self.test_command("CLEAR", True)
self.test_command("EXIT", False)
print("\n" + "="*60)
print("EDGE CASES & ROBUSTNESS TEST SUMMARY")
print("="*60)
total_tests = len(self.test_results)
passed_tests = sum(self.test_results)
failed_tests = total_tests - passed_tests
print(f"Total tests: {total_tests}")
print(f"Passed: {passed_tests}")
print(f"Failed: {failed_tests}")
print(f"Success rate: {(passed_tests/total_tests)*100:.1f}%")
if failed_tests == 0:
print("\n🎉 All edge case tests passed! GraphDBLite CLI is robust.")
else:
print(f"\n❌ {failed_tests} edge case test(s) failed. Please check the implementation.")
# Print test summary
print("\n" + "="*60)
print("TEST SUMMARY")
print("="*60)
total_tests = len(self.test_results)
passed_tests = sum(self.test_results)
failed_tests = total_tests - passed_tests
print(f"Total tests: {total_tests}")
print(f"Passed: {passed_tests}")
print(f"Failed: {failed_tests}")
print(f"Success rate: {(passed_tests/total_tests)*100:.1f}%")
if failed_tests == 0:
print("\n🎉 All tests passed! GraphDBLite CLI is working correctly.")
return True
else:
print(f"\n❌ {failed_tests} test(s) failed. Please check the implementation.")
return False
finally:
self.teardown()
def main():
"""Main test runner"""
tester = CLITester()
success = tester.run_tests()
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()