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

✨ Source Bing Ads: increase unit test coverage + config error #32088

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ data:
connectorSubtype: api
connectorType: source
definitionId: 47f25999-dd5e-4636-8c39-e7cea2453331
dockerImageTag: 1.7.0
dockerImageTag: 1.7.1
dockerRepository: airbyte/source-bing-ads
documentationUrl: https://docs.airbyte.com/integrations/sources/bing-ads
githubIssueLabel: source-bing-ads
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from typing import Any, List, Mapping, Tuple

from airbyte_cdk import AirbyteLogger
from airbyte_cdk.models import SyncMode
from airbyte_cdk.models import FailureType, SyncMode
from airbyte_cdk.sources import AbstractSource
from airbyte_cdk.sources.streams import Stream
from airbyte_cdk.utils import AirbyteTracedException
from source_bing_ads.client import Client
from source_bing_ads.streams import ( # noqa: F401
AccountImpressionPerformanceReportDaily,
Expand Down Expand Up @@ -81,7 +82,11 @@ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) ->
if account_ids:
return True, None
else:
raise Exception("You don't have accounts assigned to this user.")
raise AirbyteTracedException(
message="Config validation error: You don't have accounts assigned to this user.",
internal_message="You don't have accounts assigned to this user.",
failure_type=FailureType.config_error,
)
except Exception as error:
return False, error

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Type,Status,Id,Parent Id,Campaign,Ad Group,Client Id,Modified Time,Title,Text,Display Url,Destination Url,Promotion,Device Preference,Name,App Platform,App Id,Final Url,Mobile Final Url,Tracking Template,Final Url Suffix,Custom Parameter
Format Version,,,,,,,,,,,,,,6.0,,,,,,,
App Install Ad,Active,,-1111,ParentCampaignNameGoesHere,AdGroupNameGoesHere,ClientIdGoesHere,,Contoso Quick Setup,Find New Customers & Increase Sales!,,,,All,,Android,AppStoreIdGoesHere,FinalUrlGoesHere,,,,{_promoCode}=PROMO1; {_season}=summer
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
#

import socket
from datetime import datetime, timedelta
from unittest import mock
from unittest.mock import patch
from urllib.error import URLError

import pytest
import source_bing_ads.client
from airbyte_cdk.utils import AirbyteTracedException
from bingads.authorization import OAuthTokens
from bingads.authorization import AuthorizationData, OAuthTokens
from bingads.v13.reporting.exceptions import ReportingDownloadException
from suds import sudsobject

Expand Down Expand Up @@ -99,6 +101,13 @@ def test_get_auth_client(patched_request_tokens):
patched_request_tokens.assert_called_once_with("refresh_token")


@patch("bingads.authorization.OAuthWebAuthCodeGrant.request_oauth_tokens_by_refresh_token")
def test_get_auth_data(patched_request_tokens):
client = source_bing_ads.client.Client("tenant_id", "2020-01-01", client_id="client_id", refresh_token="refresh_token")
auth_data = client._get_auth_data()
assert isinstance(auth_data, AuthorizationData)


@patch("bingads.authorization.OAuthWebAuthCodeGrant.request_oauth_tokens_by_refresh_token")
def test_handling_ReportingDownloadException(patched_request_tokens):
client = source_bing_ads.client.Client("tenant_id", "2020-01-01", client_id="client_id", refresh_token="refresh_token")
Expand All @@ -125,3 +134,45 @@ def test_get_access_token(requests_mock):
"The user must sign in again.",
):
source_bing_ads.client.Client("tenant_id", "2020-01-01", client_id="client_id", refresh_token="refresh_token")


def test_get_access_token_success(requests_mock):
requests_mock.post(
"https://login.microsoftonline.com/tenant_id/oauth2/v2.0/token",
status_code=200,
json={"access_token": "test", "expires_in": "900", "refresh_token": "test"},
)
source_bing_ads.client.Client("tenant_id", "2020-01-01", client_id="client_id", refresh_token="refresh_token")
assert requests_mock.call_count == 1


@patch("bingads.authorization.OAuthWebAuthCodeGrant.request_oauth_tokens_by_refresh_token")
def test_should_give_up(patched_request_tokens):
client = source_bing_ads.client.Client("tenant_id", "2020-01-01", client_id="client_id", refresh_token="refresh_token")
give_up = client.should_give_up(Exception())
assert True is give_up
give_up = client.should_give_up(URLError(reason="test"))
assert True is give_up
give_up = client.should_give_up(URLError(reason=socket.timeout()))
assert False is give_up


@patch("bingads.authorization.OAuthWebAuthCodeGrant.request_oauth_tokens_by_refresh_token")
def test_get_service(patched_request_tokens):
client = source_bing_ads.client.Client("tenant_id", "2020-01-01", client_id="client_id", refresh_token="refresh_token")
service = client.get_service(service_name="CustomerManagementService")
assert "customermanagement_service.xml" in service.service_url


@patch("bingads.authorization.OAuthWebAuthCodeGrant.request_oauth_tokens_by_refresh_token")
def test_get_reporting_service(patched_request_tokens):
client = source_bing_ads.client.Client("tenant_id", "2020-01-01", client_id="client_id", refresh_token="refresh_token")
service = client._get_reporting_service()
assert (service._poll_interval_in_milliseconds, service._environment) == (client.report_poll_interval, client.environment)


@patch("bingads.authorization.OAuthWebAuthCodeGrant.request_oauth_tokens_by_refresh_token")
def test_bulk_service_manager(patched_request_tokens):
client = source_bing_ads.client.Client("tenant_id", "2020-01-01", client_id="client_id", refresh_token="refresh_token")
service = client._bulk_service_manager()
assert (service._poll_interval_in_milliseconds, service._environment) == (5000, client.environment)
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
#

from pathlib import Path
from unittest.mock import patch

import pytest
import source_bing_ads
from airbyte_cdk.models import SyncMode
from source_bing_ads.source import SourceBingAds
from source_bing_ads.streams import AccountPerformanceReportMonthly, Accounts, AdGroups, Ads, Campaigns
from source_bing_ads.streams import AccountPerformanceReportMonthly, Accounts, AdGroups, Ads, AppInstallAds, Campaigns


@pytest.fixture(name="config")
Expand Down Expand Up @@ -42,9 +43,21 @@ def test_source_check_connection_ok(mocked_client, config, logger_mock):


@patch.object(source_bing_ads.source, "Client")
def test_source_check_connection_failed(mocked_client, config, logger_mock):
with patch.object(Accounts, "read_records", return_value=0):
assert SourceBingAds().check_connection(logger_mock, config=config)[0] is False
def test_source_check_connection_failed_user_do_not_have_accounts(mocked_client, config, logger_mock):
with patch.object(Accounts, "read_records", return_value=[]):
connected, reason = SourceBingAds().check_connection(logger_mock, config=config)
assert connected is False
assert reason.message == "Config validation error: You don't have accounts assigned to this user."


def test_source_check_connection_failed_invalid_creds(config, logger_mock):
with patch.object(Accounts, "read_records", return_value=[]):
connected, reason = SourceBingAds().check_connection(logger_mock, config=config)
assert connected is False
assert (
reason.internal_message
== "Failed to get OAuth access token by refresh token. The user could not be authenticated as the grant is expired. The user must sign in again."
)


@patch.object(source_bing_ads.source, "Client")
Expand Down Expand Up @@ -145,3 +158,87 @@ def test_accounts(mocked_client, config):
accounts = Accounts(mocked_client, config)
_ = list(accounts.read_records(SyncMode.full_refresh))
mocked_client.request.assert_called_once()


@patch.object(source_bing_ads.source, "Client")
def test_ad_groups(mocked_client, config):
ad_groups = AdGroups(mocked_client, config)
_ = list(ad_groups.read_records(SyncMode.full_refresh, {"campaign_id": "campaign_id"}))
mocked_client.request.assert_called_once()


@patch.object(source_bing_ads.source, "Client")
def test_ads(mocked_client, config):
ads = Ads(mocked_client, config)
_ = list(ads.read_records(SyncMode.full_refresh, {"ad_group_id": "ad_group_id"}))
mocked_client.request.assert_called_once()


@patch.object(source_bing_ads.source, "Client")
def test_campaigns(mocked_client, config):
campaigns = Campaigns(mocked_client, config)
_ = list(campaigns.read_records(SyncMode.full_refresh, {"account_id": "account_id"}))
mocked_client.request.assert_called_once()
darynaishchenko marked this conversation as resolved.
Show resolved Hide resolved


@patch.object(source_bing_ads.source, "Client")
def test_bulk_stream_stream_slices(mocked_client, config):
slices = AppInstallAds(mocked_client, config).stream_slices()
assert list(slices) == []

app_install_ads = AppInstallAds(mocked_client, config)
accounts_read_records = iter([{"Id": 180519267, "ParentCustomerId": 100}, {"Id": 180278106, "ParentCustomerId": 200}])
with patch.object(Accounts, "read_records", return_value=accounts_read_records):
slices = app_install_ads.stream_slices()
assert list(slices) == [{"account_id": 180519267, "customer_id": 100}, {"account_id": 180278106, "customer_id": 200}]


@patch.object(source_bing_ads.source, "Client")
def test_bulk_stream_transfrom(mocked_client, config):
record = {"Ad Group": "Ad Group", "App Id": "App Id", "Campaign": "Campaign", "Custom Parameter": "Custom Parameter"}
transformed_record = AppInstallAds(mocked_client, config).transform(
record=record, stream_slice={"account_id": 180519267, "customer_id": 100}
)
assert transformed_record == {
"Account Id": 180519267,
"Ad Group": "Ad Group",
"App Id": "App Id",
"Campaign": "Campaign",
"Custom Parameter": "Custom Parameter",
}


@patch.object(source_bing_ads.source, "Client")
def test_bulk_stream_read_with_chunks(mocked_client, config):
path_to_file = Path(__file__).parent / "app_install_ads.csv"
path_to_file_base = Path(__file__).parent / "app_install_ads_base.csv"
with open(path_to_file_base, "r") as f1, open(path_to_file, "a") as f2:
for line in f1:
f2.write(line)

app_install_ads = AppInstallAds(mocked_client, config)
result = app_install_ads.read_with_chunks(path=path_to_file)
assert next(result) == {
"Ad Group": "AdGroupNameGoesHere",
"App Id": "AppStoreIdGoesHere",
"App Platform": "Android",
"Campaign": "ParentCampaignNameGoesHere",
"Client Id": "ClientIdGoesHere",
"Custom Parameter": "{_promoCode}=PROMO1; {_season}=summer",
"Destination Url": None,
"Device Preference": "All",
"Display Url": None,
"Final Url": "FinalUrlGoesHere",
"Final Url Suffix": None,
"Id": None,
"Mobile Final Url": None,
"Modified Time": None,
"Name": None,
"Parent Id": "-1111",
"Promotion": None,
"Status": "Active",
"Text": "Find New Customers & Increase Sales!",
"Title": "Contoso Quick Setup",
"Tracking Template": None,
"Type": "App Install Ad",
}
1 change: 1 addition & 0 deletions docs/integrations/sources/bing-ads.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ The Bing Ads API limits the number of requests for all Microsoft Advertising cli

| Version | Date | Pull Request | Subject |
|:--------|:-----------|:---------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------|
| 1.7.1 | 2023-11-02 | [32088](https://github.com/airbytehq/airbyte/pull/32088) | Add config error when user don't have accounts |
darynaishchenko marked this conversation as resolved.
Show resolved Hide resolved
| 1.7.0 | 2023-11-01 | [32027](https://github.com/airbytehq/airbyte/pull/32027) | Add new streams `AdGroupImpressionPerformanceReport` |
| 1.6.0 | 2023-10-31 | [32008](https://github.com/airbytehq/airbyte/pull/32008) | Add new streams `Keywords` |
| 1.5.0 | 2023-10-30 | [31952](https://github.com/airbytehq/airbyte/pull/31952) | Add new streams `Labels`, `App install ads`, `Keyword Labels`, `Campaign Labels`, `App Install Ad Labels`, `Ad Group Labels` |
Expand Down
Loading