Compare commits

..

No commits in common. "master" and "2.0.0" have entirely different histories.

32 changed files with 590 additions and 1170 deletions

View file

@ -5,16 +5,17 @@ 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 }}

View file

@ -4,6 +4,5 @@
<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
View file

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

@ -2,12 +2,12 @@
<library name="bld">
<CLASSES>
<root url="file://$PROJECT_DIR$/lib/bld" />
<root url="jar://$USER_HOME$/.bld/dist/bld-2.2.2-SNAPSHOT.jar!/" />
<root url="jar://$USER_HOME$/.bld/dist/bld-2.2.1.jar!/" />
<root url="file://$PROJECT_DIR$/lib/bld" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.bld/dist/bld-2.2.2-SNAPSHOT-sources.jar!/" />
<root url="jar://$USER_HOME$/.bld/dist/bld-2.2.1-sources.jar!/" />
<root url="file://$PROJECT_DIR$/lib/bld" />
</SOURCES>
<excluded>

View file

@ -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 {

20
bitbucket-pipelines.yml Normal file
View file

@ -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

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>
@ -19,8 +19,8 @@
<ID>FunctionParameterNaming:UpdateDeeplinks.kt$UpdateDeeplinks$brand_guid: String</ID>
<ID>FunctionParameterNaming:UpdateDeeplinks.kt$UpdateDeeplinks$install_type: InstallType</ID>
<ID>FunctionParameterNaming:UpdateDeeplinks.kt$UpdateDeeplinks$install_url: String</ID>
<ID>LongParameterList:Bitlinks.kt$Bitlinks$( bitlink: String, title: String = Constants.EMPTY, archived: Boolean = false, tags: List&lt;String&gt; = emptyList(), deeplinks: UpdateDeeplinks = UpdateDeeplinks(), toJson: Boolean = false )</ID>
<ID>LongParameterList:Bitlinks.kt$Bitlinks$( long_url: String, domain: String = Constants.EMPTY, group_guid: String = Constants.EMPTY, title: String = Constants.EMPTY, tags: List&lt;String&gt; = emptyList(), deeplinks: CreateDeeplinks = CreateDeeplinks(), toJson: Boolean = false )</ID>
<ID>LongParameterList:Bitlinks.kt$Bitlinks$( bitlink: String, title: String = Constants.EMPTY, archived: Boolean = false, tags: Array&lt;String&gt; = emptyArray(), deeplinks: UpdateDeeplinks = UpdateDeeplinks(), toJson: Boolean = false )</ID>
<ID>LongParameterList:Bitlinks.kt$Bitlinks$( long_url: String, domain: String = Constants.EMPTY, group_guid: String = Constants.EMPTY, title: String = Constants.EMPTY, tags: Array&lt;String&gt; = emptyArray(), deeplinks: CreateDeeplinks = CreateDeeplinks(), toJson: Boolean = false )</ID>
<ID>MagicNumber:CallResponse.kt$CallResponse$200</ID>
<ID>MagicNumber:CallResponse.kt$CallResponse$201</ID>
<ID>MagicNumber:CallResponse.kt$CallResponse$299</ID>
@ -42,6 +42,7 @@
<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:BitlinksTests.kt$import assertk.assertions.*</ID>
<ID>WildcardImport:BitlyTest.kt$import assertk.assertions.*</ID>
<ID>WildcardImport:BitlyTest.kt$import kotlin.test.*</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -1,6 +0,0 @@
<?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.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

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:20250517"));
.include(dependency("org.json:json:20250107"));
}
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.2.0" />
<option name="version" value="2.0.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.2.0"
kotlin("jvm") version "2.1.10"
}
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.14.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View file

@ -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.

View file

@ -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

Binary file not shown.

View file

@ -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

16
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.1-SNAPSHOT</version>
<version>2.0.0</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,37 +18,37 @@
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>2.2.0</version>
<version>2.1.10</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-common</artifactId>
<version>2.2.0</version>
<version>2.1.10</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>2.2.0</version>
<version>2.1.10</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-jvm</artifactId>
<version>5.0.0</version>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>5.0.0</version>
<version>4.12.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20250517</version>
<version>20250107</version>
<scope>compile</scope>
</dependency>
</dependencies>

View file

@ -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,61 +169,11 @@ 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()
new JacocoReportOperation()
.fromProject(this)
.command(xunitViewer.getPath(), "-r", TEST_RESULTS_DIR, "-o", reportsDir + "index.html")
.sourceFiles(srcMainKotlin)
.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 {
@ -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"));
}
}

View file

@ -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<String> = emptyList(),
tags: Array<String> = 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<String> = emptyList(),
tags: Array<String> = emptyArray(),
deeplinks: UpdateDeeplinks = UpdateDeeplinks(),
toJson: Boolean = false
): String {

View file

@ -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 {

View file

@ -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<String> = emptyList()
var tags: Array<String> = 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<String>): Builder = apply { this.tags = tags }
fun tags(tags: Array<String>): Builder = apply { this.tags = tags }
fun deeplinks(deeplinks: CreateDeeplinks): Builder = apply { this.deeplinks = deeplinks }

View file

@ -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<String> = emptyList()
var tags: Array<String> = 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<String>): Builder = apply { this.tags = tags }
fun tags(tags: Array<String>): Builder = apply { this.tags = tags }
fun deeplinks(deeplinks: UpdateDeeplinks): Builder = apply { this.deeplinks = deeplinks }
/**

View file

@ -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
}
}
}
}

View file

@ -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)
)
}
}
}

View file

@ -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
}
}
}
}

View file

@ -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")
}
}
}
}

View file

@ -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")
}
}
}

View file

@ -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()
)
}
}

View file

@ -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)
}
}
}
}

View file

@ -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\"")
}
}
}