Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 36 additions & 6 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,35 +96,65 @@ jobs:
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::521590706193:role/oidc-github-dropbox-dropbox-sdk-java-branch-main
role-to-assume: arn:aws:iam::082972943155:role/oidc-github-dropbox-dropbox-sdk-java-repo
aws-region: us-west-2

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

- name: Obtain oauth access token for integration tests
run: ./generate-ci-auth-file
env:
APP_KEY: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
APP_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}

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

- name: Run Integration Tests - OkHttpRequestor
run: ./gradlew -Pcom.dropbox.test.httpRequestor=OkHttpRequestor -Pcom.dropbox.test.authInfoFile=../auth_output integrationTest &&
./gradlew -Pcom.dropbox.test.httpRequestor=OkHttpRequestor -Pcom.dropbox.test.authInfoFile=../auth_output proguardTest
env:
SCOPED_USER_CLIENT_ID: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
SCOPED_USER_CLIENT_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
SCOPED_USER_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}
SCOPED_TEAM_CLIENT_ID: ${{ env.CREDS_SCOPED_TEAM_CLIENT_ID }}
SCOPED_TEAM_CLIENT_SECRET: ${{ env.CREDS_SCOPED_TEAM_CLIENT_SECRET }}
SCOPED_TEAM_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_TEAM_REFRESH_TOKEN }}
DROPBOX_SHARED_LINK: ${{ env.CREDS_DROPBOX_SHARED_LINK }}

- name: Run Integration Tests - OkHttp3Requestor
run: ./gradlew -Pcom.dropbox.test.httpRequestor=OkHttp3Requestor -Pcom.dropbox.test.authInfoFile=../auth_output integrationTest &&
./gradlew -Pcom.dropbox.test.httpRequestor=OkHttp3Requestor -Pcom.dropbox.test.authInfoFile=../auth_output proguardTest
env:
SCOPED_USER_CLIENT_ID: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
SCOPED_USER_CLIENT_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
SCOPED_USER_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}
SCOPED_TEAM_CLIENT_ID: ${{ env.CREDS_SCOPED_TEAM_CLIENT_ID }}
SCOPED_TEAM_CLIENT_SECRET: ${{ env.CREDS_SCOPED_TEAM_CLIENT_SECRET }}
SCOPED_TEAM_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_TEAM_REFRESH_TOKEN }}
DROPBOX_SHARED_LINK: ${{ env.CREDS_DROPBOX_SHARED_LINK }}

- name: Run Integration Tests - StandardHttpRequestor
run: ./gradlew -Pcom.dropbox.test.httpRequestor=StandardHttpRequestor -Pcom.dropbox.test.authInfoFile=../auth_output integrationTest &&
./gradlew -Pcom.dropbox.test.httpRequestor=StandardHttpRequestor -Pcom.dropbox.test.authInfoFile=../auth_output proguardTest
env:
SCOPED_USER_CLIENT_ID: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
SCOPED_USER_CLIENT_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
SCOPED_USER_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}
SCOPED_TEAM_CLIENT_ID: ${{ env.CREDS_SCOPED_TEAM_CLIENT_ID }}
SCOPED_TEAM_CLIENT_SECRET: ${{ env.CREDS_SCOPED_TEAM_CLIENT_SECRET }}
SCOPED_TEAM_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_TEAM_REFRESH_TOKEN }}
DROPBOX_SHARED_LINK: ${{ env.CREDS_DROPBOX_SHARED_LINK }}

publish:
runs-on: ubuntu-latest
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,31 @@ To run individual tests, use the `--tests` gradle test filter:
./gradlew -Pcom.dropbox.test.authInfoFile=<path-to-test.auth> integrationTest --tests '*.DbxClientV1IT.testAccountInfo'
```

### Scoped credential integration tests

A few integration tests exercise the credential model shared across the Dropbox SDKs (scoped user
and team refresh tokens plus a shared link). In addition to the `test.auth` file above, these tests
read the following environment variables and are **automatically skipped** when the variables are
not set:

- `SCOPED_USER_CLIENT_ID`, `SCOPED_USER_CLIENT_SECRET`, `SCOPED_USER_REFRESH_TOKEN`
- `SCOPED_TEAM_CLIENT_ID`, `SCOPED_TEAM_CLIENT_SECRET`, `SCOPED_TEAM_REFRESH_TOKEN`
- `DROPBOX_SHARED_LINK`

For local development you don't need to do anything with these: run `integrationTest` with your
`test.auth` file as usual, and the scoped tests simply skip. To run them locally as well, export the
variables alongside the usual command:

```shell
SCOPED_USER_CLIENT_ID=... SCOPED_USER_CLIENT_SECRET=... SCOPED_USER_REFRESH_TOKEN=... \
SCOPED_TEAM_CLIENT_ID=... SCOPED_TEAM_CLIENT_SECRET=... SCOPED_TEAM_REFRESH_TOKEN=... \
DROPBOX_SHARED_LINK=... \
./gradlew -Pcom.dropbox.test.authInfoFile=<path-to-test.auth> integrationTest
```

On CI these variables are provided automatically, so all integration tests run there. Local
development continues to use your own `test.auth` file.

## Usage on Android

Edit your project's "build.gradle" and add the following to the dependencies section:
Expand Down
4 changes: 4 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ tasks.register('integrationTest', Test) {
description 'Runs integration tests against Production or Dev servers.'
enabled = project.hasProperty(authInfoPropertyName)

// Run against the same compiled classes and classpath as the unit `test` task.
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath

useTestNG()

// only select integration tests (similar to maven-failsafe-plugin rules)
Expand Down
64 changes: 64 additions & 0 deletions core/src/test/java/com/dropbox/core/ITUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import okhttp3.Request;
import okhttp3.Response;
import org.testng.SkipException;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;

Expand All @@ -29,15 +30,28 @@
import com.dropbox.core.http.OkHttp3Requestor;
import com.dropbox.core.http.StandardHttpRequestor;
import com.dropbox.core.json.JsonReader;
import com.dropbox.core.oauth.DbxCredential;
import com.dropbox.core.v1.DbxClientV1;
import com.dropbox.core.v2.DbxClientV2;
import com.dropbox.core.v2.DbxTeamClientV2;
import com.dropbox.core.v2.files.DeleteErrorException;

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

// Environment variables mirroring the credential model shared across the Dropbox SDKs
// (see dropbox-sdk-python). Populated in CI from the "api-sdk-integration-test-creds"
// AWS Secrets Manager secret. Scoped clients authenticate via refresh tokens.
public static final String SCOPED_USER_CLIENT_ID = "SCOPED_USER_CLIENT_ID";
public static final String SCOPED_USER_CLIENT_SECRET = "SCOPED_USER_CLIENT_SECRET";
public static final String SCOPED_USER_REFRESH_TOKEN = "SCOPED_USER_REFRESH_TOKEN";
public static final String SCOPED_TEAM_CLIENT_ID = "SCOPED_TEAM_CLIENT_ID";
public static final String SCOPED_TEAM_CLIENT_SECRET = "SCOPED_TEAM_CLIENT_SECRET";
public static final String SCOPED_TEAM_REFRESH_TOKEN = "SCOPED_TEAM_REFRESH_TOKEN";
public static final String DROPBOX_SHARED_LINK = "DROPBOX_SHARED_LINK";

private static final Random RAND = new Random(0L);
private static final long READ_TIMEOUT = TimeUnit.SECONDS.toMillis(20);
private static final int MAX_RETRIES = 3;
Expand Down Expand Up @@ -196,6 +210,56 @@ public static DbxClientV2 newClientV2(DbxRequestConfig config) {
);
}

/**
* Reads an environment variable, skipping the current test (rather than failing) if it is
* not set. This lets the scoped-credential integration tests run only in environments that
* supply the shared SDK credentials while remaining opt-in elsewhere.
*/
public static String valueFromEnvOrSkip(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty()) {
throw new SkipException(
"Environment variable \"" + name + "\" is not set; skipping test that requires " +
"shared SDK integration credentials.");
}
return value;
}
Comment on lines +218 to +226

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot read environment variables that are set on the system. Per the docs:

By default, Gradle does not automatically pass all terminal environment variables to the forked test process. You need to configure your integrationTest task to inherit them.

This is why we kick off tests we use -Pcom.dropbox.test.authInfoFile=../auth_output in our gradle command, which passes the variable to test process.

Integration tests don't run on PRs, only when we land, so you'll need to run them locally and confirm

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed with a temporary PR integration run: https://github.com/dropbox/dropbox-sdk-java/actions/runs/29540327341

The integration job passed for OkHttpRequestor, OkHttp3Requestor, and StandardHttpRequestor. ScopedUserClientV2IT, SharedLinkV2IT, and TeamClientV2IT ran successfully in all three suites while reading the scoped credentials through System.getenv().

This confirms that the environment variables configured on the workflow step are inherited by the forked Gradle test process. authInfoFile uses a separate mechanism: -Pcom.dropbox.test.authInfoFile is a Gradle project property that core/build.gradle explicitly forwards as a JVM system property.


/**
* Builds a {@link DbxCredential} from a refresh token plus app key/secret. The access token
* is a placeholder with an {@code expiresAt} of {@code 0}, which forces the client to refresh
* before the first request.
*/
static DbxCredential refreshCredential(String refreshToken, String appKey, String appSecret) {
return new DbxCredential("placeholder-access-token", 0L, refreshToken, appKey, appSecret);
}

/**
* Creates a user {@link DbxClientV2} authenticated via the scoped user refresh token supplied
* through environment variables. Skips the calling test if the credentials are not set.
*/
public static DbxClientV2 newScopedUserClientV2() {
DbxCredential credential = refreshCredential(
valueFromEnvOrSkip(SCOPED_USER_REFRESH_TOKEN),
valueFromEnvOrSkip(SCOPED_USER_CLIENT_ID),
valueFromEnvOrSkip(SCOPED_USER_CLIENT_SECRET)
);
return new DbxClientV2(newRequestConfig().build(), credential);
}

/**
* Creates a {@link DbxTeamClientV2} authenticated via the scoped team refresh token supplied
* through environment variables. Skips the calling test if the credentials are not set.
*/
public static DbxTeamClientV2 newScopedTeamClientV2() {
DbxCredential credential = refreshCredential(
valueFromEnvOrSkip(SCOPED_TEAM_REFRESH_TOKEN),
valueFromEnvOrSkip(SCOPED_TEAM_CLIENT_ID),
valueFromEnvOrSkip(SCOPED_TEAM_CLIENT_SECRET)
);
return new DbxTeamClientV2(newRequestConfig().build(), credential);
}

private static final class RootContainer {
public static String ROOT = "/test/dropbox-sdk-java/" + format(new Date());
}
Expand Down
25 changes: 25 additions & 0 deletions core/src/test/java/com/dropbox/core/v2/ScopedUserClientV2IT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.dropbox.core.v2;

import static com.google.common.truth.Truth.assertThat;

import org.testng.annotations.Test;

import com.dropbox.core.ITUtil;
import com.dropbox.core.v2.users.FullAccount;

/**
* Integration test for a user {@link DbxClientV2} authenticated via the scoped user refresh token
* (mirrors the scoped-user credentials shared across the Dropbox SDKs). Requires the
* {@code SCOPED_USER_*} environment variables; otherwise the tests are skipped.
*/
public class ScopedUserClientV2IT {
@Test
public void testScopedUserRefreshAccountInfo() throws Exception {
DbxClientV2 client = ITUtil.newScopedUserClientV2();

FullAccount account = client.users().getCurrentAccount();
assertThat(account).isNotNull();
assertThat(account.getAccountId()).isNotNull();
assertThat(account.getName()).isNotNull();
}
}
27 changes: 27 additions & 0 deletions core/src/test/java/com/dropbox/core/v2/SharedLinkV2IT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.dropbox.core.v2;

import static com.google.common.truth.Truth.assertThat;

import org.testng.annotations.Test;

import com.dropbox.core.ITUtil;
import com.dropbox.core.v2.sharing.SharedLinkMetadata;

/**
* Integration test that resolves the shared link supplied via {@code DROPBOX_SHARED_LINK} using
* the sharing API (mirrors the shared-link credential shared across the Dropbox SDKs). Requires
* the {@code SCOPED_USER_*} and {@code DROPBOX_SHARED_LINK} environment variables; otherwise the
* tests are skipped.
*/
public class SharedLinkV2IT {
@Test
public void testGetSharedLinkMetadata() throws Exception {
String sharedLink = ITUtil.valueFromEnvOrSkip(ITUtil.DROPBOX_SHARED_LINK);
DbxClientV2 client = ITUtil.newScopedUserClientV2();

SharedLinkMetadata metadata = client.sharing().getSharedLinkMetadata(sharedLink);
assertThat(metadata).isNotNull();
assertThat(metadata.getUrl()).isNotNull();
assertThat(metadata.getName()).isNotNull();
}
}
25 changes: 25 additions & 0 deletions core/src/test/java/com/dropbox/core/v2/TeamClientV2IT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.dropbox.core.v2;

import static com.google.common.truth.Truth.assertThat;

import org.testng.annotations.Test;

import com.dropbox.core.ITUtil;
import com.dropbox.core.v2.team.TeamGetInfoResult;

/**
* Integration test for {@link DbxTeamClientV2} authenticated via the scoped team refresh token
* (mirrors the {@code dbx_team_from_env} fixture in dropbox-sdk-python). Requires the
* {@code SCOPED_TEAM_*} environment variables; otherwise the tests are skipped.
*/
public class TeamClientV2IT {
@Test
public void testScopedTeamGetInfo() throws Exception {
DbxTeamClientV2 client = ITUtil.newScopedTeamClientV2();

TeamGetInfoResult info = client.team().getInfo();
assertThat(info).isNotNull();
assertThat(info.getTeamId()).isNotNull();
assertThat(info.getName()).isNotNull();
}
}
3 changes: 2 additions & 1 deletion generate-ci-auth-file
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ fi
curl https://api.dropbox.com/oauth2/token \
-d grant_type=refresh_token \
-d refresh_token=$REFRESH_TOKEN \
-d client_id=$APP_KEY > auth_output
-d client_id=$APP_KEY \
-d client_secret=$APP_SECRET > auth_output

truncate -s -1 auth_output

Expand Down
Loading