Skip to content

Commit 3ed42fc

Browse files
authored
Fix the format of the data returned by Google Ads oauth to match the config accepted by the connector (#6032)
1 parent 7a6da86 commit 3ed42fc

File tree

3 files changed

+156
-2
lines changed

3 files changed

+156
-2
lines changed

airbyte-oauth/src/main/java/io/airbyte/oauth/google/GoogleAdsOauthFlow.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,45 @@
2525
package io.airbyte.oauth.google;
2626

2727
import com.fasterxml.jackson.databind.JsonNode;
28+
import com.google.common.annotations.VisibleForTesting;
29+
import com.google.common.base.Preconditions;
2830
import io.airbyte.config.persistence.ConfigRepository;
31+
import java.io.IOException;
32+
import java.net.http.HttpClient;
33+
import java.util.Map;
2934

3035
public class GoogleAdsOauthFlow extends GoogleOAuthFlow {
3136

37+
@VisibleForTesting
38+
static final String SCOPE = "https://www.googleapis.com/auth/adwords";
39+
3240
public GoogleAdsOauthFlow(ConfigRepository configRepository) {
33-
super(configRepository, "https://www.googleapis.com/auth/adwords");
41+
super(configRepository, SCOPE);
42+
}
43+
44+
@VisibleForTesting
45+
GoogleAdsOauthFlow(ConfigRepository configRepository, HttpClient client) {
46+
super(configRepository, SCOPE, client);
3447
}
3548

3649
@Override
3750
protected String getClientIdUnsafe(JsonNode config) {
3851
// the config object containing client ID and secret is nested inside the "credentials" object
52+
Preconditions.checkArgument(config.hasNonNull("credentials"));
3953
return super.getClientIdUnsafe(config.get("credentials"));
4054
}
4155

4256
@Override
4357
protected String getClientSecretUnsafe(JsonNode config) {
4458
// the config object containing client ID and secret is nested inside the "credentials" object
59+
Preconditions.checkArgument(config.hasNonNull("credentials"));
4560
return super.getClientSecretUnsafe(config.get("credentials"));
4661
}
4762

63+
@Override
64+
protected Map<String, Object> completeOAuthFlow(String clientId, String clientSecret, String code, String redirectUrl) throws IOException {
65+
// the config object containing refresh token is nested inside the "credentials" object
66+
return Map.of("credentials", super.completeOAuthFlow(clientId, clientSecret, code, redirectUrl));
67+
}
68+
4869
}

airbyte-oauth/src/main/java/io/airbyte/oauth/google/GoogleOAuthFlow.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public Map<String, Object> completeDestinationOAuth(UUID workspaceId,
144144
}
145145
}
146146

147-
private Map<String, Object> completeOAuthFlow(String clientId, String clientSecret, String code, String redirectUrl) throws IOException {
147+
protected Map<String, Object> completeOAuthFlow(String clientId, String clientSecret, String code, String redirectUrl) throws IOException {
148148
final ImmutableMap<String, String> body = new Builder<String, String>()
149149
.put("client_id", clientId)
150150
.put("client_secret", clientSecret)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2020 Airbyte
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package io.airbyte.oauth.google;
26+
27+
import static org.junit.jupiter.api.Assertions.assertEquals;
28+
import static org.junit.jupiter.api.Assertions.assertThrows;
29+
import static org.mockito.ArgumentMatchers.any;
30+
import static org.mockito.Mockito.mock;
31+
import static org.mockito.Mockito.when;
32+
33+
import com.google.common.collect.ImmutableMap;
34+
import io.airbyte.commons.json.Jsons;
35+
import io.airbyte.config.DestinationOAuthParameter;
36+
import io.airbyte.config.SourceOAuthParameter;
37+
import io.airbyte.config.persistence.ConfigNotFoundException;
38+
import io.airbyte.config.persistence.ConfigRepository;
39+
import io.airbyte.validation.json.JsonValidationException;
40+
import java.io.IOException;
41+
import java.net.http.HttpClient;
42+
import java.net.http.HttpResponse;
43+
import java.util.List;
44+
import java.util.Map;
45+
import java.util.UUID;
46+
import org.junit.jupiter.api.BeforeEach;
47+
import org.junit.jupiter.api.Test;
48+
49+
public class GoogleAdsOauthFlowTest {
50+
51+
private static final String SCOPE = "https%3A//www.googleapis.com/auth/analytics.readonly";
52+
private static final String REDIRECT_URL = "https://airbyte.io";
53+
54+
private HttpClient httpClient;
55+
private ConfigRepository configRepository;
56+
private GoogleAdsOauthFlow googleAdsOauthFlow;
57+
58+
private UUID workspaceId;
59+
private UUID definitionId;
60+
61+
@BeforeEach
62+
public void setup() {
63+
httpClient = mock(HttpClient.class);
64+
configRepository = mock(ConfigRepository.class);
65+
googleAdsOauthFlow = new GoogleAdsOauthFlow(configRepository, httpClient);
66+
67+
workspaceId = UUID.randomUUID();
68+
definitionId = UUID.randomUUID();
69+
}
70+
71+
@Test
72+
public void testCompleteSourceOAuth() throws IOException, ConfigNotFoundException, JsonValidationException, InterruptedException {
73+
when(configRepository.listSourceOAuthParam()).thenReturn(List.of(new SourceOAuthParameter()
74+
.withOauthParameterId(UUID.randomUUID())
75+
.withSourceDefinitionId(definitionId)
76+
.withWorkspaceId(workspaceId)
77+
.withConfiguration(Jsons.jsonNode(Map.of("credentials", ImmutableMap.builder()
78+
.put("client_id", "test_client_id")
79+
.put("client_secret", "test_client_secret")
80+
.build())))));
81+
82+
Map<String, String> returnedCredentials = Map.of("refresh_token", "refresh_token_response");
83+
final HttpResponse response = mock(HttpResponse.class);
84+
when(response.body()).thenReturn(Jsons.serialize(returnedCredentials));
85+
when(httpClient.send(any(), any())).thenReturn(response);
86+
final Map<String, Object> queryParams = Map.of("code", "test_code");
87+
final Map<String, Object> actualQueryParams = googleAdsOauthFlow.completeSourceOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL);
88+
89+
assertEquals(Jsons.serialize(Map.of("credentials", returnedCredentials)), Jsons.serialize(actualQueryParams));
90+
}
91+
92+
@Test
93+
public void testCompleteDestinationOAuth() throws IOException, ConfigNotFoundException, JsonValidationException, InterruptedException {
94+
when(configRepository.listDestinationOAuthParam()).thenReturn(List.of(new DestinationOAuthParameter()
95+
.withOauthParameterId(UUID.randomUUID())
96+
.withDestinationDefinitionId(definitionId)
97+
.withWorkspaceId(workspaceId)
98+
.withConfiguration(Jsons.jsonNode(Map.of("credentials", ImmutableMap.builder()
99+
.put("client_id", "test_client_id")
100+
.put("client_secret", "test_client_secret")
101+
.build())))));
102+
103+
Map<String, String> returnedCredentials = Map.of("refresh_token", "refresh_token_response");
104+
final HttpResponse response = mock(HttpResponse.class);
105+
when(response.body()).thenReturn(Jsons.serialize(returnedCredentials));
106+
when(httpClient.send(any(), any())).thenReturn(response);
107+
final Map<String, Object> queryParams = Map.of("code", "test_code");
108+
final Map<String, Object> actualQueryParams = googleAdsOauthFlow.completeDestinationOAuth(workspaceId, definitionId, queryParams, REDIRECT_URL);
109+
110+
assertEquals(Jsons.serialize(Map.of("credentials", returnedCredentials)), Jsons.serialize(actualQueryParams));
111+
}
112+
113+
@Test
114+
public void testGetClientIdUnsafe() {
115+
String clientId = "123";
116+
Map<String, String> clientIdMap = Map.of("client_id", clientId);
117+
Map<String, Map<String, String>> nestedConfig = Map.of("credentials", clientIdMap);
118+
119+
assertThrows(IllegalArgumentException.class, () -> googleAdsOauthFlow.getClientIdUnsafe(Jsons.jsonNode(clientIdMap)));
120+
assertEquals(clientId, googleAdsOauthFlow.getClientIdUnsafe(Jsons.jsonNode(nestedConfig)));
121+
}
122+
123+
@Test
124+
public void testGetClientSecretUnsafe() {
125+
String clientSecret = "secret";
126+
Map<String, String> clientIdMap = Map.of("client_secret", clientSecret);
127+
Map<String, Map<String, String>> nestedConfig = Map.of("credentials", clientIdMap);
128+
129+
assertThrows(IllegalArgumentException.class, () -> googleAdsOauthFlow.getClientSecretUnsafe(Jsons.jsonNode(clientIdMap)));
130+
assertEquals(clientSecret, googleAdsOauthFlow.getClientSecretUnsafe(Jsons.jsonNode(nestedConfig)));
131+
}
132+
133+
}

0 commit comments

Comments
 (0)