Merry

Finished
1import json
2
13class RestAPI:
24 def __init__(self, database: dict | None = None):
3 pass
4
5 """
6 Initialize the REST API with a database.
7
8 Args:
9 database: A dictionary containing user data.
10 """
11 # Edge Case: Handle None database by initializing empty structure
12 if database is None:
13 self.database = {"users": []}
14 else:
15 self.database = database
16
517 def get(self, url: str, payload: str | None = None) -> str:
6 pass
7
18 """
19 Handle GET requests to the API.
20
21 Args:
22 url: The endpoint URL.
23 payload: Optional JSON payload.
24
25 Returns:
26 JSON string response.
27 """
28 if url == "/users":
29 # Edge Case: Handle missing payload
30 if payload is None:
31 users = self._get_all_users()
32 return json.dumps({"users": users})
33 else:
34 data = json.loads(payload)
35 # Edge Case: Handle missing 'users' key in payload
36 if "users" not in data:
37 return json.dumps({"users": []})
38 user_names = data["users"]
39 users = self._get_users_by_names(user_names)
40 return json.dumps({"users": users})
41 # Edge Case: Handle unknown URL
42 return json.dumps({"error": "Not found"})
43
844 def post(self, url: str, payload: str | None = None) -> str:
9 pass
45 """
46 Handle POST requests to the API.
47
48 Args:
49 url: The endpoint URL.
50 payload: JSON payload.
51
52 Returns:
53 JSON string response.
54 """
55 # Edge Case: Handle missing payload
56 if payload is None:
57 return json.dumps({"error": "Payload required"})
58
59 data = json.loads(payload)
60
61 if url == "/add":
62 # Edge Case: Handle missing 'user' key in payload
63 if "user" not in data:
64 return json.dumps({"error": "Missing user field"})
65 user_name = data["user"]
66 # Edge Case: Handle duplicate user
67 if self._user_exists(user_name):
68 return json.dumps({"error": "User already exists"})
69 user = self._create_user(user_name)
70 return json.dumps(user)
71 elif url == "/iou":
72 # Edge Case: Handle missing required fields
73 required_fields = ["lender", "borrower", "amount"]
74 for field in required_fields:
75 if field not in data:
76 return json.dumps({"error": f"Missing {field} field"})
77
78 lender_name = data["lender"]
79 borrower_name = data["borrower"]
80 amount = data["amount"]
81
82 # Edge Case: Handle invalid amount type
83 if not isinstance(amount, (int, float)):
84 return json.dumps({"error": "Invalid amount"})
85
86 # Edge Case: Handle negative amount
87 if amount < 0:
88 return json.dumps({"error": "Amount must be positive"})
89
90 # Edge Case: Handle lender and borrower being the same person
91 if lender_name == borrower_name:
92 return json.dumps({"error": "Cannot create IOU between same user"})
93
94 # Create users if they don't exist
95 if not self._user_exists(lender_name):
96 self._create_user(lender_name)
97 if not self._user_exists(borrower_name):
98 self._create_user(borrower_name)
99
100 self._update_iou(lender_name, borrower_name, amount)
101 users = self._get_users_by_names([lender_name, borrower_name])
102 return json.dumps({"users": users})
103 # Edge Case: Handle unknown URL
104 return json.dumps({"error": "Not found"})
105
106 def _get_all_users(self) -> list:
107 """
108 Get all users from the database.
109
110 Returns:
111 List of user objects.
112 """
113 users = []
114 for user_data in self.database["users"]:
115 user = self._build_user_object(user_data)
116 users.append(user)
117 users.sort(key=lambda x: x["name"])
118 return users
119
120 def _get_users_by_names(self, names: list) -> list:
121 """
122 Get user objects for specified names, sorted by name.
123
124 Args:
125 names: List of user names.
126
127 Returns:
128 List of user objects sorted by name.
129 """
130 users = []
131 for user_data in self.database["users"]:
132 if user_data["name"] in names:
133 user = self._build_user_object(user_data)
134 users.append(user)
135 users.sort(key=lambda x: x["name"])
136 return users
137
138 def _user_exists(self, name: str) -> bool:
139 """
140 Check if a user exists in the database.
141
142 Args:
143 name: User name to check.
144
145 Returns:
146 True if user exists, False otherwise.
147 """
148 for user in self.database["users"]:
149 if user["name"] == name:
150 return True
151 return False
152
153 def _create_user(self, name: str) -> dict:
154 """
155 Create a new user in the database.
156
157 Args:
158 name: Name of the new user.
159
160 Returns:
161 User object for the new user.
162 """
163 user = {
164 "name": name,
165 "owes": {},
166 "owed_by": {},
167 "balance": 0.0
168 }
169 self.database["users"] .append({
170 "name": name,
171 "owes": {},
172 "owed_by": {}
173 })
174 return user
175
176 def _build_user_object(self, user_data: dict) -> dict:
177 """
178 Build a complete user object with calculated balance.
179
180 Args:
181 user_data: Raw user data from database.
182
183 Returns:
184 Complete user object with balance.
185 """
186 owes = user_data.get("owes", {})
187 owed_by = user_data.get("owed_by", {})
188
189 total_owes = sum(owes.values())
190 total_owed_by = sum(owed_by.values())
191 balance = round(total_owed_by - total_owes, 2)
192
193 return {
194 "name": user_data["name"],
195 "owes": owes,
196 "owed_by": owed_by,
197 "balance": balance
198 }
199
200 def _update_iou(self, lender_name: str, borrower_name: str, amount: float) -> None:
201 """
202 Update IOU records between lender and borrower.
203
204 Args:
205 lender_name: Name of the lender.
206 borrower_name: Name of the borrower.
207 amount: Amount of the IOU.
208 """
209 amount = round(amount, 2)
210
211 # Find lender and borrower in database
212 lender = None
213 borrower = None
214
215 for user in self.database["users"]:
216 if user["name"] == lender_name:
217 lender = user
218 elif user["name"] == borrower_name:
219 borrower = user
220
221 # When A lends amount X to B:
222 # 1. Reduce what B owes to A (if any)
223 # 2. Increase what A is owed by B with remaining amount
224
225 # Get current amounts
226 borrower_owes_lender = borrower["owes"].get(lender_name, 0)
227 lender_owes_borrower = lender["owes"].get(borrower_name, 0)
228 lender_owed_by_borrower = lender["owed_by"].get(borrower_name, 0)
229 borrower_owed_by_lender = borrower["owed_by"].get(lender_name, 0)
230
231 # Clean up any existing entries for this transaction
232 # Remove entries that will be recalculated
233 if lender_name in borrower["owes"]:
234 del borrower["owes"][lender_name]
235 if borrower_name in lender["owed_by"]:
236 del lender["owed_by"][borrower_name]
237 if borrower_name in lender["owes"]:
238 del lender["owes"][borrower_name]
239 if lender_name in borrower["owed_by"]:
240 del borrower["owed_by"][lender_name]
241
242 # Calculate net effect
243 # Net debt from borrower to lender = (what borrower owes lender) + (new amount) - (what lender owes borrower)
244 net_debt = borrower_owes_lender + amount - lender_owes_borrower
245
246 if net_debt > 0:
247 # Borrower owes lender
248 borrower["owes"][lender_name] = net_debt
249 lender["owed_by"][borrower_name] = net_debt
250 elif net_debt < 0:
251 # Lender owes borrower
252 lender["owes"][borrower_name] = abs(net_debt)
253 borrower["owed_by"][lender_name] = abs(net_debt)
254 # If net_debt == 0, no entries needed (debts cancel out)
255 # Handled Edge Cases: None database, missing payload, missing 'users' key, unknown URL, missing 'user' key, duplicate user, missing required fields, invalid amount type, negative amount, non-existent users, same lender/borrower
Test NameStatus
test_add_user
Pass
test_borrower_has_negative_balance
Pass
test_both_users_have_0_balance
Pass
test_get_single_user
Pass
test_lender_has_negative_balance
Pass
test_lender_owes_borrower
Pass
test_lender_owes_borrower_less_than_new_loan
Pass
test_lender_owes_borrower_same_as_new_loan
Pass
test_no_users
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.