Initial commit.

This commit is contained in:
Erik C. Thauvin 2022-09-21 02:50:11 -07:00
commit 39faa04174
29 changed files with 1512 additions and 0 deletions

View file

@ -0,0 +1,54 @@
/*
* Category.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
/**
* Categories and aliases.
*/
enum class Category(val value: String) {
ANY("Any"),
CHRISTMAS("Christmas"),
DARK("Dark"),
MISC("Misc"),
MISCELLANEOUS(MISC.value),
PROGRAMMING("Programming"),
CODING(PROGRAMMING.value),
DEVELOPMENT(PROGRAMMING.value),
PUN("Pun"),
SPOOKY("Spooky"),
HALLOWEEN(SPOOKY.value)
}

View file

@ -0,0 +1,46 @@
/*
* Flag.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
/**
* Blacklist flags.
*/
enum class Flag(val value: String) {
NSFW("nsfw"),
RELIGIOUS("religious"),
POLITICAL("political"),
RACIST("racist"),
SEXIST("sexist"),
EXPLICIT("explicit"),
ALL("${NSFW.value},${RELIGIOUS.value},${POLITICAL.value},${RACIST.value},${SEXIST.value},${EXPLICIT.value}"),
}

View file

@ -0,0 +1,40 @@
/*
* Format.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
/**
* Response formats.
*/
enum class Format(val value: String) {
JSON("json"), XML("xml"), YAML("yaml"), TEXT("txt"), TXT(TEXT.value)
}

View file

@ -0,0 +1,35 @@
/*
* IdRange.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
data class IdRange(val start: Int = -1, val end: Int = -1)

View file

@ -0,0 +1,44 @@
/*
* Joke.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
data class Joke(
val error: Boolean,
val category: Category,
val type: Type,
val joke: Set<String>,
val flags: Set<Flag>,
val id: Int,
val safe: Boolean,
val language: Language?
)

View file

@ -0,0 +1,266 @@
/*
* JokeApi.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
import org.json.JSONObject
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.util.logging.Level
import java.util.logging.Logger
import java.util.stream.Collectors
class JokeApi {
companion object {
private const val API_URL = "https://v2.jokeapi.dev/joke/"
val logger: Logger = Logger.getLogger(JokeApi::class.java.simpleName)
@JvmStatic
@JvmOverloads
@Throws(IOException::class)
fun apiCall(
categories: Set<Category> = setOf(Category.ANY),
language: Language = Language.ENGLISH,
flags: Set<Flag> = emptySet(),
type: Set<Type> = emptySet(),
format: Format = Format.JSON,
search: String = "",
idRange: IdRange = IdRange(),
amount: Int = 1,
safe: Boolean = false,
): String {
val urlBuilder = StringBuilder(API_URL)
val urlParams = mutableListOf<String>()
// Category
if (!categories.contains(Category.ANY)) {
urlBuilder.append(categories.stream().map(Category::value).collect(Collectors.joining(",")))
} else {
urlBuilder.append(Category.ANY.value)
}
// Language
if (language != Language.ENGLISH) {
urlParams.add("lang=${language.value}")
}
// Flags
if (flags.isNotEmpty()) {
if (flags.contains(Flag.ALL)) {
urlParams.add("blacklistFlags=${Flag.ALL.value}")
} else {
urlParams.add(
"blacklistFlags=" + flags.stream().map(Flag::value).collect(Collectors.joining(","))
)
}
}
// Type
if (type.isNotEmpty()) {
urlParams.add("type=" + type.stream().map(Type::value).collect(Collectors.joining(",")))
}
// Format
if (format != Format.JSON) {
urlParams.add("format=${format.value}")
}
// Contains
if (search.isNotBlank()) {
urlParams.add("contains=${URLEncoder.encode(search, StandardCharsets.UTF_8)}")
}
// Range
if (idRange.start >= 0) {
if (idRange.end == -1 || idRange.start == idRange.end) {
urlParams.add("idRange=${idRange.start}")
} else if (idRange.end > idRange.start) {
urlParams.add("idRange=${idRange.start}-${idRange.end}")
}
}
// Amount
if (amount in 2..10) {
urlParams.add("amount=${amount}")
}
// Safe
if (safe) {
urlParams.add("safe-mode")
}
if (urlParams.isNotEmpty()) {
urlBuilder.append('?')
val it = urlParams.iterator()
while (it.hasNext()) {
urlBuilder.append(it.next())
if (it.hasNext()) {
urlBuilder.append("&")
}
}
}
return fetchUrl(urlBuilder.toString())
}
@Throws(JokeException::class, IOException::class)
private fun fetchUrl(url: String) : String {
logger.log(Level.FINE, url)
val connection = URL(url).openConnection() as HttpURLConnection
connection.setRequestProperty(
"User-Agent",
"Mozilla/5.0 (Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"
)
if (connection.responseCode in 200..399) {
val body = connection.inputStream.bufferedReader().readText()
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, body)
}
return body
} else {
when (connection.responseCode) {
400 -> throw IOException(
"400: Bad Request",
IOException(
"The request you have sent to JokeAPI is formatted incorrectly and cannot be" +
" processed"
)
)
403 -> throw IOException(
"4o3: Forbidden",
IOException(
"You have been added to the blacklist due to malicious behavior and are not allowed" +
" to send requests to JokeAPI anymore"
)
)
404 -> throw IOException(
"404: Not Found",
IOException("The URL you have requested couldn't be found")
)
413 -> throw IOException(
"413: Payload Too Large",
IOException("The payload data sent to the server exceeds the maximum size of 5120 bytes")
)
428 -> throw IOException(
"429: Too Many Requests",
IOException(
"You have exceeded the limit of 120 requests per minute and have to wait a bit" +
" until you are allowed to send requests again"
)
)
500 -> throw IOException(
"500: Internal Server Error",
IOException(
"There was a general internal error within JokeAPI. You can get more info from" +
" the properties in the response text"
)
)
523 -> throw IOException(
"523: Origin Unreachable",
IOException(
"The server is temporarily offline due to maintenance or a dynamic IP update." +
" Please be patient in this case."
)
)
else -> throw IOException("${connection.responseCode}: Unknown Error")
}
}
}
@JvmStatic
@JvmOverloads
@Throws(JokeException::class, IOException::class)
fun getJoke(
categories: Set<Category> = setOf(Category.ANY),
language: Language = Language.ENGLISH,
flags: Set<Flag> = emptySet(),
type: Set<Type> = emptySet(),
search: String = "",
idRange: IdRange = IdRange(),
safe: Boolean = false
): Joke {
val json =
JSONObject(apiCall(categories, language, flags, type, search = search, idRange = idRange, safe = safe))
if (json.getBoolean("error")) {
val causedBy = json.getJSONArray("causedBy")
val causes = MutableList<String>(causedBy.length()) { i -> causedBy.getString(i) }
throw JokeException(
error = true,
internalError = json.getBoolean("internalError"),
code = json.getInt("code"),
message = json.getString("message"),
causedBy = causes,
additionalInfo = json.getString("additionalInfo"),
timestamp = json.getLong("timestamp")
)
} else {
val jokes = mutableSetOf<String>()
if (json.has("setup")) {
jokes.add(json.getString("setup"))
jokes.add(json.getString(("delivery")))
} else {
jokes.addAll(json.getString("joke").split("\n"))
}
val enabledFlags = mutableSetOf<Flag>()
val flagObject = json.getJSONObject("flags")
for (flag in Flag.values()) {
if (flag != Flag.ALL && flagObject.getBoolean(flag.value)) {
enabledFlags.add(flag)
}
}
return Joke(
error = false,
category = Category.valueOf(json.getString("category").uppercase()),
type = Type.valueOf(json.getString("type").uppercase()),
joke = jokes,
flags = enabledFlags,
safe = json.getBoolean("safe"),
id = json.getInt("id"),
language = Language.valueOf(json.getString("lang").uppercase())
)
}
}
}
}

View file

@ -0,0 +1,48 @@
/*
* JokeException.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
class JokeException @JvmOverloads constructor(
val error: Boolean,
val internalError: Boolean,
val code: Int,
message: String,
val causedBy: List<String>,
val additionalInfo: String,
val timestamp: Long,
cause: Throwable? = null
) : Exception(message, cause) {
companion object {
private const val serialVersionUID = 1L
}
}

View file

@ -0,0 +1,51 @@
/*
* Language.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
/**
* Supported languages.
*/
enum class Language(val value: String) {
ENGLISH("en"),
EN(ENGLISH.value),
CZECH("cs"),
CS(CZECH.value),
GERMAN("de"),
DE(GERMAN.value),
SPANISH("es"),
ES(SPANISH.value),
FRENCH("fr"),
FR(FRENCH.value),
PORTUGUESE("pt"),
PT(PORTUGUESE.value)
}

View file

@ -0,0 +1,41 @@
/*
* Type.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
/**
* Joke types.
*/
enum class Type(val value: String) {
SINGLE("single"),
TWOPART("twopart")
}

View file

@ -0,0 +1,174 @@
/*
* JokeApiTest.kt
*
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* 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.jokeapi
import net.thauvin.erik.jokeapi.JokeApi.Companion.apiCall
import net.thauvin.erik.jokeapi.JokeApi.Companion.getJoke
import net.thauvin.erik.jokeapi.JokeApi.Companion.logger
import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import java.util.logging.ConsoleHandler
import java.util.logging.Level
internal class JokeApiTest {
@Test
fun apiTxtCallTest() {
val response = apiCall(format = Format.TEXT)
logger.log(Level.FINE, response)
assertAll("plain text",
{ assertTrue(response.isNotEmpty(), "should be not empty") },
{ assertFalse(response.startsWith("Error "), "should not be an error") }
)
}
@Test
fun apiTxtErrorCallTest() {
val response = apiCall(format = Format.TXT, idRange = IdRange(0, 30000))
logger.log(Level.FINE, response)
assertTrue(response.startsWith("Error "), "should be an error")
}
@Test
fun apiXmlCallTest() {
val response = apiCall(format = Format.XML)
logger.log(Level.FINE, response)
assertTrue(response.startsWith("<?xml version='1.0'?>\n<data>\n <error>false</error>"), "should be xml")
}
@Test
fun apiYamlCallTest() {
val response = apiCall(format = Format.YAML)
logger.log(Level.FINE, response)
assertTrue(response.startsWith("error: false"), "should be xml")
}
@Test
fun jokeExceptionTest() {
try {
getJoke(categories = setOf(Category.CHRISTMAS), search = "man")
} catch (e: JokeException) {
assertAll("exception validation",
{ assertEquals(106, e.code, "code should be valid") },
{ assertTrue(e.error, "should be an error") },
{ assertFalse(e.internalError, "should not be internal error") },
{ assertEquals("No matching joke found", e.message, "message should match") },
{ assertEquals(1, e.causedBy.size, "causedby size should be 1") },
{ assertTrue(e.additionalInfo.isNotEmpty(), "additional info should not be empty") },
{ assertTrue(e.timestamp > 0, "timestamp should be > 0") }
)
}
}
@Test
fun getJokeTest() {
val joke = getJoke()
logger.log(Level.FINE, joke.toString())
assertAll("no param",
{ assertFalse(joke.error, "error should be false") },
{ assertTrue(joke.joke.isNotEmpty(), "joke should not be empty") },
{ assertTrue(joke.type == Type.TWOPART || joke.type == Type.SINGLE, "type should validate") },
{ assertTrue(joke.id >= 0, "id should be >= 0") },
{ assertEquals(Language.EN, joke.language, "language should be english") }
)
}
@Test
fun getJokeIdTest() {
val id = 172
val joke = getJoke(idRange = IdRange(id))
logger.log(Level.FINE, joke.toString())
assertAll("joke by id",
{ assertTrue(joke.flags.contains(Flag.NSFW) && joke.flags.contains(Flag.EXPLICIT), "nsfw & explicit") },
{ assertEquals(172, joke.id, "id is $id") },
{ assertEquals(Category.PUN, joke.category, "category should be pun") }
)
}
@Test
fun getJokeIdRangeTest() {
val idRange = IdRange(1, 100)
val joke = getJoke(idRange = idRange)
logger.log(Level.FINE, joke.toString())
assertTrue(joke.id >= idRange.start && joke.id <= idRange.end, "id should be in range")
}
@Test
fun getSafeJokeTest() {
val joke = getJoke(safe = true)
logger.log(Level.FINE, joke.toString())
assertAll("safe joke",
{ assertTrue(joke.safe, "should be safe") },
{ assertTrue(joke.flags.isEmpty(), "flags should be empty") }
)
}
@Test
fun getSingleJokeTest() {
val joke = getJoke(type = setOf(Type.SINGLE))
logger.log(Level.FINE, joke.toString())
assertEquals(Type.SINGLE, joke.type, "type should be single")
}
@Test
fun getTwoPartJokeTest() {
val joke = getJoke(type = setOf(Type.TWOPART))
logger.log(Level.FINE, joke.toString())
assertAll("two-part joke",
{ assertEquals(Type.TWOPART, joke.type, "type should be two-part") },
{ assertTrue(joke.joke.size > 1, "should have multiple lines") }
)
}
@Test
fun getJokeSearchTest() {
val id = 1
val joke = getJoke(search = "man", categories = setOf(Category.PROGRAMMING), idRange = IdRange(id), safe = true)
logger.log(Level.FINE, joke.toString())
assertEquals(id, joke.id, "id should be 1")
}
companion object {
@JvmStatic
@BeforeAll
fun beforeAll() {
with(logger) {
addHandler(ConsoleHandler().apply { level = Level.FINE })
level = Level.FINE
}
}
}
}