Skip to content

Commit 754a2bb

Browse files
Merge pull request #590 from dropbox/integration-test-scoped-creds
Add scoped-credential integration tests
2 parents b1b060b + a59fd23 commit 754a2bb

8 files changed

Lines changed: 208 additions & 7 deletions

File tree

.github/workflows/check.yml

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,35 +96,65 @@ jobs:
9696
- name: Configure AWS credentials (OIDC)
9797
uses: aws-actions/configure-aws-credentials@v6
9898
with:
99-
role-to-assume: arn:aws:iam::521590706193:role/oidc-github-dropbox-dropbox-sdk-java-branch-main
99+
role-to-assume: arn:aws:iam::082972943155:role/oidc-github-dropbox-dropbox-sdk-java-repo
100100
aws-region: us-west-2
101101

102-
- name: Get integration test secrets from AWS Secrets Manager
102+
# Shared credential model used across the Dropbox SDKs (scoped user/team refresh tokens and a
103+
# shared link). Exposed as CREDS_<KEY> env vars. The scoped user credentials also back the
104+
# auth_output file consumed by the file-based tests (examples, proguard, and the legacy IT
105+
# tests), so all of CI runs against this single shared credential.
106+
- name: Get shared SDK integration test secrets from AWS Secrets Manager
103107
uses: aws-actions/aws-secretsmanager-get-secrets@v3
104108
with:
105109
secret-ids: |
106-
APP_KEY,dropbox-sdk-java-app-key
107-
APP_SECRET,dropbox-sdk-java-app-secret
108-
REFRESH_TOKEN,dropbox-sdk-java-refresh-token
109-
parse-json-secrets: false
110+
CREDS,api-sdk-integration-test-creds
111+
parse-json-secrets: true
110112

111113
- name: Obtain oauth access token for integration tests
112114
run: ./generate-ci-auth-file
115+
env:
116+
APP_KEY: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
117+
APP_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
118+
REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}
113119

114120
- name: Run Integration Tests for Examples
115121
run: ./gradlew :examples:examples:test :examples:java:test -Pci=true --info
116122

117123
- name: Run Integration Tests - OkHttpRequestor
118124
run: ./gradlew -Pcom.dropbox.test.httpRequestor=OkHttpRequestor -Pcom.dropbox.test.authInfoFile=../auth_output integrationTest &&
119125
./gradlew -Pcom.dropbox.test.httpRequestor=OkHttpRequestor -Pcom.dropbox.test.authInfoFile=../auth_output proguardTest
126+
env:
127+
SCOPED_USER_CLIENT_ID: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
128+
SCOPED_USER_CLIENT_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
129+
SCOPED_USER_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}
130+
SCOPED_TEAM_CLIENT_ID: ${{ env.CREDS_SCOPED_TEAM_CLIENT_ID }}
131+
SCOPED_TEAM_CLIENT_SECRET: ${{ env.CREDS_SCOPED_TEAM_CLIENT_SECRET }}
132+
SCOPED_TEAM_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_TEAM_REFRESH_TOKEN }}
133+
DROPBOX_SHARED_LINK: ${{ env.CREDS_DROPBOX_SHARED_LINK }}
120134

121135
- name: Run Integration Tests - OkHttp3Requestor
122136
run: ./gradlew -Pcom.dropbox.test.httpRequestor=OkHttp3Requestor -Pcom.dropbox.test.authInfoFile=../auth_output integrationTest &&
123137
./gradlew -Pcom.dropbox.test.httpRequestor=OkHttp3Requestor -Pcom.dropbox.test.authInfoFile=../auth_output proguardTest
138+
env:
139+
SCOPED_USER_CLIENT_ID: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
140+
SCOPED_USER_CLIENT_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
141+
SCOPED_USER_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}
142+
SCOPED_TEAM_CLIENT_ID: ${{ env.CREDS_SCOPED_TEAM_CLIENT_ID }}
143+
SCOPED_TEAM_CLIENT_SECRET: ${{ env.CREDS_SCOPED_TEAM_CLIENT_SECRET }}
144+
SCOPED_TEAM_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_TEAM_REFRESH_TOKEN }}
145+
DROPBOX_SHARED_LINK: ${{ env.CREDS_DROPBOX_SHARED_LINK }}
124146

125147
- name: Run Integration Tests - StandardHttpRequestor
126148
run: ./gradlew -Pcom.dropbox.test.httpRequestor=StandardHttpRequestor -Pcom.dropbox.test.authInfoFile=../auth_output integrationTest &&
127149
./gradlew -Pcom.dropbox.test.httpRequestor=StandardHttpRequestor -Pcom.dropbox.test.authInfoFile=../auth_output proguardTest
150+
env:
151+
SCOPED_USER_CLIENT_ID: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
152+
SCOPED_USER_CLIENT_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
153+
SCOPED_USER_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}
154+
SCOPED_TEAM_CLIENT_ID: ${{ env.CREDS_SCOPED_TEAM_CLIENT_ID }}
155+
SCOPED_TEAM_CLIENT_SECRET: ${{ env.CREDS_SCOPED_TEAM_CLIENT_SECRET }}
156+
SCOPED_TEAM_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_TEAM_REFRESH_TOKEN }}
157+
DROPBOX_SHARED_LINK: ${{ env.CREDS_DROPBOX_SHARED_LINK }}
128158

129159
publish:
130160
runs-on: ubuntu-latest

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,31 @@ To run individual tests, use the `--tests` gradle test filter:
271271
./gradlew -Pcom.dropbox.test.authInfoFile=<path-to-test.auth> integrationTest --tests '*.DbxClientV1IT.testAccountInfo'
272272
```
273273

274+
### Scoped credential integration tests
275+
276+
A few integration tests exercise the credential model shared across the Dropbox SDKs (scoped user
277+
and team refresh tokens plus a shared link). In addition to the `test.auth` file above, these tests
278+
read the following environment variables and are **automatically skipped** when the variables are
279+
not set:
280+
281+
- `SCOPED_USER_CLIENT_ID`, `SCOPED_USER_CLIENT_SECRET`, `SCOPED_USER_REFRESH_TOKEN`
282+
- `SCOPED_TEAM_CLIENT_ID`, `SCOPED_TEAM_CLIENT_SECRET`, `SCOPED_TEAM_REFRESH_TOKEN`
283+
- `DROPBOX_SHARED_LINK`
284+
285+
For local development you don't need to do anything with these: run `integrationTest` with your
286+
`test.auth` file as usual, and the scoped tests simply skip. To run them locally as well, export the
287+
variables alongside the usual command:
288+
289+
```shell
290+
SCOPED_USER_CLIENT_ID=... SCOPED_USER_CLIENT_SECRET=... SCOPED_USER_REFRESH_TOKEN=... \
291+
SCOPED_TEAM_CLIENT_ID=... SCOPED_TEAM_CLIENT_SECRET=... SCOPED_TEAM_REFRESH_TOKEN=... \
292+
DROPBOX_SHARED_LINK=... \
293+
./gradlew -Pcom.dropbox.test.authInfoFile=<path-to-test.auth> integrationTest
294+
```
295+
296+
On CI these variables are provided automatically, so all integration tests run there. Local
297+
development continues to use your own `test.auth` file.
298+
274299
## Usage on Android
275300

276301
Edit your project's "build.gradle" and add the following to the dependencies section:

core/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ tasks.register('integrationTest', Test) {
160160
description 'Runs integration tests against Production or Dev servers.'
161161
enabled = project.hasProperty(authInfoPropertyName)
162162

163+
// Run against the same compiled classes and classpath as the unit `test` task.
164+
testClassesDirs = sourceSets.test.output.classesDirs
165+
classpath = sourceSets.test.runtimeClasspath
166+
163167
useTestNG()
164168

165169
// only select integration tests (similar to maven-failsafe-plugin rules)

core/src/test/java/com/dropbox/core/ITUtil.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import okhttp3.Request;
2222
import okhttp3.Response;
23+
import org.testng.SkipException;
2324
import org.testng.annotations.AfterSuite;
2425
import org.testng.annotations.BeforeSuite;
2526

@@ -29,15 +30,28 @@
2930
import com.dropbox.core.http.OkHttp3Requestor;
3031
import com.dropbox.core.http.StandardHttpRequestor;
3132
import com.dropbox.core.json.JsonReader;
33+
import com.dropbox.core.oauth.DbxCredential;
3234
import com.dropbox.core.v1.DbxClientV1;
3335
import com.dropbox.core.v2.DbxClientV2;
36+
import com.dropbox.core.v2.DbxTeamClientV2;
3437
import com.dropbox.core.v2.files.DeleteErrorException;
3538

3639
// integration test utility class
3740
public final class ITUtil {
3841
private static final String AUTH_INFO_FILE_PROPERTY = "com.dropbox.test.authInfoFile";
3942
private static final String HTTP_REQUESTOR_PROPERTY = "com.dropbox.test.httpRequestor";
4043

44+
// Environment variables mirroring the credential model shared across the Dropbox SDKs
45+
// (see dropbox-sdk-python). Populated in CI from the "api-sdk-integration-test-creds"
46+
// AWS Secrets Manager secret. Scoped clients authenticate via refresh tokens.
47+
public static final String SCOPED_USER_CLIENT_ID = "SCOPED_USER_CLIENT_ID";
48+
public static final String SCOPED_USER_CLIENT_SECRET = "SCOPED_USER_CLIENT_SECRET";
49+
public static final String SCOPED_USER_REFRESH_TOKEN = "SCOPED_USER_REFRESH_TOKEN";
50+
public static final String SCOPED_TEAM_CLIENT_ID = "SCOPED_TEAM_CLIENT_ID";
51+
public static final String SCOPED_TEAM_CLIENT_SECRET = "SCOPED_TEAM_CLIENT_SECRET";
52+
public static final String SCOPED_TEAM_REFRESH_TOKEN = "SCOPED_TEAM_REFRESH_TOKEN";
53+
public static final String DROPBOX_SHARED_LINK = "DROPBOX_SHARED_LINK";
54+
4155
private static final Random RAND = new Random(0L);
4256
private static final long READ_TIMEOUT = TimeUnit.SECONDS.toMillis(20);
4357
private static final int MAX_RETRIES = 3;
@@ -196,6 +210,56 @@ public static DbxClientV2 newClientV2(DbxRequestConfig config) {
196210
);
197211
}
198212

213+
/**
214+
* Reads an environment variable, skipping the current test (rather than failing) if it is
215+
* not set. This lets the scoped-credential integration tests run only in environments that
216+
* supply the shared SDK credentials while remaining opt-in elsewhere.
217+
*/
218+
public static String valueFromEnvOrSkip(String name) {
219+
String value = System.getenv(name);
220+
if (value == null || value.isEmpty()) {
221+
throw new SkipException(
222+
"Environment variable \"" + name + "\" is not set; skipping test that requires " +
223+
"shared SDK integration credentials.");
224+
}
225+
return value;
226+
}
227+
228+
/**
229+
* Builds a {@link DbxCredential} from a refresh token plus app key/secret. The access token
230+
* is a placeholder with an {@code expiresAt} of {@code 0}, which forces the client to refresh
231+
* before the first request.
232+
*/
233+
static DbxCredential refreshCredential(String refreshToken, String appKey, String appSecret) {
234+
return new DbxCredential("placeholder-access-token", 0L, refreshToken, appKey, appSecret);
235+
}
236+
237+
/**
238+
* Creates a user {@link DbxClientV2} authenticated via the scoped user refresh token supplied
239+
* through environment variables. Skips the calling test if the credentials are not set.
240+
*/
241+
public static DbxClientV2 newScopedUserClientV2() {
242+
DbxCredential credential = refreshCredential(
243+
valueFromEnvOrSkip(SCOPED_USER_REFRESH_TOKEN),
244+
valueFromEnvOrSkip(SCOPED_USER_CLIENT_ID),
245+
valueFromEnvOrSkip(SCOPED_USER_CLIENT_SECRET)
246+
);
247+
return new DbxClientV2(newRequestConfig().build(), credential);
248+
}
249+
250+
/**
251+
* Creates a {@link DbxTeamClientV2} authenticated via the scoped team refresh token supplied
252+
* through environment variables. Skips the calling test if the credentials are not set.
253+
*/
254+
public static DbxTeamClientV2 newScopedTeamClientV2() {
255+
DbxCredential credential = refreshCredential(
256+
valueFromEnvOrSkip(SCOPED_TEAM_REFRESH_TOKEN),
257+
valueFromEnvOrSkip(SCOPED_TEAM_CLIENT_ID),
258+
valueFromEnvOrSkip(SCOPED_TEAM_CLIENT_SECRET)
259+
);
260+
return new DbxTeamClientV2(newRequestConfig().build(), credential);
261+
}
262+
199263
private static final class RootContainer {
200264
public static String ROOT = "/test/dropbox-sdk-java/" + format(new Date());
201265
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.dropbox.core.v2;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import org.testng.annotations.Test;
6+
7+
import com.dropbox.core.ITUtil;
8+
import com.dropbox.core.v2.users.FullAccount;
9+
10+
/**
11+
* Integration test for a user {@link DbxClientV2} authenticated via the scoped user refresh token
12+
* (mirrors the scoped-user credentials shared across the Dropbox SDKs). Requires the
13+
* {@code SCOPED_USER_*} environment variables; otherwise the tests are skipped.
14+
*/
15+
public class ScopedUserClientV2IT {
16+
@Test
17+
public void testScopedUserRefreshAccountInfo() throws Exception {
18+
DbxClientV2 client = ITUtil.newScopedUserClientV2();
19+
20+
FullAccount account = client.users().getCurrentAccount();
21+
assertThat(account).isNotNull();
22+
assertThat(account.getAccountId()).isNotNull();
23+
assertThat(account.getName()).isNotNull();
24+
}
25+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.dropbox.core.v2;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import org.testng.annotations.Test;
6+
7+
import com.dropbox.core.ITUtil;
8+
import com.dropbox.core.v2.sharing.SharedLinkMetadata;
9+
10+
/**
11+
* Integration test that resolves the shared link supplied via {@code DROPBOX_SHARED_LINK} using
12+
* the sharing API (mirrors the shared-link credential shared across the Dropbox SDKs). Requires
13+
* the {@code SCOPED_USER_*} and {@code DROPBOX_SHARED_LINK} environment variables; otherwise the
14+
* tests are skipped.
15+
*/
16+
public class SharedLinkV2IT {
17+
@Test
18+
public void testGetSharedLinkMetadata() throws Exception {
19+
String sharedLink = ITUtil.valueFromEnvOrSkip(ITUtil.DROPBOX_SHARED_LINK);
20+
DbxClientV2 client = ITUtil.newScopedUserClientV2();
21+
22+
SharedLinkMetadata metadata = client.sharing().getSharedLinkMetadata(sharedLink);
23+
assertThat(metadata).isNotNull();
24+
assertThat(metadata.getUrl()).isNotNull();
25+
assertThat(metadata.getName()).isNotNull();
26+
}
27+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.dropbox.core.v2;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import org.testng.annotations.Test;
6+
7+
import com.dropbox.core.ITUtil;
8+
import com.dropbox.core.v2.team.TeamGetInfoResult;
9+
10+
/**
11+
* Integration test for {@link DbxTeamClientV2} authenticated via the scoped team refresh token
12+
* (mirrors the {@code dbx_team_from_env} fixture in dropbox-sdk-python). Requires the
13+
* {@code SCOPED_TEAM_*} environment variables; otherwise the tests are skipped.
14+
*/
15+
public class TeamClientV2IT {
16+
@Test
17+
public void testScopedTeamGetInfo() throws Exception {
18+
DbxTeamClientV2 client = ITUtil.newScopedTeamClientV2();
19+
20+
TeamGetInfoResult info = client.team().getInfo();
21+
assertThat(info).isNotNull();
22+
assertThat(info.getTeamId()).isNotNull();
23+
assertThat(info.getName()).isNotNull();
24+
}
25+
}

generate-ci-auth-file

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ fi
99
curl https://api.dropbox.com/oauth2/token \
1010
-d grant_type=refresh_token \
1111
-d refresh_token=$REFRESH_TOKEN \
12-
-d client_id=$APP_KEY > auth_output
12+
-d client_id=$APP_KEY \
13+
-d client_secret=$APP_SECRET > auth_output
1314

1415
truncate -s -1 auth_output
1516

0 commit comments

Comments
 (0)