Skip to content

Commit

Permalink
test: add unit tests for ClassApiExporterHelper (#560)
Browse files Browse the repository at this point in the history
  • Loading branch information
tangcent authored Jan 3, 2025
1 parent 42969f3 commit 3b4ad22
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,25 @@ import java.util.concurrent.LinkedBlockingQueue
open class ClassApiExporterHelper {

@Inject
protected val jvmClassHelper: JvmClassHelper? = null
protected lateinit var jvmClassHelper: JvmClassHelper

@Inject
protected val ruleComputer: RuleComputer? = null
protected lateinit var ruleComputer: RuleComputer

@Inject
private val docHelper: DocHelper? = null
private lateinit var docHelper: DocHelper

@Inject
protected val psiClassHelper: PsiClassHelper? = null
protected lateinit var psiClassHelper: PsiClassHelper

@Inject
private val linkExtractor: LinkExtractor? = null
private lateinit var linkExtractor: LinkExtractor

@Inject
private val linkResolver: LinkResolver? = null
private lateinit var linkResolver: LinkResolver

@Inject
protected val duckTypeHelper: DuckTypeHelper? = null
protected lateinit var duckTypeHelper: DuckTypeHelper

@Inject
protected lateinit var actionContext: ActionContext
Expand All @@ -58,7 +58,7 @@ open class ClassApiExporterHelper {
protected lateinit var logger: Logger

@Inject
protected val classExporter: ClassExporter? = null
protected lateinit var classExporter: ClassExporter

@Inject
protected lateinit var messagesHelper: MessagesHelper
Expand All @@ -69,7 +69,7 @@ open class ClassApiExporterHelper {
companion object : Log()

fun extractParamComment(psiMethod: PsiMethod): MutableMap<String, Any?>? {
val subTagMap = docHelper!!.getSubTagMapOfDocComment(psiMethod, "param")
val subTagMap = docHelper.getSubTagMapOfDocComment(psiMethod, "param")
if (subTagMap.isEmpty()) {
return null
}
Expand All @@ -82,36 +82,34 @@ open class ClassApiExporterHelper {
if (value.notNullOrBlank()) {

val options: ArrayList<HashMap<String, Any?>> = ArrayList()
val comment = linkExtractor!!.extract(value, psiMethod, object : AbstractLinkResolve() {
val comment = linkExtractor.extract(value, psiMethod, object : AbstractLinkResolve() {

override fun linkToPsiElement(plainText: String, linkTo: Any?): String? {

psiClassHelper!!.resolveEnumOrStatic(
psiClassHelper.resolveEnumOrStatic(
plainText,
parameters.firstOrNull { it.name == name } ?: psiMethod,
name
)
?.let { options.addAll(it) }
)?.let { options.addAll(it) }

return super.linkToPsiElement(plainText, linkTo)
}

override fun linkToClass(plainText: String, linkClass: PsiClass): String? {
return linkResolver!!.linkToClass(linkClass)
return linkResolver.linkToClass(linkClass)
}

override fun linkToType(plainText: String, linkType: PsiType): String? {
return jvmClassHelper!!.resolveClassInType(linkType)?.let {
linkResolver!!.linkToClass(it)
return jvmClassHelper.resolveClassInType(linkType)?.let {
linkResolver.linkToClass(it)
}
}

override fun linkToField(plainText: String, linkField: PsiField): String? {
return linkResolver!!.linkToProperty(linkField)
return linkResolver.linkToProperty(linkField)
}

override fun linkToMethod(plainText: String, linkMethod: PsiMethod): String? {
return linkResolver!!.linkToMethod(linkMethod)
return linkResolver.linkToMethod(linkMethod)
}

override fun linkToUnresolved(plainText: String): String {
Expand All @@ -124,7 +122,6 @@ open class ClassApiExporterHelper {
methodParamComment["$name@options"] = options
}
}

}

return methodParamComment
Expand All @@ -137,7 +134,7 @@ open class ClassApiExporterHelper {
cls: PsiClass, handle: (ExplicitMethod) -> Unit,
) {
actionContext.runInReadUI {
val methods = duckTypeHelper!!.explicit(cls)
val methods = duckTypeHelper.explicit(cls)
.methods()
.filter { !shouldIgnore(it) }
actionContext.runAsync {
Expand All @@ -158,28 +155,28 @@ open class ClassApiExporterHelper {
}

protected open fun shouldIgnore(explicitElement: ExplicitMethod): Boolean {
if (ignoreIrregularApiMethod() && (jvmClassHelper!!.isBasicMethod(explicitElement.psi().name)
if (ignoreIrregularApiMethod() && (jvmClassHelper.isBasicMethod(explicitElement.psi().name)
|| explicitElement.psi().hasModifierProperty("static")
|| explicitElement.psi().isConstructor)
) {
return true
}
return ruleComputer!!.computer(ClassExportRuleKeys.IGNORE, explicitElement) ?: false
return ruleComputer.computer(ClassExportRuleKeys.IGNORE, explicitElement) ?: false
}

protected open fun shouldIgnore(psiMethod: PsiMethod): Boolean {
if (ignoreIrregularApiMethod() && (jvmClassHelper!!.isBasicMethod(psiMethod.name)
if (ignoreIrregularApiMethod() && (jvmClassHelper.isBasicMethod(psiMethod.name)
|| psiMethod.hasModifierProperty("static")
|| psiMethod.isConstructor)
) {
return true
}
return ruleComputer!!.computer(ClassExportRuleKeys.IGNORE, psiMethod) ?: false
return ruleComputer.computer(ClassExportRuleKeys.IGNORE, psiMethod) ?: false
}

fun foreachPsiMethod(cls: PsiClass, handle: (PsiMethod) -> Unit) {
actionContext.runInReadUI {
jvmClassHelper!!.getAllMethods(cls)
jvmClassHelper.getAllMethods(cls)
.asSequence()
.filter { !shouldIgnore(it) }
.forEach(handle)
Expand All @@ -193,7 +190,7 @@ open class ClassApiExporterHelper {
}

fun export(handle: (Doc) -> Unit) {
logger.info("Start export api...")
logger.info("Starting API export process...")
val psiClassQueue: BlockingQueue<PsiClass> = LinkedBlockingQueue()

val boundary = actionContext.createBoundary()
Expand Down Expand Up @@ -238,11 +235,11 @@ open class ClassApiExporterHelper {
}
} else {
val classQualifiedName = actionContext.callInReadUI { psiClass.qualifiedName }
LOG.info("wait api parsing... $classQualifiedName")
LOG.info("Processing API for class: $classQualifiedName")
actionContext.withBoundary {
classExporter!!.export(psiClass) { handle(it) }
classExporter.export(psiClass) { handle(it) }
}
LOG.info("api parse $classQualifiedName completed.")
LOG.info("Successfully parsed API for class: $classQualifiedName")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ open class SuvApiExporter {
val docs = classApiExporterHelper.export().map { DocWrapper(it) }

if (docs.isEmpty()) {
logger.info("No api be found!")
logger.info("No API found in the selected files")
return
}

Expand Down Expand Up @@ -153,11 +153,11 @@ open class SuvApiExporter {

abstract class ApiExporterAdapter {

@Inject(optional = true)
protected var logger: Logger? = null
@Inject
protected lateinit var logger: Logger

@Inject
protected val classExporter: ClassExporter? = null
protected lateinit var classExporter: ClassExporter

@Inject
protected lateinit var actionContext: ActionContext
Expand Down Expand Up @@ -202,13 +202,13 @@ open class SuvApiExporter {
try {
doExportApisFromMethod(requests)
} catch (e: Exception) {
logger!!.error("error to export apis:" + e.message)
logger!!.error("Failed to export APIs: " + e.message)
logger!!.traceError(e)
}
}
}
} catch (e: Throwable) {
logger!!.error("error to export apis:" + e.message)
logger!!.error("Failed to export APIs: " + e.message)
logger!!.traceError(e)
}
}
Expand Down Expand Up @@ -295,7 +295,7 @@ open class SuvApiExporter {
}

if (docs.isEmpty()) {
logger!!.info("no api has be found")
logger!!.info("No APIs found")
}

doExportDocs(docs)
Expand Down Expand Up @@ -341,10 +341,10 @@ open class SuvApiExporter {
private lateinit var postmanSettingsHelper: PostmanSettingsHelper

@Inject
private val fileSaveHelper: FileSaveHelper? = null
private lateinit var fileSaveHelper: FileSaveHelper

@Inject
private val postmanFormatter: PostmanFormatter? = null
private lateinit var postmanFormatter: PostmanFormatter

@Inject
private lateinit var project: Project
Expand Down Expand Up @@ -388,10 +388,10 @@ open class SuvApiExporter {
class MarkdownApiExporterAdapter : ApiExporterAdapter() {

@Inject
private val fileSaveHelper: FileSaveHelper? = null
private lateinit var fileSaveHelper: FileSaveHelper

@Inject
private val markdownFormatter: MarkdownFormatter? = null
private lateinit var markdownFormatter: MarkdownFormatter

@Inject
private lateinit var markdownSettingsHelper: MarkdownSettingsHelper
Expand Down Expand Up @@ -423,15 +423,15 @@ open class SuvApiExporter {
override fun doExportDocs(docs: MutableList<Doc>) {
try {
if (docs.isEmpty()) {
logger!!.info("No api be found to export!")
logger!!.info("No API found in the selected scope")
return
}
logger!!.info("Start parse apis")
val apiInfo = markdownFormatter!!.parseRequests(docs)
val apiInfo = markdownFormatter.parseRequests(docs)
docs.clear()
actionContext.runAsync {
try {
fileSaveHelper!!.saveOrCopy(apiInfo, markdownSettingsHelper.outputCharset(), {
fileSaveHelper.saveOrCopy(apiInfo, markdownSettingsHelper.outputCharset(), {
logger!!.info("Exported data are copied to clipboard,you can paste to a md file now")
}, {
logger!!.info("Apis save success: $it")
Expand Down Expand Up @@ -482,7 +482,7 @@ open class SuvApiExporter {
val requests = docs.filterAs(Request::class)
try {
if (docs.isEmpty()) {
logger!!.info("No api be found to export!")
logger!!.info("No API found in the selected scope")
return
}
curlExporter.export(requests)
Expand Down Expand Up @@ -524,7 +524,7 @@ open class SuvApiExporter {
val requests = docs.filterAs(Request::class)
try {
if (docs.isEmpty()) {
logger!!.info("No api be found to export!")
logger!!.info("No API found in the selected scope")
return
}
httpClientExporter.export(requests)
Expand All @@ -536,7 +536,7 @@ open class SuvApiExporter {

private fun doExport(channel: ApiExporterWrapper, requests: List<DocWrapper>) {
if (requests.isEmpty()) {
logger.info("no api has be selected")
logger.info("No API found in the selected scope")
return
}
val adapter = channel.adapter.createInstance() as ApiExporterAdapter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.itangcent.idea.plugin.api

import com.google.inject.Inject
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassOwner
import com.intellij.psi.PsiFile
import com.itangcent.common.model.Request
import com.itangcent.idea.plugin.api.export.core.ClassExporter
import com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporter
import com.itangcent.intellij.context.ActionContext
import com.itangcent.intellij.extend.guice.singleton
import com.itangcent.intellij.extend.guice.with
import com.itangcent.test.workAt
import com.itangcent.testFramework.PluginContextLightCodeInsightFixtureTestCase
import org.junit.jupiter.api.Assertions.assertInstanceOf
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*

/**
* Test case for [ClassApiExporterHelper]
*/
class ClassApiExporterHelperTest : PluginContextLightCodeInsightFixtureTestCase() {

@Inject
private lateinit var classApiExporterHelper: ClassApiExporterHelper

private lateinit var userCtrlPsiClass: PsiClass
private lateinit var userCtrlPsiFile: PsiFile


override fun beforeBind() {
super.beforeBind()
loadSource(Object::class)
loadSource(java.lang.Boolean::class)
loadSource(java.lang.String::class)
loadSource(java.lang.Integer::class)
loadSource(java.lang.Long::class)
loadSource(Collection::class)
loadSource(Map::class)
loadSource(List::class)
loadSource(LinkedList::class)
loadSource(LocalDate::class)
loadSource(LocalDateTime::class)
loadSource(HashMap::class)
loadFile("spring/GetMapping.java")
loadFile("spring/PutMapping.java")
loadFile("spring/ModelAttribute.java")
loadFile("spring/PostMapping.java")
loadFile("spring/RequestBody.java")
loadFile("spring/RequestMapping.java")
loadFile("spring/RequestHeader.java")
loadFile("spring/RequestParam.java")
loadFile("spring/RestController.java")
userCtrlPsiFile = loadFile("api/UserCtrl.java")!!
userCtrlPsiClass = (userCtrlPsiFile as? PsiClassOwner)?.classes?.firstOrNull()!!
}

override fun bind(builder: ActionContext.ActionContextBuilder) {
super.bind(builder)
builder.bind(ClassExporter::class) { it.with(SpringRequestClassExporter::class).singleton() }
builder.workAt(userCtrlPsiFile)
}

fun testExtractParamComment() {
val method = userCtrlPsiClass.methods.first { it.name == "get" }
val comments = classApiExporterHelper.extractParamComment(method)

assertNotNull(comments)
assertTrue(comments!!.containsKey("id"))
assertEquals("user id", comments["id"])
}

fun testForeachMethod() {
val methods = mutableListOf<String>()
classApiExporterHelper.foreachMethod(userCtrlPsiClass) { method ->
methods.add(method.name())
}
actionContext.waitComplete()

assertTrue(methods.contains("create"))
assertTrue(methods.contains("get"))
assertFalse(methods.contains("toString"))
}

fun testExport() {
val docs = classApiExporterHelper.export()
actionContext.waitComplete()

assertNotNull(docs)
assertTrue(docs.isNotEmpty())

// Verify first API doc
docs[0].let { doc ->
assertInstanceOf(Request::class.java, doc)
doc as Request
assertEquals("say hello", doc.name)
assertEquals("GET", doc.method)
assertEquals("user/greeting", doc.path.toString())
}
}
}
Loading

0 comments on commit 3b4ad22

Please sign in to comment.