-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(source-google-sheets): add integration tests (#48835)
Co-authored-by: maxi297 <maxime@airbyte.io>
- Loading branch information
1 parent
e5c439a
commit c9ac352
Showing
40 changed files
with
4,995 additions
and
15 deletions.
There are no files selected for viewing
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
Empty file.
73 changes: 73 additions & 0 deletions
73
...integrations/connectors/source-google-sheets/unit_tests/integration/custom_http_mocker.py
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,73 @@ | ||
# Copyright (c) 2024 Airbyte, Inc., all rights reserved. | ||
|
||
|
||
from functools import wraps | ||
from typing import Dict | ||
from unittest.mock import patch | ||
from urllib.parse import parse_qsl, unquote, urlencode, urlunparse | ||
|
||
from airbyte_cdk.test.mock_http import HttpResponse | ||
from airbyte_cdk.test.mock_http.request import HttpRequest | ||
from httplib2 import Response | ||
|
||
|
||
def parse_and_transform(parse_result_str: str): | ||
""" | ||
Parse the input string representation of a HttpRequest transform it into the URL. | ||
""" | ||
parse_result_part = parse_result_str.split("ParseResult(", 1)[1].split(")", 1)[0] | ||
|
||
# Convert the ParseResult string into a dictionary | ||
components = eval(f"dict({parse_result_part})") | ||
|
||
url = urlunparse(( | ||
components["scheme"], | ||
components["netloc"], | ||
components["path"], | ||
components["params"], | ||
components["query"], | ||
components["fragment"], | ||
)) | ||
|
||
return url | ||
|
||
|
||
class CustomHttpMocker: | ||
""" | ||
This is a limited mocker for usage with httplib2.Http.request | ||
It has a similar interface to airbyte HttpMocker such than when we move this connector to low-code only with | ||
a http retriever we will be able to substitute CustomHttpMocker => HttpMocker in out integration testing with minimal changes. | ||
Note: there is only support for get and post method and url matching ignoring the body but this is enough for the current test set. | ||
""" | ||
requests_mapper: Dict = {} | ||
|
||
def post(self, request: HttpRequest, response: HttpResponse): | ||
custom_response = (Response({"status": response.status_code}), response.body.encode("utf-8")) | ||
uri = parse_and_transform(str(request)) | ||
decoded_url = unquote(uri) | ||
self.requests_mapper[("POST", decoded_url)] = custom_response | ||
|
||
def get(self, request: HttpRequest, response: HttpResponse): | ||
custom_response = (Response({"status": response.status_code}), response.body.encode("utf-8")) | ||
uri = parse_and_transform(str(request)) | ||
decoded_url = unquote(uri) | ||
self.requests_mapper[("GET", decoded_url)] = custom_response | ||
|
||
def mock_request(self, uri, method="GET", body=None, headers=None, **kwargs): | ||
decoded_url = unquote(uri) | ||
mocked_response = self.requests_mapper.get((method, decoded_url)) | ||
if not mocked_response: | ||
raise Exception(f"Mock response not found {uri} {method}") | ||
return mocked_response | ||
|
||
# trying to type that using callables provides the error `incompatible with return type "_F" in supertype "ContextDecorator"` | ||
def __call__(self, test_func): # type: ignore | ||
@wraps(test_func) | ||
def wrapper(*args, **kwargs): # type: ignore # this is a very generic wrapper that does not need to be typed | ||
kwargs["http_mocker"] = self | ||
|
||
with patch("httplib2.Http.request", side_effect=self.mock_request): | ||
return test_func(*args, **kwargs) | ||
|
||
return wrapper |
72 changes: 72 additions & 0 deletions
72
...te-integrations/connectors/source-google-sheets/unit_tests/integration/request_builder.py
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,72 @@ | ||
# Copyright (c) 2024 Airbyte, Inc., all rights reserved. | ||
|
||
from __future__ import annotations | ||
|
||
from airbyte_cdk.test.mock_http.request import HttpRequest | ||
|
||
# todo: this should be picked from manifest in the future | ||
GOOGLE_SHEETS_BASE_URL = "https://sheets.googleapis.com/v4/spreadsheets" | ||
OAUTH_AUTHORIZATION_ENDPOINT = "https://oauth2.googleapis.com" | ||
|
||
class RequestBuilder: | ||
@classmethod | ||
def get_account_endpoint(cls) -> RequestBuilder: | ||
return cls(resource="values:batchGet") | ||
|
||
|
||
def __init__(self, resource:str=None) -> None: | ||
self._spreadsheet_id = None | ||
self._query_params = {} | ||
self._body = None | ||
self.resource = resource | ||
|
||
def with_include_grid_data(self, include_grid_data: bool) -> RequestBuilder: | ||
self._query_params["includeGridData"] = "true" if include_grid_data else "false" | ||
return self | ||
|
||
def with_alt(self, alt: str) -> RequestBuilder: | ||
self._query_params["alt"] = alt | ||
return self | ||
|
||
def with_ranges(self, ranges: str) -> RequestBuilder: | ||
self._query_params["ranges"] = ranges | ||
return self | ||
|
||
def with_major_dimension(self, dimension: str) -> RequestBuilder: | ||
self._query_params["majorDimension"] = dimension | ||
return self | ||
|
||
def with_spreadsheet_id(self, spreadsheet_id: str) -> RequestBuilder: | ||
self._spreadsheet_id = spreadsheet_id | ||
return self | ||
|
||
def build(self) -> HttpRequest: | ||
endpoint = f"/{self.resource}" if self.resource else "" | ||
return HttpRequest( | ||
url=f"{GOOGLE_SHEETS_BASE_URL}/{self._spreadsheet_id}{endpoint}", | ||
query_params=self._query_params, | ||
body=self._body, | ||
) | ||
|
||
|
||
class AuthBuilder: | ||
@classmethod | ||
def get_token_endpoint(cls) -> AuthBuilder: | ||
return cls(resource="token") | ||
|
||
def __init__(self, resource): | ||
self._body = "" | ||
self._resource = resource | ||
self._query_params = "" | ||
|
||
def with_body(self, body: str): | ||
self._body = body | ||
return self | ||
|
||
def build(self) -> HttpRequest: | ||
endpoint = f"/{self._resource}" if self._resource else "" | ||
return HttpRequest( | ||
url=f"{OAUTH_AUTHORIZATION_ENDPOINT}{endpoint}", | ||
query_params=self._query_params, | ||
body=self._body, | ||
) |
63 changes: 63 additions & 0 deletions
63
...e-integrations/connectors/source-google-sheets/unit_tests/integration/test_credentials.py
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,63 @@ | ||
# Copyright (c) 2024 Airbyte, Inc., all rights reserved. | ||
import json | ||
|
||
# this key was generated with rsa library from cryptography | ||
test_private_key = """ | ||
-----BEGIN PRIVATE KEY----- | ||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCjM57/r+LuNv9z | ||
Bbe7+sMCJJm6t1MafRfxYVEz9c52/mLa5iEnpJfE+NRFgyp8hE8t493Btt/bJk94 | ||
2bMthZqMh2n9dIJZOUYBzd5MHLOc6vCUHFZT9fzHX/yTrz/bxa31BQs6c1p2HOiY | ||
Kr3r0Dyj5jXsKo0mgt+iVKGgcSZ6NCzMRL8N9M++13k2RUPwaGksuyNqzAqgNhHd | ||
wQcuS42AEFEOT1sfa4xG5mMY6+tPKDP92/ISHNUMD9NpzIA8A+tFX/w/L5VQKR3r | ||
fTfrSTtkG6qF+3ARdeKqpxrW4ZPHuzNH8Y2I1uBuVaDvmZMvi+BLKgwwhWuEGjB1 | ||
j6Tv4TgXAgMBAAECggEAJTXLXlPdg1/hzXlzw3XwyYfLz0EmPwdfkqcUKysz2Hi2 | ||
1F8dFxtViVEMoQ6/fKV0Iivur1DBaIerHgxQ6KOqMblcRrAuWiaPWjD0qtjucOw2 | ||
TybI3hrbeB/gCFIwVq0TNSbhwQF1EjIULEGujNotQVdnWwH2rd2wHKR8N4ck9T6b | ||
SKz8+u21RY2cBprneS6wxh+dvba8+7cpHn4cB+TB6UMeUow01LF4ye+hYVDNx6j9 | ||
VcdWXlH9fCy/GUTF4um+ABunlMCm6D5DAUVeiugd+ChSKzqOlV/H17EK1MF4HAjh | ||
Alo2FJrKd8/ZwX1Xmngi9Y2Dlggmfiw4HzeNZNFFPQKBgQDmeQxDBvZapjdJntOM | ||
DccoQGJyZMznd277xefKTfZLetcWWtgantW7IAxEEbwZOfrQFnBsNIlnreGPoZ7n | ||
DL2jv/oVeEcr29FxlbDR2R1/h8Mp7d41Qi1Sd9RAhGcVtYZhLCCMMxt4DN7/v77M | ||
2lc3B0NhgC3kxOJCC8kN2gqSTQKBgQC1RyENp1UTOSUbFsx1WbAzy2K974CRnL54 | ||
uK7efoLQ7fLh6OsRlBoy60aUpHv2tCb5ac+xdMELoAQu3PQKJMDJITstwgM4p6AP | ||
x32lAHzOYnhG9/3P7kc29OaY8tlkDAn0ckv05DLAGLbKAemcGKW3/u08EITQtdW7 | ||
dEH75Ow98wKBgQDb5tV3QrZeKcgI251XPXIwCrakFV+Y3tErM1qFIbwFqtB8yPL2 | ||
+2RM5jgt3ooNu89/KlncNIiCP1s/k2Mta2+qRStVvuyRgWympsAOic1meGATqp1h | ||
TaI21JTVdj9xbEEqiFMJ0l28PvOrLAXeKdobbDezWPzxEZYclGgiak+55QKBgBVU | ||
6W7R4hEBCHzHkge9Jh7yMAxpwpdf+on6MZm9CWfMmGg9IGxRIUQcq5GSSYQebveq | ||
m+Yl9xGHIvbgyVboPEduwagAzKA+GXfB4ecox4cBz2WKiTOOtpKg/wHAkhRT1lgN | ||
myKWN+KjBd9/mh3kSJv+Q6xtxTNKMnx8kccyiRpBAoGBAJV9AAXj4icaDiPKoQw5 | ||
UERTGuVoEpWbc3yi/PXJ99fQxHZIHQa7a7VyyTHsDplqWu/qfHFHj+IJops8+l1F | ||
U7PQBfXvIpubb55EhNCaID1VaRauGjW2x8PGA/27KQ3mB1uxEZUO8crcDYvPsZJf | ||
jHfASOY3OsGgYW95pkyx5TH7 | ||
-----END PRIVATE KEY----- | ||
""" | ||
|
||
service_account_info = { | ||
"type": "service_account", | ||
"project_id": "test-project-id", | ||
"private_key_id": "test-private-key-id", | ||
"private_key": test_private_key, | ||
"client_email": "test-service-account@test-project.iam.gserviceaccount.com", | ||
"client_id": "1234567890", | ||
"auth_uri": "https://accounts.google.com/o/oauth2/auth", | ||
"token_uri": "https://oauth2.googleapis.com/token", | ||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", | ||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-service-account%40test-project.iam.gserviceaccount.com", | ||
} | ||
|
||
service_account_info_encoded = json.dumps(service_account_info).encode("utf-8") | ||
|
||
service_account_credentials = { | ||
"auth_type": "Service", | ||
"service_account_info": service_account_info_encoded, | ||
} | ||
|
||
|
||
oauth_credentials = { | ||
"auth_type": "Client", | ||
"client_id": "43987534895734985.apps.googleusercontent.com", | ||
"client_secret": "2347586435987643598", | ||
"refresh_token": "1//4398574389537495437983457985437" | ||
} |
Oops, something went wrong.