From 89af298837dc46f2a5ad51c220be00b518621c39 Mon Sep 17 00:00:00 2001 From: Stefano Tabacco Date: Wed, 2 Feb 2022 20:40:30 +1100 Subject: [PATCH] Issue 138 funding (#139) * Using templates * removed response headers * Fixed fundings * Commit from GitHub Actions --- coverage.svg | 4 +- stake/constant.py | 2 +- stake/funding.py | 58 ++++--------- stake/transaction.py | 43 ++++++++-- .../test_funding/test_funds_in_flight.yaml | 86 +------------------ .../test_funding/test_list_fundings.yaml | 49 ++++------- tests/conftest.py | 3 + tests/test_funding.py | 41 +++++---- 8 files changed, 101 insertions(+), 185 deletions(-) diff --git a/coverage.svg b/coverage.svg index 3438732..0fa9649 100644 --- a/coverage.svg +++ b/coverage.svg @@ -15,7 +15,7 @@ coverage coverage - 97% - 97% + 98% + 98% diff --git a/stake/constant.py b/stake/constant.py index 019253a..ff39870 100644 --- a/stake/constant.py +++ b/stake/constant.py @@ -10,7 +10,7 @@ class Url(str, Enum): create_session: str = "sessions/v2/createSession" equity_positions: str = "users/accounts/v2/equityPositions" fund_details: str = "fund/details" - fundings: str = "utils/activityLog/fundingOnly" + fundings: str = "users/accounts/transactionHistory" market_status: str = "utils/marketStatus" orders: str = "users/accounts/v2/orders" quotes: str = "quotes/marketData/${symbols}" diff --git a/stake/funding.py b/stake/funding.py index ea1dc3b..0553b98 100644 --- a/stake/funding.py +++ b/stake/funding.py @@ -1,45 +1,17 @@ """Your current fundings.""" -from datetime import date, datetime, timedelta +import json +from datetime import datetime from typing import List, Optional from pydantic import BaseModel, Field -from pydantic.types import UUID4 from stake.common import BaseClient, camelcase from stake.constant import Url - -__all__ = ["FundingRequest"] - - -class FundingRequest(BaseModel): - """Request to be issued to the fundings endpoint.""" - - end_date: date = Field(default_factory=date.today) - start_date: date = Field( - default_factory=lambda *_: date.today() - timedelta(days=365) - ) - - -class Funding(BaseModel): - id: UUID4 - timestamp: datetime - order_type: str - event_type: str - status: str - title: str - amount: str - description: str - currency_from: str - currency_to: str - spot_rate: float - total_fee: float - amount_from: float - amount_to: float - rate: float - reference_number: str - - class Config: - alias_generator = camelcase +from stake.transaction import ( + TransactionHistoryElement, + TransactionHistoryType, + TransactionRecordRequest, +) class CashSettlement(BaseModel): @@ -80,14 +52,16 @@ class Config: class FundingsClient(BaseClient): - async def list(self, request: FundingRequest) -> List[Funding]: - payload = { - "endDate": request.end_date.strftime("%d/%m/%Y"), - "startDate": request.start_date.strftime("%d/%m/%Y"), - } + async def list( + self, request: TransactionRecordRequest + ) -> List[TransactionHistoryElement]: + payload = json.loads(request.json(by_alias=True)) data = await self._client.post(Url.fundings, payload=payload) - - return [Funding(**d) for d in data] + return [ + TransactionHistoryElement(**d) + for d in data + if d["referenceType"] == TransactionHistoryType.FUNDING.value + ] async def in_flight(self) -> List[FundsInFlight]: """Returns the funds currently in flight.""" diff --git a/stake/transaction.py b/stake/transaction.py index 8f1fc51..7c577a8 100644 --- a/stake/transaction.py +++ b/stake/transaction.py @@ -1,3 +1,5 @@ +import enum +import json from datetime import datetime, timedelta from enum import Enum from typing import Dict, List, Optional @@ -19,7 +21,7 @@ class TransactionRecordEnumDirection(str, Enum): class TransactionRecordRequest(BaseModel): to: datetime = Field(default_factory=datetime.utcnow) from_: datetime = Field( - default_factory=lambda *_: datetime.utcnow() - timedelta(days=365) + default_factory=lambda *_: datetime.utcnow() - timedelta(days=365), alias="from" ) limit: int = 1000 offset: Optional[int] @@ -68,6 +70,40 @@ class Config: alias_generator = camelcase +class TransactionHistoryType(str, enum.Enum): + BUY = "Buy" + CORPORATE_ACTION = "Corporate Action" + DIVIDEND = "Dividend" + DIVIDEND_TAX = "Dividend Tax" + FUNDING = "Funding" + SELL = "Sell" + + +class TransactionHistorySide(str, enum.Enum): + CREDIT = "CREDIT" + DEBIT = "DEBIT" + + +class TransactionHistoryElement(BaseModel): + transaction_type: TransactionHistoryType + fin_tran_type_id: Optional[str] = None + timestamp: datetime + tran_amount: Optional[float] = None + fee_amount: Optional[float] = None + side: TransactionHistorySide + text: str + comment: str + amount_per_share: float + tax_rate: float + order_id: Optional[str] = None + symbol: Optional[str] = None + reference: Optional[str] = None + reference_type: Optional[TransactionHistoryType] = None + + class Config: + alias_generator = camelcase + + class TransactionsClient(BaseClient): async def list(self, request: TransactionRecordRequest) -> List[Transaction]: """Returns the transactions executed by the user. @@ -79,10 +115,7 @@ async def list(self, request: TransactionRecordRequest) -> List[Transaction]: Returns: List[Transaction]: the transactions executed in the time frame. """ - payload = request.dict() - payload.update( - {"to": payload["to"].isoformat(), "from": payload.pop("from_").isoformat()} - ) + payload = json.loads(request.json(by_alias=True)) data = await self._client.post(Url.account_transactions, payload=payload) transactions = [] diff --git a/tests/cassettes/test_funding/test_funds_in_flight.yaml b/tests/cassettes/test_funding/test_funds_in_flight.yaml index ab8aa73..194badf 100644 --- a/tests/cassettes/test_funding/test_funds_in_flight.yaml +++ b/tests/cassettes/test_funding/test_funds_in_flight.yaml @@ -27,41 +27,7 @@ interactions: "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": null, "documentsStatus": "NO_ACTION", "mfaenabled": false}' - headers: - Access-Control-Allow-Headers: - - Content-type, Accept, Stake-Session-Token, stake_tid - Access-Control-Allow-Methods: - - GET, POST, DELETE, PUT - Access-Control-Allow-Origin: - - '*' - Connection: - - keep-alive - Content-Length: - - '1187' - Content-Type: - - application/json - Date: - - Wed, 19 Jan 2022 23:47:25 GMT - Via: - - 1.1 e3d6764a647541ed814ff5842b8b1476.cloudfront.net (CloudFront) - X-Amz-Cf-Id: - - k3bkL5K_B72p2N5kVZDNcc1Y40RN6yIA731WDQscLeiZ9Cb8aBQ9aA== - X-Amz-Cf-Pop: - - SYD62-P2 - X-Cache: - - Miss from cloudfront - stake_tid: - - 656bf946-1cca-49b2-8c30-8ed012bf8467 - x-amz-apigw-id: - - MN5qBERFywMF-sQ= - x-amzn-Remapped-Content-Length: - - '1187' - x-amzn-Remapped-Date: - - Wed, 19 Jan 2022 23:47:24 GMT - x-amzn-Remapped-Server: - - Jetty(9.4.44.v20210927) - x-amzn-RequestId: - - fc247b8a-2fcc-42d0-9ff2-4e1f17397d60 + headers: {} status: code: 200 message: OK @@ -77,54 +43,8 @@ interactions: uri: https://global-prd-api.hellostake.com/api/fund/details response: body: - string: | - '{"fundsInFlight": [ - { - "type": "Funding", - "insertDateTime": "17 Sep 2020 05:21AM", - "estimatedArrivalTime": "19 Sep 2020 05:00AM", - "estimatedArrivalTimeUS": "Fri 1:00pm NY time", - "transactionType": "Poli", - "toAmount": 1512.0, - "fromAmount": 2100.0 - } - ]} - ' - headers: - Access-Control-Allow-Headers: - - Content-type, Accept, Stake-Session-Token, stake_tid - Access-Control-Allow-Methods: - - GET, POST, DELETE, PUT - Access-Control-Allow-Origin: - - '*' - Connection: - - keep-alive - Content-Length: - - '20' - Content-Type: - - application/json - Date: - - Wed, 19 Jan 2022 23:47:25 GMT - Via: - - 1.1 b96ad58427ffff8b9d3959350f8c9f16.cloudfront.net (CloudFront) - X-Amz-Cf-Id: - - ya0CFD1VuJq5VOB_JdqS4gLyUgtFQBrdZI8OMv7PFkzxYQ-gqLtnuw== - X-Amz-Cf-Pop: - - SYD62-P2 - X-Cache: - - Miss from cloudfront - stake_tid: - - 179618fa-5fb5-4281-8e0e-1a28093ed963 - x-amz-apigw-id: - - MN5qHEYpSwMF74w= - x-amzn-Remapped-Content-Length: - - '20' - x-amzn-Remapped-Date: - - Wed, 19 Jan 2022 23:47:25 GMT - x-amzn-Remapped-Server: - - Jetty(9.4.44.v20210927) - x-amzn-RequestId: - - 513d1f21-81a5-4a7d-b960-f6cdc9f96dde + string: '{"fundsInFlight": [{"type": "Funding","insertDateTime": "17 Sep 2020 05:21AM","estimatedArrivalTime": "19 Sep 2020 05:00AM","estimatedArrivalTimeUS": "Fri 1:00pm NY time","transactionType": "Poli", "toAmount": 1512.0,"fromAmount": 2100.0}]}' + headers: {} status: code: 200 message: OK diff --git a/tests/cassettes/test_funding/test_list_fundings.yaml b/tests/cassettes/test_funding/test_list_fundings.yaml index 14d48f5..bfec192 100644 --- a/tests/cassettes/test_funding/test_list_fundings.yaml +++ b/tests/cassettes/test_funding/test_list_fundings.yaml @@ -27,43 +27,26 @@ interactions: "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": null, "documentsStatus": "NO_ACTION", "mfaenabled": false}' + headers: {} + status: + code: 200 + message: OK + url: https://global-prd-api.hellostake.com/api/user +- request: + body: null headers: - Access-Control-Allow-Headers: - - Content-type, Accept, Stake-Session-Token, stake_tid - Access-Control-Allow-Methods: - - GET, POST, DELETE, PUT - Access-Control-Allow-Origin: - - '*' - Connection: - - keep-alive - Content-Length: - - '1187' + Accept: + - application/json Content-Type: - application/json - Date: - - Wed, 19 Jan 2022 23:47:22 GMT - Via: - - 1.1 f1add8f4c4c2d3927809bab0bfad9b82.cloudfront.net (CloudFront) - X-Amz-Cf-Id: - - x4D2vnxhunv51D7owRTp1faZ6Au0cDkdVnFiZiL_ns05SW8nhzwnYA== - X-Amz-Cf-Pop: - - SYD62-P2 - X-Cache: - - Miss from cloudfront - stake_tid: - - 1d0beda3-3154-4fd2-bc31-139b5d5a2b5d - x-amz-apigw-id: - - MN5poG8mywMFY2Q= - x-amzn-Remapped-Content-Length: - - '1187' - x-amzn-Remapped-Date: - - Wed, 19 Jan 2022 23:47:22 GMT - x-amzn-Remapped-Server: - - Jetty(9.4.44.v20210927) - x-amzn-RequestId: - - 329118f9-1a4d-4acd-980b-940d0970b644 + method: POST + uri: https://global-prd-api.hellostake.com/api/users/accounts/transactionHistory + response: + body: + string: '[{"transactionType": "Funding","finTranTypeID": "JNLC","timestamp": "2021-09-30T15:45:50.425Z","tranAmount": 1000,"feeAmount": 0.0,"orderID": "HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59","symbol": null,"side": "CREDIT","text": "Deposit","comment": "S7-1945193t","amountPerShare": 0.0,"taxRate": 0.0,"reference": "P0-3814688Z","referenceType": "Funding"},{"transactionType": "Funding","finTranTypeID": "JNLC","timestamp": "2021-09-30T15:45:50.425Z","tranAmount": 1000,"feeAmount": 0.0,"orderID": "HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59","symbol": null,"side": "CREDIT","text": "Deposit","comment": "S7-1945193t","amountPerShare": 0.0,"taxRate": 0.0,"reference": "P0-3814688Z","referenceType": "Funding"}]' + headers: {} status: code: 200 message: OK - url: https://global-prd-api.hellostake.com/api/user + url: https://global-prd-api.hellostake.com/api/users/accounts/transactionHistory version: 1 diff --git a/tests/conftest.py b/tests/conftest.py index 17fa2b0..bb2756b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -61,6 +61,9 @@ def redact_sensitive_data(response): "cashAvailableForWithdrawal": 1000, "cashAvailableForTrade": 800, "dwCashAvailableForWithdrawal": 1000, + "reference": fake.pystr_format(), + "comment": fake.pystr_format(), + "tranAmount": 1000, } def _redact_response_body(body): diff --git a/tests/test_funding.py b/tests/test_funding.py index 9084fc1..8da026e 100644 --- a/tests/test_funding.py +++ b/tests/test_funding.py @@ -1,23 +1,26 @@ -# import pytest +import pytest -# from stake.funding import FundingRequest +from stake.funding import TransactionRecordRequest -# @pytest.mark.vcr() -# @pytest.mark.asyncio -# async def test_list_fundings(tracing_client): -# fundings = await tracing_client.fundings.list(FundingRequest()) -# assert len(fundings) == 11 -# @pytest.mark.vcr() -# @pytest.mark.asyncio -# async def test_cash_available(tracing_client): -# cash_available = await tracing_client.fundings.cash_available() -# assert cash_available.cash_available_for_withdrawal == 1000.0 -# assert cash_available.cash_settlement[0].utc_time.month == 1 +@pytest.mark.vcr() +@pytest.mark.asyncio +async def test_list_fundings(tracing_client): + fundings = await tracing_client.fundings.list(TransactionRecordRequest()) + assert len(fundings) == 2 -# @pytest.mark.vcr() -# @pytest.mark.asyncio -# async def test_funds_in_flight(tracing_client): -# funds_in_flight = await tracing_client.fundings.in_flight() -# assert len(funds_in_flight) == 1 -# assert funds_in_flight[0].transaction_type == "Poli" + +@pytest.mark.vcr() +@pytest.mark.asyncio +async def test_cash_available(tracing_client): + cash_available = await tracing_client.fundings.cash_available() + assert cash_available.cash_available_for_withdrawal == 1000.0 + assert cash_available.cash_settlement[0].utc_time.month == 1 + + +@pytest.mark.vcr() +@pytest.mark.asyncio +async def test_funds_in_flight(tracing_client): + funds_in_flight = await tracing_client.fundings.in_flight() + assert len(funds_in_flight) == 1 + assert funds_in_flight[0].transaction_type == "Poli"