fistinz

Finished
1import json
2from typing import Dict, List, Optional, Any
3
14class RestAPI:
25 def __init__(self, database: dict | None = None):
3 pass
4
6 """
7 Initialize the REST API with an optional database.
8
9 Args:
10 database: A dictionary containing initial user data.
11 """
12 self.database = database or {"users": []}
13 # Ensure all users have the required fields
14 for user in self.database["users"]:
15 if "owes" not in user:
16 user["owes"] = {}
17 if "owed_by" not in user:
18 user["owed_by"] = {}
19 if "balance" not in user:
20 user["balance"] = 0.0
21
22 def get_user(self, name: str) -> Optional[Dict]:
23 """
24 Retrieve a user by name from the database.
25
26 Args:
27 name: The name of the user to retrieve.
28
29 Returns:
30 The user dictionary if found, None otherwise.
31 """
32 for user in self.database["users"]:
33 if user["name"] == name:
34 return user
35 return None
36
37 def calculate_balance(self, user: Dict) -> float:
38 """
39 Calculate the balance for a user based on what they owe and are owed.
40
41 Args:
42 user: The user dictionary.
43
44 Returns:
45 The calculated balance.
46 """
47 total_owed_by = sum(user.get("owed_by", {}).values())
48 total_owes = sum(user.get("owes", {}).values())
49 return round(total_owed_by - total_owes, 2)
50
551 def get(self, url: str, payload: str | None = None) -> str:
6 pass
7
52 """
53 Handle GET requests to the API.
54
55 Args:
56 url: The endpoint URL.
57 payload: Optional JSON payload.
58
59 Returns:
60 JSON response as a string.
61 """
62 if url == "/users":
63 # Edge Case: Handle request with no payload (list all users)
64 if payload is None:
65 # Return all users with updated balances
66 for user in self.database["users"]:
67 user["balance"] = self.calculate_balance(user)
68 # Sort users by name
69 self.database["users"].sort(key=lambda x: x["name"])
70 return json.dumps(self.database)
71
72 # Edge Case: Handle request with payload (list specific users)
73 try:
74 data = json.loads(payload)
75 if "users" not in data:
76 return json.dumps({"error": "Invalid payload"})
77
78 # Filter users by the provided list
79 result_users = []
80 for username in sorted(data["users"]): # Sort by name as required
81 user = self.get_user(username)
82 if user:
83 user["balance"] = self.calculate_balance(user)
84 result_users.append(user)
85
86 return json.dumps({"users": result_users})
87 except json.JSONDecodeError:
88 return json.dumps({"error": "Invalid JSON payload"})
89
90 return json.dumps({"error": "Invalid URL"})
91 # Handled Edge Cases: Request with no payload, request with valid payload, request with invalid payload, invalid JSON payload
92
893 def post(self, url: str, payload: str | None = None) -> str:
9 pass
94 """
95 Handle POST requests to the API.
96
97 Args:
98 url: The endpoint URL.
99 payload: JSON payload.
100
101 Returns:
102 JSON response as a string.
103 """
104 if payload is None:
105 return json.dumps({"error": "Payload required"})
106
107 try:
108 data = json.loads(payload)
109 except json.JSONDecodeError:
110 return json.dumps({"error": "Invalid JSON payload"})
111
112 if url == "/add":
113 # Edge Case: Handle adding a new user
114 if "user" not in data:
115 return json.dumps({"error": "Missing user field"})
116
117 username = data["user"]
118
119 # Edge Case: Handle attempt to add a user that already exists
120 if self.get_user(username) is not None:
121 return json.dumps({"error": "User already exists"})
122
123 # Create new user
124 new_user = {
125 "name": username,
126 "owes": {},
127 "owed_by": {},
128 "balance": 0.0
129 }
130
131 self.database["users"].append(new_user)
132 return json.dumps(new_user)
133 # Handled Edge Cases: Missing user field, user already exists
134
135 elif url == "/iou":
136 # Edge Case: Handle creating an IOU
137 required_fields = ["lender", "borrower", "amount"]
138 for field in required_fields:
139 if field not in data:
140 return json.dumps({"error": f"Missing {field} field"})
141
142 lender_name = data["lender"]
143 borrower_name = data["borrower"]
144 amount = data["amount"]
145
146 # Edge Case: Handle invalid amount (non-numeric or negative)
147 if not isinstance(amount, (int, float)) or amount < 0:
148 return json.dumps({"error": "Invalid amount"})
149
150 lender = self.get_user(lender_name)
151 borrower = self.get_user(borrower_name)
152
153 # Edge Case: Handle lender or borrower not existing
154 if lender is None or borrower is None:
155 return json.dumps({"error": "User does not exist"})
156
157 # Edge Case: Handle lender and borrower being the same person
158 if lender_name == borrower_name:
159 # Return the user unchanged
160 lender["balance"] = self.calculate_balance(lender)
161 return json.dumps({"users": [lender]})
162
163 # Update debts with netting logic
164 # Get current net debt from lender's perspective
165 # Positive means lender is owed by borrower
166 # Negative means lender owes borrower
167 current_net_debt = lender["owed_by"].get(borrower_name, 0) - lender["owes"].get(borrower_name, 0)
168
169 # When lender lends borrower amount, it creates a debt where borrower owes lender
170 # But we need to net this with existing debts
171 # The transaction amount is added to what the borrower owes the lender
172 new_net_debt = current_net_debt + amount
173
174 # Update the debts based on the new net position
175 if new_net_debt > 0:
176 # Lender is owed by borrower
177 lender["owed_by"][borrower_name] = new_net_debt
178 borrower["owes"][lender_name] = new_net_debt
179 # Remove any reverse debt entries
180 lender["owes"].pop(borrower_name, None)
181 borrower["owed_by"].pop(lender_name, None)
182 elif new_net_debt < 0:
183 # Borrower is owed by lender (lender owes borrower)
184 absolute_debt = abs(new_net_debt)
185 lender["owes"][borrower_name] = absolute_debt
186 borrower["owed_by"][lender_name] = absolute_debt
187 # Remove any reverse debt entries
188 lender["owed_by"].pop(borrower_name, None)
189 borrower["owes"].pop(lender_name, None)
190 else:
191 # No debt
192 lender["owed_by"].pop(borrower_name, None)
193 lender["owes"].pop(borrower_name, None)
194 borrower["owed_by"].pop(lender_name, None)
195 borrower["owes"].pop(lender_name, None)
196
197 # Recalculate balances
198 lender["balance"] = self.calculate_balance(lender)
199 borrower["balance"] = self.calculate_balance(borrower)
200
201 # Return updated users sorted by name
202 result_users = sorted([lender, borrower], key=lambda x: x["name"])
203 return json.dumps({"users": result_users})
204 # Handled Edge Cases: Missing fields, invalid amount, lender equals borrower, user does not exist
205
206 return json.dumps({"error": "Invalid URL"})
207 # Handled Edge Cases: Missing payload, invalid JSON payload, invalid URL
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.