diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..d91f848
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml
index 078daff..da48b7c 100644
--- a/config/detekt/baseline.xml
+++ b/config/detekt/baseline.xml
@@ -12,6 +12,7 @@
ReturnCount:Akismet.kt$Akismet$@JvmOverloads fun executeMethod(apiUrl: HttpUrl, formBody: FormBody, trueOnError: Boolean = false): Boolean
ReturnCount:AkismetTest.kt$fun getKey(key: String): String
TooManyFunctions:CommentConfig.kt$CommentConfig$Builder
+ WildcardImport:AkismetCommentTest.kt$import assertk.assertions.*
WildcardImport:AkismetTests.kt$import assertk.assertions.*
diff --git a/src/main/kotlin/net/thauvin/erik/akismet/AkismetComment.kt b/src/main/kotlin/net/thauvin/erik/akismet/AkismetComment.kt
index 218cb67..dba58c0 100644
--- a/src/main/kotlin/net/thauvin/erik/akismet/AkismetComment.kt
+++ b/src/main/kotlin/net/thauvin/erik/akismet/AkismetComment.kt
@@ -32,8 +32,10 @@
package net.thauvin.erik.akismet
import jakarta.servlet.http.HttpServletRequest
+import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonIgnoreUnknownKeys
private fun String?.ifNull() = this ?: ""
@@ -54,6 +56,8 @@ private fun String?.ifNull() = this ?: ""
* @param userAgent User agent string of the web browser submitting the comment
*/
@Serializable
+@OptIn(ExperimentalSerializationApi::class)
+@JsonIgnoreUnknownKeys
open class AkismetComment(val userIp: String, val userAgent: String) {
companion object {
/**
diff --git a/src/test/kotlin/net/thauvin/erik/akismet/AkismetCommentTest.kt b/src/test/kotlin/net/thauvin/erik/akismet/AkismetCommentTest.kt
new file mode 100644
index 0000000..8021f1d
--- /dev/null
+++ b/src/test/kotlin/net/thauvin/erik/akismet/AkismetCommentTest.kt
@@ -0,0 +1,267 @@
+/*
+ * AkismetCommentTest.kt
+ *
+ * Copyright 2019-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.akismet
+
+import assertk.all
+import assertk.assertThat
+import assertk.assertions.*
+import jakarta.servlet.http.HttpServletRequest
+import kotlinx.serialization.json.Json
+import org.junit.Assert.assertThrows
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotEquals
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.whenever
+import java.time.LocalDateTime
+import java.util.*
+import kotlin.test.assertTrue
+
+@ExtendWith(BeforeAllTests::class)
+class AkismetCommentTest {
+ private val apiKey = TestUtils.getKey("AKISMET_API_KEY")
+ private val blog = TestUtils.getKey("AKISMET_BLOG")
+ private val config = CommentConfig.Builder(
+ userIp = "127.0.0.1",
+ userAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6"
+ )
+ .referrer("https://www.google.com")
+ .permalink("https://yourblogdomainname.com/blog/post=1")
+ .type(CommentType.COMMENT)
+ .author("admin")
+ .authorEmail("test@test.com")
+ .authorUrl("https://www.CheckOutMyCoolSite.com")
+ .content("It means a lot that you would take the time to review our software. Thanks again.")
+ .dateGmt(Akismet.dateToGmt(LocalDateTime.of(2025, 5, 29, 0, 0, 0)))
+ .postModifiedGmt(Akismet.dateToGmt(LocalDateTime.of(2025, 5, 29, 1, 0, 0)))
+ .blogLang("en")
+ .blogCharset("UTF-8")
+ .userRole(AkismetComment.ADMIN_ROLE)
+ .recheckReason("edit")
+ .isTest(true)
+ .build()
+
+
+ @Test
+ fun `toJson returns correct JSON representation`() {
+ val comment = AkismetComment("127.0.0.1", "TestAgent")
+ comment.referrer = "https://example.com"
+ comment.permalink = "https://permalink.com"
+ comment.type = CommentType.COMMENT
+ comment.author = "Author"
+ comment.content = "Sample comment"
+
+ val json = comment.toJson()
+ assertEquals(Json.encodeToString(comment), json)
+ assertThat(comment).prop(AkismetComment::toString).isEqualTo(json)
+ }
+
+ @Test
+ fun `Equals and hashCode methods work as expected`() {
+ val comment1 = AkismetComment("127.0.0.1", "TestAgent")
+ val comment2 = AkismetComment("127.0.0.1", "TestAgent")
+ val comment3 = AkismetComment("192.168.0.1", "OtherAgent")
+
+ assertEquals(comment1, comment2)
+ assertNotEquals(comment1, comment3)
+ assertEquals(comment1.hashCode(), comment2.hashCode())
+ assertNotEquals(comment1.hashCode(), comment3.hashCode())
+ }
+
+ @Test
+ fun `Property setters handle null values correctly`() {
+ val comment = AkismetComment("127.0.0.1", "TestAgent")
+
+ comment.referrer = null
+ comment.permalink = null
+ comment.author = null
+ comment.authorEmail = null
+ comment.authorUrl = null
+ comment.content = null
+ comment.dateGmt = null
+ comment.postModifiedGmt = null
+ comment.blogLang = null
+ comment.blogCharset = null
+ comment.userRole = null
+ comment.recheckReason = null
+
+ assertThat(comment).all {
+ prop(AkismetComment::referrer).isEqualTo("")
+ prop(AkismetComment::permalink).isEqualTo("")
+ prop(AkismetComment::author).isEqualTo("")
+ prop(AkismetComment::authorEmail).isEqualTo("")
+ prop(AkismetComment::authorUrl).isEqualTo("")
+ prop(AkismetComment::content).isEqualTo("")
+ prop(AkismetComment::dateGmt).isEqualTo("")
+ prop(AkismetComment::postModifiedGmt).isEqualTo("")
+ prop(AkismetComment::blogLang).isEqualTo("")
+ prop(AkismetComment::blogCharset).isEqualTo("")
+ prop(AkismetComment::userRole).isEqualTo("")
+ prop(AkismetComment::recheckReason).isEqualTo("")
+ prop(AkismetComment::isTest).isFalse()
+ prop(AkismetComment::serverEnv).isEmpty()
+ }
+ }
+
+ @Nested
+ @DisplayName("Check Comment Tests")
+ inner class CheckCommentTests {
+ @Test
+ fun `Check comment with admin role`() {
+ val akismet = Akismet(apiKey, blog)
+ val comment = AkismetComment(config).apply {
+ userRole = AkismetComment.ADMIN_ROLE
+ isTest = true
+ }
+ assertThat(akismet.checkComment(comment), "checkComment()").isFalse()
+ assertThat(akismet).prop(Akismet::response).isEqualTo("false")
+ }
+
+ @Test
+ fun `Check comment with no user role`() {
+ val akismet = Akismet(apiKey, blog)
+ val comment = AkismetComment(config).apply {
+ userRole = ""
+ isTest = true
+ }
+ assertTrue(akismet.checkComment(comment), "checkComment()")
+ assertThat(akismet).prop(Akismet::response).isEqualTo("true")
+ }
+
+ @Test
+ fun `Check comment with no user IP or user agent`() {
+ val akismet = Akismet(apiKey, blog)
+ assertThrows(
+ java.lang.IllegalArgumentException::class.java
+ ) { akismet.checkComment(AkismetComment("", "")) }
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Constructor Tests")
+ inner class ConstructorTests {
+ @Test
+ fun `Constructor with userIp and userAgent initializes fields correctly`() {
+ val comment = AkismetComment("127.0.0.1", "TestAgent")
+
+ assertThat(comment).all {
+ prop(AkismetComment::userIp).isEqualTo("127.0.0.1")
+ prop(AkismetComment::userAgent).isEqualTo("TestAgent")
+ prop(AkismetComment::referrer).isEqualTo("")
+ prop(AkismetComment::permalink).isEqualTo("")
+ prop(AkismetComment::type).isEqualTo(CommentType.NONE)
+ }
+ }
+
+ @Test
+ fun `Constructor with HttpServletRequest initializes fields correctly`() {
+ val request = mock(HttpServletRequest::class.java)
+ whenever(request.remoteAddr).thenReturn("192.168.0.1")
+ whenever(request.getHeader("User-Agent")).thenReturn("MockAgent")
+ whenever(request.getHeader("referer")).thenReturn("https://example.com")
+ whenever(request.requestURI).thenReturn("/test-uri")
+ whenever(request.headerNames)
+ .thenReturn(Collections.enumeration(listOf("header1", "header2", "cookie")))
+ whenever(request.getHeader("header1")).thenReturn("value1")
+ whenever(request.getHeader("header2")).thenReturn("value2")
+ whenever(request.getHeader("cookie")).thenReturn("foo")
+
+ val comment = AkismetComment(request)
+
+ assertThat(comment).all {
+ prop(AkismetComment::userIp).isEqualTo("192.168.0.1")
+ prop(AkismetComment::userAgent).startsWith("MockAgent")
+ prop(AkismetComment::referrer).isEqualTo("https://example.com")
+ prop(AkismetComment::serverEnv).isEqualTo(
+ mapOf(
+ "REMOTE_ADDR" to "192.168.0.1",
+ "REQUEST_URI" to "/test-uri",
+ "HTTP_HEADER1" to "value1",
+ "HTTP_HEADER2" to "value2"
+ )
+ )
+ }
+ }
+
+ @Test
+ fun `Constructor with CommentConfig initializes fields correctly`() {
+ val comment = AkismetComment(config).apply {
+ serverEnv = mapOf("key" to "value")
+ }
+
+ assertThat(comment).all {
+ prop(AkismetComment::userIp).isEqualTo("127.0.0.1")
+ prop(AkismetComment::userAgent).startsWith("Mozilla/5.0")
+ prop(AkismetComment::referrer).isEqualTo("https://www.google.com")
+ prop(AkismetComment::permalink).isEqualTo("https://yourblogdomainname.com/blog/post=1")
+ prop(AkismetComment::type).isEqualTo(CommentType.COMMENT)
+ prop(AkismetComment::author).isEqualTo("admin")
+ prop(AkismetComment::authorEmail).isEqualTo("test@test.com")
+ prop(AkismetComment::authorUrl).isEqualTo("https://www.CheckOutMyCoolSite.com")
+ prop(AkismetComment::content)
+ .isEqualTo("It means a lot that you would take the time to review our software. Thanks again.")
+ prop(AkismetComment::dateGmt).isEqualTo("2025-05-29T00:00:00-07:00")
+ prop(AkismetComment::postModifiedGmt).isEqualTo("2025-05-29T01:00:00-07:00")
+ prop(AkismetComment::blogLang).isEqualTo("en")
+ prop(AkismetComment::blogCharset).isEqualTo("UTF-8")
+ prop(AkismetComment::userRole).isEqualTo(AkismetComment.ADMIN_ROLE)
+ prop(AkismetComment::recheckReason).isEqualTo("edit")
+ prop(AkismetComment::isTest).isEqualTo(true)
+ prop(AkismetComment::serverEnv).isEqualTo(mapOf("key" to "value"))
+ }
+ }
+ }
+
+ @Nested
+ @DisplayName("Submit Test")
+ inner class SubmitTests {
+ val akismet = Akismet(apiKey, blog)
+ val comment = AkismetComment(config).apply {
+ isTest = true
+ userRole = AkismetComment.ADMIN_ROLE
+ }
+
+ @Test
+ fun `Submit ham`() {
+ assertTrue(akismet.submitHam(comment), "submitHam")
+ }
+
+ @Test
+ fun `Submit spam`() {
+ assertTrue(akismet.submitSpam(comment), "submitSpam")
+ }
+ }
+}
diff --git a/src/test/kotlin/net/thauvin/erik/akismet/AkismetTests.kt b/src/test/kotlin/net/thauvin/erik/akismet/AkismetTests.kt
index 5a13186..07acdd3 100644
--- a/src/test/kotlin/net/thauvin/erik/akismet/AkismetTests.kt
+++ b/src/test/kotlin/net/thauvin/erik/akismet/AkismetTests.kt
@@ -34,285 +34,46 @@ package net.thauvin.erik.akismet
import assertk.all
import assertk.assertThat
import assertk.assertions.*
-import jakarta.servlet.http.HttpServletRequest
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.MissingFieldException
+import kotlinx.serialization.SerializationException
+import net.thauvin.erik.akismet.Akismet.Companion.jsonComment
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Assert.assertThrows
-import org.junit.BeforeClass
+import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
-import org.mockito.Mockito
-import org.mockito.kotlin.whenever
-import java.io.File
-import java.io.FileInputStream
+import org.junit.jupiter.api.extension.ExtendWith
import java.time.LocalDateTime
import java.time.ZoneId
+import java.time.ZonedDateTime
import java.util.*
-import java.util.concurrent.atomic.AtomicBoolean
-import java.util.logging.ConsoleHandler
-import java.util.logging.Level
import kotlin.test.assertEquals
import kotlin.test.assertFalse
-import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
+import kotlin.text.contains
/**
* [Akismet] Tests
*
* `AKISMET_API_KEY` and `AKISMET_BLOG` should be set in env vars or `local.properties`
*/
+@ExtendWith(BeforeAllTests::class)
class AkismetTests {
private val emptyFormBody = FormBody.Builder().build()
companion object {
- private const val REFERER = "https://www.google.com"
- private val apiKey = getKey("AKISMET_API_KEY")
- private val blog = getKey("AKISMET_BLOG")
- private val akismet = Akismet(apiKey, blog)
- private val comment = AkismetComment(
- userIp = "127.0.0.1",
- userAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6"
- )
- private val date = Date()
- private val config = CommentConfig.Builder(comment.userIp, comment.userAgent)
- .referrer(REFERER)
- .permalink("https://yourblogdomainname.com/blog/post=1")
- .type(CommentType.COMMENT)
- .author("admin")
- .authorEmail("test@test.com")
- .authorUrl("https://www.CheckOutMyCoolSite.com")
- .content("It means a lot that you would take the time to review our software. Thanks again.")
- .dateGmt(Akismet.dateToGmt(date))
- .postModifiedGmt(Akismet.dateToGmt(date))
- .blogLang("en")
- .blogCharset("UTF-8")
- .userRole(AkismetComment.ADMIN_ROLE)
- .recheckReason("edit")
- .isTest(true)
- .build()
- private val mockComment: AkismetComment = AkismetComment(request = getMockRequest())
- private val isFirstRun = AtomicBoolean(true)
-
- init {
- with(comment) {
- referrer = config.referrer
- permalink = config.permalink
- type = CommentType("comment")
- author = config.author
- authorEmail = config.authorEmail
- authorUrl = config.authorUrl
- content = config.content
- dateGmt = config.dateGmt
- postModifiedGmt = config.postModifiedGmt
- blogLang = config.blogLang
- blogCharset = config.blogCharset
- userRole = config.userRole
- recheckReason = config.recheckReason
- isTest = config.isTest
- }
-
- with(mockComment) {
- permalink = comment.permalink
- type = comment.type
- authorEmail = comment.authorEmail
- author = comment.author
- authorUrl = comment.authorUrl
- content = comment.content
- dateGmt = comment.dateGmt
- postModifiedGmt = comment.dateGmt
- blogLang = comment.blogLang
- blogCharset = comment.blogCharset
- userRole = AkismetComment.ADMIN_ROLE
- recheckReason = comment.recheckReason
- isTest = true
- }
- }
-
- @JvmStatic
- @BeforeClass
- fun beforeClass() {
- if (isFirstRun.getAndSet(false)) {
- with(akismet.logger) {
- addHandler(ConsoleHandler().apply { level = Level.FINE })
- level = Level.FINE
- }
- }
-
- akismet.logger.info(comment.toString())
- akismet.logger.info(mockComment.toJson())
- }
-
- private fun getKey(key: String): String {
- return System.getenv(key)?.takeUnless { it.isBlank() }
- ?: loadPropertyValue(key)
- }
-
- private fun getMockRequest(): HttpServletRequest {
- val request = Mockito.mock(HttpServletRequest::class.java)
- with(request) {
- whenever(remoteAddr).thenReturn(comment.userIp)
- whenever(requestURI).thenReturn("/blog/post=1")
- whenever(getHeader("referer")).thenReturn(REFERER)
- whenever(getHeader("Cookie")).thenReturn("name=value; name2=value2; name3=value3")
- whenever(getHeader("User-Agent")).thenReturn(comment.userAgent)
- whenever(getHeader("Accept-Encoding")).thenReturn("gzip")
- whenever(headerNames).thenReturn(
- Collections.enumeration(listOf("User-Agent", "referer", "Cookie", "Accept-Encoding", "Null"))
- )
- }
- return request
- }
-
- private fun loadPropertyValue(key: String): String {
- return File("local.properties")
- .takeIf { it.exists() }
- ?.let { file ->
- FileInputStream(file).use { fis ->
- Properties().apply { load(fis) }.getProperty(key, "")
- }
- }.orEmpty()
- }
- }
-
- @Nested
- @DisplayName("Comment Tests")
- inner class CommentTests {
- @Test
- fun checkComment() {
- with(akismet) {
- assertFalse(checkComment(comment), "checkComment(admin)")
- assertThat(akismet::response).isEqualTo("false")
-
- comment.userRole = ""
- assertTrue(checkComment(comment), "checkComment()")
- assertThat(akismet::response).isEqualTo("true")
-
- assertFalse(checkComment(mockComment), "checkComment(mock)")
- assertThat(akismet::response).isEqualTo("false")
-
- mockComment.userRole = ""
- assertTrue(checkComment(mockComment), "checkComment(mock)")
- assertThat(akismet::response).isEqualTo("true")
-
- assertThat(akismet::httpStatusCode).isEqualTo(200)
-
- comment.userRole = AkismetComment.ADMIN_ROLE
- }
- }
-
- @Test
- fun emptyComment() {
- assertThrows(
- java.lang.IllegalArgumentException::class.java
- ) { akismet.checkComment(AkismetComment("", "")) }
-
-
- val empty = AkismetComment("", "")
- assertThat(empty, "AkismetComment(empty)").all {
- prop(AkismetComment::isTest).isFalse()
- prop(AkismetComment::referrer).isEqualTo("")
- prop(AkismetComment::permalink).isEqualTo("")
- prop(AkismetComment::type).isEqualTo(CommentType.NONE)
- prop(AkismetComment::authorEmail).isEqualTo("")
- prop(AkismetComment::author).isEqualTo("")
- prop(AkismetComment::authorUrl).isEqualTo("")
- prop(AkismetComment::content).isEqualTo("")
- prop(AkismetComment::dateGmt).isEqualTo("")
- prop(AkismetComment::postModifiedGmt).isEqualTo("")
- prop(AkismetComment::blogLang).isEqualTo("")
- prop(AkismetComment::blogCharset).isEqualTo("")
- prop(AkismetComment::userRole).isEqualTo("")
- prop(AkismetComment::recheckReason).isEqualTo("")
- prop(AkismetComment::serverEnv).size().isEqualTo(0)
- }
-
- with(receiver = empty) {
- for (s in listOf("test", "", null)) {
- referrer = s
- permalink = s
- if (s != null) type = CommentType(s)
- authorEmail = s
- author = s
- authorUrl = s
- content = s
- dateGmt = s
- postModifiedGmt = s
- blogLang = s
- blogCharset = s
- userRole = s
- recheckReason = s
-
- val expected = if (s.isNullOrEmpty()) "" else s
-
- assertThat(empty, "AkismetComment($s)").all {
- prop(AkismetComment::referrer).isEqualTo(expected)
- prop(AkismetComment::permalink).isEqualTo(expected)
- prop(AkismetComment::type).isEqualTo(CommentType(expected))
- prop(AkismetComment::authorEmail).isEqualTo(expected)
- prop(AkismetComment::author).isEqualTo(expected)
- prop(AkismetComment::authorUrl).isEqualTo(expected)
- prop(AkismetComment::content).isEqualTo(expected)
- prop(AkismetComment::dateGmt).isEqualTo(expected)
- prop(AkismetComment::postModifiedGmt).isEqualTo(expected)
- prop(AkismetComment::blogLang).isEqualTo(expected)
- prop(AkismetComment::blogCharset).isEqualTo(expected)
- prop(AkismetComment::userRole).isEqualTo(expected)
- prop(AkismetComment::recheckReason).isEqualTo(expected)
- prop(AkismetComment::serverEnv).size().isEqualTo(0)
- }
- }
- }
- }
-
- @Test
- fun mockComment() {
- assertThat(mockComment, "mockComment").all {
- prop(AkismetComment::userIp).isEqualTo(comment.userIp)
- prop(AkismetComment::userAgent).isEqualTo(comment.userAgent)
- prop(AkismetComment::referrer).isEqualTo(comment.referrer)
- prop(AkismetComment::serverEnv).all {
- key("HTTP_ACCEPT_ENCODING").isEqualTo("gzip")
- key("REMOTE_ADDR").isEqualTo(comment.userIp)
- key("HTTP_NULL").isEmpty()
- size().isEqualTo(6)
- }
- }
- }
+ private val apiKey = TestUtils.getKey("AKISMET_API_KEY")
+ private val blog = TestUtils.getKey("AKISMET_BLOG")
}
@Nested
@DisplayName("Constructor Tests")
inner class ConstructorTests {
@Test
- fun apiKeyTooLong() {
- assertThrows(
- IllegalArgumentException::class.java
- ) {
- Akismet("123456789 12")
- }
- }
-
- @Test
- fun apiKeyTooShort() {
- assertThrows(
- IllegalArgumentException::class.java
- ) {
- Akismet("1234")
- }
- }
-
- @Test
- fun invalidKeyAndBlog() {
- assertThrows(
- IllegalArgumentException::class.java
- ) {
- Akismet("1234", "foo")
- }
- }
-
- @Test
- fun noApiKey() {
+ fun `Constructor with API key arg empty`() {
assertThrows(
IllegalArgumentException::class.java
) {
@@ -321,64 +82,159 @@ class AkismetTests {
}
@Test
- fun noBlog() {
+ fun `Constructor with API key arg too long`() {
+ assertThrows(
+ IllegalArgumentException::class.java
+ ) {
+ Akismet("123456789 12")
+ }
+ }
+
+ @Test
+ fun `Constructor with API key arg too short`() {
+ assertThrows(
+ IllegalArgumentException::class.java
+ ) {
+ Akismet("1234")
+ }
+ }
+
+ @Test
+ fun `Constructor with empty blog arg`() {
assertThrows(
IllegalArgumentException::class.java
) {
Akismet("123456789012", "")
}
}
- }
- @Test
- fun dateToGmt() {
- val localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
- val utcDate = Akismet.dateToGmt(date)
- assertEquals(Akismet.dateToGmt(localDateTime), utcDate, "dateGmt(localDateTime)")
- assertThat(comment::dateGmt).isEqualTo(utcDate)
+ @Test
+ fun `Constructor with invalid key and blog args`() {
+ assertThrows(
+ IllegalArgumentException::class.java
+ ) {
+ Akismet("1234", "foo")
+ }
+ }
}
@Nested
- @DisplayName("JSON Comment Tests")
- inner class JsonCommentTest {
+ @DisplayName("Date Conversion Tests")
+ inner class DateConversionTests {
+ val sampleDate: ZonedDateTime = LocalDateTime.of(1997, 8, 29, 2, 0, 0)
+ .atZone(ZoneId.of("America/New_York"))
+
@Test
- fun jsonCommentEqualHashCode() {
- val jsonComment = Akismet.jsonComment(mockComment.toJson())
- assertEquals(
- jsonComment.hashCode(),
- mockComment.hashCode(),
- "jsonComment.hashCode = mockComment.hashcode"
- )
+ fun `Date should convert correctly to GMT string`() {
+ val date = Date.from(sampleDate.toInstant())
+ val result = Akismet.dateToGmt(date)
+ assertEquals("1997-08-28T23:00:00-07:00", result)
}
@Test
- fun jsonCommentEqualsMockComment() {
- val jsonComment = Akismet.jsonComment(mockComment.toJson())
- assertEquals(jsonComment, mockComment, "jsonComment = mockComment")
+ fun `LocalDateTime should convert correctly to GMT string`() {
+ val result = Akismet.dateToGmt(sampleDate.toLocalDateTime())
+ assertEquals("1997-08-29T02:00:00-07:00", result)
+ }
+ }
+
+ @Nested
+ @DisplayName("JSON Deserialization Tests")
+ inner class JsonDeserializationTests {
+ @Test
+ fun `Validate JSON deserialization`() {
+ val config = CommentConfig.Builder("127.0.0.1", "Mozilla/5.0")
+ .referrer("https://example.com")
+ .type(CommentType.COMMENT)
+ .author("John Doe")
+ .authorEmail("john.doe@example.com")
+ .authorUrl("https://johndoe.com")
+ .content("This is a comment")
+ .dateGmt("2023-10-20T10:20:30Z")
+ .postModifiedGmt("2023-10-20T11:00:00Z")
+ .blogLang("en")
+ .blogCharset("UTF-8")
+ .userRole("administrator")
+ .isTest(true)
+ .recheckReason("Check the spam detection")
+ .serverEnv(mapOf("key1" to "value1", "key2" to "value2"))
+ .build()
+ val validJson = AkismetComment(config).toJson();
+
+ val comment = jsonComment(validJson)
+
+ Assertions.assertEquals("127.0.0.1", comment.userIp)
+ Assertions.assertEquals("Mozilla/5.0", comment.userAgent)
+ Assertions.assertEquals("https://example.com", comment.referrer)
+ Assertions.assertEquals("comment", comment.type.value)
+ Assertions.assertEquals("John Doe", comment.author)
+ Assertions.assertEquals("john.doe@example.com", comment.authorEmail)
+ Assertions.assertEquals("https://johndoe.com", comment.authorUrl)
+ Assertions.assertEquals("This is a comment", comment.content)
+ Assertions.assertEquals("2023-10-20T10:20:30Z", comment.dateGmt)
+ Assertions.assertEquals("2023-10-20T11:00:00Z", comment.postModifiedGmt)
+ Assertions.assertEquals("en", comment.blogLang)
+ Assertions.assertEquals("UTF-8", comment.blogCharset)
+ Assertions.assertEquals("administrator", comment.userRole)
+ Assertions.assertTrue(comment.isTest)
+ Assertions.assertEquals("Check the spam detection", comment.recheckReason)
+ Assertions.assertEquals(mapOf("key1" to "value1", "key2" to "value2"), comment.serverEnv)
}
@Test
- fun jsonCommentNotEqualsComment() {
- val jsonComment = Akismet.jsonComment(mockComment.toJson())
- assertNotEquals(jsonComment, comment, "json")
- assertNotEquals(
- jsonComment.hashCode(),
- comment.hashCode(),
- "jsonComment.hashCode != mockComment.hashcode"
- )
+ fun `Invalid JSON deserialization`() {
+ val invalidJson = """
+ {
+ "userIp": "127.0.0.1",
+ "userAgent": "Mozilla/5.0"
+ // Missing closing brace
+ """.trimIndent()
+
+ val exception = Assertions.assertThrows(SerializationException::class.java) {
+ jsonComment(invalidJson)
+ }
+
+ Assertions.assertTrue(exception.message?.contains("Unexpected JSON token") == true)
}
@Test
- fun jsonCommentNotEqualsMockComment() {
- val jsonComment = Akismet.jsonComment(mockComment.toJson())
- jsonComment.recheckReason = ""
- assertNotEquals(jsonComment, mockComment, "jsonComment != jsonComment")
+ @OptIn(ExperimentalSerializationApi::class)
+ fun `Empty JSON deserialization`() {
+ val emptyJson = "{}"
+
+ Assertions.assertThrows(MissingFieldException::class.java) {
+ jsonComment(emptyJson)
+ }
}
@Test
- fun jsonCommentNotEqualsThis() {
- Akismet.jsonComment(mockComment.toJson())
- assertThat(this, "this != comment").isNotEqualTo(comment)
+ @OptIn(ExperimentalSerializationApi::class)
+ fun `JSON deserialization with missing mandatory fields`() {
+ val partialJson = """
+ {
+ "userIp": "127.0.0.1"
+ }
+ """.trimIndent()
+
+ Assertions.assertThrows(MissingFieldException::class.java) {
+ jsonComment(partialJson)
+ }
+ }
+
+ @Test
+ fun `JSON deserialization with unexpected fields`() {
+ val extraFieldJson = """
+ {
+ "userIp": "127.0.0.1",
+ "userAgent": "Mozilla/5.0",
+ "extraField": "unexpected"
+ }
+ """.trimIndent()
+
+ val comment = jsonComment(extraFieldJson)
+
+ Assertions.assertEquals("127.0.0.1", comment.userIp)
+ Assertions.assertEquals("Mozilla/5.0", comment.userAgent)
}
}
@@ -386,30 +242,14 @@ class AkismetTests {
@DisplayName("Response Tests")
inner class ResponseTests {
@Test
- fun emptyResponse() {
- assertTrue(
- akismet.executeMethod(
- "https://postman-echo.com/status/200".toHttpUrl(), emptyFormBody, true
- )
- )
- var expected = "{\n \"status\": 200\n}"
- assertThat(akismet, "executeMethod(200)").all {
- prop(Akismet::response).isEqualTo(expected)
- prop(Akismet::errorMessage).contains(expected)
- }
-
- akismet.reset()
- assertThat(akismet, "akismet.reset()").all {
- prop(Akismet::httpStatusCode).isEqualTo(0)
- prop(Akismet::errorMessage).isEmpty()
- }
-
+ fun `Handle blank response`() {
+ val akismet = Akismet(apiKey)
assertTrue(
akismet.executeMethod(
"https://erik.thauvin.net/blank.html".toHttpUrl(), emptyFormBody, true
)
)
- expected = ""
+ val expected = ""
assertThat(akismet, "executeMethod(blank)").all {
prop(Akismet::response).isEqualTo(expected)
prop(Akismet::errorMessage).contains("blank")
@@ -417,12 +257,17 @@ class AkismetTests {
}
@Test
- fun executeMethod() {
+ fun `Handle debug help header`() {
+ val akismet = Akismet(apiKey)
akismet.executeMethod(
"https://$apiKey.rest.akismet.com/1.1/comment-check".toHttpUrl(),
FormBody.Builder().apply { add("is_test", "1") }.build()
)
- assertThat(akismet::debugHelp).isNotEmpty()
+
+ assertThat(akismet, "x-akismet-debug-help").all {
+ prop(Akismet::httpStatusCode).isEqualTo(200)
+ prop(Akismet::debugHelp).isEqualTo("Empty \"blog\" value")
+ }
akismet.reset()
assertThat(akismet, "akismet.reset()").all {
@@ -433,20 +278,44 @@ class AkismetTests {
}
@Test
- fun invalidApi() {
+ fun `Handle invalid response`() {
+ val akismet = Akismet(apiKey)
+ assertTrue(
+ akismet.executeMethod(
+ "https://postman-echo.com/status/200".toHttpUrl(), emptyFormBody, true
+ )
+ )
+ val expected = "{\n \"status\": 200\n}"
+ assertThat(akismet, "executeMethod(200)").all {
+ prop(Akismet::response).isEqualTo(expected)
+ prop(Akismet::errorMessage).contains(expected)
+ }
+
+ akismet.reset()
+ assertThat(akismet, "akismet.reset()").all {
+ prop(Akismet::httpStatusCode).isEqualTo(0)
+ prop(Akismet::errorMessage).isEmpty()
+ }
+ }
+
+ @Test
+ fun `Handle IO error`() {
+ val akismet = Akismet(apiKey)
+ akismet.executeMethod("https://www.foobarxyz.com".toHttpUrl(), emptyFormBody)
+ assertThat(akismet).prop(Akismet::errorMessage).contains("IO error")
+ }
+
+ @Test
+ fun `Handle invalid API URL`() {
+ val akismet = Akismet(apiKey)
assertThrows(
java.lang.IllegalArgumentException::class.java
) { akismet.executeMethod("https://.com".toHttpUrl(), emptyFormBody) }
}
@Test
- fun ioError() {
- akismet.executeMethod("https://www.foobarxyz.com".toHttpUrl(), emptyFormBody)
- assertThat(akismet::errorMessage).contains("IO error")
- }
-
- @Test
- fun proTip() {
+ fun `Handle pro tip header`() {
+ val akismet = Akismet(apiKey)
assertFalse(
akismet.executeMethod(
"https://postman-echo.com/response-headers?x-akismet-pro-tip=discard".toHttpUrl(),
@@ -465,90 +334,70 @@ class AkismetTests {
prop(Akismet::httpStatusCode).isEqualTo(0)
}
}
- }
- @Nested
- @DisplayName("Submit Test")
- inner class SubmitTests {
- @Test
- fun submitHam() {
- assertTrue(akismet.submitHam(comment), "submitHam")
- }
- @Test
- fun submitHamMocked() {
- assertTrue(akismet.submitHam(mockComment), "submitHam(mock)")
- }
+ @Nested
+ @DisplayName("Validation Tests")
+ inner class ValidationTests {
+ @Test
+ fun `Validate api key`() {
+ val akismet = Akismet(apiKey, blog)
+ assertThat(akismet, "akismet").all {
+ prop(Akismet::isVerifiedKey).isFalse()
+ prop(Akismet::verifyKey).isTrue()
+ prop(Akismet::response).isEqualTo("valid")
+ prop(Akismet::isVerifiedKey).isTrue()
+ }
- @Test
- fun submitSpam() {
- assertTrue(akismet.submitSpam(comment), "submitHam")
- }
+ akismet.reset()
+ assertThat(akismet, "akismet.reset()").all {
+ prop(Akismet::isVerifiedKey).isFalse()
+ prop(Akismet::response).isEmpty()
+ prop(Akismet::httpStatusCode).isEqualTo(0)
+ }
- @Test
- fun submitSpamMocked() {
- assertTrue(akismet.submitSpam(mockComment), "submitHam(mock)")
- }
- }
-
- @Nested
- @DisplayName("User Agent Tests")
- inner class UserAgentTests {
- val libAgent = "${GeneratedVersion.PROJECT}/${GeneratedVersion.VERSION}"
-
- @Test
- fun userAgentCustom() {
- akismet.appUserAgent = "My App/1.0"
-
- assertEquals(
- akismet.buildUserAgent(),
- "${akismet.appUserAgent} | $libAgent",
- "buildUserAgent(My App/1.0)"
- )
- }
-
- @Test
- fun userAgentDefault() {
- assertEquals(akismet.buildUserAgent(), libAgent, "buildUserAgent($libAgent)")
- }
- }
-
- @Nested
- @DisplayName("Validation Tests")
- inner class ValidationTests {
- @Test
- fun blogProperty() {
- assertThrows(IllegalArgumentException::class.java) {
- akismet.blog = ""
+ assertThat(Akismet("123456789012"), "akismet(123456789012)")
+ .prop(Akismet::verifyKey)
+ .isFalse()
}
- assertThat(akismet::blog).isEqualTo(blog)
- }
+ @Test
+ fun `Validate blog property`() {
+ val akismet = Akismet(apiKey, blog)
+ assertThrows(IllegalArgumentException::class.java) {
+ akismet.blog = ""
+ }
- @Test
- fun validateConfig() {
- assertThat(AkismetComment(config)).isEqualTo(comment)
- }
-
- @Test
- fun verifyKey() {
- assertThat(akismet, "akismet").all {
- prop(Akismet::isVerifiedKey).isFalse()
- prop(Akismet::verifyKey).isTrue()
- prop(Akismet::response).isEqualTo("valid")
- prop(Akismet::isVerifiedKey).isTrue()
+ assertThat(akismet).prop(Akismet::blog).isEqualTo(blog)
}
- akismet.reset()
- assertThat(akismet, "akismet.reset()").all {
- prop(Akismet::isVerifiedKey).isFalse()
- prop(Akismet::response).isEmpty()
- prop(Akismet::httpStatusCode).isEqualTo(0)
- }
- assertThat(Akismet("123456789012"), "akismet(123456789012)")
- .prop(Akismet::verifyKey)
- .isFalse()
+ @Nested
+ @DisplayName("User Agent Validation Tests")
+ inner class UserAgentValidationTests {
+ val libAgent = "${GeneratedVersion.PROJECT}/${GeneratedVersion.VERSION}"
+
+ @Test
+ fun `Validate custom user agent`() {
+ val akismet = Akismet(apiKey)
+ akismet.appUserAgent = "My App/1.0"
+
+ assertEquals(
+ "${akismet.appUserAgent} | $libAgent",
+ akismet.buildUserAgent(),
+ "buildUserAgent(My App/1.0)"
+ )
+ }
+
+ @Test
+ fun `Validate default user agent`() {
+ val akismet = Akismet(apiKey)
+ assertEquals(
+ libAgent, akismet.buildUserAgent(),
+ "buildUserAgent($libAgent)"
+ )
+ }
+ }
}
}
}
diff --git a/src/test/kotlin/net/thauvin/erik/akismet/BeforeAllTests.kt b/src/test/kotlin/net/thauvin/erik/akismet/BeforeAllTests.kt
new file mode 100644
index 0000000..364e36d
--- /dev/null
+++ b/src/test/kotlin/net/thauvin/erik/akismet/BeforeAllTests.kt
@@ -0,0 +1,51 @@
+/*
+ * BeforeAllTests.kt
+ *
+ * Copyright 2019-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.akismet
+
+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(Akismet.logger) {
+ addHandler(ConsoleHandler().apply { level = Level.FINE })
+ level = Level.FINE
+ }
+ }
+ }
+}
diff --git a/src/test/kotlin/net/thauvin/erik/akismet/CommentConfigTests.kt b/src/test/kotlin/net/thauvin/erik/akismet/CommentConfigTests.kt
index 6f81ba5..e2abad4 100644
--- a/src/test/kotlin/net/thauvin/erik/akismet/CommentConfigTests.kt
+++ b/src/test/kotlin/net/thauvin/erik/akismet/CommentConfigTests.kt
@@ -39,7 +39,7 @@ import kotlin.test.assertTrue
class CommentConfigTests {
@Test
- fun `test default optional fields`() {
+ fun `Default optional fields`() {
val commentConfig = CommentConfig.Builder("192.168.0.1", "DefaultAgent").build()
assertEquals("", commentConfig.referrer)
@@ -60,7 +60,7 @@ class CommentConfigTests {
}
@Test
- fun `test empty server environment`() {
+ fun `Empty server environment`() {
val commentConfig = CommentConfig.Builder("127.0.0.1", "TestUserAgent")
.serverEnv(emptyMap())
.build()
@@ -69,7 +69,7 @@ class CommentConfigTests {
@Test
- fun `test invalid inputs for mandatory fields`() {
+ fun `Invalid inputs for mandatory fields`() {
try {
CommentConfig.Builder("", "UserAgent").build()
} catch (e: IllegalArgumentException) {
@@ -88,7 +88,7 @@ class CommentConfigTests {
@DisplayName("Builder Tests")
inner class BuilderTests {
@Test
- fun `test builder with all optional fields`() {
+ fun `Builder with all optional fields`() {
val builder = CommentConfig.Builder("127.0.0.1", "TestUserAgent")
.referrer("http://example.com")
.permalink("http://example.com/post")
@@ -128,7 +128,7 @@ class CommentConfigTests {
}
@Test
- fun `test builder with mandatory fields only`() {
+ fun `Builder with mandatory fields only`() {
val builder = CommentConfig.Builder("127.0.0.1", "TestUserAgent")
val commentConfig = builder.build()
@@ -152,7 +152,7 @@ class CommentConfigTests {
}
@Test
- fun `test builder with modified mandatory fields`() {
+ fun `Builder with modified mandatory fields`() {
val builder = CommentConfig.Builder("127.0.0.1", "TestUserAgent")
.userIp("192.168.1.1")
.userAgent("ModifiedUserAgent")
diff --git a/src/test/kotlin/net/thauvin/erik/akismet/CommentTypeTest.kt b/src/test/kotlin/net/thauvin/erik/akismet/CommentTypeTest.kt
index a3101cb..35bb4b4 100644
--- a/src/test/kotlin/net/thauvin/erik/akismet/CommentTypeTest.kt
+++ b/src/test/kotlin/net/thauvin/erik/akismet/CommentTypeTest.kt
@@ -36,67 +36,67 @@ import org.junit.jupiter.api.Test
class CommentTypeTest {
@Test
- fun `verify BLOG_POST value`() {
+ fun `Verify BLOG_POST value`() {
val commentType = CommentType.BLOG_POST
assertEquals("blog-post", commentType.value)
}
@Test
- fun `verify COMMENT value`() {
+ fun `Verify COMMENT value`() {
val commentType = CommentType.COMMENT
assertEquals("comment", commentType.value)
}
@Test
- fun `verify CONTACT_FORM value`() {
+ fun `Verify CONTACT_FORM value`() {
val commentType = CommentType.CONTACT_FORM
assertEquals("contact-form", commentType.value)
}
@Test
- fun `verify FORUM_POST value`() {
+ fun `Verify FORUM_POST value`() {
val commentType = CommentType.FORUM_POST
assertEquals("forum-post", commentType.value)
}
@Test
- fun `verify MESSAGE value`() {
+ fun `Verify MESSAGE value`() {
val commentType = CommentType.MESSAGE
assertEquals("message", commentType.value)
}
@Test
- fun `verify NONE value`() {
+ fun `Verify NONE value`() {
val commentType = CommentType.NONE
assertEquals("", commentType.value)
}
@Test
- fun `verify PINGBACK value`() {
+ fun `Verify PINGBACK value`() {
val commentType = CommentType.PINGBACK
assertEquals("pingback", commentType.value)
}
@Test
- fun `verify REPLY value`() {
+ fun `Verify REPLY value`() {
val commentType = CommentType.REPLY
assertEquals("reply", commentType.value)
}
@Test
- fun `verify SIGNUP value`() {
+ fun `Verify SIGNUP value`() {
val commentType = CommentType.SIGNUP
assertEquals("signup", commentType.value)
}
@Test
- fun `verify TRACKBACK value`() {
+ fun `Verify TRACKBACK value`() {
val commentType = CommentType.TRACKBACK
assertEquals("trackback", commentType.value)
}
@Test
- fun `verify TWEET value`() {
+ fun `Verify TWEET value`() {
val commentType = CommentType.TWEET
assertEquals("tweet", commentType.value)
}
diff --git a/src/test/kotlin/net/thauvin/erik/akismet/TestUtils.kt b/src/test/kotlin/net/thauvin/erik/akismet/TestUtils.kt
new file mode 100644
index 0000000..e3a06a0
--- /dev/null
+++ b/src/test/kotlin/net/thauvin/erik/akismet/TestUtils.kt
@@ -0,0 +1,52 @@
+/*
+ * TestUtils.kt
+ *
+ * Copyright 2019-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.akismet
+
+import java.io.File
+import java.io.FileInputStream
+import java.util.Properties
+
+object TestUtils {
+ fun getKey(key: String): String {
+ return System.getenv(key)?.takeUnless { it.isBlank() } ?: loadPropertyValue(key)
+ }
+
+ private fun loadPropertyValue(key: String): String {
+ return File("local.properties")
+ .takeIf { it.exists() }
+ ?.let { file ->
+ FileInputStream(file).use { fis ->
+ Properties().apply { load(fis) }.getProperty(key, "")
+ }
+ }.orEmpty()
+ }
+}