perfect

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 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 Raises:
29 ValueError: If the URL is not recognized.
30 """
31 if url == "/users":
32 if payload is None:
33 # Return all users sorted by name
34 users = sorted(self.database["users"], key=lambda x: x["name"])
35 return json.dumps({"users": users})
36 else:
37 # Return only specified users sorted by name
38 payload_data = json.loads(payload)
39 requested_users = payload_data.get("users", [])
40
41 # Edge Case: Handle when users list is empty
42 if not requested_users:
43 return json.dumps({"users": []})
44
45 # Filter users
46 result_users = [user for user in self.database["users"] if user["name"] in requested_users]
47 # Sort by name
48 result_users = sorted(result_users, key=lambda x: x["name"])
49 return json.dumps({"users": result_users})
50 else:
51 raise ValueError(f"Unknown URL: {url}")
52
853 def post(self, url: str, payload: str | None = None) -> str:
9 pass
54 """
55 Handle POST requests to the API.
56
57 Args:
58 url: The endpoint URL.
59 payload: JSON payload.
60
61 Returns:
62 JSON string response.
63
64 Raises:
65 ValueError: If the URL is not recognized or if there are validation errors.
66 """
67 if url == "/add":
68 if payload is None:
69 raise ValueError("Payload required for /add endpoint")
70
71 payload_data = json.loads(payload)
72 new_user_name = payload_data.get("user")
73
74 # Edge Case: Handle missing user field
75 if new_user_name is None:
76 raise ValueError("User field is required")
77
78 # Edge Case: Handle duplicate user
79 for user in self.database["users"]:
80 if user["name"] == new_user_name:
81 raise ValueError(f"User {new_user_name} already exists")
82
83 # Create new user
84 new_user = {
85 "name": new_user_name,
86 "owes": {},
87 "owed_by": {},
88 "balance": 0.0
89 }
90
91 self.database["users"].append(new_user)
92
93 # Sort users by name
94 self.database["users"] = sorted(self.database["users"], key=lambda x: x["name"])
95
96 return json.dumps(new_user)
97
98 elif url == "/iou":
99 if payload is None:
100 raise ValueError("Payload required for /iou endpoint")
101
102 payload_data = json.loads(payload)
103 lender_name = payload_data.get("lender")
104 borrower_name = payload_data.get("borrower")
105 amount = payload_data.get("amount")
106
107 # Edge Case: Handle missing fields
108 if lender_name is None or borrower_name is None or amount is None:
109 raise ValueError("Lender, borrower, and amount fields are required")
110
111 # Edge Case: Handle lender and borrower being the same person
112 if lender_name == borrower_name:
113 raise ValueError("Lender and borrower cannot be the same person")
114
115 # Edge Case: Handle negative amount
116 if amount < 0:
117 raise ValueError("Amount must be non-negative")
118
119 # Find lender and borrower
120 lender = None
121 borrower = None
122
123 for user in self.database["users"]:
124 if user["name"] == lender_name:
125 lender = user
126 elif user["name"] == borrower_name:
127 borrower = user
128
129 # Edge Case: Handle unknown lender or borrower
130 if lender is None:
131 raise ValueError(f"User {lender_name} does not exist")
132 if borrower is None:
133 raise ValueError(f"User {borrower_name} does not exist")
134
135 # Update owes and owed_by with proper netting logic
136 # Check if lender already owes money to borrower
137 existing_lender_owes_borrower = lender["owes"].get(borrower_name, 0)
138
139 if existing_lender_owes_borrower > 0:
140 # Lender owes borrower money, so we need to net the amounts
141 if amount == existing_lender_owes_borrower:
142 # Exact cancellation - remove all entries (net zero)
143 del lender["owes"][borrower_name]
144 del borrower["owed_by"][lender_name]
145 elif amount > existing_lender_owes_borrower:
146 # New loan is larger - borrower now owes lender the difference
147 del lender["owes"][borrower_name]
148 del borrower["owed_by"][lender_name]
149
150 difference = amount - existing_lender_owes_borrower
151 lender["owed_by"][borrower_name] = difference
152 borrower["owes"][lender_name] = difference
153 else:
154 # Existing debt is larger - reduce the amount lender owes
155 new_amount = existing_lender_owes_borrower - amount
156 lender["owes"][borrower_name] = new_amount
157 borrower["owed_by"][lender_name] = new_amount
158 else:
159 # No existing debt from lender to borrower, just add the new IOU
160 # For lender: add borrower to owed_by
161 if borrower_name in lender["owed_by"]:
162 lender["owed_by"][borrower_name] += amount
163 else:
164 lender["owed_by"][borrower_name] = amount
165
166 # For borrower: add lender to owes
167 if lender_name in borrower["owes"]:
168 borrower["owes"][lender_name] += amount
169 else:
170 borrower["owes"][lender_name] = amount
171
172 # Recalculate balances
173 lender_total_owed = sum(lender["owed_by"].values())
174 lender_total_owes = sum(lender["owes"].values())
175 lender["balance"] = lender_total_owed - lender_total_owes
176
177 borrower_total_owed = sum(borrower["owed_by"].values())
178 borrower_total_owes = sum(borrower["owes"].values())
179 borrower["balance"] = borrower_total_owed - borrower_total_owes
180
181
182
183 # Return updated users sorted by name
184 result_users = sorted([lender, borrower], key=lambda x: x["name"])
185 return json.dumps({"users": result_users})
186
187 else:
188 raise ValueError(f"Unknown URL: {url}")
189
190 # Handled Edge Cases: Missing payload for /add, missing user field, duplicate user,
191 # missing fields for /iou, lender and borrower being the same,
192 # negative amount, unknown lender or borrower, zero balances
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.