diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41f7de963..fe3ba3317 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ on: jobs: lint: - timeout-minutes: 10 + timeout-minutes: 15 name: lint runs-on: ${{ github.repository == 'stainless-sdks/lithic-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork @@ -37,7 +37,7 @@ jobs: - name: Run lints run: ./scripts/lint test: - timeout-minutes: 10 + timeout-minutes: 15 name: test runs-on: ${{ github.repository == 'stainless-sdks/lithic-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a81246476..a579a4349 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.98.0" + ".": "0.99.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 990677dc4..df54f798b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.99.0 (2025-07-31) + +Full Changelog: [v0.98.0...v0.99.0](https://github.com/lithic-com/lithic-java/compare/v0.98.0...v0.99.0) + +### Features + +* add retryable exception ([9d0e333](https://github.com/lithic-com/lithic-java/commit/9d0e333fa86f6ca6feafd1ea77663082212d4fbd)) +* **client:** ensure compat with proguard ([0cf8803](https://github.com/lithic-com/lithic-java/commit/0cf88037c4534479827ab86f6cfc4e01aa6c9f5e)) + + +### Bug Fixes + +* **client:** r8 support ([3f77652](https://github.com/lithic-com/lithic-java/commit/3f776524d6c643f14ce1e4a29d5a61919028d27e)) + + +### Chores + +* **internal:** bump ci test timeout ([dc67b43](https://github.com/lithic-com/lithic-java/commit/dc67b43b859593e8a7a302e2d74935fa45e11584)) +* **internal:** reduce proguard ci logging ([7af8774](https://github.com/lithic-com/lithic-java/commit/7af8774633df28cc77bf86cf3bde7073bc1caf04)) + ## 0.98.0 (2025-07-28) Full Changelog: [v0.97.1...v0.98.0](https://github.com/lithic-com/lithic-java/compare/v0.97.1...v0.98.0) diff --git a/README.md b/README.md index d8a1548f7..247ce2123 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.lithic.api/lithic-java)](https://central.sonatype.com/artifact/com.lithic.api/lithic-java/0.98.0) -[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.98.0/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.98.0) +[![Maven Central](https://img.shields.io/maven-central/v/com.lithic.api/lithic-java)](https://central.sonatype.com/artifact/com.lithic.api/lithic-java/0.99.0) +[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.99.0/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.99.0) @@ -13,7 +13,7 @@ The Lithic Java SDK is similar to the Lithic Kotlin SDK but with minor differenc -The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.lithic.api/lithic-java/0.98.0). +The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.lithic.api/lithic-java/0.99.0). @@ -24,7 +24,7 @@ The REST API documentation can be found on [docs.lithic.com](https://docs.lithic ### Gradle ```kotlin -implementation("com.lithic.api:lithic-java:0.98.0") +implementation("com.lithic.api:lithic-java:0.99.0") ``` ### Maven @@ -33,7 +33,7 @@ implementation("com.lithic.api:lithic-java:0.98.0") com.lithic.api lithic-java - 0.98.0 + 0.99.0 ``` @@ -232,6 +232,8 @@ The SDK throws custom unchecked exception types: - [`LithicIoException`](lithic-java-core/src/main/kotlin/com/lithic/api/errors/LithicIoException.kt): I/O networking errors. +- [`LithicRetryableException`](lithic-java-core/src/main/kotlin/com/lithic/api/errors/LithicRetryableException.kt): Generic error indicating a failure that could be retried by the client. + - [`LithicInvalidDataException`](lithic-java-core/src/main/kotlin/com/lithic/api/errors/LithicInvalidDataException.kt): Failure to interpret successfully parsed data. For example, when accessing a property that's supposed to be required, but the API unexpectedly omitted it from the response. - [`LithicException`](lithic-java-core/src/main/kotlin/com/lithic/api/errors/LithicException.kt): Base class for all exceptions. Most errors will result in one of the previously mentioned ones, but completely generic errors may be thrown using the base class. @@ -362,6 +364,12 @@ both of which will raise an error if the signature is invalid. Note that the "body" parameter must be the raw JSON string sent from the server (do not parse it first). The `.unwrap()` method can parse this JSON for you. +## ProGuard and R8 + +Although the SDK uses reflection, it is still usable with [ProGuard](https://github.com/Guardsquare/proguard) and [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) because `lithic-java-core` is published with a [configuration file](lithic-java-core/src/main/resources/META-INF/proguard/lithic-java-core.pro) containing [keep rules](https://www.guardsquare.com/manual/configuration/usage). + +ProGuard and R8 should automatically detect and use the published rules, but you can also manually copy the keep rules if necessary. + ## Jackson The SDK depends on [Jackson](https://github.com/FasterXML/jackson) for JSON serialization/deserialization. It is compatible with version 2.13.4 or higher, but depends on version 2.18.2 by default. diff --git a/build.gradle.kts b/build.gradle.kts index e99ae9d22..5439db507 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.lithic.api" - version = "0.98.0" // x-release-please-version + version = "0.99.0" // x-release-please-version } subprojects { diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/RetryingHttpClient.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/RetryingHttpClient.kt index f267f4786..b27f1442a 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/RetryingHttpClient.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/http/RetryingHttpClient.kt @@ -3,6 +3,7 @@ package com.lithic.api.core.http import com.lithic.api.core.RequestOptions import com.lithic.api.core.checkRequired import com.lithic.api.errors.LithicIoException +import com.lithic.api.errors.LithicRetryableException import java.io.IOException import java.time.Clock import java.time.Duration @@ -176,9 +177,10 @@ private constructor( } private fun shouldRetry(throwable: Throwable): Boolean = - // Only retry IOException and LithicIoException, other exceptions are not intended to be - // retried. - throwable is IOException || throwable is LithicIoException + // Only retry known retryable exceptions, other exceptions are not intended to be retried. + throwable is IOException || + throwable is LithicIoException || + throwable is LithicRetryableException private fun getRetryBackoffDuration(retries: Int, response: HttpResponse?): Duration { // About the Retry-After header: diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/errors/LithicRetryableException.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/errors/LithicRetryableException.kt new file mode 100644 index 000000000..45f054257 --- /dev/null +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/errors/LithicRetryableException.kt @@ -0,0 +1,14 @@ +package com.lithic.api.errors + +/** + * Exception that indicates a transient error that can be retried. + * + * When this exception is thrown during an HTTP request, the SDK will automatically retry the + * request up to the maximum number of retries. + * + * @param message A descriptive error message + * @param cause The underlying cause of this exception, if any + */ +class LithicRetryableException +@JvmOverloads +constructor(message: String? = null, cause: Throwable? = null) : LithicException(message, cause) diff --git a/lithic-java-core/src/main/resources/META-INF/proguard/lithic-java-core.pro b/lithic-java-core/src/main/resources/META-INF/proguard/lithic-java-core.pro new file mode 100644 index 000000000..c8d9e044d --- /dev/null +++ b/lithic-java-core/src/main/resources/META-INF/proguard/lithic-java-core.pro @@ -0,0 +1,32 @@ +# Jackson uses reflection and depends heavily on runtime attributes. +-keepattributes Exceptions,InnerClasses,Signature,Deprecated,*Annotation* + +# Jackson uses Kotlin reflection utilities, which themselves use reflection to access things. +-keep class kotlin.reflect.** { *; } +-keep class kotlin.Metadata { *; } + +# Jackson uses reflection to access enum members (e.g. via `java.lang.Class.getEnumConstants()`). +-keepclassmembers class com.fasterxml.jackson.** extends java.lang.Enum { + ; + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +# Jackson uses reflection to access annotation members. +-keepclassmembers @interface com.fasterxml.jackson.annotation.** { + *; +} + +# Jackson uses reified type information to serialize and deserialize our classes (via `TypeReference`). +-keep class com.fasterxml.jackson.core.type.TypeReference { *; } +-keep class * extends com.fasterxml.jackson.core.type.TypeReference { *; } + +# Jackson uses reflection to access our class serializers and deserializers. +-keep @com.fasterxml.jackson.databind.annotation.JsonSerialize class com.lithic.api.** { *; } +-keep @com.fasterxml.jackson.databind.annotation.JsonDeserialize class com.lithic.api.** { *; } + +# Jackson uses reflection to serialize and deserialize our classes based on their constructors and annotated members. +-keepclassmembers class com.lithic.api.** { + (...); + @com.fasterxml.jackson.annotation.* *; +} \ No newline at end of file diff --git a/lithic-java-core/src/test/kotlin/com/lithic/api/core/http/RetryingHttpClientTest.kt b/lithic-java-core/src/test/kotlin/com/lithic/api/core/http/RetryingHttpClientTest.kt index 9745e4daf..a58f57c34 100644 --- a/lithic-java-core/src/test/kotlin/com/lithic/api/core/http/RetryingHttpClientTest.kt +++ b/lithic-java-core/src/test/kotlin/com/lithic/api/core/http/RetryingHttpClientTest.kt @@ -6,6 +6,7 @@ import com.github.tomakehurst.wiremock.junit5.WireMockTest import com.github.tomakehurst.wiremock.stubbing.Scenario import com.lithic.api.client.okhttp.OkHttpClient import com.lithic.api.core.RequestOptions +import com.lithic.api.errors.LithicRetryableException import java.io.InputStream import java.time.Duration import java.util.concurrent.CompletableFuture @@ -251,6 +252,82 @@ internal class RetryingHttpClientTest { assertNoResponseLeaks() } + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryableException(async: Boolean) { + stubFor(post(urlPathEqualTo("/something")).willReturn(ok())) + + var callCount = 0 + val failingHttpClient = + object : HttpClient { + override fun execute( + request: HttpRequest, + requestOptions: RequestOptions, + ): HttpResponse { + callCount++ + if (callCount == 1) { + throw LithicRetryableException("Simulated retryable failure") + } + return httpClient.execute(request, requestOptions) + } + + override fun executeAsync( + request: HttpRequest, + requestOptions: RequestOptions, + ): CompletableFuture { + callCount++ + if (callCount == 1) { + val future = CompletableFuture() + future.completeExceptionally( + LithicRetryableException("Simulated retryable failure") + ) + return future + } + return httpClient.executeAsync(request, requestOptions) + } + + override fun close() = httpClient.close() + } + + val retryingClient = + RetryingHttpClient.builder() + .httpClient(failingHttpClient) + .maxRetries(2) + .sleeper( + object : RetryingHttpClient.Sleeper { + + override fun sleep(duration: Duration) {} + + override fun sleepAsync(duration: Duration): CompletableFuture = + CompletableFuture.completedFuture(null) + } + ) + .build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + verify( + 1, + postRequestedFor(urlPathEqualTo("/something")) + .withHeader("x-stainless-retry-count", equalTo("1")), + ) + verify( + 0, + postRequestedFor(urlPathEqualTo("/something")) + .withHeader("x-stainless-retry-count", equalTo("0")), + ) + assertNoResponseLeaks() + } + private fun retryingHttpClientBuilder() = RetryingHttpClient.builder() .httpClient(httpClient) diff --git a/lithic-java-proguard-test/build.gradle.kts b/lithic-java-proguard-test/build.gradle.kts new file mode 100644 index 000000000..06512330c --- /dev/null +++ b/lithic-java-proguard-test/build.gradle.kts @@ -0,0 +1,103 @@ +plugins { + id("lithic.kotlin") + id("com.gradleup.shadow") version "8.3.8" +} + +buildscript { + repositories { + google() + } + + dependencies { + classpath("com.guardsquare:proguard-gradle:7.4.2") + classpath("com.android.tools:r8:8.3.37") + } +} + +dependencies { + testImplementation(project(":lithic-java")) + testImplementation(kotlin("test")) + testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3") + testImplementation("org.assertj:assertj-core:3.25.3") + testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") +} + +tasks.shadowJar { + from(sourceSets.test.get().output) + configurations = listOf(project.configurations.testRuntimeClasspath.get()) +} + +val proguardJarPath = "${layout.buildDirectory.get()}/libs/${project.name}-${project.version}-proguard.jar" +val proguardJar by tasks.registering(proguard.gradle.ProGuardTask::class) { + group = "verification" + dependsOn(tasks.shadowJar) + notCompatibleWithConfigurationCache("ProGuard") + + injars(tasks.shadowJar) + outjars(proguardJarPath) + printmapping("${layout.buildDirectory.get()}/proguard-mapping.txt") + + dontwarn() + + val javaHome = System.getProperty("java.home") + if (System.getProperty("java.version").startsWith("1.")) { + // Before Java 9, the runtime classes were packaged in a single jar file. + libraryjars("$javaHome/lib/rt.jar") + } else { + // As of Java 9, the runtime classes are packaged in modular jmod files. + libraryjars( + // Filters must be specified first, as a map. + mapOf("jarfilter" to "!**.jar", "filter" to "!module-info.class"), + "$javaHome/jmods/java.base.jmod" + ) + } + + configuration("./test.pro") + configuration("../lithic-java-core/src/main/resources/META-INF/proguard/lithic-java-core.pro") +} + +val testProGuard by tasks.registering(JavaExec::class) { + group = "verification" + dependsOn(proguardJar) + notCompatibleWithConfigurationCache("ProGuard") + + mainClass.set("com.lithic.api.proguard.ProGuardCompatibilityTest") + classpath = files(proguardJarPath) +} + +val r8JarPath = "${layout.buildDirectory.get()}/libs/${project.name}-${project.version}-r8.jar" +val r8Jar by tasks.registering(JavaExec::class) { + group = "verification" + dependsOn(tasks.shadowJar) + notCompatibleWithConfigurationCache("R8") + + mainClass.set("com.android.tools.r8.R8") + classpath = buildscript.configurations["classpath"] + + args = listOf( + "--release", + "--classfile", + "--output", r8JarPath, + "--lib", System.getProperty("java.home"), + "--pg-conf", "./test.pro", + "--pg-conf", "../lithic-java-core/src/main/resources/META-INF/proguard/lithic-java-core.pro", + "--pg-map-output", "${layout.buildDirectory.get()}/r8-mapping.txt", + tasks.shadowJar.get().archiveFile.get().asFile.absolutePath, + ) +} + +val testR8 by tasks.registering(JavaExec::class) { + group = "verification" + dependsOn(r8Jar) + notCompatibleWithConfigurationCache("R8") + + mainClass.set("com.lithic.api.proguard.ProGuardCompatibilityTest") + classpath = files(r8JarPath) +} + +tasks.test { + dependsOn(testProGuard) + dependsOn(testR8) + // We defer to the tests run via the ProGuard JAR. + enabled = false +} diff --git a/lithic-java-proguard-test/src/test/kotlin/com/lithic/api/proguard/ProGuardCompatibilityTest.kt b/lithic-java-proguard-test/src/test/kotlin/com/lithic/api/proguard/ProGuardCompatibilityTest.kt new file mode 100644 index 000000000..fed75087f --- /dev/null +++ b/lithic-java-proguard-test/src/test/kotlin/com/lithic/api/proguard/ProGuardCompatibilityTest.kt @@ -0,0 +1,299 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.lithic.api.proguard + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.lithic.api.client.okhttp.LithicOkHttpClient +import com.lithic.api.core.jsonMapper +import com.lithic.api.models.AccountHolderUpdateResponse +import com.lithic.api.models.CardSpendLimits +import com.lithic.api.models.KybBusinessEntity +import com.lithic.api.models.RequiredDocument +import com.lithic.api.models.SpendLimitDuration +import java.time.OffsetDateTime +import kotlin.reflect.full.memberFunctions +import kotlin.reflect.jvm.javaMethod +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ProGuardCompatibilityTest { + + companion object { + + @JvmStatic + fun main(args: Array) { + // To debug that we're using the right JAR. + val jarPath = this::class.java.getProtectionDomain().codeSource.location + println("JAR being used: $jarPath") + + // We have to manually run the test methods instead of using the JUnit runner because it + // seems impossible to get working with R8. + val test = ProGuardCompatibilityTest() + test::class + .memberFunctions + .asSequence() + .filter { function -> + function.javaMethod?.isAnnotationPresent(Test::class.java) == true + } + .forEach { it.call(test) } + } + } + + @Test + fun proguardRules() { + val rulesFile = + javaClass.classLoader.getResourceAsStream("META-INF/proguard/lithic-java-core.pro") + + assertThat(rulesFile).isNotNull() + } + + @Test + fun client() { + val client = LithicOkHttpClient.builder().apiKey("My Lithic API Key").build() + + assertThat(client).isNotNull() + assertThat(client.accounts()).isNotNull() + assertThat(client.accountHolders()).isNotNull() + assertThat(client.authRules()).isNotNull() + assertThat(client.authStreamEnrollment()).isNotNull() + assertThat(client.tokenizationDecisioning()).isNotNull() + assertThat(client.tokenizations()).isNotNull() + assertThat(client.cards()).isNotNull() + assertThat(client.balances()).isNotNull() + assertThat(client.aggregateBalances()).isNotNull() + assertThat(client.disputes()).isNotNull() + assertThat(client.events()).isNotNull() + assertThat(client.transfers()).isNotNull() + assertThat(client.financialAccounts()).isNotNull() + assertThat(client.transactions()).isNotNull() + assertThat(client.responderEndpoints()).isNotNull() + assertThat(client.externalBankAccounts()).isNotNull() + assertThat(client.payments()).isNotNull() + assertThat(client.threeDS()).isNotNull() + assertThat(client.reports()).isNotNull() + assertThat(client.cardPrograms()).isNotNull() + assertThat(client.digitalCardArt()).isNotNull() + assertThat(client.bookTransfers()).isNotNull() + assertThat(client.creditProducts()).isNotNull() + assertThat(client.externalPayments()).isNotNull() + assertThat(client.managementOperations()).isNotNull() + assertThat(client.fundingEvents()).isNotNull() + assertThat(client.fraud()).isNotNull() + assertThat(client.networkPrograms()).isNotNull() + } + + @Test + fun cardSpendLimitsRoundtrip() { + val jsonMapper = jsonMapper() + val cardSpendLimits = + CardSpendLimits.builder() + .availableSpendLimit( + CardSpendLimits.AvailableSpendLimit.builder() + .annually(200000L) + .forever(300000L) + .monthly(200000L) + .build() + ) + .spendLimit( + CardSpendLimits.SpendLimit.builder() + .annually(500000L) + .forever(500000L) + .monthly(500000L) + .build() + ) + .spendVelocity( + CardSpendLimits.SpendVelocity.builder() + .annually(300000L) + .forever(200000L) + .monthly(300000L) + .build() + ) + .build() + + val roundtrippedCardSpendLimits = + jsonMapper.readValue( + jsonMapper.writeValueAsString(cardSpendLimits), + jacksonTypeRef(), + ) + + assertThat(roundtrippedCardSpendLimits).isEqualTo(cardSpendLimits) + } + + @Test + fun accountHolderUpdateResponseRoundtrip() { + val jsonMapper = jsonMapper() + val accountHolderUpdateResponse = + AccountHolderUpdateResponse.ofKybKycPatch( + AccountHolderUpdateResponse.KybKycPatchResponse.builder() + .token("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .accountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .addBeneficialOwnerEntity( + KybBusinessEntity.builder() + .address( + KybBusinessEntity.Address.builder() + .address1("123 Old Forest Way") + .city("Omaha") + .country("USA") + .postalCode("68022") + .state("NE") + .address2("address2") + .build() + ) + .governmentId("114-123-1513") + .legalBusinessName("Acme, Inc.") + .addPhoneNumber("+15555555555") + .dbaBusinessName("dba_business_name") + .parentCompany("parent_company") + .build() + ) + .addBeneficialOwnerIndividual( + AccountHolderUpdateResponse.KybKycPatchResponse.Individual.builder() + .address( + AccountHolderUpdateResponse.KybKycPatchResponse.Individual.Address + .builder() + .address1("123 Old Forest Way") + .city("Omaha") + .country("USA") + .postalCode("68022") + .state("NE") + .address2("address2") + .build() + ) + .dob("1991-03-08 08:00:00") + .email("tom@middle-earth.com") + .firstName("Tom") + .governmentId("111-23-1412") + .lastName("Bombadil") + .phoneNumber("+15555555555") + .build() + ) + .businessAccountToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .businessEntity( + KybBusinessEntity.builder() + .address( + KybBusinessEntity.Address.builder() + .address1("123 Old Forest Way") + .city("Omaha") + .country("USA") + .postalCode("68022") + .state("NE") + .address2("address2") + .build() + ) + .governmentId("114-123-1513") + .legalBusinessName("Acme, Inc.") + .addPhoneNumber("+15555555555") + .dbaBusinessName("dba_business_name") + .parentCompany("parent_company") + .build() + ) + .controlPerson( + AccountHolderUpdateResponse.KybKycPatchResponse.Individual.builder() + .address( + AccountHolderUpdateResponse.KybKycPatchResponse.Individual.Address + .builder() + .address1("123 Old Forest Way") + .city("Omaha") + .country("USA") + .postalCode("68022") + .state("NE") + .address2("address2") + .build() + ) + .dob("1991-03-08 08:00:00") + .email("tom@middle-earth.com") + .firstName("Tom") + .governmentId("111-23-1412") + .lastName("Bombadil") + .phoneNumber("+15555555555") + .build() + ) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .email("email") + .exemptionType( + AccountHolderUpdateResponse.KybKycPatchResponse.ExemptionType + .AUTHORIZED_USER + ) + .externalId("external_id") + .individual( + AccountHolderUpdateResponse.KybKycPatchResponse.Individual.builder() + .address( + AccountHolderUpdateResponse.KybKycPatchResponse.Individual.Address + .builder() + .address1("123 Old Forest Way") + .city("Omaha") + .country("USA") + .postalCode("68022") + .state("NE") + .address2("address2") + .build() + ) + .dob("1991-03-08 08:00:00") + .email("tom@middle-earth.com") + .firstName("Tom") + .governmentId("111-23-1412") + .lastName("Bombadil") + .phoneNumber("+15555555555") + .build() + ) + .natureOfBusiness("nature_of_business") + .phoneNumber("phone_number") + .addRequiredDocument( + RequiredDocument.builder() + .entityToken("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + .addStatusReason("string") + .addValidDocument("string") + .build() + ) + .status(AccountHolderUpdateResponse.KybKycPatchResponse.Status.ACCEPTED) + .addStatusReason( + AccountHolderUpdateResponse.KybKycPatchResponse.StatusReasons + .ADDRESS_VERIFICATION_FAILURE + ) + .userType(AccountHolderUpdateResponse.KybKycPatchResponse.UserType.BUSINESS) + .verificationApplication( + AccountHolderUpdateResponse.KybKycPatchResponse.VerificationApplication + .builder() + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .status( + AccountHolderUpdateResponse.KybKycPatchResponse + .VerificationApplication + .Status + .ACCEPTED + ) + .addStatusReason( + AccountHolderUpdateResponse.KybKycPatchResponse + .VerificationApplication + .StatusReasons + .ADDRESS_VERIFICATION_FAILURE + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .build() + ) + .websiteUrl("website_url") + .build() + ) + + val roundtrippedAccountHolderUpdateResponse = + jsonMapper.readValue( + jsonMapper.writeValueAsString(accountHolderUpdateResponse), + jacksonTypeRef(), + ) + + assertThat(roundtrippedAccountHolderUpdateResponse).isEqualTo(accountHolderUpdateResponse) + } + + @Test + fun spendLimitDurationRoundtrip() { + val jsonMapper = jsonMapper() + val spendLimitDuration = SpendLimitDuration.ANNUALLY + + val roundtrippedSpendLimitDuration = + jsonMapper.readValue( + jsonMapper.writeValueAsString(spendLimitDuration), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSpendLimitDuration).isEqualTo(spendLimitDuration) + } +} diff --git a/lithic-java-proguard-test/test.pro b/lithic-java-proguard-test/test.pro new file mode 100644 index 000000000..2405d4e26 --- /dev/null +++ b/lithic-java-proguard-test/test.pro @@ -0,0 +1,8 @@ +# Specify the entrypoint where ProGuard starts to determine what's reachable. +-keep class com.lithic.api.proguard.** { *; } + +# For the testing framework. +-keep class org.junit.** { *; } + +# Many warnings don't apply for our testing purposes. +-dontwarn \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 906a2f632..fdce5b627 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,4 +3,5 @@ rootProject.name = "lithic-java-root" include("lithic-java") include("lithic-java-client-okhttp") include("lithic-java-core") +include("lithic-java-proguard-test") include("lithic-java-example")