From a59fd2307d2a92ace5237b19417fbce6d571a762 Mon Sep 17 00:00:00 2001 From: Andrey Markelov Date: Thu, 16 Jul 2026 15:57:47 -0700 Subject: [PATCH] Add scoped integration tests --- .github/workflows/check.yml | 42 ++++++++++-- README.md | 25 ++++++++ core/build.gradle | 4 ++ .../test/java/com/dropbox/core/ITUtil.java | 64 +++++++++++++++++++ .../dropbox/core/v2/ScopedUserClientV2IT.java | 25 ++++++++ .../com/dropbox/core/v2/SharedLinkV2IT.java | 27 ++++++++ .../com/dropbox/core/v2/TeamClientV2IT.java | 25 ++++++++ generate-ci-auth-file | 3 +- 8 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 core/src/test/java/com/dropbox/core/v2/ScopedUserClientV2IT.java create mode 100644 core/src/test/java/com/dropbox/core/v2/SharedLinkV2IT.java create mode 100644 core/src/test/java/com/dropbox/core/v2/TeamClientV2IT.java diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5f7bfa6b9b..a1c3a1e057 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -96,20 +96,26 @@ 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_ 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 @@ -117,14 +123,38 @@ jobs: - 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 diff --git a/README.md b/README.md index 1c94f391cc..ea79248608 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,31 @@ To run individual tests, use the `--tests` gradle test filter: ./gradlew -Pcom.dropbox.test.authInfoFile= 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= 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: diff --git a/core/build.gradle b/core/build.gradle index ba24455df0..1b5f96f446 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -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) diff --git a/core/src/test/java/com/dropbox/core/ITUtil.java b/core/src/test/java/com/dropbox/core/ITUtil.java index fbb73aed3c..60e26c49d4 100644 --- a/core/src/test/java/com/dropbox/core/ITUtil.java +++ b/core/src/test/java/com/dropbox/core/ITUtil.java @@ -20,6 +20,7 @@ import okhttp3.Request; import okhttp3.Response; +import org.testng.SkipException; import org.testng.annotations.AfterSuite; import org.testng.annotations.BeforeSuite; @@ -29,8 +30,10 @@ 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 @@ -38,6 +41,17 @@ 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; @@ -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; + } + + /** + * 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()); } diff --git a/core/src/test/java/com/dropbox/core/v2/ScopedUserClientV2IT.java b/core/src/test/java/com/dropbox/core/v2/ScopedUserClientV2IT.java new file mode 100644 index 0000000000..7e26c0406e --- /dev/null +++ b/core/src/test/java/com/dropbox/core/v2/ScopedUserClientV2IT.java @@ -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(); + } +} diff --git a/core/src/test/java/com/dropbox/core/v2/SharedLinkV2IT.java b/core/src/test/java/com/dropbox/core/v2/SharedLinkV2IT.java new file mode 100644 index 0000000000..48e15affad --- /dev/null +++ b/core/src/test/java/com/dropbox/core/v2/SharedLinkV2IT.java @@ -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(); + } +} diff --git a/core/src/test/java/com/dropbox/core/v2/TeamClientV2IT.java b/core/src/test/java/com/dropbox/core/v2/TeamClientV2IT.java new file mode 100644 index 0000000000..6517896970 --- /dev/null +++ b/core/src/test/java/com/dropbox/core/v2/TeamClientV2IT.java @@ -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(); + } +} diff --git a/generate-ci-auth-file b/generate-ci-auth-file index 311eea1d29..11f78348e9 100755 --- a/generate-ci-auth-file +++ b/generate-ci-auth-file @@ -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