Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions monarchmoney/monarchmoney.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,72 @@ async def get_account_history(self, account_id: int) -> Dict[str, Any]:

return account_balance_history

async def get_categories(self) -> Dict[str, Any]:
"""
Gets transaction category data from the account.
"""

query = gql(
"""
query GetCategories {
categories {
...CategoryFields
__typename
}
}

fragment CategoryFields on Category {
id
order
name
icon
systemCategory
isSystemCategory
isDisabled
group {
id
name
type
__typename
}
__typename
}
"""
)
return await self.gql_call(
operation="GetCategories",
graphql_query=query,
)

async def get_merchants(self) -> Dict[str, Any]:
query = gql(
"""
query Web_GetMerchantSettingsPage($offset: Int, $orderBy: MerchantOrdering, $search: String) {
merchants(offset: $offset, orderBy: $orderBy, search: $search) {
id
name
transactionCount
createdAt
logoUrl
recurringTransactionStream {
id
__typename
}
__typename
}
merchantCount
}
"""
)
return await self.gql_call(
operation="",
graphql_query=query,
variables={
"search" : "",
"orderBy": "NAME",
},
)

async def get_institutions(self) -> Dict[str, Any]:
"""
Gets institution data from the account.
Expand Down
70 changes: 70 additions & 0 deletions tests/get_categories.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"data": {
"categories": [
{
"id": "92233350202671406",
"order": 0,
"name": "Advertising & Promotion",
"icon": "\ud83d\udce3",
"systemCategory": "advertising_promotion",
"isSystemCategory": true,
"isDisabled": false,
"group": {
"id": "92233350076842492",
"name": "Business",
"type": "expense",
"__typename": "CategoryGroup"
},
"__typename": "Category"
},
{
"id": "92233350125076734",
"order": 0,
"name": "Auto Payment",
"icon": "\ud83d\ude97",
"systemCategory": "auto_payment",
"isSystemCategory": true,
"isDisabled": false,
"group": {
"id": "92233350063210993",
"name": "Auto & Transport",
"type": "expense",
"__typename": "CategoryGroup"
},
"__typename": "Category"
},
{
"id": "92233350121931003",
"order": 0,
"name": "Charity",
"icon": "\ud83c\udf97",
"systemCategory": "charity",
"isSystemCategory": true,
"isDisabled": false,
"group": {
"id": "92233350062162416",
"name": "Gifts & Donations",
"type": "expense",
"__typename": "CategoryGroup"
},
"__typename": "Category"
},
{
"id": "92233350180651293",
"order": 0,
"name": "Child Education",
"icon": "\ud83d\udcda",
"systemCategory": null,
"isSystemCategory": false,
"isDisabled": false,
"group": {
"id": "92233350070551031",
"name": "Children",
"type": "expense",
"__typename": "CategoryGroup"
},
"__typename": "Category"
}
]
}
}
34 changes: 34 additions & 0 deletions tests/get_merchants.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"data": {
"merchants": [
{
"id": "1",
"name": "Apple, Inc.",
"transactionCount": 645,
"createdAt": "2024-08-01 16:50:23.104034+00:00",
"logoUrl": null,
"recurringTransactionStream": null,
"__typename": "Merchant"
},
{
"id": "2",
"name": "Amazon",
"transactionCount": 470,
"createdAt": "2022-02-25 02:53:30.486595+00:00",
"logoUrl": "https://res.cloudinary.com/monarch-money/image/authenticated/s--qvSwD8cA--/c_thumb,h_132,w_132/v1/production/merchant_logos/provider/amazon_qefftq",
"recurringTransactionStream": null,
"__typename": "Merchant"
},
{
"id": "3",
"name": "Rando Bank",
"transactionCount": 340,
"createdAt": "2022-09-02 08:17:00.284680+00:00",
"logoUrl": null,
"recurringTransactionStream": null,
"__typename": "Merchant"
}
],
"merchantCount": 3
}
}
94 changes: 94 additions & 0 deletions tests/test_monarchmoney.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,100 @@ async def test_get_account_type_options(self, mock_execute_async):
"Expected third account type option name to be 'real_estate'",
)

@patch.object(Client, "execute_async")
async def test_get_merchants(self, mock_execute_async):
"""
Test the get_merchants method.
"""
# Mock the execute_async method to return test data
mock_execute_async.return_value = TestMonarchMoney.loadTestData(
filename="get_merchants.json",
)

# Call the get_merchants method
result = await self.monarch_money.get_merchants()

# Assert execute_async was called once
mock_execute_async.assert_called_once()

# Verify result is not None
self.assertIsNotNone(result, "Expected result to not be None")

# Verify merchants data structure
self.assertIn("merchants", result, "Expected merchants key in response")
self.assertEqual(
len(result["merchants"]), 3, "Expected 3 merchants"
)
self.assertEqual(
result["merchants"][0]["name"],
"Amazon",
"Expected first merchant name to be 'Amazon'"
)
self.assertEqual(
result["merchants"][1]["name"],
"Target",
"Expected second merchant name to be 'Target'"
)

@patch.object(Client, "execute_async")
async def test_get_categories(self, mock_execute_async):
"""
Test the get_categories method.
"""
# Mock the execute_async method to return a test result
mock_execute_async.return_value = TestMonarchMoney.loadTestData(
filename="get_categories.json",
)

# Call the get_categories method
result = await self.monarch_money.get_categories()

# Assert that the execute_async method was called once
mock_execute_async.assert_called_once()

# Assert that the result is not None
self.assertIsNotNone(result, "Expected result to not be None")

# Assert that the result matches the expected output
self.assertEqual(
len(result["categories"]), 5, "Expected 5 categories"
)
self.assertEqual(
result["categories"][0]["name"],
"Groceries",
"Expected first category name to be 'Groceries'",
)
self.assertEqual(
result["categories"][1]["name"],
"Utilities",
"Expected second category name to be 'Utilities'",
)
self.assertEqual(
result["categories"][2]["name"],
"Rent",
"Expected third category name to be 'Rent'",
)
self.assertEqual(
result["categories"][3]["name"],
"Entertainment",
"Expected fourth category name to be 'Entertainment'",
)
self.assertEqual(
result["categories"][4]["name"],
"Transportation",
"Expected fifth category name to be 'Transportation'",
)
self.assertEqual(
result["accountTypeOptions"][1]["type"]["name"],
"brokerage",
"Expected second account type option name to be 'brokerage'",
)
self.assertEqual(
result["accountTypeOptions"][2]["type"]["name"],
"real_estate",
"Expected third account type option name to be 'real_estate'",
)

@patch.object(Client, "execute_async")
async def test_get_account_holdings(self, mock_execute_async):
"""
Expand Down