forked from tomasvotava/tap-airtable
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from harrystech/ynaim94/deng-2960/modify-types-…
…airtable Modifying the singer types and add more tests
- Loading branch information
Showing
8 changed files
with
386 additions
and
149 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import pytest | ||
import requests | ||
import requests_mock | ||
from tap_airtable.client import AirtableClient, NonRetryableError | ||
|
||
@pytest.fixture | ||
def airtable_client(): | ||
return AirtableClient(token="fake_token") | ||
|
||
@pytest.fixture | ||
def requests_mock_adapter(): | ||
with requests_mock.Mocker() as m: | ||
yield m | ||
|
||
def test_get_with_200_response(airtable_client, requests_mock_adapter): | ||
requests_mock_adapter.get("https://api.airtable.com/v0/test_endpoint", text='{"data": "test"}') | ||
response = airtable_client._get("test_endpoint") | ||
assert response.json() == {"data": "test"} | ||
|
||
def test_get_with_429_response(airtable_client, requests_mock_adapter): | ||
requests_mock_adapter.get("https://api.airtable.com/v0/test_endpoint", status_code=429) | ||
with pytest.raises(requests.HTTPError): | ||
airtable_client._get("test_endpoint") | ||
|
||
def test_get_with_400_response(airtable_client, requests_mock_adapter): | ||
requests_mock_adapter.get("https://api.airtable.com/v0/test_endpoint", status_code=400, text='Bad Request') | ||
with pytest.raises(NonRetryableError): | ||
airtable_client._get("test_endpoint") | ||
|
||
def test_get_base_schema(airtable_client, requests_mock_adapter): | ||
mock_response = {"tables": [{"id": "tbl123", "name": "Test Table", "fields": []}]} | ||
requests_mock_adapter.get("https://api.airtable.com/v0/meta/bases/base123/tables", json=mock_response) | ||
schema = airtable_client._get_base_schema("base123") | ||
assert schema == mock_response["tables"] | ||
|
||
def test_map_field_type_formula_with_result(airtable_client): | ||
field = {"type": "formula", | ||
"options": { | ||
"isValid": "true", | ||
"formula": "SUM({fldf82WQBqZkvrYWR},{fldiGss4dvzDqPuqr},{fldbJtC0hYnUHSYuy}, {fld99YG0EGM0uvn1q},{fldWlAQ5dZUtq77Vy},{fldjz4RFBLMLQHDxz},{fldQsBEv0OV0yR91f}, {fld8Rysm79Q0TwNhh}, {fldmHDA67quleaFef},{fldLFfTFEPthOEAhf}, {fldJecvcAVQ2R6y9I}, {fldJtx7D2Zt8YvHHY} )", | ||
"referencedFieldIds": [ | ||
"fldf82WQBqZkvrYWR", | ||
"fldiGss4dvzDqPuqr", | ||
"fldbJtC0hYnUHSYuy", | ||
"fld99YG0EGM0uvn1q", | ||
"fldWlAQ5dZUtq77Vy", | ||
"fldjz4RFBLMLQHDxz", | ||
"fldQsBEv0OV0yR91f", | ||
"fld8Rysm79Q0TwNhh", | ||
"fldmHDA67quleaFef", | ||
"fldLFfTFEPthOEAhf", | ||
"fldJecvcAVQ2R6y9I", | ||
"fldJtx7D2Zt8YvHHY" | ||
], | ||
"result": { | ||
"type": "currency", | ||
"options": { "precision": 0, "symbol": "$" } | ||
} | ||
}, | ||
} | ||
assert airtable_client.map_field_type(field) == ["currency", "formula"] | ||
|
||
def test_map_field_type_formula_without_result(airtable_client): | ||
field = {"type": "formula", "options": {}} | ||
assert airtable_client.map_field_type(field) == "formula" | ||
|
||
def test_map_field_type_non_formula(airtable_client): | ||
field = {"type": "text"} | ||
assert airtable_client.map_field_type(field) == "text" | ||
|
||
# Example test for get_records, assuming pagination and mocking two pages of records | ||
def test_get_records_pagination(airtable_client, requests_mock_adapter): | ||
page_1 = {"records": [{"id": "rec1"}], "offset": "page_2"} | ||
page_2 = {"records": [{"id": "rec2"}]} | ||
requests_mock_adapter.get("https://api.airtable.com/v0/base123/table123", [{'json': page_1, 'status_code': 200}, {'json': page_2, 'status_code': 200}]) | ||
|
||
records = list(airtable_client.get_records("base123", "table123")) | ||
assert len(records) == 2 | ||
assert records[0]["id"] == "rec1" | ||
assert records[1]["id"] == "rec2" | ||
|
||
@pytest.fixture | ||
def mock_response(): | ||
return { | ||
"bases": [ | ||
{"id": "base1", "name": "Base 1"}, | ||
{"id": "base2", "name": "Base 2"} | ||
] | ||
} | ||
|
||
def test_get_bases_success(airtable_client, mock_response): | ||
with requests_mock.Mocker() as m: | ||
# Mock the initial request for getting bases | ||
m.get("https://api.airtable.com/v0/meta/bases", json=mock_response) | ||
|
||
# Add mock responses for each base's schema request | ||
for base in mock_response["bases"]: | ||
mock_base_schema = {"tables": [{"id": "tbl123", "name": "Test Table", "fields": []}]} | ||
m.get(f"https://api.airtable.com/v0/meta/bases/{base['id']}/tables", json=mock_base_schema) | ||
|
||
# Now calling get_bases should not raise NoMockAddress | ||
bases = airtable_client.get_bases() | ||
assert len(bases) == 2 | ||
assert bases[0].id == "base1" | ||
assert bases[1].id == "base2" | ||
|
||
def test_get_bases_missing_ids(airtable_client, mock_response): | ||
with requests_mock.Mocker() as m: | ||
m.get("https://api.airtable.com/v0/meta/bases", json=mock_response) | ||
with pytest.raises(ValueError) as excinfo: | ||
airtable_client.get_bases(base_ids=["base3"]) | ||
assert "Base ids missing {'base3'}" in str(excinfo.value) | ||
|
||
def test_get_bases_http_error(airtable_client): | ||
with requests_mock.Mocker() as m: | ||
m.get("https://api.airtable.com/v0/meta/bases", status_code=500) | ||
with pytest.raises(requests.HTTPError): | ||
airtable_client.get_bases() | ||
|
||
def test_get_bases_non_retryable_error(airtable_client): | ||
with requests_mock.Mocker() as m: | ||
m.get("https://api.airtable.com/v0/meta/bases", status_code=400, text="Bad Request") | ||
with pytest.raises(NonRetryableError) as excinfo: | ||
airtable_client.get_bases() | ||
assert "Server response: 400, Bad Request" in str(excinfo.value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import math | ||
import pytest | ||
from unittest.mock import MagicMock, patch | ||
from singer_sdk import Tap | ||
|
||
from tap_airtable.streams import airtable_stream_factory | ||
from tap_airtable.entities import AirtableTable, AirtableField | ||
|
||
@pytest.fixture | ||
def mock_tap(): | ||
return MagicMock() | ||
|
||
@pytest.fixture | ||
def airtable_field_normal(): | ||
return AirtableField(field_type="singleLineText", id="1", name="NormalField", is_formula=False) | ||
|
||
@pytest.fixture | ||
def airtable_field_formula(): | ||
return AirtableField(field_type="formula", id="2", name="FormulaField", is_formula=True) | ||
|
||
@pytest.fixture | ||
def airtable_table(airtable_field_normal, airtable_field_formula): | ||
return AirtableTable(id="tbl123", name="TestTable", fields=[airtable_field_normal, airtable_field_formula]) | ||
|
||
@pytest.fixture | ||
def base_airtable_stream(mock_tap, airtable_table): | ||
stream = airtable_stream_factory("app123", airtable_table) | ||
instance = stream(tap=mock_tap) | ||
instance._config = {"token": "fake_token"} | ||
return instance | ||
|
||
@pytest.fixture | ||
def mock_airtable_client(): | ||
with patch('tap_airtable.streams.AirtableClient') as mock: | ||
yield mock | ||
|
||
def test_get_records_normal_field(base_airtable_stream, mock_airtable_client): | ||
mock_airtable_client.return_value.get_records.return_value = [ | ||
{"id": "rec1", "fields": {"NormalField": "Value1"}} | ||
] | ||
records = list(base_airtable_stream.get_records(None)) | ||
assert len(records) == 1 | ||
print(records[0]) | ||
assert records[0]["normalfield"] == "Value1" | ||
|
||
def test_get_records_formula_field_special_values(base_airtable_stream, mock_airtable_client): | ||
mock_airtable_client.return_value.get_records.return_value = [ | ||
{"id": "rec1", "fields": {"FormulaField": {"error": "#ERROR!"}}}, | ||
{"id": "rec2", "fields": {"FormulaField": {"specialValue": "NaN"}}}, | ||
{"id": "rec3", "fields": {"FormulaField": {"specialValue": "Infinity"}}} | ||
] | ||
records = list(base_airtable_stream.get_records(None)) | ||
assert len(records) == 3 | ||
assert records[0]["formulafield"] == "#ERROR!" | ||
assert records[1]["formulafield"] == str(float('nan')) | ||
assert records[2]["formulafield"] == str(float('inf')) | ||
|
||
def test_handle_special_values(base_airtable_stream): | ||
assert base_airtable_stream._handle_special_values({"error": "#ERROR!"}) == "#ERROR!" | ||
assert base_airtable_stream._handle_special_values({"specialValue": "NaN"}) == str(float('nan')) | ||
assert base_airtable_stream._handle_special_values({"specialValue": "Infinity"}) == str(float('inf')) | ||
assert base_airtable_stream._handle_special_values("NormalValue") == "NormalValue" |