diff --git a/.github/workflows/bld.yml b/.github/workflows/bld.yml index dd17a9c..92c82da 100644 --- a/.github/workflows/bld.yml +++ b/.github/workflows/bld.yml @@ -1,20 +1,21 @@ name: bld-ci -on: [ push, pull_request, workflow_dispatch ] +on: [push, pull_request, workflow_dispatch] env: BITLY_ACCESS_TOKEN: ${{ secrets.BITLY_ACCESS_TOKEN }} COVERAGE_JDK: "21" - KOTLIN_VERSION: "2.2.0" + COVERAGE_KOTLIN: "2.0.0" + KOTLIN_HOME: /usr/share/kotlinc jobs: build-bld-project: + runs-on: ubuntu-latest + strategy: matrix: - java-version: [ 17, 21, 24 ] - os: [ ubuntu-latest, windows-latest, macos-latest ] - - runs-on: ${{ matrix.os }} + java-version: [17, 21, 23] + kotlin-version: [1.9.25, 2.1.10] steps: - name: Checkout source repository @@ -22,36 +23,12 @@ jobs: with: fetch-depth: 0 - - name: Set up JDK ${{ matrix.java-version }} + - name: Set up JDK ${{ matrix.java-version }} with Kotlin ${{ matrix.kotlin-version }} uses: actions/setup-java@v4 with: distribution: "zulu" java-version: ${{ matrix.java-version }} - - name: Setup Kotlin ${{ env.KOTLIN_VERSION }} - uses: fwilhe2/setup-kotlin@main - with: - version: ${{ env.KOTLIN_VERSION }} - - - name: Download dependencies [bld example] - working-directory: examples/bld - run: ./bld download - - - name: Compile and run examples [bld example] - working-directory: examples/bld - run: | - ./bld compile - ./bld run --args='https://erik.thauvin.net/ https://bit.ly/2PsNMAA' - ./bld run-retrieve - ./bld run-java --args='https://erik.thauvin.net/ https://bit.ly/2PsNMAA' - - - name: Run examples [gradle example] - working-directory: examples/gradle - run: | - ./gradlew run --args='https://erik.thauvin.net/ https://bit.ly/2PsNMAA' - ./gradlew runRetrieve - ./gradlew runJava --args='https://erik.thauvin.net/ https://bit.ly/2PsNMAA' - - name: Download dependencies run: ./bld download @@ -62,12 +39,12 @@ jobs: run: ./bld jacoco - name: Remove pom.xml - if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.os == 'ubuntu-latest' + if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.kotlin-version == env.COVERAGE_KOTLIN run: rm -rf pom.xml - name: SonarCloud Scan - uses: SonarSource/sonarqube-scan-action@v5.2.0 - if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.os == 'ubuntu-latest' + uses: sonarsource/sonarcloud-github-action@master + if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.kotlin-version == env.COVERAGE_KOTLIN env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 94f28ea..1e01b48 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -4,6 +4,5 @@ - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 9fc28bd..0000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/bld.xml b/.idea/libraries/bld.xml index 2b5bd87..c11c5e9 100644 --- a/.idea/libraries/bld.xml +++ b/.idea/libraries/bld.xml @@ -2,12 +2,12 @@ - + - + diff --git a/README.md b/README.md index 52c5f8b..00001fc 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ [![License (3-Clause BSD)](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg?style=flat-square)](https://opensource.org/licenses/BSD-3-Clause) -[![Kotlin](https://img.shields.io/badge/kotlin-2.2.0-7f52ff)](https://kotlinlang.org/) +[![Kotlin](https://img.shields.io/badge/kotlin-2.1.10-7f52ff)](https://kotlinlang.org/) [![bld](https://img.shields.io/badge/2.2.1-FA9052?label=bld&labelColor=2392FF)](https://rife2.com/bld) [![Release](https://img.shields.io/github/release/ethauvin/bitly-shorten.svg)](https://github.com/ethauvin/bitly-shorten/releases/latest) [![Maven Central](https://img.shields.io/maven-central/v/net.thauvin.erik/bitly-shorten.svg?color=blue)](https://central.sonatype.com/artifact/net.thauvin.erik/bitly-shorten) -[![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fcentral.sonatype.com%2Frepository%2Fmaven-snapshots%2Fnet%2Fthauvin%2Ferik%2Fbitly-shorten%2Fmaven-metadata.xml&label=snapshot)](https://github.com/ethauvin/bitly-shorten/packages/2260734/versions) +[![Nexus Snapshot](https://img.shields.io/nexus/s/net.thauvin.erik/bitly-shorten?label=snapshot&server=https%3A%2F%2Foss.sonatype.org%2F)](https://oss.sonatype.org/content/repositories/snapshots/net/thauvin/erik/bitly-shorten/) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ethauvin_bitly-shorten&metric=alert_status)](https://sonarcloud.io/dashboard?id=ethauvin_bitly-shorten) @@ -60,7 +60,7 @@ BITLY_ACCESS_TOKEN=abc123def456ghi789jkl0 To use with [bld](https://rife2.com/bld), include the following dependency in your [build](https://github.com/ethauvin/bitly-shorten/blob/master/examples/bld/src/bld/java/com/example/ExampleBuild.java) file: ```java -repositories = List.of(MAVEN_CENTRAL, CENTRAL_SNAPSHOTS); +repositories = List.of(MAVEN_CENTRAL, SONATYPE_SNAPSHOTS_LEGACY); scope(compile) .include(dependency("net.thauvin.erik:bitly-shorten:2.0.0")); @@ -74,11 +74,8 @@ To use with [Gradle](https://gradle.org/), include the following dependency in y ```gradle repositories { - maven { - name = 'Central Portal Snapshots' - url = 'https://central.sonatype.com/repository/maven-snapshots/' - } mavenCentral() + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } // only needed for SNAPSHOT } dependencies { diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml new file mode 100644 index 0000000..ace99d2 --- /dev/null +++ b/bitbucket-pipelines.yml @@ -0,0 +1,20 @@ +image: ubuntu:latest + +pipelines: + default: + - step: + name: Test with bld + script: + # Install latest Java & Kotlin via SDKMAN! + - apt-get update -qq && apt-get install -y curl zip + - curl -s "https://get.sdkman.io" | bash + - echo sdkman_auto_answer=true > $HOME/.sdkman/etc/config + - echo sdkman_auto_selfupdate=true >> $HOME/.sdkman/etc/config + - source "$HOME/.sdkman/bin/sdkman-init.sh" + - sdk install java + - sdk install kotlin + - source "$HOME/.sdkman/bin/sdkman-init.sh" + # Download, compile and test with bld + - ./bld download + - ./bld compile + - ./bld test diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 717fd7b..dc8efc2 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -1,6 +1,6 @@ - + ConstructorParameterNaming:CreateConfig.kt$CreateConfig.Builder$var long_url: String FunctionParameterNaming:Bitlinks.kt$Bitlinks$bitlink_id: String @@ -19,8 +19,8 @@ FunctionParameterNaming:UpdateDeeplinks.kt$UpdateDeeplinks$brand_guid: String FunctionParameterNaming:UpdateDeeplinks.kt$UpdateDeeplinks$install_type: InstallType FunctionParameterNaming:UpdateDeeplinks.kt$UpdateDeeplinks$install_url: String - LongParameterList:Bitlinks.kt$Bitlinks$( bitlink: String, title: String = Constants.EMPTY, archived: Boolean = false, tags: List<String> = emptyList(), deeplinks: UpdateDeeplinks = UpdateDeeplinks(), toJson: Boolean = false ) - LongParameterList:Bitlinks.kt$Bitlinks$( long_url: String, domain: String = Constants.EMPTY, group_guid: String = Constants.EMPTY, title: String = Constants.EMPTY, tags: List<String> = emptyList(), deeplinks: CreateDeeplinks = CreateDeeplinks(), toJson: Boolean = false ) + LongParameterList:Bitlinks.kt$Bitlinks$( bitlink: String, title: String = Constants.EMPTY, archived: Boolean = false, tags: Array<String> = emptyArray(), deeplinks: UpdateDeeplinks = UpdateDeeplinks(), toJson: Boolean = false ) + LongParameterList:Bitlinks.kt$Bitlinks$( long_url: String, domain: String = Constants.EMPTY, group_guid: String = Constants.EMPTY, title: String = Constants.EMPTY, tags: Array<String> = emptyArray(), deeplinks: CreateDeeplinks = CreateDeeplinks(), toJson: Boolean = false ) MagicNumber:CallResponse.kt$CallResponse$200 MagicNumber:CallResponse.kt$CallResponse$201 MagicNumber:CallResponse.kt$CallResponse$299 @@ -42,6 +42,7 @@ VariableNaming:CreateConfig.kt$CreateConfig$val group_guid = builder.group_guid VariableNaming:CreateConfig.kt$CreateConfig$val long_url = builder.long_url VariableNaming:CreateConfig.kt$CreateConfig.Builder$var group_guid: String = Constants.EMPTY - WildcardImport:BitlinksTests.kt$import assertk.assertions.* + WildcardImport:BitlyTest.kt$import assertk.assertions.* + WildcardImport:BitlyTest.kt$import kotlin.test.* diff --git a/examples/bld/.idea/bld.xml b/examples/bld/.idea/bld.xml deleted file mode 100644 index 6600cee..0000000 --- a/examples/bld/.idea/bld.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/examples/bld/lib/bld/bld-wrapper.properties b/examples/bld/lib/bld/bld-wrapper.properties index ec89e30..1ffe3ff 100644 --- a/examples/bld/lib/bld/bld-wrapper.properties +++ b/examples/bld/lib/bld/bld-wrapper.properties @@ -1,7 +1,7 @@ bld.downloadExtensionJavadoc=false bld.downloadExtensionSources=true bld.downloadLocation= -bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.1.0 +bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.0.4 bld.repositories=MAVEN_LOCAL,MAVEN_CENTRAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES bld.sourceDirectories= bld.version=2.2.1 diff --git a/examples/bld/src/bld/java/com/example/ExampleBuild.java b/examples/bld/src/bld/java/com/example/ExampleBuild.java index 6706974..b23667e 100644 --- a/examples/bld/src/bld/java/com/example/ExampleBuild.java +++ b/examples/bld/src/bld/java/com/example/ExampleBuild.java @@ -25,7 +25,7 @@ public class ExampleBuild extends BaseProject { scope(compile) .include(dependency("net.thauvin.erik:bitly-shorten:2.0.0")) - .include(dependency("org.json:json:20250517")); + .include(dependency("org.json:json:20250107")); } public static void main(String[] args) { diff --git a/examples/gradle/.idea/kotlinc.xml b/examples/gradle/.idea/kotlinc.xml index 70661d3..6d0ee1c 100644 --- a/examples/gradle/.idea/kotlinc.xml +++ b/examples/gradle/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - - + \ No newline at end of file diff --git a/examples/gradle/build.gradle.kts b/examples/gradle/build.gradle.kts index f153e96..e44fb22 100644 --- a/examples/gradle/build.gradle.kts +++ b/examples/gradle/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("application") id("com.github.ben-manes.versions") version "0.51.0" - kotlin("jvm") version "2.2.0" + kotlin("jvm") version "2.1.10" } repositories { diff --git a/examples/gradle/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/gradle/wrapper/gradle-wrapper.jar index 1b33c55..9bbc975 100644 Binary files a/examples/gradle/gradle/wrapper/gradle-wrapper.jar and b/examples/gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/gradle/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/gradle/wrapper/gradle-wrapper.properties index ff23a68..37f853b 100644 --- a/examples/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/examples/gradle/gradlew b/examples/gradle/gradlew index 23d15a9..faf9300 100755 --- a/examples/gradle/gradlew +++ b/examples/gradle/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + org.gradle.wrapper.GradleWrapperMain \ "$@" # Stop when "xargs" is not available. diff --git a/examples/gradle/gradlew.bat b/examples/gradle/gradlew.bat index db3a6ac..9d21a21 100644 --- a/examples/gradle/gradlew.bat +++ b/examples/gradle/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/lib/bld/bld-wrapper.jar b/lib/bld/bld-wrapper.jar index da7265b..9c4b27e 100644 Binary files a/lib/bld/bld-wrapper.jar and b/lib/bld/bld-wrapper.jar differ diff --git a/lib/bld/bld-wrapper.properties b/lib/bld/bld-wrapper.properties index 5ac65f0..154abb0 100644 --- a/lib/bld/bld-wrapper.properties +++ b/lib/bld/bld-wrapper.properties @@ -1,11 +1,10 @@ bld.downloadExtensionJavadoc=false bld.downloadExtensionSources=true bld.downloadLocation= -bld.extension-detekt=com.uwyn.rife2:bld-detekt:0.9.10-SNAPSHOT -bld.extension-dokka=com.uwyn.rife2:bld-dokka:1.0.4-SNAPSHOT -bld.extension-exec=com.uwyn.rife2:bld-exec:1.0.5 -bld.extension-jacoco=com.uwyn.rife2:bld-jacoco-report:0.9.11-SNAPSHOT -bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.1.0 -bld.repositories=CENTRAL_SNAPSHOTS,MAVEN_LOCAL,MAVEN_CENTRAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES +bld.extension-detekt=com.uwyn.rife2:bld-detekt:0.9.9 +bld.extension-dokka=com.uwyn.rife2:bld-dokka:1.0.3 +bld.extension-jacoco=com.uwyn.rife2:bld-jacoco-report:0.9.9 +bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.0.4 +bld.repositories=MAVEN_LOCAL,MAVEN_CENTRAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES bld.sourceDirectories= -bld.version=2.2.2-SNAPSHOT +bld.version=2.2.1 diff --git a/pom.xml b/pom.xml index a05dd59..7dfc770 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 net.thauvin.erik bitly-shorten - 2.0.1-SNAPSHOT + 2.0.0 bitly-shorten A simple implementation of the Bitly link shortening API v4 https://github.com/ethauvin/bitly-shorten @@ -18,37 +18,37 @@ org.jetbrains.kotlin kotlin-stdlib - 2.2.0 + 2.1.10 compile org.jetbrains.kotlin kotlin-stdlib-common - 2.2.0 + 2.1.10 compile org.jetbrains.kotlin kotlin-stdlib-jdk8 - 2.2.0 + 2.1.10 compile com.squareup.okhttp3 - okhttp-jvm - 5.0.0 + okhttp + 4.12.0 compile com.squareup.okhttp3 logging-interceptor - 5.0.0 + 4.12.0 compile org.json json - 20250517 + 20250107 compile diff --git a/src/bld/java/net/thauvin/erik/bitly/BitlyShortenBuild.java b/src/bld/java/net/thauvin/erik/bitly/BitlyShortenBuild.java index 004c31b..d007e1e 100644 --- a/src/bld/java/net/thauvin/erik/bitly/BitlyShortenBuild.java +++ b/src/bld/java/net/thauvin/erik/bitly/BitlyShortenBuild.java @@ -33,7 +33,10 @@ package net.thauvin.erik.bitly; import rife.bld.BuildCommand; import rife.bld.Project; -import rife.bld.extension.*; +import rife.bld.extension.CompileKotlinOperation; +import rife.bld.extension.DetektOperation; +import rife.bld.extension.DokkaOperation; +import rife.bld.extension.JacocoReportOperation; import rife.bld.extension.dokka.LoggingLevel; import rife.bld.extension.dokka.OutputFormat; import rife.bld.extension.dokka.SourceSet; @@ -46,56 +49,48 @@ import rife.tools.exceptions.FileUtilsErrorException; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.List; -import java.util.logging.ConsoleHandler; -import java.util.logging.Level; -import java.util.logging.Logger; import static rife.bld.dependencies.Repository.*; import static rife.bld.dependencies.Scope.compile; import static rife.bld.dependencies.Scope.test; public class BitlyShortenBuild extends Project { - static final String TEST_RESULTS_DIR = "build/test-results/test/"; final File srcMainKotlin = new File(srcMainDirectory(), "kotlin"); public BitlyShortenBuild() { pkg = "net.thauvin.erik"; name = "bitly-shorten"; - version = version(2, 0, 1, "SNAPSHOT"); + version = version(2, 0, 0); javaRelease = 11; - downloadSources = true; autoDownloadPurge = true; - repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL); - var okHttp = version(5, 0, 0); - final var kotlin = version(2, 2, 0); + var okHttp = version(4, 12, 0); + final var kotlin = version(2, 1, 10); scope(compile) // Kotlin .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlin)) .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib-common", kotlin)) .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib-jdk8", kotlin)) // OkHttp - .include(dependency("com.squareup.okhttp3", "okhttp-jvm", okHttp)) + .include(dependency("com.squareup.okhttp3", "okhttp", okHttp)) .include(dependency("com.squareup.okhttp3", "logging-interceptor", okHttp)) // JSON - .include(dependency("org.json", "json", "20250517")); + .include(dependency("org.json", "json", "20250107")); scope(test) .include(dependency("org.jetbrains.kotlin", "kotlin-test-junit5", kotlin)) - .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 13, 3))) - .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 13, 3))) - .include(dependency("org.junit.platform", "junit-platform-launcher", version(1, 13, 3))) + .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 12, 1))) + .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 12, 1))) .include(dependency("com.willowtreeapps.assertk", "assertk-jvm", version(0, 28, 1))); publishOperation() - .repository(version.isSnapshot() ? repository(CENTRAL_SNAPSHOTS.location()) - .withCredentials(property("central.user"), property("central.password")) - : repository(CENTRAL_RELEASES.location())) + .repository(version.isSnapshot() ? repository(SONATYPE_SNAPSHOTS_LEGACY.location()) + .withCredentials(property("sonatype.user"), property("sonatype.password")) + : repository(SONATYPE_RELEASES_LEGACY.location()) + .withCredentials(property("sonatype.user"), property("sonatype.password"))) .repository(repository("github")) .info() .groupId(pkg) @@ -124,25 +119,15 @@ public class BitlyShortenBuild extends Project { } public static void main(String[] args) { - // Enable detailed logging for the extensions - var level = Level.ALL; - var logger = Logger.getLogger("rife.bld.extension"); - var consoleHandler = new ConsoleHandler(); - - consoleHandler.setLevel(level); - logger.addHandler(consoleHandler); - logger.setLevel(level); - logger.setUseParentHandlers(false); - new BitlyShortenBuild().start(args); } @BuildCommand(summary = "Compiles the Kotlin project") @Override public void compile() throws Exception { - var op = new CompileKotlinOperation().fromProject(this); - op.compileOptions().languageVersion("1.9").verbose(true); - op.execute(); + new CompileKotlinOperation() + .fromProject(this) + .execute(); } @BuildCommand(summary = "Checks source with Detekt") @@ -184,60 +169,10 @@ public class BitlyShortenBuild extends Project { @BuildCommand(summary = "Generates JaCoCo Reports") public void jacoco() throws Exception { - var op = new JacocoReportOperation().fromProject(this); - op.testToolOptions("--reports-dir=" + TEST_RESULTS_DIR); - - Exception ex = null; - try { - op.execute(); - } catch (Exception e) { - ex = e; - } - - renderWithXunitViewer(); - - if (ex != null) { - throw ex; - } - } - - @BuildCommand(value = "pom-root", summary = "Generates the POM file in the root directory") - public void pomRoot() throws FileUtilsErrorException { - PomBuilder.generateInto(publishOperation().fromProject(this).info(), dependencies(), - new File(workDirectory, "pom.xml")); - } - - private void renderWithXunitViewer() throws Exception { - var xunitViewer = new File("/usr/bin/xunit-viewer"); - if (xunitViewer.exists() && xunitViewer.canExecute()) { - var reportsDir = "build/reports/tests/test/"; - - Files.createDirectories(Path.of(reportsDir)); - - new ExecOperation() - .fromProject(this) - .command(xunitViewer.getPath(), "-r", TEST_RESULTS_DIR, "-o", reportsDir + "index.html") - .execute(); - } - } - - @Override - public void test() throws Exception { - var op = testOperation().fromProject(this); - op.testToolOptions().reportsDir(new File(TEST_RESULTS_DIR)); - - Exception ex = null; - try { - op.execute(); - } catch (Exception e) { - ex = e; - } - - renderWithXunitViewer(); - - if (ex != null) { - throw ex; - } + new JacocoReportOperation() + .fromProject(this) + .sourceFiles(srcMainKotlin) + .execute(); } @Override @@ -263,4 +198,10 @@ public class BitlyShortenBuild extends Project { super.publishLocal(); pomRoot(); } + + @BuildCommand(value = "pom-root", summary = "Generates the POM file in the root directory") + public void pomRoot() throws FileUtilsErrorException { + PomBuilder.generateInto(publishOperation().fromProject(this).info(), dependencies(), + new File(workDirectory, "pom.xml")); + } } diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt index f7cb3a4..78ec984 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt @@ -136,7 +136,7 @@ open class Bitlinks(private val accessToken: String) { domain: String = Constants.EMPTY, group_guid: String = Constants.EMPTY, title: String = Constants.EMPTY, - tags: List = emptyList(), + tags: Array = emptyArray(), deeplinks: CreateDeeplinks = CreateDeeplinks(), toJson: Boolean = false ): String { @@ -282,7 +282,7 @@ open class Bitlinks(private val accessToken: String) { bitlink: String, title: String = Constants.EMPTY, archived: Boolean = false, - tags: List = emptyList(), + tags: Array = emptyArray(), deeplinks: UpdateDeeplinks = UpdateDeeplinks(), toJson: Boolean = false ): String { diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt b/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt index 956a640..17441e0 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt @@ -124,7 +124,7 @@ object Utils { var message = response.message var description = "" var json = Constants.EMPTY_JSON - response.body.string().let { body -> + response.body?.string()?.let { body -> json = body if (!response.isSuccessful && body.isNotEmpty()) { try { @@ -174,9 +174,8 @@ object Utils { return false } - /** - * Removes the `http` or `https` schemes from a string. + * Removes http(s) scheme from string. */ @JvmStatic fun String.removeHttp(): String { @@ -184,7 +183,7 @@ object Utils { } /** - * Converts a path to an API endpoint URL using the [Constants.API_BASE_URL], unless a URL is already specified. + * Builds the full API endpoint URL using the [Constants.API_BASE_URL]. */ @JvmStatic fun String.toEndPoint(): String { 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 928cee1..25503b7 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt @@ -60,7 +60,7 @@ class CreateConfig private constructor(builder: Builder) { var domain: String = Constants.EMPTY var group_guid: String = Constants.EMPTY var title: String = Constants.EMPTY - var tags: List = emptyList() + var tags: Array = emptyArray() var deeplinks: CreateDeeplinks = CreateDeeplinks() var toJson: Boolean = false @@ -76,7 +76,7 @@ class CreateConfig private constructor(builder: Builder) { fun title(title: String): Builder = apply { this.title = title } - fun tags(tags: List): Builder = apply { this.tags = tags } + fun tags(tags: Array): Builder = apply { this.tags = tags } fun deeplinks(deeplinks: CreateDeeplinks): Builder = apply { this.deeplinks = deeplinks } 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 52ba5ab..d531d1b 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/config/UpdateConfig.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/config/UpdateConfig.kt @@ -57,7 +57,7 @@ class UpdateConfig private constructor(builder: Builder) { data class Builder(var bitlink: String) { var title: String = Constants.EMPTY var archived: Boolean = false - var tags: List = emptyList() + var tags: Array = emptyArray() var deeplinks: UpdateDeeplinks = UpdateDeeplinks() var toJson: Boolean = false @@ -68,7 +68,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: List): Builder = apply { this.tags = tags } + fun tags(tags: Array): Builder = apply { this.tags = tags } fun deeplinks(deeplinks: UpdateDeeplinks): Builder = apply { this.deeplinks = deeplinks } /** diff --git a/src/test/kotlin/net/thauvin/erik/bitly/BeforeAllTests.kt b/src/test/kotlin/net/thauvin/erik/bitly/BeforeAllTests.kt deleted file mode 100644 index 06e21ab..0000000 --- a/src/test/kotlin/net/thauvin/erik/bitly/BeforeAllTests.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * BeforeAllTests.kt - * - * Copyright 2020-2025 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 - -import org.junit.jupiter.api.extension.BeforeAllCallback -import org.junit.jupiter.api.extension.ExtensionContext -import java.util.concurrent.atomic.AtomicBoolean -import java.util.logging.ConsoleHandler -import java.util.logging.Level - -class BeforeAllTests : BeforeAllCallback { - private val isFirstTime: AtomicBoolean = AtomicBoolean(true) - - override fun beforeAll(context: ExtensionContext?) { - if (isFirstTime.getAndSet(false)) { - with(Utils.logger) { - addHandler(ConsoleHandler().apply { level = Level.FINE }) - level = Level.FINE - } - } - } -} - diff --git a/src/test/kotlin/net/thauvin/erik/bitly/BitlinksTests.kt b/src/test/kotlin/net/thauvin/erik/bitly/BitlinksTests.kt deleted file mode 100644 index b248de0..0000000 --- a/src/test/kotlin/net/thauvin/erik/bitly/BitlinksTests.kt +++ /dev/null @@ -1,417 +0,0 @@ -/* - * BitlinksTests.kt - * - * Copyright 2020-2025 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 - -import DisableOnCi -import assertk.all -import assertk.assertThat -import assertk.assertions.* -import net.thauvin.erik.bitly.config.CreateConfig -import net.thauvin.erik.bitly.config.UpdateConfig -import net.thauvin.erik.bitly.config.deeplinks.CreateDeeplinks -import net.thauvin.erik.bitly.config.deeplinks.UpdateDeeplinks -import net.thauvin.erik.bitly.config.deeplinks.enums.InstallType -import net.thauvin.erik.bitly.config.deeplinks.enums.Os -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable -import org.junit.jupiter.api.extension.ExtendWith -import java.io.File -import java.util.* -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue - - -@ExtendWith(BeforeAllTests::class) -class BitlinksTests { - private val bitly = with(File("local.properties")) { - if (exists()) { - Bitly(toPath()) - } else { - Bitly() - } - } - private val longUrl = "https://erik.thauvin.net/blog" - private val shortUrl = "https://bit.ly/380ojFd" - - @Nested - @DisplayName("Bitlinks Tests") - inner class BitlinksTests { - @Test - fun `Clicks summary`() { - val bl = bitly.bitlinks() - val clicks = bl.clicks(shortUrl, unit = Units.MONTH, units = 1) - assertThat(bl.lastCallResponse).all { - prop(CallResponse::description).isEmpty() - prop(CallResponse::isSuccessful).isTrue() - prop(CallResponse::statusCode).isEqualTo(200) - } - assertThat(clicks.toInt()).isGreaterThanOrEqualTo(0) - } - - @Test - fun `Clicks summary as json`() { - val bl = bitly.bitlinks() - val clicks = bl.clicks(shortUrl, toJson = true) - assertThat(bl.lastCallResponse).all { - prop(CallResponse::description).isEmpty() - prop(CallResponse::isSuccessful).isTrue() - prop(CallResponse::statusCode).isEqualTo(200) - } - assertThat(clicks).startsWith("{\"unit_reference\":") - } - } - - @Nested - @DisplayName("Constructor Tests") - inner class ConstructorTests { - @Test - fun `Constructor with access token string should set the token correctly`() { - val expectedToken = "my-secret-token" - - val bitly = Bitly(expectedToken) - - assertThat(bitly.accessToken).isEqualTo(expectedToken) - } - - @Test - fun `Constructor with Path should not change token if file does not exist`() { - val nonExistentPath = File("non/existent/path/file.properties").toPath() - val bitlyWithDefaultToken = Bitly() - val initialToken = bitlyWithDefaultToken.accessToken - - val bitly = Bitly(nonExistentPath) - - assertThat(bitly.accessToken).isEqualTo(initialToken) - } - - @Test - fun `Constructor with Properties and custom key should set token correctly`() { - val customKey = "MY_CUSTOM_BITLY_KEY" - val expectedToken = "token-from-custom-key" - val properties = Properties().apply { - setProperty(customKey, expectedToken) - } - - val bitly = Bitly(properties, customKey) - - assertThat(bitly.accessToken).isEqualTo(expectedToken) - } - - @Test - fun `Constructor with Properties should keep default token if key not found`() { - val properties = Properties() // Empty properties - val bitlyWithDefaultToken = Bitly() // Has "" as token by default - val initialToken = bitlyWithDefaultToken.accessToken - - val bitly = Bitly(properties, "non-existent-key") - - // The token should remain the default empty string - assertThat(bitly.accessToken).isEqualTo(initialToken) - } - - @Test - fun `Constructor with Properties should set token using default key`() { - val expectedToken = "token-from-props" - val properties = Properties().apply { - setProperty(Constants.ENV_ACCESS_TOKEN, expectedToken) - } - - val bitly = Bitly(properties) - - assertThat(bitly.accessToken).isEqualTo(expectedToken) - } - - @Test - @DisableOnCi - fun `Default constructor should default to empty string if no token is provided`() { - System.clearProperty(Constants.ENV_ACCESS_TOKEN) - - val bitly = Bitly() - - assertThat(bitly.accessToken).isEmpty() - } - - @Test - @DisableOnCi - fun `Default constructor should use system property if env var is not set`() { - val expectedToken = "token-from-property" - System.setProperty(Constants.ENV_ACCESS_TOKEN, expectedToken) - - val bitly = Bitly() - - assertThat(bitly.accessToken).isEqualTo(expectedToken) - } - } - - @Nested - @DisplayName("Create Bitlink Tests") - inner class CreateBitlinkTests { - - @Test - fun `Create bitlink`() { - assertThat(bitly.bitlinks().create(long_url = longUrl), "create(longUrl)") - .matches("https://\\w+.\\w{2}/\\w{7}".toRegex()) - assertEquals( - shortUrl, - bitly.bitlinks().create( - domain = "bit.ly", - title = "Erik's Blog", - tags = listOf("erik", "thauvin", "blog", "weblog"), - long_url = longUrl - ) - ) - } - - @Test - fun `Create bitlink with config`() { - var config = CreateConfig.Builder(longUrl).build() - assertThat(bitly.bitlinks().create(config), "create(config)") - .matches("https://\\w+.\\w{2}/\\w{7}".toRegex()) - - config = CreateConfig.Builder(longUrl) - .domain("bit.ly") - .title("Erik's Blog") - .tags(listOf("erik", "thauvin", "blog", "weblog")) - .build() - assertEquals( - shortUrl, - bitly.bitlinks().create(config) - ) - } - - @Test - fun `Create bitlink with deep links`() { - val bl = bitly.bitlinks() - val dl = CreateDeeplinks().apply { - app_uri_path("/store?id=123456") - install_type(InstallType.NO_INSTALL) - install_url("https://play.google.com/store/apps/details?id=com.bitly.app&hl=en_US") - } - - val config = CreateConfig.Builder(longUrl) - .deeplinks(dl) - .domain("bit.ly") - .build() - - assertThat(bl.create(config)).isEqualTo(Constants.EMPTY) - assertThat(bl.lastCallResponse.isUpgradeRequired).isTrue() - } - } - - @Nested - @DisplayName("Expand Test") - inner class ExpandTests { - - @Test - fun `Expand as json`() { - assertTrue( - bitly.bitlinks().expand(shortUrl, toJson = true) - .startsWith("{\"created_at\":") - ) - } - - @Test - fun `Expand link`() { - assertEquals(longUrl, Bitlinks(bitly.accessToken).expand(shortUrl)) - } - } - - @Nested - @DisplayName("Shorten Tests") - inner class ShortenTests { - - @Test - fun `Shorten as json`() { - assertTrue( - bitly.bitlinks().shorten(longUrl, toJson = true) - .startsWith("{\"created_at\":") - ) - } - - @Test - fun `Shorten last call response`() { - val bl = Bitlinks(bitly.accessToken) - bl.shorten(longUrl, domain = "bit.ly") - assertThat(bl.lastCallResponse, "shorten(longUrl)").all { - prop(CallResponse::body).contains("\"link\":\"$shortUrl\"") - prop(CallResponse::isSuccessful).isTrue() - prop(CallResponse::message).isEmpty() - prop(CallResponse::statusCode).isEqualTo(200) - } - - bl.shorten(shortUrl) - assertThat(bl.lastCallResponse, "shorten(shortUrl)").all { - prop(CallResponse::description).isEqualTo("The value provided is invalid.") - prop(CallResponse::isBadRequest).isTrue() - prop(CallResponse::isSuccessful).isFalse() - prop(CallResponse::message).isEqualTo("ALREADY_A_BITLY_LINK") - prop(CallResponse::statusCode).isEqualTo(400) - } - } - - @Test - fun `Shorten link`() { - assertEquals( - shortUrl, Bitlinks(bitly.accessToken) - .shorten(longUrl, domain = "bit.ly") - ) - } - - @Test - fun `Shorten with invalid domain`() { - val bl = bitly.bitlinks() - bl.shorten("https://www.examples.com", domain = "foo.com") - assertThat(bl.lastCallResponse).all { - prop(CallResponse::description).contains("invalid") - prop(CallResponse::isBadRequest).isTrue() - prop(CallResponse::isSuccessful).isFalse() - prop(CallResponse::message).isEqualTo("INVALID_ARG_DOMAIN") - } - } - } - - @Nested - @DisplayName("Update Bitlink Tests") - inner class UpdateBitlinkTests { - - @Test - fun `Update bitlink`() { - val bl = bitly.bitlinks() - assertEquals( - Constants.TRUE, - bl.update( - shortUrl, title = "Erik's Weblog", tags = listOf("blog", "weblog"), archived = true - ) - ) - - assertThat(bl.update(shortUrl, tags = emptyList(), toJson = true), "update(tags)") - .contains("\"tags\":[]") - } - - @Test - fun `Update bitlink with config`() { - val bl = bitly.bitlinks() - var config = UpdateConfig.Builder(shortUrl) - .archived(true) - .tags(listOf("blog", "weblog")) - .title("Erik's Weblog") - .build() - - assertEquals(Constants.TRUE, bl.update(config)) - - config = UpdateConfig.Builder(shortUrl) - .toJson(true) - .build() - - assertThat(bl.update(config), "update(tags)").contains("\"tags\":[]") - } - - @Test - fun `Update bitlink with deep links`() { - 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 - @DisableOnCi - fun `Update custom bitlink`() { - val bl = bitly.bitlinks() - assertEquals( - Constants.TRUE, - bl.updateCustom("https://thauv.in/2NwtljT", "thauv.in/2NwtljT") - ) - } - } - - @Nested - @DisplayName("Validation Tests") - inner class ValidationTests { - - @Test - fun `Empty URL should not shorten`() { - assertEquals(Constants.EMPTY, bitly.bitlinks().shorten(Constants.EMPTY)) - } - - @Test - fun `Short URL should not shorten`() { - assertEquals(shortUrl, bitly.bitlinks().shorten(shortUrl)) - } - - @Test - @DisableOnCi - fun `Token not specified`() { - val test = Bitly() - - assertFailsWith(IllegalArgumentException::class) { - test.bitlinks().shorten(longUrl) - } - } - - @Test - @EnabledIfEnvironmentVariable(named = "CI", matches = "true") - fun `Token not specified on CI`() { - val test = Bitly(Constants.EMPTY) // to avoid picking up the environment variable - - assertFailsWith(IllegalArgumentException::class) { - test.bitlinks().shorten(longUrl) - } - } - - @Test - fun `Token not specified with API call`() { - assertFailsWith(IllegalArgumentException::class, "Utils.call()") { - Utils.call("", "foo") - } - } - - @Test - fun `Token should be valid`() { - val test = Bitly().apply { accessToken = "12345679" } - assertEquals( - "{\"message\":\"FORBIDDEN\"}", - test.bitlinks().shorten("https://erik.thauvin.net/blog", toJson = true) - ) - } - } -} diff --git a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt new file mode 100644 index 0000000..2af5f86 --- /dev/null +++ b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt @@ -0,0 +1,324 @@ +/* + * BitlyTest.kt + * + * Copyright 2020-2025 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 + +import DisableOnCi +import assertk.all +import assertk.assertThat +import assertk.assertions.* +import net.thauvin.erik.bitly.Utils.isValidUrl +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.config.deeplinks.CreateDeeplinks +import net.thauvin.erik.bitly.config.deeplinks.UpdateDeeplinks +import net.thauvin.erik.bitly.config.deeplinks.enums.InstallType +import net.thauvin.erik.bitly.config.deeplinks.enums.Os +import org.json.JSONObject +import org.junit.jupiter.api.BeforeAll +import java.io.File +import java.util.logging.Level +import kotlin.test.* + +class BitlyTest { + private val bitly = with(File("local.properties")) { + if (exists()) { + Bitly(toPath()) + } else { + Bitly() + } + } + private val longUrl = "https://erik.thauvin.net/blog" + private val shortUrl = "https://bit.ly/380ojFd" + + @Test + fun `token should be specified`() { + val test = Bitly() + if (System.getenv("CI") == "true") { + test.accessToken = Constants.EMPTY + } + assertFailsWith(IllegalArgumentException::class) { + test.bitlinks().shorten(longUrl) + } + assertFailsWith(IllegalArgumentException::class, "Utils.call()") { + Utils.call("", "foo") + } + } + + @Test + fun `token should be valid`() { + val test = Bitly().apply { accessToken = "12345679" } + assertEquals( + "{\"message\":\"FORBIDDEN\"}", + test.bitlinks().shorten("https://erik.thauvin.net/blog", toJson = true) + ) + } + + @Test + fun `long url should be valid`() { + assertEquals(Constants.EMPTY, bitly.bitlinks().shorten(Constants.EMPTY)) + } + + @Test + fun `long url should not be short`() { + assertEquals(shortUrl, bitly.bitlinks().shorten(shortUrl)) + } + + @Test + fun `endPoint should be specified`() { + assertFailsWith(IllegalArgumentException::class, "bitly.call()") { + bitly.call("") + } + assertFailsWith(IllegalArgumentException::class, "Utils.call()") { + Utils.call("1234568", "") + } + } + + @Test + fun `endPoint conversion`() { + assertThat(Constants.API_BASE_URL.toEndPoint()).isEqualTo(Constants.API_BASE_URL) + assertThat("path".toEndPoint()).isEqualTo("${Constants.API_BASE_URL}/path") + assertThat("/path".toEndPoint()).isEqualTo("${Constants.API_BASE_URL}/path") + } + + @Test + fun `shorten = expand`() { + val shortUrl = bitly.bitlinks().shorten(longUrl, domain = "bit.ly") + assertEquals(longUrl, bitly.bitlinks().expand(shortUrl)) + } + + @Test + fun `shorten as json`() { + assertTrue(bitly.bitlinks().shorten(longUrl, toJson = true).startsWith("{\"created_at\":")) + } + + @Test + fun `get user`() { + assertThat(bitly.call("user", method = Methods.GET), "call(user)") + .prop(CallResponse::isSuccessful).isTrue() + assertThat(Utils.call(bitly.accessToken, "user".toEndPoint(), method = Methods.GET), "call(/user)").all { + prop(CallResponse::isSuccessful).isTrue() + prop(CallResponse::body).contains("login") + } + } + + @Test + fun `created by`() { + assertEquals( + "ethauvin", + JSONObject( + bitly.call( + "/bitlinks/${shortUrl.removeHttp()}", + method = Methods.GET + ).body + ).getString("created_by") + ) + } + + @Test + fun `bitlinks shorten`() { + assertEquals(shortUrl, Bitlinks(bitly.accessToken).shorten(longUrl, domain = "bit.ly")) + } + + @Test + fun `bitlinks expand`() { + assertEquals(longUrl, Bitlinks(bitly.accessToken).expand(shortUrl)) + } + + @Test + fun `bitlinks lastCallResponse`() { + val bl = Bitlinks(bitly.accessToken) + bl.shorten(longUrl, domain = "bit.ly") + assertThat(bl.lastCallResponse, "shorten(longUrl)").all { + prop(CallResponse::isSuccessful).isTrue() + prop(CallResponse::statusCode).isEqualTo(200) + prop(CallResponse::body).contains("\"link\":\"$shortUrl\"") + prop(CallResponse::message).isEmpty() + } + + bl.shorten(shortUrl) + assertThat(bl.lastCallResponse, "shorten(shortUrl)").all { + prop(CallResponse::isSuccessful).isFalse() + prop(CallResponse::statusCode).isEqualTo(400) + prop(CallResponse::isBadRequest).isTrue() + prop(CallResponse::message).isEqualTo("ALREADY_A_BITLY_LINK") + prop(CallResponse::description).isEqualTo("The value provided is invalid.") + } + } + + @Test + fun `clicks summary`() { + val bl = bitly.bitlinks() + assertThat(bl.clicks(shortUrl)).isNotEqualTo(Constants.EMPTY) + val clicks = bl.clicks(shortUrl, unit = Units.MONTH, units = 1) + assertThat(bl.lastCallResponse).all { + prop(CallResponse::isSuccessful).isTrue() + prop(CallResponse::statusCode).isEqualTo(200) + prop(CallResponse::description).isEmpty() + } + assertThat(clicks.toInt()).isGreaterThanOrEqualTo(0) + } + + @Test + fun `create bitlink`() { + assertThat(bitly.bitlinks().create(long_url = longUrl), "create(longUrl)") + .matches("https://\\w+.\\w{2}/\\w{7}".toRegex()) + assertEquals( + shortUrl, + bitly.bitlinks().create( + domain = "bit.ly", + title = "Erik's Blog", + tags = arrayOf("erik", "thauvin", "blog", "weblog"), + long_url = longUrl + ) + ) + } + + @Test + fun `create bitlink with config`() { + var config = CreateConfig.Builder(longUrl).build() + assertThat(bitly.bitlinks().create(config), "create(config)") + .matches("https://\\w+.\\w{2}/\\w{7}".toRegex()) + + config = CreateConfig.Builder(longUrl) + .domain("bit.ly") + .title("Erik's Blog") + .tags(arrayOf("erik", "thauvin", "blog", "weblog")) + .build() + assertEquals( + shortUrl, + bitly.bitlinks().create(config) + ) + } + + @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() + bl.shorten("https://www.examples.com", domain = "foo.com") + assertThat(bl.lastCallResponse).all { + prop(CallResponse::isSuccessful).isFalse() + prop(CallResponse::isBadRequest).isTrue() + prop(CallResponse::message).isEqualTo("INVALID_ARG_DOMAIN") + prop(CallResponse::description).contains("invalid") + } + } + + @Test + @DisableOnCi + fun `update custom bitlink`() { + val bl = bitly.bitlinks() + assertEquals( + Constants.TRUE, + bl.updateCustom("https://thauv.in/2NwtljT", "thauv.in/2NwtljT") + ) + } + + @Test + fun `update bitlink`() { + val bl = bitly.bitlinks() + assertEquals( + Constants.TRUE, + bl.update(shortUrl, title = "Erik's Weblog", tags = arrayOf("blog", "weblog"), archived = true) + ) + + assertThat(bl.update(shortUrl, tags = emptyArray(), toJson = true), "update(tags)") + .contains("\"tags\":[]") + } + + @Test + fun `update bitlink with config`() { + val bl = bitly.bitlinks() + var config = UpdateConfig.Builder(shortUrl) + .title("Erik's Weblog") + .tags(arrayOf("blog", "weblog")) + .archived(true) + .build() + + assertEquals(Constants.TRUE, bl.update(config)) + + config = UpdateConfig.Builder(shortUrl) + .toJson(true) + .build() + + 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") + assertFalse("this is a test".isValidUrl(), "invalid url") + } + + companion object { + @JvmStatic + @BeforeAll + fun before() { + with(Utils.logger) { + level = Level.FINE + } + } + } +} diff --git a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTests.kt b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTests.kt deleted file mode 100644 index afd24e5..0000000 --- a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTests.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * BitlyTests.kt - * - * Copyright 2020-2025 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 - -import assertk.all -import assertk.assertThat -import assertk.assertions.contains -import assertk.assertions.isTrue -import assertk.assertions.prop -import net.thauvin.erik.bitly.Utils.removeHttp -import net.thauvin.erik.bitly.Utils.toEndPoint -import org.json.JSONObject -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.extension.ExtendWith -import java.io.File -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -@ExtendWith(BeforeAllTests::class) -class BitlyTests { - private val bitly = with(File("local.properties")) { - if (exists()) { - Bitly(toPath()) - } else { - Bitly() - } - } - private val shortUrl = "https://bit.ly/380ojFd" - - @Nested - @DisplayName("API Call Tests") - inner class ApiCallTests { - @Test - fun `Created by`() { - assertEquals( - "ethauvin", - JSONObject( - bitly.call( - "/bitlinks/${shortUrl.removeHttp()}", - method = Methods.GET - ).body - ).getString("created_by") - ) - } - - @Test - fun `EndPoint should be specified`() { - assertFailsWith(IllegalArgumentException::class, "bitly.call()") { - bitly.call("") - } - assertFailsWith(IllegalArgumentException::class, "Utils.call()") { - Utils.call("1234568", "") - } - } - - @Test - fun `Get user`() { - assertThat(bitly.call("user", method = Methods.GET), "call(user)") - .prop(CallResponse::isSuccessful).isTrue() - assertThat( - Utils.call( - bitly.accessToken, "user".toEndPoint(), method = Methods.GET - ), "call(/user)" - ).all { - prop(CallResponse::isSuccessful).isTrue() - prop(CallResponse::body).contains("login") - } - } - } -} diff --git a/src/test/kotlin/net/thauvin/erik/bitly/UtilsTests.kt b/src/test/kotlin/net/thauvin/erik/bitly/UtilsTests.kt deleted file mode 100644 index 66f31e5..0000000 --- a/src/test/kotlin/net/thauvin/erik/bitly/UtilsTests.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * UtilsTests.kt - * - * Copyright 2020-2025 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 - -import assertk.assertThat -import assertk.assertions.isEqualTo -import net.thauvin.erik.bitly.Utils.isValidUrl -import net.thauvin.erik.bitly.Utils.removeHttp -import net.thauvin.erik.bitly.Utils.toEndPoint -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class UtilsTests { - @Nested - @DisplayName("Endpoint Conversion Tests") - inner class EndpointConversionTests { - @Test - fun `Convert endpoint with empty string`() { - assertThat("".toEndPoint()).isEqualTo("") - } - - @Test - fun `Convert endpoint with blank string`() { - assertThat(" ".toEndPoint()).isEqualTo(" ") - } - - @Test - fun `Convert endpoint with full URL`() { - assertThat("https://example.com/path".toEndPoint()).isEqualTo("https://example.com/path") - } - - @Test - fun `Convert endpoint with leading slash`() { - assertThat("/path".toEndPoint()).isEqualTo("${Constants.API_BASE_URL}/path") - } - - @Test - fun `Convert endpoint with multiple path segments`() { - assertThat("/existing/path".toEndPoint()).isEqualTo("${Constants.API_BASE_URL}/existing/path") - } - - @Test - fun `Convert endpoint with no leading slash`() { - assertThat("path".toEndPoint()).isEqualTo("${Constants.API_BASE_URL}/path") - } - - @Test - fun `Convert endpoint with trailing slash`() { - assertThat("path/".toEndPoint()).isEqualTo("${Constants.API_BASE_URL}/path/") - } - } - - @Nested - @DisplayName("Remote HTTP Tests") - inner class RemoteHTTPTests { - @Test - @Suppress("HttpUrlsUsage") - fun `Remove HTTP`() { - assertThat("http://example.com".removeHttp()).isEqualTo("example.com") - } - - @Test - fun `Remove HTTPS`() { - assertThat("https://example.com".removeHttp()).isEqualTo("example.com") - } - - @Test - fun `Remove mixed case`() { - assertThat("HtTPs://EXAMPLE.Com".removeHttp()).isEqualTo("EXAMPLE.Com") - } - - @Test - fun `Remove no scheme`() { - assertThat("example.com".removeHttp()).isEqualTo("example.com") - } - } - - @Nested - @DisplayName("URL Validation Tests") - inner class URLValidationTests { - @Test - fun `Validate invalid URL`() { - assertFalse("this is a test".isValidUrl(), "invalid url") - } - - @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..1472d1e --- /dev/null +++ b/src/test/kotlin/net/thauvin/erik/bitly/config/ConfigTest.kt @@ -0,0 +1,105 @@ +/* + * ConfigTest.kt + * + * Copyright 2020-2025 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.config.deeplinks.CreateDeeplinks +import net.thauvin.erik.bitly.config.deeplinks.UpdateDeeplinks +import net.thauvin.erik.bitly.config.deeplinks.enums.InstallType +import net.thauvin.erik.bitly.config.deeplinks.enums.Os +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.Builder("long_url") + .domain("domain") + .groupGuid("group_guid") + .title("title") + .tags(arrayOf("tag", "tag2")) + .deeplinks(deeplinks) + .build() + + 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.Builder("blink") + .title("title") + .archived(true) + .tags(arrayOf("tag", "tag2")) + .deeplinks(deeplinks) + .build() + + 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() + ) + + } +} diff --git a/src/test/kotlin/net/thauvin/erik/bitly/config/ConfigTests.kt b/src/test/kotlin/net/thauvin/erik/bitly/config/ConfigTests.kt deleted file mode 100644 index 42f9b13..0000000 --- a/src/test/kotlin/net/thauvin/erik/bitly/config/ConfigTests.kt +++ /dev/null @@ -1,223 +0,0 @@ -/* - * ConfigTests.kt - * - * Copyright 2020-2025 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.all -import assertk.assertThat -import assertk.assertions.isEqualTo -import assertk.assertions.isTrue -import assertk.assertions.prop -import net.thauvin.erik.bitly.Constants -import net.thauvin.erik.bitly.config.deeplinks.CreateDeeplinks -import net.thauvin.erik.bitly.config.deeplinks.UpdateDeeplinks -import net.thauvin.erik.bitly.config.deeplinks.enums.InstallType -import net.thauvin.erik.bitly.config.deeplinks.enums.Os -import org.json.JSONObject -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import kotlin.test.Test - -class ConfigTests { - @Nested - @DisplayName("Build Configuration Tests") - inner class BuildConfigurationTests { - @Test - fun `Build create configuration`() { - val deeplinks = CreateDeeplinks().apply { - app_id("app_id") - install_type(InstallType.AUTO_INSTALL) - } - - val config = CreateConfig.Builder("long_url") - .deeplinks(deeplinks) - .domain("domain") - .groupGuid("group_guid") - .tags(listOf("tag", "tag2")) - .title("title") - .build() - - assertThat(config).all { - prop(CreateConfig::deeplinks).isEqualTo(deeplinks) - prop(CreateConfig::domain).isEqualTo("domain") - prop(CreateConfig::group_guid).isEqualTo("group_guid") - prop(CreateConfig::long_url).isEqualTo("long_url") - prop(CreateConfig::tags).isEqualTo(listOf("tag", "tag2")) - prop(CreateConfig::title).isEqualTo("title") - prop(CreateConfig::toJson).isEqualTo(false) - } - - val map = mapOf( - "deeplinks" to listOf(deeplinks.links()), - "domain" to config.domain, - "group_guid" to config.group_guid, - "long_url" to config.long_url, - "tags" to config.tags, - "title" to config.title - ) - - assertThat(JSONObject(map).toString()).isEqualTo( - """ - {"group_guid":"group_guid","deeplinks":[{"app_id":"app_id","install_type":"auto_install"}],"long_url":"long_url","title":"title","domain":"domain","tags":["tag","tag2"]} - """.trimIndent() - ) - } - } - - @Test - fun `Build update configuration`() { - val deeplinks = UpdateDeeplinks().apply { - os(Os.IOS) - install_type(InstallType.PROMOTE_INSTALL) - app_guid("app_guid") - } - - val config = UpdateConfig.Builder("blink") - .archived(true) - .deeplinks(deeplinks) - .tags(listOf("tag", "tag2")) - .title("title") - .build() - - assertThat(config).all { - prop(UpdateConfig::archived).isTrue() - prop(UpdateConfig::bitlink).isEqualTo("blink") - prop(UpdateConfig::deeplinks).isEqualTo(deeplinks) - prop(UpdateConfig::tags).isEqualTo(listOf("tag", "tag2")) - prop(UpdateConfig::title).isEqualTo("title") - prop(UpdateConfig::toJson).isEqualTo(false) - } - - val map = mapOf( - "archived" to config.archived, - "bitlink" to config.bitlink, - "deeplinks" to listOf(deeplinks.links()), - "tags" to config.tags, - "title" to config.title - ) - - assertThat(JSONObject(map).toString()).isEqualTo( - """ - {"archived":true,"bitlink":"blink","deeplinks":[{"os":"ios","app_guid":"app_guid","install_type":"promote_install"}],"title":"title","tags":["tag","tag2"]} - """.trimIndent() - ) - } - - @Nested - @DisplayName("Validate Configuration Tests") - inner class ValidateConfigurationTests { - @Test - fun `Validate create configuration`() { - val deeplinks = CreateDeeplinks().apply { - app_id("app_id") - install_type(InstallType.AUTO_INSTALL) - } - - val config = CreateConfig.Builder("long_url") - .deeplinks(deeplinks) - .domain("domain") - .groupGuid("group_guid") - .tags(listOf("tag", "tag2")) - .title("title") - .toJson(true) - - assertThat(config).all { - prop(CreateConfig.Builder::deeplinks).prop(CreateDeeplinks::links).isEqualTo(deeplinks.links()) - prop(CreateConfig.Builder::domain).isEqualTo("domain") - prop(CreateConfig.Builder::group_guid).isEqualTo("group_guid") - prop(CreateConfig.Builder::long_url).isEqualTo("long_url") - prop(CreateConfig.Builder::tags).isEqualTo(listOf("tag", "tag2")) - prop(CreateConfig.Builder::title).isEqualTo("title") - prop(CreateConfig.Builder::toJson).isTrue() - } - - config.longUrl("longer_url") - assertThat(config).prop(CreateConfig.Builder::long_url).isEqualTo("longer_url") - } - - @Test - fun `Validate create default configuration`() { - val config = CreateConfig.Builder("long_url") - - assertThat(config).all { - prop(CreateConfig.Builder::long_url).isEqualTo("long_url") - prop(CreateConfig.Builder::domain).isEqualTo(Constants.EMPTY) - prop(CreateConfig.Builder::group_guid).isEqualTo(Constants.EMPTY) - prop(CreateConfig.Builder::title).isEqualTo(Constants.EMPTY) - prop(CreateConfig.Builder::tags).isEqualTo(emptyList()) - prop(CreateConfig.Builder::deeplinks).prop(CreateDeeplinks::links).isEqualTo(CreateDeeplinks().links()) - prop(CreateConfig.Builder::toJson).isEqualTo(false) - } - } - - @Test - fun `Validate update configuration`() { - val deeplinks = UpdateDeeplinks().apply { - os(Os.IOS) - install_type(InstallType.PROMOTE_INSTALL) - app_guid("app_guid") - } - - val config = UpdateConfig.Builder("bitlink") - .title("title") - .archived(true) - .tags(listOf("tag", "tag2")) - .deeplinks(deeplinks) - .toJson(true) - - assertThat(config).all { - prop(UpdateConfig.Builder::bitlink).isEqualTo("bitlink") - prop(UpdateConfig.Builder::title).isEqualTo("title") - prop(UpdateConfig.Builder::archived).isTrue() - prop(UpdateConfig.Builder::tags).isEqualTo(listOf("tag", "tag2")) - prop(UpdateConfig.Builder::deeplinks).isEqualTo(deeplinks) - prop(UpdateConfig.Builder::toJson).isTrue() - } - - config.bitlink("blink") - assertThat(config).prop(UpdateConfig.Builder::bitlink).isEqualTo("blink") - } - - @Test - fun `Validate update default configuration`() { - val config = UpdateConfig.Builder("bitlink") - - assertThat(config).all { - prop(UpdateConfig.Builder::bitlink).isEqualTo("bitlink") - prop(UpdateConfig.Builder::title).isEqualTo(Constants.EMPTY) - prop(UpdateConfig.Builder::archived).isEqualTo(false) - prop(UpdateConfig.Builder::tags).isEqualTo(emptyList()) - prop(UpdateConfig.Builder::deeplinks).prop(UpdateDeeplinks::links).isEqualTo(UpdateDeeplinks().links()) - prop(UpdateConfig.Builder::toJson).isEqualTo(false) - } - } - } -} diff --git a/src/test/kotlin/net/thauvin/erik/bitly/config/deeplinks/DeeplinksTests.kt b/src/test/kotlin/net/thauvin/erik/bitly/config/deeplinks/DeeplinksTest.kt similarity index 56% rename from src/test/kotlin/net/thauvin/erik/bitly/config/deeplinks/DeeplinksTests.kt rename to src/test/kotlin/net/thauvin/erik/bitly/config/deeplinks/DeeplinksTest.kt index cf3ecbb..c5dff39 100644 --- a/src/test/kotlin/net/thauvin/erik/bitly/config/deeplinks/DeeplinksTests.kt +++ b/src/test/kotlin/net/thauvin/erik/bitly/config/deeplinks/DeeplinksTest.kt @@ -1,5 +1,5 @@ /* - * DeeplinksTests.kt + * DeeplinksTest.kt * * Copyright 2020-2025 Erik C. Thauvin (erik@thauvin.net) * @@ -31,11 +31,11 @@ package net.thauvin.erik.bitly.config.deeplinks -import assertk.all import assertk.assertThat +import assertk.assertions.contains +import assertk.assertions.doesNotContain import assertk.assertions.isEqualTo import assertk.assertions.isNull -import assertk.assertions.prop import net.thauvin.erik.bitly.config.deeplinks.enums.InstallType import net.thauvin.erik.bitly.config.deeplinks.enums.Os import org.json.JSONObject @@ -43,94 +43,90 @@ import org.junit.Test import java.time.ZoneId import java.time.ZonedDateTime -class DeeplinksTests { +class DeeplinksTest { @Test - fun `Create deeplink`() { + fun `create test`() { val deeplinks = CreateDeeplinks().apply { app_uri_path("app_uri_path") install_type(InstallType.NO_INSTALL) } - assertThat(deeplinks).all { - prop(CreateDeeplinks::app_id).isNull() - prop(CreateDeeplinks::app_uri_path).isEqualTo("app_uri_path") - prop(CreateDeeplinks::install_type).isEqualTo(InstallType.NO_INSTALL) - prop(CreateDeeplinks::install_url).isNull() - prop(CreateDeeplinks::links).isEqualTo(deeplinks.links()) - } - - assertThat(JSONObject(deeplinks.links()).toString()).isEqualTo( - """ - {"app_uri_path":"app_uri_path","install_type":"no_install"} - """.trimIndent() - ) - - deeplinks.app_id("app_id") - deeplinks.install_type(InstallType.PROMOTE_INSTALL) + assertThat(deeplinks.install_url()).isNull() deeplinks.install_url("install_url") - assertThat(deeplinks).all { - prop(CreateDeeplinks::app_id).isEqualTo("app_id") - prop(CreateDeeplinks::install_type).isEqualTo(InstallType.PROMOTE_INSTALL) - prop(CreateDeeplinks::install_url).isEqualTo("install_url") - } + assertThat(deeplinks.app_uri_path()).isEqualTo("app_uri_path") + assertThat(deeplinks.install_type()).isEqualTo(InstallType.NO_INSTALL) assertThat(JSONObject(deeplinks.links()).toString()).isEqualTo( """ - {"install_url":"install_url","app_id":"app_id","app_uri_path":"app_uri_path","install_type":"promote_install"} + {"app_uri_path":"app_uri_path","install_type":"no_install","install_url":"install_url"} """.trimIndent() ) + + deeplinks.install_type(InstallType.PROMOTE_INSTALL) + deeplinks.app_id("app_id") + + assertThat(deeplinks.app_id()).isEqualTo("app_id") + + assertThat(JSONObject(deeplinks.links()).toString()).apply { + doesNotContain(InstallType.NO_INSTALL.type) + contains(InstallType.PROMOTE_INSTALL.type) + contains("\"app_id\":\"app_id\"") + } } @Test - fun `Update deeplink`() { + fun `update test`() { val deeplinks = UpdateDeeplinks().apply { app_guid("app_guid") + os(Os.IOS) + install_type(InstallType.NO_INSTALL) + guid("guid") + install_url("install_url") app_uri_path("app_uri_path") created("created") - guid("guid") - install_type(InstallType.NO_INSTALL) - install_url("install_url") modified("modified") - os(Os.IOS) } - assertThat(deeplinks).all { - prop(UpdateDeeplinks::app_guid).isEqualTo("app_guid") - prop(UpdateDeeplinks::app_uri_path).isEqualTo("app_uri_path") - prop(UpdateDeeplinks::bitlink).isNull() - prop(UpdateDeeplinks::brand_guid).isNull() - prop(UpdateDeeplinks::created).isEqualTo("created") - prop(UpdateDeeplinks::guid).isEqualTo("guid") - prop(UpdateDeeplinks::install_type).isEqualTo(InstallType.NO_INSTALL) - prop(UpdateDeeplinks::install_url).isEqualTo("install_url") - prop(UpdateDeeplinks::links).isEqualTo(deeplinks.links()) - prop(UpdateDeeplinks::modified).isEqualTo("modified") - prop(UpdateDeeplinks::os).isEqualTo(Os.IOS) - } - - val zdt = ZonedDateTime.of(1997, 8, 29, 2, 14, 0, 0, ZoneId.of("US/Eastern")) - - deeplinks.bitlink("bitlink") + assertThat(deeplinks.brand_guid()).isNull() deeplinks.brand_guid("brand_guid") - deeplinks.created(zdt) - deeplinks.install_type(InstallType.PROMOTE_INSTALL) - deeplinks.modified(zdt) - deeplinks.os(Os.ANDROID) - assertThat(deeplinks).all { - prop(UpdateDeeplinks::bitlink).isEqualTo("bitlink") - prop(UpdateDeeplinks::brand_guid).isEqualTo("brand_guid") - prop(UpdateDeeplinks::created).isEqualTo("1997-08-29T02:14:00-0400") - prop(UpdateDeeplinks::install_type).isEqualTo(InstallType.PROMOTE_INSTALL) - prop(UpdateDeeplinks::modified).isEqualTo("1997-08-29T02:14:00-0400") - prop(UpdateDeeplinks::os).isEqualTo(Os.ANDROID) - } + assertThat(deeplinks.app_uri_path()).isEqualTo("app_uri_path") + assertThat(deeplinks.install_url()).isEqualTo("install_url") + + assertThat(deeplinks.os()).isEqualTo(Os.IOS) + assertThat(deeplinks.install_type()).isEqualTo(InstallType.NO_INSTALL) + assertThat(deeplinks.app_guid()).isEqualTo("app_guid") + assertThat(deeplinks.modified()).isEqualTo("modified") + assertThat(deeplinks.brand_guid()).isEqualTo("brand_guid") + assertThat(JSONObject(deeplinks.links()).toString()).isEqualTo( """ - {"app_guid":"app_guid","install_url":"install_url","bitlink":"bitlink","os":"android","app_uri_path":"app_uri_path","created":"1997-08-29T02:14:00-0400","brand_guid":"brand_guid","guid":"guid","modified":"1997-08-29T02:14:00-0400","install_type":"promote_install"} + {"app_guid":"app_guid","install_url":"install_url","os":"ios","app_uri_path":"app_uri_path","created":"created","brand_guid":"brand_guid","guid":"guid","modified":"modified","install_type":"no_install"} """.trimIndent() ) + + deeplinks.install_type(InstallType.PROMOTE_INSTALL) + deeplinks.os(Os.ANDROID) + deeplinks.bitlink("bitlink") + + val zdt = ZonedDateTime.of(1997, 8, 29, 2, 14, 0, 0, ZoneId.of("US/Eastern")) + deeplinks.modified(zdt) + deeplinks.created(zdt) + + assertThat(deeplinks.bitlink()).isEqualTo("bitlink") + assertThat(deeplinks.created()).isEqualTo("1997-08-29T02:14:00-0400") + assertThat(deeplinks.modified()).isEqualTo("1997-08-29T02:14:00-0400") + + assertThat(JSONObject(deeplinks.links()).toString()).apply { + doesNotContain(InstallType.NO_INSTALL.type) + contains(InstallType.PROMOTE_INSTALL.type) + + doesNotContain(Os.IOS.type) + contains("\"os\":\"android\"") + + contains("\"bitlink\":\"bitlink\"") + } } }