diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt index 455a783..2f07240 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt @@ -37,6 +37,8 @@ import net.thauvin.erik.bitly.Utils.removeHttp import net.thauvin.erik.bitly.Utils.toEndPoint import net.thauvin.erik.bitly.config.CreateConfig import net.thauvin.erik.bitly.config.UpdateConfig +import net.thauvin.erik.bitly.deeplinks.CreateDeeplinks +import net.thauvin.erik.bitly.deeplinks.UpdateDeeplinks import org.json.JSONException import org.json.JSONObject import java.net.URLEncoder @@ -48,6 +50,7 @@ import java.util.logging.Level * * See the [Bitly API](https://dev.bitly.com/api-reference) for more information. */ +@Suppress("LocalVariableName") open class Bitlinks(private val accessToken: String) { /** * The last API call response. @@ -110,7 +113,7 @@ open class Bitlinks(private val accessToken: String) { config.group_guid, config.title, config.tags, - config.deepLinks, + config.deeplinks, config.toJson ) } @@ -127,13 +130,14 @@ open class Bitlinks(private val accessToken: String) { * @return The shortened URL or an empty string on error. */ @Synchronized + @JvmOverloads fun create( long_url: String, domain: String = Constants.EMPTY, group_guid: String = Constants.EMPTY, title: String = Constants.EMPTY, tags: Array = emptyArray(), - deeplinks: Array> = emptyArray(), + deeplinks: CreateDeeplinks = CreateDeeplinks(), toJson: Boolean = false ): String { var link = if (toJson) Constants.EMPTY_JSON else Constants.EMPTY @@ -146,7 +150,7 @@ open class Bitlinks(private val accessToken: String) { if (group_guid.isNotBlank()) put("group_guid", group_guid) if (title.isNotBlank()) put("title", title) if (tags.isNotEmpty()) put("tags", tags) - if (deeplinks.isNotEmpty()) put("deeplinks", deeplinks) + if (deeplinks.isNotEmpty()) put("deeplinks", arrayOf(deeplinks.links())) } ) link = parseJsonResponse(lastCallResponse, "link", link, toJson) @@ -258,7 +262,7 @@ open class Bitlinks(private val accessToken: String) { config.title, config.archived, config.tags, - config.deepLinks, + config.deeplinks, config.toJson ) } @@ -273,12 +277,13 @@ open class Bitlinks(private val accessToken: String) { * @return [Constants.TRUE] if the update was successful, [Constants.FALSE] otherwise. */ @Synchronized + @JvmOverloads fun update( bitlink: String, title: String = Constants.EMPTY, archived: Boolean = false, tags: Array = emptyArray(), - deeplinks: Array> = emptyArray(), + deeplinks: UpdateDeeplinks = UpdateDeeplinks(), toJson: Boolean = false ): String { var result = if (toJson) Constants.EMPTY_JSON else Constants.FALSE @@ -288,7 +293,7 @@ open class Bitlinks(private val accessToken: String) { if (title.isNotBlank()) put("title", title) if (archived) put("archived", true) if (tags.isNotEmpty()) put("tags", tags) - if (deeplinks.isNotEmpty()) put("deeplinks", deeplinks) + if (deeplinks.isNotEmpty()) put("deeplinks", arrayOf(deeplinks.links())) }, Methods.PATCH ) diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Bitly.kt b/src/main/kotlin/net/thauvin/erik/bitly/Bitly.kt index 68c8c70..49da858 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/Bitly.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/Bitly.kt @@ -43,9 +43,10 @@ import java.util.* * @constructor Creates new instance. */ open class Bitly() { - /** The API access token. + /** + * The API access token. * - * See [Generic Access Token](https://bitly.is/accesstoken) or + * See [Generic Access Token](https://app.bitly.com/settings/api/) or * [Authentication](https://dev.bitly.com/docs/getting-started/authentication). **/ var accessToken: String = System.getenv(Constants.ENV_ACCESS_TOKEN) @@ -105,7 +106,7 @@ open class Bitly() { /** * Executes an API call. * - * @param endPoint The REST endpoint path. (eg. `shorten`, `expand`, etc.) + * @param endPoint The REST endpoint path. (e.g. `shorten`, `expand`, etc.) * @param params The request parameters key/value map. * @param method The submission [Method][Methods]. * @return A [CallResponse] object. diff --git a/src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt b/src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt index ef4aa97..403e9dc 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt @@ -32,26 +32,29 @@ package net.thauvin.erik.bitly.config import net.thauvin.erik.bitly.Constants +import net.thauvin.erik.bitly.deeplinks.CreateDeeplinks /** - * Provides a builder to create a Bitlink. + * Provides a configuration to create a [Bitlink][net.thauvin.erik.bitly.Bitlinks] + * + * See the [Bit.ly API](https://dev.bitly.com/api-reference#createFullBitlink) for more information. */ -class CreateConfig private constructor(builder: Builder) { - val domain: String - val group_guid: String - val title: String - val tags: Array - val deepLinks: Array> - val long_url: String - val toJson: Boolean - - init { +@Suppress("LocalVariableName", "PropertyName") +class CreateConfig @JvmOverloads constructor( + var long_url: String, + var domain: String = Constants.EMPTY, + var group_guid: String = Constants.EMPTY, + var title: String = Constants.EMPTY, + var tags: Array = emptyArray(), + var deeplinks: CreateDeeplinks = CreateDeeplinks(), + var toJson: Boolean = false +) { + constructor(builder: Builder) : this(builder.long_url) { domain = builder.domain group_guid = builder.group_guid title = builder.title tags = builder.tags - deepLinks = builder.deeplinks - long_url = builder.long_url + deeplinks = builder.deeplinks toJson = builder.toJson } @@ -65,7 +68,7 @@ class CreateConfig private constructor(builder: Builder) { var group_guid: String = Constants.EMPTY var title: String = Constants.EMPTY var tags: Array = emptyArray() - var deeplinks: Array> = emptyArray() + var deeplinks: CreateDeeplinks = CreateDeeplinks() var toJson: Boolean = false /** @@ -82,7 +85,7 @@ class CreateConfig private constructor(builder: Builder) { fun tags(tags: Array): Builder = apply { this.tags = tags } - fun deeplinks(deeplinks: Array>): Builder = apply { this.deeplinks = deeplinks } + fun deeplinks(deeplinks: CreateDeeplinks): Builder = apply { this.deeplinks = deeplinks } /** * The long URL. diff --git a/src/main/kotlin/net/thauvin/erik/bitly/config/UpdateConfig.kt b/src/main/kotlin/net/thauvin/erik/bitly/config/UpdateConfig.kt index 13ec5f0..7f218e6 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/config/UpdateConfig.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/config/UpdateConfig.kt @@ -32,24 +32,26 @@ package net.thauvin.erik.bitly.config import net.thauvin.erik.bitly.Constants +import net.thauvin.erik.bitly.deeplinks.UpdateDeeplinks /** - * Provides a builder to update a Bitlink. + * Provides a configuration to update a [Bitlink][net.thauvin.erik.bitly.Bitlinks]. + * + * See the [Bit.ly API](https://dev.bitly.com/api-reference#updateBitlink) for more information. */ -class UpdateConfig private constructor(builder: Builder) { - val bitlink: String - val title: String - val archived: Boolean - val tags: Array - val deepLinks: Array> - val toJson: Boolean - - init { - bitlink = builder.bitlink +class UpdateConfig @JvmOverloads constructor( + var bitlink: String, + var title: String = Constants.EMPTY, + var archived: Boolean = false, + var tags: Array = emptyArray(), + var deeplinks: UpdateDeeplinks = UpdateDeeplinks(), + var toJson: Boolean = false +) { + constructor(builder: Builder) : this(builder.bitlink) { title = builder.title archived = builder.archived tags = builder.tags - deepLinks = builder.deeplinks + deeplinks = builder.deeplinks toJson = builder.toJson } @@ -62,7 +64,7 @@ class UpdateConfig private constructor(builder: Builder) { var title: String = Constants.EMPTY var archived: Boolean = false var tags: Array = emptyArray() - var deeplinks: Array> = emptyArray() + var deeplinks: UpdateDeeplinks = UpdateDeeplinks() var toJson: Boolean = false /** @@ -73,7 +75,7 @@ class UpdateConfig private constructor(builder: Builder) { fun title(title: String): Builder = apply { this.title = title } fun archived(archived: Boolean): Builder = apply { this.archived = archived } fun tags(tags: Array): Builder = apply { this.tags = tags } - fun deepLinks(deepLinks: Array>): Builder = apply { this.deeplinks = deepLinks } + fun deeplinks(deeplinks: UpdateDeeplinks): Builder = apply { this.deeplinks = deeplinks } /** * Returns the full JSON response if `true`. diff --git a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt index f60e4fa..fc916ca 100644 --- a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt +++ b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt @@ -40,6 +40,10 @@ import net.thauvin.erik.bitly.Utils.removeHttp import net.thauvin.erik.bitly.Utils.toEndPoint import net.thauvin.erik.bitly.config.CreateConfig import net.thauvin.erik.bitly.config.UpdateConfig +import net.thauvin.erik.bitly.deeplinks.CreateDeeplinks +import net.thauvin.erik.bitly.deeplinks.InstallType +import net.thauvin.erik.bitly.deeplinks.Os +import net.thauvin.erik.bitly.deeplinks.UpdateDeeplinks import org.json.JSONObject import org.junit.jupiter.api.BeforeAll import java.io.File @@ -216,6 +220,24 @@ class BitlyTest { ) } + @Test + fun `create bitlink with deeplinks`() { + val bl = bitly.bitlinks() + val dl = CreateDeeplinks().apply { + install_type(InstallType.NO_INSTALL) + app_uri_path("/store?id=123456") + install_url("https://play.google.com/store/apps/details?id=com.bitly.app&hl=en_US") + } + + val config = CreateConfig.Builder(longUrl) + .domain("bit.ly") + .deeplinks(dl) + .build() + + assertThat(bl.create(config)).isEqualTo(Constants.EMPTY) + assertThat(bl.lastCallResponse.isUpgradeRequired).isTrue() + } + @Test fun `shorten with invalid domain`() { val bl = bitly.bitlinks() @@ -268,6 +290,21 @@ class BitlyTest { assertThat(bl.update(config), "update(tags)").contains("\"tags\":[]") } + @Test + fun `update bitlink with deeplinks`() { + val bl = bitly.bitlinks() + val dl = UpdateDeeplinks().apply { + os(Os.ANDROID) + brand_guid("Ba1bc23dE4F") + } + val config = UpdateConfig.Builder(shortUrl) + .deeplinks(dl) + .build() + + assertThat(bl.update(config)).isEqualTo(Constants.FALSE) + assertThat(bl.lastCallResponse.isUpgradeRequired).isTrue() + } + @Test fun `validate URL`() { assertTrue("https://www.example.com".isValidUrl(), "valid url") diff --git a/src/test/kotlin/net/thauvin/erik/bitly/config/ConfigTest.kt b/src/test/kotlin/net/thauvin/erik/bitly/config/ConfigTest.kt new file mode 100644 index 0000000..db235ab --- /dev/null +++ b/src/test/kotlin/net/thauvin/erik/bitly/config/ConfigTest.kt @@ -0,0 +1,106 @@ +/* + * ConfigTest.kt + * + * Copyright 2020-2024 Erik C. Thauvin (erik@thauvin.net) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of this project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.thauvin.erik.bitly.config + +import assertk.assertThat +import assertk.assertions.isEqualTo +import net.thauvin.erik.bitly.deeplinks.CreateDeeplinks +import net.thauvin.erik.bitly.deeplinks.InstallType +import net.thauvin.erik.bitly.deeplinks.Os +import net.thauvin.erik.bitly.deeplinks.UpdateDeeplinks +import org.json.JSONObject +import kotlin.test.Test + +class ConfigTest { + @Test + fun `create config test`() { + val deeplinks = CreateDeeplinks().apply { + app_id("app_id") + install_type(InstallType.AUTO_INSTALL) + } + + val config = CreateConfig( + "long_url", + "domain", + "group_guid", + "title", + arrayOf("tag", "tag2"), + deeplinks, + ) + + val map = mapOf( + "long_url" to config.long_url, + "domain" to config.domain, + "group_guid" to config.group_guid, + "title" to config.title, + "tags" to config.tags, + "deeplinks" to arrayOf(deeplinks.links()) + ) + + assertThat(JSONObject(map).toString()).isEqualTo( + """ + {"group_guid":"group_guid","long_url":"long_url","title":"title","deeplinks":[{"app_id":"app_id","install_type":"auto_install"}],"domain":"domain","tags":["tag","tag2"]} + """.trimIndent() + ) + } + + @Test + fun `update config test`() { + val deeplinks = UpdateDeeplinks().apply { + os(Os.IOS) + install_type(InstallType.PROMOTE_INSTALL) + app_guid("app_guid") + } + + val config = UpdateConfig( + "blink", + "title", + true, + arrayOf("tag", "tag2"), + deeplinks + ) + val map = mapOf( + "bitlink" to config.bitlink, + "title" to config.title, + "archived" to config.archived, + "tags" to config.tags, + "deeplinks" to arrayOf(deeplinks.links()) + ) + + assertThat(JSONObject(map).toString()).isEqualTo( + """ + {"archived":true,"bitlink":"blink","title":"title","deeplinks":[{"os":"ios","app_guid":"app_guid","install_type":"promote_install"}],"tags":["tag","tag2"]} + """.trimIndent() + ) + + } +}