Skip to content

Commit fd2fe7e

Browse files
authored
Fail connection for deprecated streams (#128)
* Fix unit tests * Fail connection for specific selected streams * Remove preserve_refresh_token * Revert warning message * Replace tap-tester_sandbox with vm reds * Update config.yml * Enable dev mode * Update error message * Update changelog
1 parent 1a9dcc2 commit fd2fe7e

File tree

8 files changed

+38
-21
lines changed

8 files changed

+38
-21
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 2.5.0
4+
* Fail connection for deprecated streams [#128](https://github.com/singer-io/tap-xero/pull/128)
5+
36
## 2.4.0
47
* Bump singer-python version [#127](https://github.com/singer-io/tap-xero/pull/127)
58

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from setuptools import setup, find_packages
33

44
setup(name="tap-xero",
5-
version="2.4.0",
5+
version="2.5.0",
66
description="Singer.io tap for extracting data from the Xero API",
77
author="Stitch",
88
url="http://singer.io",

tap_xero/__init__.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919

2020
LOGGER = singer.get_logger()
2121

22+
DEPRECATED_STREAM_IDS = [
23+
"expense_claims",
24+
"employees",
25+
"receipts",
26+
]
27+
2228
BAD_CREDS_MESSAGE = (
2329
"Failed to refresh OAuth token using the credentials from both the config and S3. "
2430
"The token might need to be reauthorized from the integration's properties "
@@ -110,7 +116,18 @@ def sync(ctx):
110116
stream.sync(ctx)
111117
ctx.state["currently_syncing"] = None
112118
ctx.write_state()
113-
119+
for stream_id in stream_ids_to_sync:
120+
if stream_id in DEPRECATED_STREAM_IDS:
121+
raise Exception(
122+
"Employees, Expense Claims, and Receipts endpoints are deprecated and associated streams "
123+
"`employees`, `expense_claims` and `receipts` will be removed on 28th April 2026. "
124+
"Please deselect these streams to avoid sync errors. "
125+
"Consider using the Invoices stream as a replacement for Expense Claims and Receipts. "
126+
"For more details, see the Xero API documentation: "
127+
"https://developer.xero.com/documentation/api/accounting/receipts, "
128+
"https://developer.xero.com/documentation/api/accounting/employees, "
129+
"https://developer.xero.com/documentation/api/accounting/expenseclaims"
130+
)
114131

115132

116133
def main_impl():

tests/base.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,10 @@ def expected_bookmarks(self):
9292
"overpayments": ["UpdatedDateUTC"],
9393
"prepayments": ["UpdatedDateUTC"],
9494
"purchase_orders": ["UpdatedDateUTC"],
95-
"journals": ["JournalNumber"],
9695
"accounts": ["UpdatedDateUTC"],
9796
"bank_transfers": ["CreatedDateUTC"],
98-
"employees": ["UpdatedDateUTC"],
99-
"expense_claims": ["UpdatedDateUTC"],
10097
"items": ["UpdatedDateUTC"],
10198
"payments": ["UpdatedDateUTC"],
102-
"receipts": ["UpdatedDateUTC"],
10399
"users": ["UpdatedDateUTC"],
104100
"linked_transactions": ["UpdatedDateUTC"],
105101
"quotes": ["UpdatedDateUTC"]
@@ -164,6 +160,8 @@ def select_found_catalogs(self, found_catalogs):
164160
# menagerie.post_annotated_catalogs(self.conn_id, selected)
165161
for catalog in found_catalogs:
166162
schema = menagerie.get_annotated_schema(self.conn_id, catalog['stream_id'])
163+
if catalog['tap_stream_id'] in ["journals", "expense_claims", "receipts", "employees"]:
164+
continue
167165
non_selected_properties = []
168166
additional_md = []
169167

tests/test_xero_bookmarks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def check_bookmarks(self, bookmarks, max_bookmarks_from_records):
4747
def check_offsets(self, bookmarks):
4848
for stream, offset in self.expected_offsets.items():
4949
self.assertEqual(
50-
bookmarks.get(stream, {}).get("offset"), offset,
50+
bookmarks.get(stream, {}).get("offset", {}), offset,
5151
msg=("unexpected offset found for stream {} {}. bookmarks: {}"
5252
.format(stream, offset, bookmarks))
5353
)

tests/test_xero_future_dates_no_data.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def state(self):
2525
"overpayments": {"UpdatedDateUTC": future_date},
2626
"prepayments": {"UpdatedDateUTC": future_date},
2727
"purchase_orders": {"UpdatedDateUTC": future_date},
28-
"journals": {"JournalNumber": 10e10},
2928
"accounts": {"UpdatedDateUTC": future_date},
3029
"bank_transfers": {"CreatedDateUTC": future_date},
3130
"employees": {"UpdatedDateUTC": future_date},

tests/unittests/test_datetime_parsing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def test_normal_datetimes(self):
2020
strptime_to_utc('2020-01-01T12:30:00+0'),
2121
]
2222

23-
self.assertEquals(parsed_dates, expected_dates)
23+
self.assertEqual(parsed_dates, expected_dates)
2424

2525
def test_epoch_datetimes(self):
2626
dates = [
@@ -39,7 +39,7 @@ def test_epoch_datetimes(self):
3939
datetime.datetime(1920, 5, 23, 00, 00, 00),
4040
]
4141

42-
self.assertEquals(parsed_dates, expected_dates)
42+
self.assertEqual(parsed_dates, expected_dates)
4343

4444
def test_not_datetimes(self):
4545
dates = [
@@ -53,4 +53,4 @@ def test_not_datetimes(self):
5353

5454
expected_dates = [None, None, None, None]
5555

56-
self.assertEquals(parsed_dates, expected_dates)
56+
self.assertEqual(parsed_dates, expected_dates)

tests/unittests/test_exception_handling.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def test_badrequest_400_error(self, mocked_session, mocked_badrequest_400_error)
191191
expected_error_message = "HTTP-error-code: 400, Error: A validation exception has occurred."
192192

193193
# Verifying the message formed for the custom exception
194-
self.assertEquals(str(e), expected_error_message)
194+
self.assertEqual(str(e), expected_error_message)
195195
pass
196196

197197

@@ -210,7 +210,7 @@ def test_unauthorized_401_error(self, mocked_session, mocked_unauthorized_401_er
210210
expected_error_message = "HTTP-error-code: 401, Error: Invalid authorization credentials."
211211

212212
# Verifying the message formed for the custom exception
213-
self.assertEquals(str(e), expected_error_message)
213+
self.assertEqual(str(e), expected_error_message)
214214
pass
215215

216216

@@ -229,7 +229,7 @@ def test_forbidden_403_exception(self, mocked_session, mocked_forbidden_403_exce
229229
expected_error_message = "HTTP-error-code: 403, Error: User doesn't have permission to access the resource."
230230

231231
# Verifying the message formed for the custom exception
232-
self.assertEquals(str(e), expected_error_message)
232+
self.assertEqual(str(e), expected_error_message)
233233
pass
234234

235235

@@ -248,7 +248,7 @@ def test_notfound_404_error(self, mocked_session, mocked_notfound_404_error):
248248
expected_error_message = "HTTP-error-code: 404, Error: The resource you have specified cannot be found."
249249

250250
# Verifying the message formed for the custom exception
251-
self.assertEquals(str(e), expected_error_message)
251+
self.assertEqual(str(e), expected_error_message)
252252
pass
253253

254254
@mock.patch('requests.Request', side_effect=mocked_precondition_failed_412_error)
@@ -266,7 +266,7 @@ def test_precondition_failed_412_error(self, mocked_session, mocked_precondition
266266
expected_error_message = "HTTP-error-code: 412, Error: One or more conditions given in the request header fields were invalid."
267267

268268
# Verifying the message formed for the custom exception
269-
self.assertEquals(str(e), expected_error_message)
269+
self.assertEqual(str(e), expected_error_message)
270270
pass
271271

272272
@mock.patch('requests.Request', side_effect=mocked_internalservererror_500_error)
@@ -284,7 +284,7 @@ def test_internalservererror_500_error(self, mocked_session, mocked_internalserv
284284
expected_error_message = "HTTP-error-code: 500, Error: An unhandled error with the Xero API. Contact the Xero API team if problems persist."
285285

286286
# Verifying the message formed for the custom exception
287-
self.assertEquals(str(e), expected_error_message)
287+
self.assertEqual(str(e), expected_error_message)
288288
pass
289289

290290

@@ -303,7 +303,7 @@ def test_notimplemented_501_error(self, mocked_session, mocked_notimplemented_50
303303
expected_error_message = "HTTP-error-code: 501, Error: The method you have called has not been implemented."
304304

305305
# Verifying the message formed for the custom exception
306-
self.assertEquals(str(e), expected_error_message)
306+
self.assertEqual(str(e), expected_error_message)
307307
pass
308308

309309
@mock.patch('requests.Request', side_effect=mocked_not_available_503_error)
@@ -321,7 +321,7 @@ def test_not_available_503_error(self, mocked_session, mocked_not_available_503_
321321
expected_error_message = "HTTP-error-code: 503, Error: API service is currently unavailable."
322322

323323
# Verifying the message formed for the custom exception
324-
self.assertEquals(str(e), expected_error_message)
324+
self.assertEqual(str(e), expected_error_message)
325325
pass
326326

327327

@@ -341,7 +341,7 @@ def test_too_many_requests_429_in_day_error(self, mocked_session, mocked_failed_
341341
expected_error_message = "HTTP-error-code: 429, Error: The API rate limit for your organisation/application pairing has been exceeded. Please retry after 1000 seconds"
342342

343343
# Verifying the message formed for the custom exception
344-
self.assertEquals(str(e), expected_error_message)
344+
self.assertEqual(str(e), expected_error_message)
345345
pass
346346

347347

@@ -361,7 +361,7 @@ def test_too_many_requests_429_in_minute_error(self, mocked_session, mocked_fail
361361
expected_error_message = "HTTP-error-code: 429, Error: The API rate limit for your organisation/application pairing has been exceeded. Please retry after 5 seconds"
362362

363363
# Verifying the message formed for the custom exception
364-
self.assertEquals(str(e), expected_error_message)
364+
self.assertEqual(str(e), expected_error_message)
365365
pass
366366

367367

0 commit comments

Comments
 (0)