fortitude

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 supported.
30 """
31 if url == "/users":
32 if payload is None:
33 # Return all users
34 users = [self._create_user_object(user) for user in self.database["users"]]
35 return json.dumps({"users": users})
36 else:
37 # Return specific users
38 payload_data = json.loads(payload)
39 user_names = payload_data.get("users", [])
40
41 # Edge Case: Empty user list in payload
42 if not user_names:
43 return json.dumps({"users": []})
44
45 # Edge Case: Non-existent users in payload
46 result_users = []
47 for user_name in user_names:
48 user = self._find_user(user_name)
49 if user:
50 result_users.append(self._create_user_object(user))
51
52 # Sort by name
53 result_users.sort(key=lambda u: u["name"])
54 return json.dumps({"users": result_users})
55 else:
56 # Edge Case: Unsupported URL
57 raise ValueError(f"Unsupported URL: {url}")
58
859 def post(self, url: str, payload: str | None = None) -> str:
9 pass
60 """
61 Handle POST requests to the API.
62
63 Args:
64 url: The endpoint URL.
65 payload: JSON payload.
66
67 Returns:
68 JSON string response.
69
70 Raises:
71 ValueError: If the URL is not supported or if required data is missing.
72 """
73 if payload is None:
74 # Edge Case: Missing payload
75 raise ValueError("Payload is required for POST requests")
76
77 payload_data = json.loads(payload)
78
79 if url == "/add":
80 user_name = payload_data.get("user")
81
82 # Edge Case: Missing user name
83 if not user_name:
84 raise ValueError("User name is required")
85
86 # Edge Case: User already exists
87 if self._find_user(user_name):
88 raise ValueError(f"User {user_name} already exists")
89
90 new_user = {
91 "name": user_name,
92 "owes": {},
93 "owed_by": {},
94 "balance": 0.0
95 }
96 self.database["users"].append(new_user)
97 return json.dumps(self._create_user_object(new_user))
98
99 elif url == "/iou":
100 lender_name = payload_data.get("lender")
101 borrower_name = payload_data.get("borrower")
102 amount = payload_data.get("amount")
103
104 # Edge Case: Missing required fields
105 if not lender_name or not borrower_name or amount is None:
106 raise ValueError("Lender, borrower, and amount are required")
107
108 # Edge Case: Negative amount
109 if amount < 0:
110 raise ValueError("Amount must be non-negative")
111
112 # Edge Case: Lender and borrower are the same
113 if lender_name == borrower_name:
114 raise ValueError("Lender and borrower cannot be the same person")
115
116 lender = self._find_user(lender_name)
117 borrower = self._find_user(borrower_name)
118
119 # Edge Case: Non-existent lender or borrower
120 if not lender:
121 raise ValueError(f"User {lender_name} does not exist")
122 if not borrower:
123 raise ValueError(f"User {borrower_name} does not exist")
124
125 # Handle debt offsetting
126 # If lender owes borrower money, reduce that debt first
127 if borrower_name in lender["owes"]:
128 existing_debt = lender["owes"][borrower_name]
129 if amount > existing_debt:
130 # Remove the existing debt and create new owed amount
131 del lender["owes"][borrower_name]
132 del borrower["owed_by"][lender_name]
133
134 # Add the remaining amount to owed_by
135 new_amount = amount - existing_debt
136 lender["owed_by"][borrower_name] = new_amount
137 borrower["owes"][lender_name] = new_amount
138 elif amount == existing_debt:
139 # Exactly offset the debt
140 del lender["owes"][borrower_name]
141 del borrower["owed_by"][lender_name]
142 else:
143 # Reduce the existing debt
144 lender["owes"][borrower_name] -= amount
145 borrower["owed_by"][lender_name] -= amount
146 else:
147 # No existing debt from lender to borrower
148 # Update lender's owed_by
149 if borrower_name in lender["owed_by"]:
150 lender["owed_by"][borrower_name] += amount
151 else:
152 lender["owed_by"][borrower_name] = amount
153
154 # Update borrower's owes
155 if lender_name in borrower["owes"]:
156 borrower["owes"][lender_name] += amount
157 else:
158 borrower["owes"][lender_name] = amount
159
160 # Recalculate balances from scratch
161 lender_total_owed_by = sum(lender["owed_by"].values())
162 lender_total_owes = sum(lender["owes"].values())
163 lender["balance"] = lender_total_owed_by - lender_total_owes
164
165 borrower_total_owed_by = sum(borrower["owed_by"].values())
166 borrower_total_owes = sum(borrower["owes"].values())
167 borrower["balance"] = borrower_total_owed_by - borrower_total_owes
168
169 # Return updated users
170 users = [self._create_user_object(lender), self._create_user_object(borrower)]
171 users.sort(key=lambda u: u["name"])
172 return json.dumps({"users": users})
173 else:
174 # Edge Case: Unsupported URL
175 raise ValueError(f"Unsupported URL: {url}")
176
177 def _find_user(self, name: str) -> Optional[Dict]:
178 """
179 Find a user by name in the database.
180
181 Args:
182 name: The name of the user to find.
183
184 Returns:
185 The user dictionary if found, None otherwise.
186 """
187 for user in self.database["users"]:
188 if user["name"] == name:
189 return user
190 return None
191
192 def _create_user_object(self, user: Dict) -> Dict:
193 """
194 Create a user object for API response.
195
196 Args:
197 user: The user dictionary from the database.
198
199 Returns:
200 A user object formatted for API response.
201 """
202 # Calculate balance from owes and owed_by
203 total_owed_by = sum(user["owed_by"].values())
204 total_owes = sum(user["owes"].values())
205 balance = total_owed_by - total_owes
206
207 return {
208 "name": user["name"],
209 "owes": user["owes"],
210 "owed_by": user["owed_by"],
211 "balance": balance
212 }
213
214# Handled Edge Cases: Unsupported URL, Missing payload, Missing user name, User already exists, Missing required fields, Negative amount, Lender and borrower are the same, Non-existent lender or borrower, Empty user list in payload, Non-existent users in payload
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.