Skip to content

Commit c266e9a

Browse files
committed
feat: Keploy schema-match sample app
Signed-off-by: Harshit Pathak <harshit07pathak@gmail.com>
1 parent a8453d3 commit c266e9a

3 files changed

Lines changed: 350 additions & 0 deletions

File tree

python-schema-match/app-test.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env python3
2+
"""
3+
TEST VERSION of the HTTP server with MODIFIED responses.
4+
Use this for TESTING mode.
5+
6+
EXPECTED RESULTS (Total 10):
7+
PASS: 7
8+
FAIL: 3 (Missing Field, Type Mismatch, Hierarchy Mismatch)
9+
"""
10+
import socket
11+
import json
12+
import random
13+
import string
14+
import time
15+
16+
PORT = 5000
17+
18+
RESPONSES = {
19+
# 1. PASS: Different values, same types
20+
'/user/profile': {
21+
"id": 999,
22+
"username": "different_user",
23+
"active": False,
24+
"profile": {
25+
"age": 99,
26+
"city": "New York",
27+
"preferences": {"theme": "light", "notifications": False}
28+
},
29+
"roles": ["viewer"]
30+
},
31+
32+
# 2. PASS: Array length diff (Standard schema matching allows this)
33+
'/user/history': {
34+
"user_id": 999,
35+
"login_history": [
36+
{"ip": "1.1.1.1", "timestamp": 9999999999}
37+
]
38+
},
39+
40+
# 3. PASS: Extra fields (Superset is allowed)
41+
'/product/search': {
42+
"query": "laptop",
43+
"total_results": 1500,
44+
"page": 1,
45+
"items": [
46+
{"id": "p1", "name": "Laptop Pro", "price": 1299.99, "stock": 50},
47+
{"id": "p2", "name": "Laptop Air", "price": 999.99, "stock": 0}
48+
],
49+
"extra_field": "This field was not in original",
50+
"metadata": {"source": "api", "cache": True}
51+
},
52+
53+
# 4. FAIL: FIELD DELETION (Missing 'feature_flags')
54+
'/admin/config': {
55+
"maintenance_mode": False,
56+
# "feature_flags": MISSING! -> Should Fail
57+
"deprecated_since": None,
58+
"retry_limit": 3,
59+
"added_config": "extra"
60+
},
61+
62+
# 5. FAIL: TYPE MISMATCH (Int -> String)
63+
'/data/matrix': {
64+
"matrix": [["1", "0", "0"], ["0", "1", "0"], ["0", "0", "1"]],
65+
"dimension": "3x3"
66+
},
67+
68+
# 6. FAIL: HIERARCHY/TUPLE MISMATCH
69+
# Original: [int, string, bool...]
70+
# Here: [string, int, bool...] (Swapped first two)
71+
'/data/mixed_array': {
72+
"mixed": ["string", 1, True, {"obj": "val"}, None, [1, 2]]
73+
},
74+
75+
# 7. PASS: Exact match
76+
'/edge/empty_response': {},
77+
78+
# 8. PASS: Exact match
79+
'/edge/null_root': None,
80+
81+
# 9. PASS: Exact match
82+
'/edge/special_chars': {
83+
"text": "Hello Hello",
84+
"emoji": "🚀 🔥 🐛",
85+
"symbols": "!@#$%^&*()_+-=[]{}|;':\",./<>?",
86+
"path": "C:\\Program Files\\Keploy"
87+
},
88+
89+
# 10. PASS: Exact match
90+
'/edge/nested_null': {
91+
"data": {"value": None}
92+
}
93+
}
94+
95+
def get_large_payload():
96+
return {
97+
"payload_size": "5KB",
98+
"content": "".join(random.choices(string.ascii_letters, k=5000))
99+
}
100+
101+
def handle_request(client_socket):
102+
try:
103+
client_socket.settimeout(5.0)
104+
request = client_socket.recv(4096).decode('utf-8', errors='ignore')
105+
106+
if not request: return
107+
lines = request.split('\r\n')
108+
if not lines: return
109+
request_line = lines[0]
110+
parts = request_line.split(' ')
111+
if len(parts) < 2: return
112+
path = parts[1]
113+
114+
print(f"Test Request: {path}")
115+
116+
if path == '/edge/large_payload':
117+
body_data = get_large_payload()
118+
elif path in RESPONSES:
119+
body_data = RESPONSES[path]
120+
else:
121+
response = "HTTP/1.0 404 Not Found\r\n\r\n"
122+
client_socket.sendall(response.encode('utf-8'))
123+
return
124+
125+
if body_data is None:
126+
body = "null"
127+
else:
128+
body = json.dumps(body_data)
129+
130+
response = f"HTTP/1.0 200 OK\r\nContent-Type: application/json\r\nContent-Length: {len(body)}\r\n\r\n{body}"
131+
client_socket.sendall(response.encode('utf-8'))
132+
133+
except Exception as e:
134+
print(f"Error: {e}")
135+
finally:
136+
client_socket.close()
137+
138+
def main():
139+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
140+
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
141+
server_socket.bind(('0.0.0.0', PORT))
142+
server_socket.listen(10)
143+
print(f"Starting TEST Server on port {PORT}...")
144+
145+
while True:
146+
try:
147+
client_socket, addr = server_socket.accept()
148+
handle_request(client_socket)
149+
except KeyboardInterrupt:
150+
break
151+
server_socket.close()
152+
153+
if __name__ == "__main__":
154+
main()

python-schema-match/app.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Simple socket-based HTTP server for Keploy compatibility.
4+
ORIGINAL STATE - Use this for RECORDING.
5+
"""
6+
import socket
7+
import json
8+
import random
9+
import string
10+
import time
11+
import sys
12+
13+
PORT = 5000
14+
15+
# Pre-compute responses for each endpoint
16+
RESPONSES = {
17+
'/user/profile': {
18+
"id": 101,
19+
"username": "keploy_user",
20+
"active": True,
21+
"profile": {
22+
"age": 25,
23+
"city": "San Francisco",
24+
"preferences": {"theme": "dark", "notifications": True}
25+
},
26+
"roles": ["admin", "editor"]
27+
},
28+
'/user/history': {
29+
"user_id": 101,
30+
"login_history": [
31+
{"ip": "192.168.1.1", "timestamp": 1700000001},
32+
{"ip": "10.0.0.1", "timestamp": 1700000050}
33+
]
34+
},
35+
'/product/search': {
36+
"query": "laptop",
37+
"total_results": 1500,
38+
"page": 1,
39+
"items": [
40+
{"id": "p1", "name": "Laptop Pro", "price": 1299.99, "stock": 50},
41+
{"id": "p2", "name": "Laptop Air", "price": 999.99, "stock": 0}
42+
]
43+
},
44+
'/admin/config': {
45+
"maintenance_mode": False,
46+
"feature_flags": {"beta_access": True, "legacy_support": False},
47+
"deprecated_since": None,
48+
"retry_limit": 3
49+
},
50+
'/data/matrix': {
51+
"matrix": [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
52+
"dimension": "3x3"
53+
},
54+
'/data/mixed_array': {
55+
"mixed": [1, "string", True, {"obj": "val"}, None, [1, 2]]
56+
},
57+
'/edge/empty_response': {},
58+
'/edge/null_root': None,
59+
'/edge/special_chars': {
60+
"text": "Hello Hello",
61+
"emoji": "🚀 🔥 🐛",
62+
"symbols": "!@#$%^&*()_+-=[]{}|;':\",./<>?",
63+
"path": "C:\\Program Files\\Keploy"
64+
},
65+
# Added 10th endpoint for clean 7/3 split
66+
'/edge/nested_null': {
67+
"data": {"value": None}
68+
}
69+
}
70+
71+
def get_large_payload():
72+
return {
73+
"payload_size": "5KB",
74+
"content": "".join(random.choices(string.ascii_letters, k=5000))
75+
}
76+
77+
def handle_request(client_socket):
78+
try:
79+
client_socket.settimeout(5.0)
80+
request = client_socket.recv(4096).decode('utf-8', errors='ignore')
81+
82+
if not request: return
83+
84+
lines = request.split('\r\n')
85+
if not lines: return
86+
87+
request_line = lines[0]
88+
parts = request_line.split(' ')
89+
if len(parts) < 2: return
90+
91+
method = parts[0]
92+
path = parts[1]
93+
94+
print(f"Request: {method} {path}")
95+
96+
if path == '/edge/large_payload':
97+
body_data = get_large_payload()
98+
elif path in RESPONSES:
99+
body_data = RESPONSES[path]
100+
else:
101+
response = "HTTP/1.0 404 Not Found\r\nConnection: close\r\n\r\nNot Found"
102+
client_socket.sendall(response.encode('utf-8'))
103+
return
104+
105+
if body_data is None:
106+
body = "null"
107+
else:
108+
body = json.dumps(body_data)
109+
110+
response = f"HTTP/1.0 200 OK\r\nContent-Type: application/json\r\nContent-Length: {len(body)}\r\nConnection: close\r\n\r\n{body}"
111+
client_socket.sendall(response.encode('utf-8'))
112+
113+
except socket.timeout:
114+
pass
115+
except Exception as e:
116+
print(f"Error: {e}")
117+
finally:
118+
client_socket.close()
119+
120+
def main():
121+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
122+
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
123+
124+
try:
125+
server_socket.bind(('0.0.0.0', PORT))
126+
except OSError:
127+
print("Port in use, waiting...")
128+
time.sleep(2)
129+
server_socket.bind(('0.0.0.0', PORT))
130+
131+
server_socket.listen(10)
132+
print(f"Starting ORIGINAL Server on port {PORT}...")
133+
134+
while True:
135+
try:
136+
client_socket, addr = server_socket.accept()
137+
handle_request(client_socket)
138+
except KeyboardInterrupt:
139+
break
140+
except Exception as e:
141+
print(f"Accept error: {e}")
142+
143+
server_socket.close()
144+
145+
if __name__ == "__main__":
146+
main()
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import urllib.request
2+
import time
3+
import sys
4+
5+
BASE_URL = "http://localhost:5000"
6+
ENDPOINTS = [
7+
'/user/profile',
8+
'/user/history',
9+
'/product/search',
10+
'/admin/config',
11+
'/data/matrix',
12+
'/data/mixed_array',
13+
'/edge/empty_response',
14+
'/edge/null_root',
15+
'/edge/special_chars',
16+
'/edge/nested_null'
17+
]
18+
19+
def check_endpoints():
20+
print(f"Checking {len(ENDPOINTS)} endpoints on {BASE_URL}...")
21+
success_count = 0
22+
fail_count = 0
23+
24+
for ep in ENDPOINTS:
25+
url = BASE_URL + ep
26+
try:
27+
# Short timeout to fail fast
28+
with urllib.request.urlopen(url, timeout=2) as response:
29+
status = response.status
30+
body = response.read().decode('utf-8')
31+
32+
if status == 200:
33+
print(f"✅ {ep} [200 OK]")
34+
success_count += 1
35+
else:
36+
print(f"❌ {ep} [{status}]")
37+
fail_count += 1
38+
except Exception as e:
39+
print(f"❌ {ep} Error: {e}")
40+
fail_count += 1
41+
42+
print(f"\n--- Summary ---")
43+
print(f"Total: {len(ENDPOINTS)}")
44+
print(f"Passed: {success_count}")
45+
print(f"Failed: {fail_count}")
46+
47+
if __name__ == "__main__":
48+
# Small delay to ensure server is ready
49+
time.sleep(2)
50+
check_endpoints()

0 commit comments

Comments
 (0)