Skip to content

🎉Source Facebook Marketing: have at least 90% unit test coverage #10545

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
cdd38fa
Add AdAccount and Images stream implementation
lazebnyi Feb 8, 2022
65539b2
Update PR number
lazebnyi Feb 8, 2022
a911e12
Merge branch 'master' into lazebnyi/9840-add-adaccount-and-images-str…
lazebnyi Feb 8, 2022
72a36c1
Updated docker version
lazebnyi Feb 8, 2022
dcc581b
Merge master to branch
lazebnyi Feb 8, 2022
a283e33
Updated to linter
lazebnyi Feb 8, 2022
083ca38
Update to review
lazebnyi Feb 9, 2022
30003f3
Add comment to AdAccount read_records method
lazebnyi Feb 9, 2022
6c8f59f
Bumped version in seed, definitions and specs files
lazebnyi Feb 9, 2022
6864ea5
Fix backoff errors code list
lazebnyi Feb 15, 2022
1d8c57a
Merged master to branch
lazebnyi Feb 15, 2022
b4b9cff
Deleted empty file
lazebnyi Feb 15, 2022
bc38523
Updated to linter
lazebnyi Feb 15, 2022
385113c
Updated PR number
lazebnyi Feb 15, 2022
9ffd0f8
Merged master to branch
lazebnyi Feb 18, 2022
b4cc156
Added connection_reset_error to backoff
lazebnyi Feb 18, 2022
0507927
Fix unittest
lazebnyi Feb 18, 2022
ce47f0c
Merge master to branch
lazebnyi Feb 22, 2022
198ca57
Fix integration tests
lazebnyi Feb 22, 2022
a024560
Increased unit test coverage to 90%
lazebnyi Feb 22, 2022
291e4d3
Merge branch 'master' into lazebnyi/10453-facebook-marketing-improve-…
lazebnyi Feb 22, 2022
3952e35
Updated PR number
lazebnyi Feb 22, 2022
ef59ea0
Merge master to branch
lazebnyi Feb 22, 2022
81881e4
Refactored fixtures in test client
lazebnyi Feb 22, 2022
234ae18
Downgrade version to master version
lazebnyi Feb 22, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ RUN pip install .
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py"
ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]


LABEL io.airbyte.version=0.2.35
LABEL io.airbyte.name=airbyte/source-facebook-marketing
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,54 @@
# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
#

import pendulum
from facebook_business import FacebookAdsApi, FacebookSession
from pytest import fixture
from source_facebook_marketing.api import API

FB_API_VERSION = FacebookAdsApi.API_VERSION


@fixture(autouse=True)
def time_sleep_mock(mocker):
time_mock = mocker.patch("time.sleep")
yield time_mock


@fixture(scope="session", name="account_id")
def account_id_fixture():
return "unknown_account"


@fixture(scope="session", name="some_config")
def some_config_fixture(account_id):
return {"start_date": "2021-01-23T00:00:00Z", "account_id": f"{account_id}", "access_token": "unknown_token"}


@fixture(autouse=True)
def mock_default_sleep_interval(mocker):
mocker.patch("source_facebook_marketing.streams.common.DEFAULT_SLEEP_INTERVAL", return_value=pendulum.duration(seconds=5))


@fixture(name="fb_account_response")
def fb_account_response_fixture(account_id):
return {
"json": {
"data": [
{
"account_id": account_id,
"id": f"act_{account_id}",
}
],
"paging": {"cursors": {"before": "MjM4NDYzMDYyMTcyNTAwNzEZD", "after": "MjM4NDYzMDYyMTcyNTAwNzEZD"}},
},
"status_code": 200,
}


@fixture(name="api")
def api_fixture(some_config, requests_mock, fb_account_response):
api = API(account_id=some_config["account_id"], access_token=some_config["access_token"])

requests_mock.register_uri("GET", FacebookSession.GRAPH + f"/{FB_API_VERSION}/me/adaccounts", [fb_account_response])
return api
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,11 @@
from airbyte_cdk.models import SyncMode
from facebook_business import FacebookAdsApi, FacebookSession
from facebook_business.exceptions import FacebookRequestError
from source_facebook_marketing.api import API
from source_facebook_marketing.streams import AdCreatives, AdsInsights, Campaigns

FB_API_VERSION = FacebookAdsApi.API_VERSION


@pytest.fixture(scope="session", name="account_id")
def account_id_fixture():
return "unknown_account"


@pytest.fixture(scope="session", name="some_config")
def some_config_fixture(account_id):
return {"start_date": "2021-01-23T00:00:00Z", "account_id": f"{account_id}", "access_token": "unknown_token"}


@pytest.fixture(autouse=True)
def mock_default_sleep_interval(mocker):
mocker.patch("source_facebook_marketing.streams.common.DEFAULT_SLEEP_INTERVAL", return_value=pendulum.duration(seconds=5))


@pytest.fixture(name="api")
def api_fixture(some_config, requests_mock, fb_account_response):
api = API(account_id=some_config["account_id"], access_token=some_config["access_token"])

requests_mock.register_uri("GET", FacebookSession.GRAPH + f"/{FB_API_VERSION}/me/adaccounts", [fb_account_response])
return api


@pytest.fixture(name="fb_call_rate_response")
def fb_call_rate_response_fixture():
error = {
Expand All @@ -63,22 +39,6 @@ def fb_call_rate_response_fixture():
}


@pytest.fixture(name="fb_account_response")
def fb_account_response_fixture(account_id):
return {
"json": {
"data": [
{
"account_id": account_id,
"id": f"act_{account_id}",
}
],
"paging": {"cursors": {"before": "MjM4NDYzMDYyMTcyNTAwNzEZD", "after": "MjM4NDYzMDYyMTcyNTAwNzEZD"}},
},
"status_code": 200,
}


class TestBackoff:
def test_limit_reached(self, mocker, requests_mock, api, fb_call_rate_response, account_id):
"""Error once, check that we retry and not fail"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@
import pytest
from airbyte_cdk.models import ConnectorSpecification
from source_facebook_marketing import SourceFacebookMarketing
from source_facebook_marketing.spec import ConnectorConfig


@pytest.fixture(name="config")
def config_fixture():
config = {"account_id": 123, "access_token": "TOKEN", "start_date": "2019-10-10T00:00:00"}
config = {
"account_id": 123,
"access_token": "TOKEN",
"start_date": "2019-10-10T00:00:00",
"end_date": "2020-10-10T00:00:00",
}

return config

Expand Down Expand Up @@ -43,6 +49,14 @@ def test_check_connection_end_date_before_start_date(self, api, config, logger_m
with pytest.raises(ValueError, match="end_date must be equal or after start_date."):
SourceFacebookMarketing().check_connection(logger_mock, config=config)

def test_check_connection_empty_config(self, api, logger_mock):
config = {}

with pytest.raises(pydantic.ValidationError):
SourceFacebookMarketing().check_connection(logger_mock, config=config)

assert not api.called

def test_check_connection_invalid_config(self, api, config, logger_mock):
config.pop("start_date")

Expand All @@ -66,3 +80,16 @@ def test_spec(self):
spec = SourceFacebookMarketing().spec()

assert isinstance(spec, ConnectorSpecification)

def test_update_insights_streams(self, api, config):
config["custom_insights"] = [
{"name": "test", "fields": ["account_id"], "breakdowns": ["ad_format_asset"], "action_breakdowns": ["action_device"]},
]
streams = SourceFacebookMarketing().streams(config)
config = ConnectorConfig.parse_obj(config)
insights_args = dict(
api=api,
start_date=config.start_date,
end_date=config.end_date,
)
assert SourceFacebookMarketing()._update_insights_streams(insights=config.custom_insights, args=insights_args, streams=streams)
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,53 @@
# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
#

#
import pytest
from pendulum import duration
from source_facebook_marketing.api import MyFacebookAdsApi
from source_facebook_marketing.streams.base_streams import FBMarketingStream
from source_facebook_marketing.streams.streams import fetch_thumbnail_data_url


def test_filter_all_statuses(api, mocker):
mocker.patch.multiple(FBMarketingStream, __abstractmethods__=set())
expected = {
"filtering": [
{
"field": "None.delivery_info",
"operator": "IN",
"value": [
"active",
"archived",
"completed",
"limited",
"not_delivering",
"deleted",
"not_published",
"pending_review",
"permanently_deleted",
"recently_completed",
"recently_rejected",
"rejected",
"scheduled",
"inactive",
],
}
]
}
assert FBMarketingStream(api=api)._filter_all_statuses() == expected


@pytest.mark.parametrize(
"url", ["https://graph.facebook.com", "https://graph.facebook.com?test=123%23%24%25%2A&test2=456", "https://graph.facebook.com?"]
)
def test_fetch_thumbnail_data_url(url, requests_mock):
requests_mock.get(url, status_code=200, headers={"content-type": "content-type"}, content=b"")
assert fetch_thumbnail_data_url(url) == "data:content-type;base64,"


def test_parse_call_rate_header():
headers = {
"x-business-use-case-usage": '{"test":[{"type":"ads_management","call_count":1,"total_cputime":1,'
'"total_time":1,"estimated_time_to_regain_access":1}]}'
}
assert MyFacebookAdsApi._parse_call_rate_header(headers) == (1, duration(minutes=1))