Compare commits

..

28 commits

Author SHA1 Message Date
1b07597252
Revert to version 2.0.0 in the example until the new Maven Central API is implemented in bld 2025-06-30 16:45:07 -07:00
82b45b0bc1
Revert to version 2.0.0 in the example until the new Maven Central API is implemented in bld 2025-06-30 16:40:54 -07:00
9f2c399b6e
Remove bitbucket pipelines configuration file 2025-06-30 16:22:55 -07:00
c7c70f1b90
Rename BeforeAll class to BeforeAllTests 2025-06-30 16:08:46 -07:00
f46f958de0
Bump Gradle to version 8.14.2 2025-06-30 16:07:41 -07:00
6283fc8aef
Bump Kotlin to version 2.2.0 2025-06-30 16:07:17 -07:00
58fbc4ec2c
Bump JUnit to version 5.13.2 2025-06-24 09:01:59 -07:00
8c03669240
Rename tests class 2025-06-24 09:01:40 -07:00
3fe4c9cfc5
Generate and convert JUnit reports for xunit-viewer 2025-05-28 18:21:29 -07:00
3eeae9c19b
Bump Gradle to version 8.14.1 2025-05-28 18:20:22 -07:00
4b4b273461
Bump org.json to version 20250517 2025-05-28 18:19:41 -07:00
820ec736bb
Generates and convert JUnit test reports for xunit-view 2025-05-28 18:16:59 -07:00
fc3e641793
Bumped Kotlin to version 2.1.21 2025-05-14 22:01:27 -07:00
cce4ddbcb9
Cleanup tests 2025-05-14 22:00:55 -07:00
a72af5cac6
Bump Gradle to version 8.14 2025-04-25 09:28:04 -07:00
f57295637f
Bump JaCoCo Report extension to version 0.9.10 2025-04-13 13:34:54 -07:00
54b6a09572
Bump JUnit to version 5.12.2 2025-04-13 13:34:39 -07:00
4dd96b7cdb
Fix typo 2025-03-26 12:38:39 -07:00
d79f225325
Add bld example compile step 2025-03-26 11:41:26 -07:00
a8ce2a04b8
Add OS matrix for Ubuntu, Windows and macOS 2025-03-26 11:37:44 -07:00
ed5dd2110a
Update to the latest snapshots 2025-03-26 11:37:19 -07:00
6d8525c30c
Enable extensions logging 2025-03-26 11:36:02 -07:00
f6a1e7bc3d
Combine Kotlin compile options 2025-03-20 20:39:41 -07:00
7357756667
Add Kotlin compile options for JDK 24 2025-03-20 09:33:22 -07:00
2f974c46da
2.0.1-SNAPSHOT 2025-03-20 09:32:15 -07:00
854ea4e434
Bump Kotlin to version 2.1.20 2025-03-20 09:31:52 -07:00
8d5ac54078
JDK 24 2025-03-18 23:49:18 -07:00
8ff8044982
Add Junit Platform Launcher dependency 2025-03-18 04:25:26 -07:00
27 changed files with 1043 additions and 563 deletions

View file

@ -5,17 +5,16 @@ on: [push, pull_request, workflow_dispatch]
env:
BITLY_ACCESS_TOKEN: ${{ secrets.BITLY_ACCESS_TOKEN }}
COVERAGE_JDK: "21"
COVERAGE_KOTLIN: "2.0.0"
KOTLIN_HOME: /usr/share/kotlinc
KOTLIN_VERSION: "2.2.0"
jobs:
build-bld-project:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: [17, 21, 23]
kotlin-version: [1.9.25, 2.1.10]
java-version: [ 17, 21, 24 ]
os: [ ubuntu-latest, windows-latest, macos-latest ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source repository
@ -23,12 +22,36 @@ jobs:
with:
fetch-depth: 0
- name: Set up JDK ${{ matrix.java-version }} with Kotlin ${{ matrix.kotlin-version }}
- name: Set up JDK ${{ matrix.java-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
@ -39,12 +62,12 @@ jobs:
run: ./bld jacoco
- name: Remove pom.xml
if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.kotlin-version == env.COVERAGE_KOTLIN
if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.os == 'ubuntu-latest'
run: rm -rf pom.xml
- name: SonarCloud Scan
uses: sonarsource/sonarcloud-github-action@master
if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.kotlin-version == env.COVERAGE_KOTLIN
uses: SonarSource/sonarqube-scan-action@v5.2.0
if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.os == 'ubuntu-latest'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

View file

@ -4,5 +4,6 @@
<inspection_tool class="JavadocDeclaration" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ADDITIONAL_TAGS" value="created" />
</inspection_tool>
<inspection_tool class="UsePropertyAccessSyntax" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile>
</component>

16
.idea/kotlinc.xml generated Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JsCompilerArguments">
<option name="moduleKind" value="plain" />
</component>
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="1.9" />
<option name="languageVersion" value="1.9" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.2.0" />
</component>
</project>

View file

@ -1,5 +1,5 @@
[![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.1.10-7f52ff)](https://kotlinlang.org/)
[![Kotlin](https://img.shields.io/badge/kotlin-2.2.0-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)

View file

@ -1,20 +0,0 @@
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

View file

@ -1,6 +1,6 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ConstructorParameterNaming:CreateConfig.kt$CreateConfig.Builder$var long_url: String</ID>
<ID>FunctionParameterNaming:Bitlinks.kt$Bitlinks$bitlink_id: String</ID>
@ -42,7 +42,6 @@
<ID>VariableNaming:CreateConfig.kt$CreateConfig$val group_guid = builder.group_guid</ID>
<ID>VariableNaming:CreateConfig.kt$CreateConfig$val long_url = builder.long_url</ID>
<ID>VariableNaming:CreateConfig.kt$CreateConfig.Builder$var group_guid: String = Constants.EMPTY</ID>
<ID>WildcardImport:BitlyTest.kt$import assertk.assertions.*</ID>
<ID>WildcardImport:BitlyTest.kt$import kotlin.test.*</ID>
<ID>WildcardImport:BitlinksTests.kt$import assertk.assertions.*</ID>
</CurrentIssues>
</SmellBaseline>

6
examples/bld/.idea/bld.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BldConfiguration">
<events />
</component>
</project>

View file

@ -1,7 +1,7 @@
bld.downloadExtensionJavadoc=false
bld.downloadExtensionSources=true
bld.downloadLocation=
bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.0.4
bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.1.0
bld.repositories=MAVEN_LOCAL,MAVEN_CENTRAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES
bld.sourceDirectories=
bld.version=2.2.1

View file

@ -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:20250107"));
.include(dependency("org.json:json:20250517"));
}
public static void main(String[] args) {

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.0" />
<option name="version" value="2.2.0" />
</component>
</project>

View file

@ -1,7 +1,7 @@
plugins {
id("application")
id("com.github.ben-manes.versions") version "0.51.0"
kotlin("jvm") version "2.1.10"
kotlin("jvm") version "2.2.0"
}
repositories {

Binary file not shown.

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View file

@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# 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" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

View file

@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%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" %*
:end
@rem End local scope for the variables with windows NT shell

View file

@ -1,10 +1,11 @@
bld.downloadExtensionJavadoc=false
bld.downloadExtensionSources=true
bld.downloadLocation=
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.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=MAVEN_LOCAL,MAVEN_CENTRAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES
bld.sourceDirectories=
bld.version=2.2.1

10
pom.xml
View file

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.thauvin.erik</groupId>
<artifactId>bitly-shorten</artifactId>
<version>2.0.0</version>
<version>2.0.1-SNAPSHOT</version>
<name>bitly-shorten</name>
<description>A simple implementation of the Bitly link shortening API v4</description>
<url>https://github.com/ethauvin/bitly-shorten</url>
@ -18,19 +18,19 @@
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>2.1.10</version>
<version>2.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-common</artifactId>
<version>2.1.10</version>
<version>2.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>2.1.10</version>
<version>2.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -48,7 +48,7 @@
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20250107</version>
<version>20250517</version>
<scope>compile</scope>
</dependency>
</dependencies>

View file

@ -33,10 +33,7 @@ package net.thauvin.erik.bitly;
import rife.bld.BuildCommand;
import rife.bld.Project;
import rife.bld.extension.CompileKotlinOperation;
import rife.bld.extension.DetektOperation;
import rife.bld.extension.DokkaOperation;
import rife.bld.extension.JacocoReportOperation;
import rife.bld.extension.*;
import rife.bld.extension.dokka.LoggingLevel;
import rife.bld.extension.dokka.OutputFormat;
import rife.bld.extension.dokka.SourceSet;
@ -49,27 +46,35 @@ 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, 0);
version = version(2, 0, 1, "SNAPSHOT");
javaRelease = 11;
downloadSources = true;
autoDownloadPurge = true;
repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL);
var okHttp = version(4, 12, 0);
final var kotlin = version(2, 1, 10);
final var kotlin = version(2, 2, 0);
scope(compile)
// Kotlin
.include(dependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlin))
@ -79,11 +84,12 @@ public class BitlyShortenBuild extends Project {
.include(dependency("com.squareup.okhttp3", "okhttp", okHttp))
.include(dependency("com.squareup.okhttp3", "logging-interceptor", okHttp))
// JSON
.include(dependency("org.json", "json", "20250107"));
.include(dependency("org.json", "json", "20250517"));
scope(test)
.include(dependency("org.jetbrains.kotlin", "kotlin-test-junit5", kotlin))
.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("org.junit.jupiter", "junit-jupiter", version(5, 13, 2)))
.include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 13, 2)))
.include(dependency("org.junit.platform", "junit-platform-launcher", version(1, 13, 2)))
.include(dependency("com.willowtreeapps.assertk", "assertk-jvm", version(0, 28, 1)));
publishOperation()
@ -119,15 +125,25 @@ 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 {
new CompileKotlinOperation()
.fromProject(this)
.execute();
var op = new CompileKotlinOperation().fromProject(this);
op.compileOptions().languageVersion("1.9").verbose(true);
op.execute();
}
@BuildCommand(summary = "Checks source with Detekt")
@ -169,11 +185,61 @@ public class BitlyShortenBuild extends Project {
@BuildCommand(summary = "Generates JaCoCo Reports")
public void jacoco() throws Exception {
new JacocoReportOperation()
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)
.sourceFiles(srcMainKotlin)
.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;
}
}
@Override
public void javadoc() throws ExitStatusException, IOException, InterruptedException {
@ -198,10 +264,4 @@ 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"));
}
}

View file

@ -174,8 +174,9 @@ object Utils {
return false
}
/**
* Removes http(s) scheme from string.
* Removes the `http` or `https` schemes from a string.
*/
@JvmStatic
fun String.removeHttp(): String {
@ -183,7 +184,7 @@ object Utils {
}
/**
* Builds the full API endpoint URL using the [Constants.API_BASE_URL].
* Converts a path to an API endpoint URL using the [Constants.API_BASE_URL], unless a URL is already specified.
*/
@JvmStatic
fun String.toEndPoint(): String {

View file

@ -0,0 +1,52 @@
/*
* 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
}
}
}
}

View file

@ -0,0 +1,328 @@
/*
* 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 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("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 = 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 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 = 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)
.archived(true)
.tags(arrayOf("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
fun `Token not specified with API call`() {
assertFailsWith(IllegalArgumentException::class, "Utils.call()") {
Utils.call("", "foo")
}
}
@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 should be valid`() {
val test = Bitly().apply { accessToken = "12345679" }
assertEquals(
"{\"message\":\"FORBIDDEN\"}",
test.bitlinks().shorten("https://erik.thauvin.net/blog", toJson = true)
)
}
}
}

View file

@ -1,324 +0,0 @@
/*
* 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
}
}
}
}

View file

@ -0,0 +1,101 @@
/*
* 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")
}
}
}
}

View file

@ -0,0 +1,114 @@
/*
* 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 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")
}
}
@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")
}
}

View file

@ -1,105 +0,0 @@
/*
* 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()
)
}
}

View file

@ -0,0 +1,223 @@
/*
* 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(arrayOf("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(arrayOf("tag", "tag2"))
prop(CreateConfig::title).isEqualTo("title")
prop(CreateConfig::toJson).isEqualTo(false)
}
val map = mapOf(
"deeplinks" to arrayOf(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(arrayOf("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(arrayOf("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 arrayOf(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(arrayOf("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(arrayOf("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(emptyArray())
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(arrayOf("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(arrayOf("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(emptyArray())
prop(UpdateConfig.Builder::deeplinks).prop(UpdateDeeplinks::links).isEqualTo(UpdateDeeplinks().links())
prop(UpdateConfig.Builder::toJson).isEqualTo(false)
}
}
}
}

View file

@ -1,5 +1,5 @@
/*
* DeeplinksTest.kt
* DeeplinksTests.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,90 +43,94 @@ import org.junit.Test
import java.time.ZoneId
import java.time.ZonedDateTime
class DeeplinksTest {
class DeeplinksTests {
@Test
fun `create test`() {
fun `Create deeplink`() {
val deeplinks = CreateDeeplinks().apply {
app_uri_path("app_uri_path")
install_type(InstallType.NO_INSTALL)
}
assertThat(deeplinks.install_url()).isNull()
deeplinks.install_url("install_url")
assertThat(deeplinks.app_uri_path()).isEqualTo("app_uri_path")
assertThat(deeplinks.install_type()).isEqualTo(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","install_url":"install_url"}
{"app_uri_path":"app_uri_path","install_type":"no_install"}
""".trimIndent()
)
deeplinks.install_type(InstallType.PROMOTE_INSTALL)
deeplinks.app_id("app_id")
deeplinks.install_type(InstallType.PROMOTE_INSTALL)
deeplinks.install_url("install_url")
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\"")
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(JSONObject(deeplinks.links()).toString()).isEqualTo(
"""
{"install_url":"install_url","app_id":"app_id","app_uri_path":"app_uri_path","install_type":"promote_install"}
""".trimIndent()
)
}
@Test
fun `update test`() {
fun `Update deeplink`() {
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.brand_guid()).isNull()
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")
deeplinks.brand_guid("brand_guid")
deeplinks.created(zdt)
deeplinks.install_type(InstallType.PROMOTE_INSTALL)
deeplinks.modified(zdt)
deeplinks.os(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(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(JSONObject(deeplinks.links()).toString()).isEqualTo(
"""
{"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"}
{"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"}
""".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\"")
}
}
}