Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🧪 new test cassettes #95

Merged
merged 2 commits into from
Dec 15, 2023
Merged
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
3 changes: 0 additions & 3 deletions .github/FUNDING.yaml

This file was deleted.

4 changes: 3 additions & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ jobs:
- name: Test Suite
run: |
echo "::add-matcher::.github/matchers/python.json"
hatch run test tests/ --vcr-record=none
hatch run test
echo "::remove-matcher owner=python::"
env:
VCR_RECORD_MODE: none
4 changes: 3 additions & 1 deletion lunchable/models/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ class CryptoObject(LunchableModel):
)
currency: Optional[str] = Field(description="Abbreviation for the cryptocurrency")
status: Optional[str] = Field(description=_status_description)
institution_name: str = Field(description="Name of provider holding the asset")
institution_name: Optional[str] = Field(
default=None, description="Name of provider holding the asset"
)
created_at: datetime.datetime = Field(description=_created_at_description)


Expand Down
4 changes: 3 additions & 1 deletion lunchable/models/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ class TransactionSplitObject(TransactionBaseObject):
"""

date: datetime.date = Field(description=_date_description)
category_id: int = Field(description=_category_id_description)
category_id: Optional[int] = Field(
default=None, description=_category_id_description
)
notes: Optional[str] = Field(description=_notes_description)
amount: float = Field(description=_amount_description)

Expand Down
2 changes: 1 addition & 1 deletion lunchable/plugins/base/base_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ def __builtin_data_models__(self) -> List[LunchableDataModel]:
),
]

def refresh_transactions(
def refresh_transactions( # type: ignore[override]
self,
start_date: Optional[datetime.date] = None,
end_date: Optional[datetime.date] = None,
Expand Down
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ dependencies = [
"pytest~=7.3.1",
"pytest-cov~=4.0.0",
"pytest-mock~=3.10.0",
"pytest-vcr~=1.0.2",
"vcrpy~=5.1.0",
# Tooling
"black~=23.3.0",
"ruff~=0.0.261",
Expand All @@ -92,8 +92,6 @@ dependencies = [
features = [
"all"
]
path = ".venv"
pre-install-commands = ["pip install -U --no-deps -r requirements/dev.txt"]

[tool.hatch.envs.default.env-vars]
LUNCHMONEY_ACCESS_TOKEN = "{env:LUNCHMONEY_ACCESS_TOKEN:LUNCHMONEY_ACCESS_TOKEN_PLACEHOLDER}"
Expand Down Expand Up @@ -181,7 +179,8 @@ ignore = [
"PLR0913", # Too many arguments to function call
"PLW2901", # Outer for loop variable overwritten by inner assignment target
"RUF001", # String contains ambiguous unicode character `’`
"PLR2004" # Magic value used in comparison
"PLR2004", # Magic value used in comparison
"RUF012" # Mutable class attributes should be annotated with `typing.ClassVar`
]
select = [
"E", # pycodestyle errors
Expand Down
21 changes: 8 additions & 13 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,9 @@ coverage[toml]==7.2.5 \
--hash=sha256:f6f5cab2d7f0c12f8187a376cc6582c477d2df91d63f75341307fcdcb5d60303 \
--hash=sha256:f81c9b4bd8aa747d417407a7f6f0b1469a43b36a85748145e144ac4e8d303cb5 \
--hash=sha256:f99ef080288f09ffc687423b8d60978cf3a465d3f404a18d1a05474bd8575a47
# via pytest-cov
# via
# coverage
# pytest-cov
docutils==0.18.1 \
--hash=sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c \
--hash=sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06
Expand Down Expand Up @@ -567,7 +569,6 @@ pytest==7.3.1 \
# lunchable (pyproject.toml)
# pytest-cov
# pytest-mock
# pytest-vcr
pytest-cov==4.0.0 \
--hash=sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b \
--hash=sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470
Expand All @@ -576,10 +577,6 @@ pytest-mock==3.10.0 \
--hash=sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b \
--hash=sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f
# via lunchable (pyproject.toml)
pytest-vcr==1.0.2 \
--hash=sha256:23ee51b75abbcc43d926272773aae4f39f93aceb75ed56852d0bf618f92e1896 \
--hash=sha256:2f316e0539399bea0296e8b8401145c62b6f85e9066af7e57b6151481b0d6d9c
# via lunchable (pyproject.toml)
python-dateutil==2.8.2 \
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
--hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
Expand Down Expand Up @@ -672,9 +669,7 @@ ruff==0.0.264 \
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via
# python-dateutil
# vcrpy
# via python-dateutil
snowballstemmer==2.2.0 \
--hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \
--hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a
Expand Down Expand Up @@ -775,10 +770,10 @@ urllib3==1.26.15 \
--hash=sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305 \
--hash=sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42
# via requests
vcrpy==4.2.1 \
--hash=sha256:7cd3e81a2c492e01c281f180bcc2a86b520b173d2b656cb5d89d99475423e013 \
--hash=sha256:efac3e2e0b2af7686f83a266518180af7a048619b2f696e7bad9520f5e2eac09
# via pytest-vcr
vcrpy==5.1.0 \
--hash=sha256:605e7b7a63dcd940db1df3ab2697ca7faf0e835c0852882142bafb19649d599e \
--hash=sha256:bbf1532f2618a04f11bce2a99af3a9647a32c880957293ff91e0a5f187b6b3d2
# via lunchable (pyproject.toml)
wheel==0.40.0 \
--hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \
--hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247
Expand Down
100 changes: 40 additions & 60 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
"""

import datetime
from os import getenv
from typing import Dict, List
import os
import pathlib
from typing import List

import pytest
from vcr import VCR

from lunchable import LunchMoney
from lunchable.models import TransactionObject
Expand All @@ -28,59 +30,6 @@ def obscure_start_date() -> datetime.datetime:
return obscure_start_date_object


def scrub_string(string, replacement=""):
"""
Nested Scrubbing Function
"""

def before_record_response(response):
body = response["body"]["string"]
sensitive_strings = string.split(",")
try:
sensitive_strings.remove("<LUNCH>")
except ValueError:
pass
for string_part in sensitive_strings:
string_part = string_part.strip()
if isinstance(body, bytes):
try:
body = body.decode("utf-8").replace(string_part, replacement)
body = str.encode(body)
except UnicodeDecodeError:
pass
else:
body = body.replace(string, replacement)
response["body"]["string"] = body
return response

return before_record_response


@module_scope
def vcr_config() -> Dict[str, list]:
"""
VCR Cassette Privacy Enforcer

This fixture ensures the API Credentials are obfuscated

Returns
-------
Dict[str, list]:
"""
return {
"filter_headers": [("authorization", "XXXXXXXXXX")],
"filter_query_parameters": [("user", "XXXXXXXXXX"), ("token", "XXXXXXXXXX")],
"before_record_response": scrub_string(
getenv("SENSITIVE_REQUEST_STRINGS", "<LUNCH>"), "XXXXXXXXXX"
),
}


# Decorator Object to Use pyvcr Cassettes on Unit Tests (see `pytest-vcr`)
# pass `--vcr-record=none` to pytest CI runs to ensure new cassettes are generated
lunchable_cassette = pytest.mark.vcr(scope="module")


@pytest.fixture
def lunch_money_obj() -> LunchMoney:
"""
Expand All @@ -102,14 +51,14 @@ def test_transactions() -> List[TransactionObject]:
"""
transaction_dict_1 = {
"amount": 1.0,
"asset_id": 23043,
"category_id": 229134,
"asset_id": 49335,
"category_id": 658761,
"currency": "usd",
"date": "2021-09-19",
"external_id": None,
"fees": None,
"group_id": None,
"id": 55907882,
"id": 546434801,
"is_group": False,
"notes": "Test Transaction 1",
"original_name": "Test 1",
Expand All @@ -132,7 +81,7 @@ def test_transactions() -> List[TransactionObject]:
"external_id": None,
"fees": None,
"group_id": None,
"id": 55907976,
"id": 546452296,
"is_group": False,
"notes": "Test Transaction 2",
"original_name": "Test 2",
Expand All @@ -155,7 +104,7 @@ def test_transactions() -> List[TransactionObject]:
"external_id": None,
"fees": None,
"group_id": None,
"id": 55907977,
"id": 546434806,
"is_group": False,
"notes": "Test Transaction 3",
"original_name": "Test 3",
Expand All @@ -173,3 +122,34 @@ def test_transactions() -> List[TransactionObject]:
transaction_2 = TransactionObject(**transaction_dict_2)
transaction_3 = TransactionObject(**transaction_dict_3)
return [transaction_1, transaction_2, transaction_3]


###########################################################
# VCR Configuration : Offload Epic API Calls to Cassettes #
###########################################################


def path_transformer(path: str) -> str:
"""
Cassette Path Transformer
"""
suffix = ".yaml"
if not path.endswith(suffix):
path = path + suffix
cassette_path = pathlib.Path(path)
cassette_path = cassette_path.parent / "cassettes" / cassette_path.name
return str(cassette_path)


vcr = VCR(
filter_headers=(("authorization", "XXXXXXXXXX"),),
filter_query_parameters=(("user", "XXXXXXXXXX"), ("token", "XXXXXXXXXX")),
decode_compressed_response=True,
path_transformer=path_transformer,
record_mode=os.getenv("VCR_RECORD_MODE", "once"),
)


# Decorator Object to Use pyvcr Cassettes on Unit Tests
# pass `--vcr-record=none` to pytest CI runs to ensure new cassettes are generated
lunchable_cassette = vcr.use_cassette
20 changes: 13 additions & 7 deletions tests/models/cassettes/test_add_to_category_group.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
interactions:
- request:
body: '{"category_ids": [343125], "new_categories": ["Another Another Test Category"]}'
body: '{"category_ids": [443128], "new_categories": ["Another Another Test Category"]}'
headers:
Accept:
- '*/*'
Expand All @@ -13,27 +13,33 @@ interactions:
Content-Type:
- application/json
User-Agent:
- python-requests/2.27.1
- python-requests/2.29.0
authorization:
- XXXXXXXXXX
method: POST
uri: https://dev.lunchmoney.app/v1/categories/group/343124/add
uri: https://dev.lunchmoney.app/v1/categories/group/658694/add
response:
body:
string: '{"id":343124,"name":"Test Category Group","description":"","is_income":false,"exclude_from_budget":false,"exclude_from_totals":false,"is_group":true,"group_id":null,"children":[{"id":343126,"name":"Another Another Test Category","description":null,"created_at":"2022-06-07T19:39:33.608Z"},{"id":343125,"name":"Another Test Category","description":null,"created_at":"2022-06-07T19:37:02.688Z"},{"id":343106,"name":"Test Category","description":"Test Category Description Updated","created_at":"2022-06-07T16:31:40.780Z"}]}'
string: '{"id":658694,"name":"Test Category Group","description":"Test Category Group!!","is_income":false,"exclude_from_budget":true,"exclude_from_totals":false,"is_group":true,"group_id":null,"children":[{"id":658761,"name":"Another Another Test Category","description":null,"created_at":"2023-12-15T02:42:01.147Z","archived":false,"archived_on":null},{"id":443128,"name":"Groceries","description":"Test Category Description Updated","created_at":"2023-03-07T02:10:27.268Z","archived":false,"archived_on":null}],"archived":false,"archived_on":null}'
headers:
Access-Control-Allow-Credentials:
- 'true'
Connection:
- keep-alive
Content-Length:
- '521'
- '541'
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 07 Jun 2022 19:39:33 GMT
- Fri, 15 Dec 2023 02:42:01 GMT
Etag:
- W/"209-CNJytM5IsDz5pPHDFW8MY9tiVtQ"
- W/"21d-JdapYXJpKm5jckYYF084zl/FWR8"
Nel:
- '{"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}'
Report-To:
- '{"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1702608121&sid=1b10b0ff-8a76-4548-befa-353fc6c6c045&s=IhPV7bOJdhOd%2BTJ73mF%2BY5R1AE8%2BA1EFE91jTMBZ51A%3D"}]}'
Reporting-Endpoints:
- heroku-nel=https://nel.heroku.com/reports?ts=1702608121&sid=1b10b0ff-8a76-4548-befa-353fc6c6c045&s=IhPV7bOJdhOd%2BTJ73mF%2BY5R1AE8%2BA1EFE91jTMBZ51A%3D
Server:
- Cowboy
Vary:
Expand Down
Loading