Skip to content

Commit 09e9355

Browse files
eyo-chenEyo Chen
andauthored
Feat/get stock list adapter (#9)
* refactor: file structure * feat: add list stock adapter * test: add testing --------- Co-authored-by: Eyo Chen <eyo.chen@amazingtalker.com>
1 parent eedb207 commit 09e9355

File tree

6 files changed

+178
-7
lines changed

6 files changed

+178
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
from typing import List
12
from abc import ABC, abstractmethod
2-
from domain.stock import CreateStock
3+
4+
from domain.stock import CreateStock, Stock
35

46

57
class AbstractStockRepository(ABC):
68
@abstractmethod
79
def create(self, stock: CreateStock) -> str:
810
"""Create a new stock entry in the repository."""
11+
12+
def list(self, user_id: int) -> List[Stock]:
13+
"""List all stock by user id"""
Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from typing import List
2+
13
from pymongo import MongoClient
24
from pymongo.database import Database
5+
36
from .base import AbstractStockRepository
4-
from domain.stock import CreateStock
7+
from domain.stock import CreateStock, Stock, ActionType
58

69

710
class StockRepository(AbstractStockRepository):
@@ -24,5 +27,24 @@ def create(self, stock: CreateStock) -> str:
2427
result = self.collection.insert_one(stock_dict)
2528
return str(result.inserted_id)
2629

30+
def list(self, user_id: int) -> List[Stock]:
31+
stock_docs = self.collection.find({"user_id": user_id})
32+
33+
stocks = [
34+
Stock(
35+
id=str(doc["_id"]),
36+
user_id=doc["user_id"],
37+
symbol=doc["symbol"],
38+
price=doc["price"],
39+
quantity=doc["quantity"],
40+
action_type=ActionType(doc["action_type"]),
41+
created_at=doc["created_at"],
42+
updated_at=doc["updated_at"],
43+
)
44+
for doc in stock_docs
45+
]
46+
47+
return stocks
48+
2749
def __del__(self):
2850
self.client.close()

src/domain/stock.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,15 @@ class CreateStock:
2020
quantity: int
2121
action_type: ActionType
2222
created_at: datetime
23+
24+
25+
@dataclass
26+
class Stock:
27+
id: str
28+
user_id: int
29+
symbol: str
30+
price: float
31+
quantity: int
32+
action_type: ActionType
33+
created_at: datetime
34+
updated_at: datetime

src/index.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pymongo import MongoClient
55
from dotenv import load_dotenv
66
from handler.stock import StockService
7-
from adapters.repositories.stock.stock import StockRepository
7+
from adapters.stock import StockRepository
88
from usecase.stock.stock import StockUsecase
99

1010

src/tests/test_stock_adapters.py

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import pytest
2-
from datetime import datetime
2+
from datetime import datetime, timezone
3+
34
from pymongo import MongoClient
45
from bson.objectid import ObjectId
5-
from domain.stock import CreateStock, ActionType
6-
from adapters.repositories.stock.stock import StockRepository
6+
7+
from domain.stock import CreateStock, ActionType, Stock
8+
from adapters.stock import StockRepository
79

810

911
@pytest.fixture(scope="module")
@@ -115,3 +117,133 @@ def test_create_multiple_stocks(self, stock_repository):
115117

116118
# Compare the sets
117119
assert expected_set == actual_set, f"Expected {expected_set}, but got {actual_set}"
120+
121+
def test_list_stocks(self, stock_repository):
122+
# Create mock stock data
123+
created_at = datetime.now(timezone.utc)
124+
mock_stock1 = {
125+
"user_id": 1,
126+
"symbol": "AAPL",
127+
"price": 150.25,
128+
"quantity": 100,
129+
"action_type": ActionType.BUY.value,
130+
"created_at": created_at,
131+
"updated_at": created_at,
132+
}
133+
mock_stock2 = {
134+
"user_id": 1,
135+
"symbol": "TSLA",
136+
"price": 110.25,
137+
"quantity": 50,
138+
"action_type": ActionType.SELL.value,
139+
"created_at": created_at,
140+
"updated_at": created_at,
141+
}
142+
mock_stock3 = {
143+
"user_id": 2, # Different user_id
144+
"symbol": "GOOGL",
145+
"price": 2500.50,
146+
"quantity": 10,
147+
"action_type": ActionType.BUY.value,
148+
"created_at": created_at,
149+
"updated_at": created_at,
150+
}
151+
152+
# Insert mock stocks directly into the MongoDB collection
153+
result1 = stock_repository.collection.insert_one(mock_stock1)
154+
result2 = stock_repository.collection.insert_one(mock_stock2)
155+
stock_repository.collection.insert_one(mock_stock3)
156+
157+
# Get the inserted IDs
158+
stock_id1 = str(result1.inserted_id)
159+
stock_id2 = str(result2.inserted_id)
160+
161+
# Expected Stock objects for user_id=1
162+
expected_stocks = [
163+
Stock(
164+
id=stock_id1,
165+
user_id=mock_stock1["user_id"],
166+
symbol=mock_stock1["symbol"],
167+
price=mock_stock1["price"],
168+
quantity=mock_stock1["quantity"],
169+
action_type=ActionType(mock_stock1["action_type"]),
170+
created_at=mock_stock1["created_at"],
171+
updated_at=mock_stock1["updated_at"],
172+
),
173+
Stock(
174+
id=stock_id2,
175+
user_id=mock_stock2["user_id"],
176+
symbol=mock_stock2["symbol"],
177+
price=mock_stock2["price"],
178+
quantity=mock_stock2["quantity"],
179+
action_type=ActionType(mock_stock2["action_type"]),
180+
created_at=mock_stock2["created_at"],
181+
updated_at=mock_stock2["updated_at"],
182+
),
183+
]
184+
185+
# Action
186+
result = stock_repository.list(1)
187+
188+
# Assertions
189+
assert len(result) == 2, f"Expected 2 stocks, but got {len(result)}"
190+
assert all(isinstance(stock, Stock) for stock in result), "All results should be Stock objects"
191+
192+
# Convert to sets for comparison (to ignore order)
193+
result_set = {
194+
(
195+
stock.id,
196+
stock.user_id,
197+
stock.symbol,
198+
stock.price,
199+
stock.quantity,
200+
stock.action_type,
201+
)
202+
for stock in result
203+
}
204+
expected_set = {
205+
(
206+
stock.id,
207+
stock.user_id,
208+
stock.symbol,
209+
stock.price,
210+
stock.quantity,
211+
stock.action_type,
212+
)
213+
for stock in expected_stocks
214+
}
215+
216+
assert result_set == expected_set, f"Expected {expected_set}, but got {result_set}"
217+
218+
def test_list_stocks_no_data(self, stock_repository):
219+
# Create mock stock data for user_id=1
220+
created_at = datetime.now(timezone.utc)
221+
mock_stock1 = {
222+
"user_id": 1,
223+
"symbol": "AAPL",
224+
"price": 150.25,
225+
"quantity": 100,
226+
"action_type": ActionType.BUY.value,
227+
"created_at": created_at,
228+
"updated_at": created_at,
229+
}
230+
mock_stock2 = {
231+
"user_id": 1,
232+
"symbol": "TSLA",
233+
"price": 110.25,
234+
"quantity": 50,
235+
"action_type": ActionType.SELL.value,
236+
"created_at": created_at,
237+
"updated_at": created_at,
238+
}
239+
240+
# Insert mock stocks directly into the MongoDB collection
241+
stock_repository.collection.insert_many([mock_stock1, mock_stock2])
242+
243+
# Query for a user_id with no stock data
244+
result = stock_repository.list(999) # Non-existent user_id
245+
246+
# Assertions
247+
assert len(result) == 0, f"Expected empty list, but got {len(result)} stocks"
248+
assert isinstance(result, list), "Result should be a list"
249+
assert all(isinstance(stock, Stock) for stock in result), "All results should be Stock objects (if any)"

src/usecase/stock/stock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from domain.stock import CreateStock
2-
from adapters.repositories.stock.base import AbstractStockRepository
2+
from adapters.base import AbstractStockRepository
33
from .base import AbstractStockUsecase
44

55

0 commit comments

Comments
 (0)