Implemented errorMessage and JSON (de)serialization.

This commit is contained in:
Erik C. Thauvin 2019-09-22 12:03:53 -07:00
parent 139048d7c4
commit 8a20a5cdb2
5 changed files with 154 additions and 43 deletions

View file

@ -16,6 +16,7 @@ plugins {
id("net.thauvin.erik.gradle.semver") version "1.0.4" id("net.thauvin.erik.gradle.semver") version "1.0.4"
id("org.jetbrains.dokka") version "0.9.18" id("org.jetbrains.dokka") version "0.9.18"
id("org.jetbrains.kotlin.kapt").version("1.3.50") id("org.jetbrains.kotlin.kapt").version("1.3.50")
id("org.jetbrains.kotlin.plugin.serialization").version("1.3.50")
id("org.jmailen.kotlinter") version "2.1.1" id("org.jmailen.kotlinter") version "2.1.1"
id("org.sonarqube") version "2.7.1" id("org.sonarqube") version "2.7.1"
} }
@ -59,6 +60,7 @@ dependencies {
compile("com.squareup.okhttp3:logging-interceptor:4.2.0") compile("com.squareup.okhttp3:logging-interceptor:4.2.0")
compile(kotlin("stdlib")) compile(kotlin("stdlib"))
compile("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.13.0")
testImplementation("org.mockito:mockito-core:3.0.0") testImplementation("org.mockito:mockito-core:3.0.0")
testImplementation("org.testng:testng:7.0.0") testImplementation("org.testng:testng:7.0.0")

View file

@ -2,12 +2,15 @@
<SmellBaseline> <SmellBaseline>
<Blacklist></Blacklist> <Blacklist></Blacklist>
<Whitelist> <Whitelist>
<ID>ComplexMethod:Akismet.kt$Akismet$private fun buildFormBody( userIp: String, userAgent: String, referrer: String, permalink: String, type: String, author: String, authorEmail: String, authorUrl: String, content: String, dateGmt: String, postModifiedGmt: String, blogLang: String, blogCharset: String, userRole: String, isTest: Boolean, recheckReason: String, other: Map&lt;String, String&gt; ): FormBody</ID> <ID>ComplexMethod:Akismet.kt$Akismet$ @JvmOverloads fun executeMethod(apiUrl: HttpUrl?, formBody: FormBody, trueOnError: Boolean = false): Boolean</ID>
<ID>LongMethod:Akismet.kt$Akismet$private fun buildFormBody( userIp: String, userAgent: String, referrer: String, permalink: String, type: String, author: String, authorEmail: String, authorUrl: String, content: String, dateGmt: String, postModifiedGmt: String, blogLang: String, blogCharset: String, userRole: String, isTest: Boolean, recheckReason: String, other: Map&lt;String, String&gt; ): FormBody</ID> <ID>ComplexMethod:Akismet.kt$Akismet$private fun buildFormBody(comment: AkismetComment): FormBody</ID>
<ID>ComplexMethod:AkismetComment.kt$AkismetComment$ @Suppress("DuplicatedCode") override fun equals(other: Any?): Boolean</ID>
<ID>MagicNumber:Akismet.kt$Akismet$12</ID> <ID>MagicNumber:Akismet.kt$Akismet$12</ID>
<ID>MagicNumber:Akismet.kt$Akismet.&lt;no name provided&gt;$8</ID> <ID>MagicNumber:Akismet.kt$Akismet.&lt;no name provided&gt;$8</ID>
<ID>NestedBlockDepth:Akismet.kt$Akismet$ @Suppress("MemberVisibilityCanBePrivate") protected fun executeMethod(apiUrl: HttpUrl?, formBody: FormBody): Boolean</ID> <ID>MaxLineLength:Akismet.kt$Akismet$/** * The _x-akismet-pro-tip_ header from the last operation, if any. * * If the _x-akismet-pro-tip_ header is set to discard, then Akismet has determined that the comment is blatant spam, * and you can safely discard it without saving it in any spam queue. * * Read more about this feature in this * [Akismet blog post](https://blog.akismet.com/2014/04/23/theres-a-ninja-in-your-akismet/). * * @see [Akismet.isDiscard] */ @Suppress("MemberVisibilityCanBePrivate") var proTip: String = "" private set</ID>
<ID>NestedBlockDepth:AkismetTest.kt$fun getApiKey(): String</ID> <ID>NestedBlockDepth:Akismet.kt$Akismet$ @JvmOverloads fun executeMethod(apiUrl: HttpUrl?, formBody: FormBody, trueOnError: Boolean = false): Boolean</ID>
<ID>NestedBlockDepth:AkismetTest.kt$fun getKey(key: String): String</ID>
<ID>ReturnCount:Akismet.kt$Akismet$ @JvmOverloads fun executeMethod(apiUrl: HttpUrl?, formBody: FormBody, trueOnError: Boolean = false): Boolean</ID>
<ID>TooManyFunctions:Akismet.kt$Akismet</ID> <ID>TooManyFunctions:Akismet.kt$Akismet</ID>
</Whitelist> </Whitelist>
</SmellBaseline> </SmellBaseline>

View file

@ -31,6 +31,8 @@
*/ */
package net.thauvin.erik.akismet package net.thauvin.erik.akismet
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import net.thauvin.erik.semver.Version import net.thauvin.erik.semver.Version
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl import okhttp3.HttpUrl
@ -108,6 +110,16 @@ open class Akismet(apiKey: String) {
var response: String = "" var response: String = ""
private set private set
/**
* The error message.
*
* The error (IO, empty response from Akismet, etc.) message is also logged as a warning.
*
* @see [Akismet.checkComment]
*/
var errorMessage: String = ""
private set
/** /**
* The _x-akismet-pro-tip_ header from the last operation, if any. * The _x-akismet-pro-tip_ header from the last operation, if any.
* *
@ -269,6 +281,15 @@ open class Akismet(apiKey: String) {
return executeMethod(buildApiUrl("submit-ham"), buildFormBody(comment)) return executeMethod(buildApiUrl("submit-ham"), buildFormBody(comment))
} }
/**
* (Re)Create a [comment][AkismetComment] from a JSON string.
*
* @see [AkismetComment.toString]
*/
fun jsonComment(json: String): AkismetComment {
return Json(JsonConfiguration.Stable).parse(AkismetComment.serializer(), json)
}
/** /**
* Convert a date to a UTC timestamp. (ISO 8601) * Convert a date to a UTC timestamp. (ISO 8601)
* *
@ -304,7 +325,11 @@ open class Akismet(apiKey: String) {
fun executeMethod(apiUrl: HttpUrl?, formBody: FormBody, trueOnError: Boolean = false): Boolean { fun executeMethod(apiUrl: HttpUrl?, formBody: FormBody, trueOnError: Boolean = false): Boolean {
reset() reset()
if (apiUrl != null) { if (apiUrl != null) {
val request = Request.Builder().url(apiUrl).post(formBody).header("User-Agent", buildUserAgent()).build() val request = if (formBody.size == 0) {
Request.Builder().url(apiUrl).header("User-Agent", buildUserAgent()).build()
} else {
Request.Builder().url(apiUrl).post(formBody).header("User-Agent", buildUserAgent()).build()
}
try { try {
val result = client.newCall(request).execute() val result = client.newCall(request).execute()
httpStatusCode = result.code httpStatusCode = result.code
@ -317,26 +342,28 @@ open class Akismet(apiKey: String) {
if (response == "valid" || response == "true" || response.startsWith("Thanks")) { if (response == "valid" || response == "true" || response.startsWith("Thanks")) {
return true return true
} else if (response != "false" && response != "invalid") { } else if (response != "false" && response != "invalid") {
logger.warning("Unexpected response: $body") errorMessage = "Unexpected response: " + if (body.isBlank()) "(0-byte body)" else body
return trueOnError
} }
} else { } else {
val message = "An empty response was received from Akismet." val message = "An empty response was received from Akismet."
if (debugHelp.isNotBlank()) { errorMessage = if (debugHelp.isNotBlank()) {
logger.warning("$message: $debugHelp") "$message: $debugHelp"
} else { } else {
logger.warning(message) message
} }
return trueOnError
} }
} catch (e: IOException) { } catch (e: IOException) {
logger.log(Level.SEVERE, "An IO error occurred while communicating with the Akismet service.", e) errorMessage = "An IO error occurred while communicating with the Akismet service."
return trueOnError
} }
} else { } else {
logger.severe("Invalid API end point URL.") errorMessage = "Invalid API end point URL."
}
if (errorMessage.isNotEmpty()) {
logger.warning(errorMessage)
return trueOnError return trueOnError
} }
return false return false
} }
@ -345,6 +372,7 @@ open class Akismet(apiKey: String) {
*/ */
fun reset() { fun reset() {
debugHelp = "" debugHelp = ""
errorMessage = ""
httpStatusCode = 0 httpStatusCode = 0
isDiscard = false isDiscard = false
isVerifiedKey = false isVerifiedKey = false

View file

@ -32,6 +32,9 @@
package net.thauvin.erik.akismet package net.thauvin.erik.akismet
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
/** /**
@ -50,6 +53,7 @@ import javax.servlet.http.HttpServletRequest
* @param userIp IP address of the comment submitter. * @param userIp IP address of the comment submitter.
* @param userAgent User agent string of the web browser submitting the comment. * @param userAgent User agent string of the web browser submitting the comment.
*/ */
@Serializable
open class AkismetComment(val userIp: String, val userAgent: String) { open class AkismetComment(val userIp: String, val userAgent: String) {
@Suppress("unused") @Suppress("unused")
companion object { companion object {
@ -171,27 +175,68 @@ open class AkismetComment(val userIp: String, val userAgent: String) {
} }
/** /**
* Returns a string representation of the comment. * Returns a JSON representation of the comment.
*
* @see [Akismet.jsonComment]
*/ */
override fun toString(): String { override fun toString(): String {
return this.javaClass.simpleName + return Json(JsonConfiguration.Stable).stringify(serializer(), this)
"(userIp=$userIp" + }
", userAgent=$userAgent" +
", referrer=$referrer" + /**
", permalink=$permalink" + * Indicates whether some other object is _equal to_ this one.
", type=$type" + */
", author=$author" + @Suppress("DuplicatedCode")
", authorEmail=$authorEmail" + override fun equals(other: Any?): Boolean {
", authorUrl=$authorUrl" + if (this === other) return true
", content=$content" + if (javaClass != other?.javaClass) return false
", dateGmt=$dateGmt" +
", postModifiedGmt=$postModifiedGmt" + other as AkismetComment
", blogLang=$blogLang" +
", blogCharset=$blogCharset" + if (userIp != other.userIp) return false
", userRole=$userRole" + if (userAgent != other.userAgent) return false
", isTest=$isTest" + if (referrer != other.referrer) return false
", recheckReason=$recheckReason" + if (permalink != other.permalink) return false
", serverEnv=$serverEnv)" if (type != other.type) return false
if (author != other.author) return false
if (authorEmail != other.authorEmail) return false
if (authorUrl != other.authorUrl) return false
if (content != other.content) return false
if (dateGmt != other.dateGmt) return false
if (postModifiedGmt != other.postModifiedGmt) return false
if (blogLang != other.blogLang) return false
if (blogCharset != other.blogCharset) return false
if (userRole != other.userRole) return false
if (isTest != other.isTest) return false
if (recheckReason != other.recheckReason) return false
if (serverEnv != other.serverEnv) return false
return true
}
/**
* Returns a hash code value for the object.
*/
@Suppress("DuplicatedCode")
override fun hashCode(): Int {
var result = userIp.hashCode()
result = 31 * result + userAgent.hashCode()
result = 31 * result + referrer.hashCode()
result = 31 * result + permalink.hashCode()
result = 31 * result + type.hashCode()
result = 31 * result + author.hashCode()
result = 31 * result + authorEmail.hashCode()
result = 31 * result + authorUrl.hashCode()
result = 31 * result + content.hashCode()
result = 31 * result + dateGmt.hashCode()
result = 31 * result + postModifiedGmt.hashCode()
result = 31 * result + blogLang.hashCode()
result = 31 * result + blogCharset.hashCode()
result = 31 * result + userRole.hashCode()
result = 31 * result + isTest.hashCode()
result = 31 * result + recheckReason.hashCode()
result = 31 * result + serverEnv.hashCode()
return result
} }
} }

View file

@ -37,6 +37,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.mockito.Mockito import org.mockito.Mockito
import org.testng.Assert.assertEquals import org.testng.Assert.assertEquals
import org.testng.Assert.assertFalse import org.testng.Assert.assertFalse
import org.testng.Assert.assertNotEquals
import org.testng.Assert.assertTrue import org.testng.Assert.assertTrue
import org.testng.Assert.expectThrows import org.testng.Assert.expectThrows
import org.testng.annotations.BeforeClass import org.testng.annotations.BeforeClass
@ -179,12 +180,37 @@ class AkismetTest {
} }
} }
@Test
fun testEmptyResponse() {
assertTrue(
akismet.executeMethod(
"https://postman-echo.com/status/200".toHttpUrlOrNull(), FormBody.Builder().build(), true
)
)
val expected = "{\"status\":200}"
assertEquals(akismet.response, expected, expected)
assertTrue(akismet.errorMessage.contains(expected), "errorMessage contains $expected")
}
@Test
fun testProTipResponse() {
assertFalse(
akismet.executeMethod(
"https://postman-echo.com/response-headers?x-akismet-pro-tip=test".toHttpUrlOrNull(),
FormBody.Builder().build()
)
)
assertEquals(akismet.proTip, "test")
}
@Test @Test
fun resetTest() { fun resetTest() {
akismet.reset() akismet.reset()
//@TODO fix
assertTrue( assertTrue(
akismet.debugHelp == "" && akismet.httpStatusCode == 0 && !akismet.isDiscard && akismet.debugHelp == "" && akismet.errorMessage == "" && akismet.httpStatusCode == 0 &&
!akismet.isVerifiedKey && akismet.proTip == "" && akismet.response == "" !akismet.isDiscard && !akismet.isVerifiedKey && akismet.proTip == "" && akismet.response == ""
) )
} }
@ -225,17 +251,24 @@ class AkismetTest {
assertTrue(akismet.submitSpam(mockComment), "submitHam(request)") assertTrue(akismet.submitSpam(mockComment), "submitHam(request)")
} }
@Test
fun testJsonComment() {
val jsonComment = akismet.jsonComment(mockComment.toString())
assertEquals(jsonComment, mockComment, "equals")
assertEquals(jsonComment.hashCode(), mockComment.hashCode(), "hashcode")
assertNotEquals(jsonComment, comment, "json is different")
assertNotEquals(jsonComment.hashCode(), comment.hashCode(), "json hashcode is different")
}
@Test @Test
fun testBuildUserAgent() { fun testBuildUserAgent() {
val libAgent = "${GeneratedVersion.PROJECT}/${GeneratedVersion.VERSION}" val libAgent = "${GeneratedVersion.PROJECT}/${GeneratedVersion.VERSION}"
assertEquals( assertEquals(akismet.buildUserAgent(), libAgent, "libAgent")
akismet.buildUserAgent(), libAgent, "libAgent"
)
akismet.applicationName = "My App"
assertEquals( akismet.applicationName = "My App"
akismet.buildUserAgent(), libAgent, "libAgent, no app" assertEquals(akismet.buildUserAgent(), libAgent, "libAgent, no app")
)
akismet.applicationVersion = "1.0-test" akismet.applicationVersion = "1.0-test"
assertEquals( assertEquals(