add unit test to check whether there are some modify in frozen types

This commit is contained in:
shuang 2019-08-03 18:33:29 +08:00
parent 3f34a0ae9c
commit 9258f4c689
19 changed files with 235 additions and 29 deletions

3
.gitignore vendored
View File

@ -11,7 +11,8 @@
.cache
target
obp-api/src/main/resources/
obp-api/src/test/resources/
obp-api/src/test/resources/**
!obp-api/src/test/resources/frozen_type_meta_data
*.iml
obp-api/src/main/resources/log4j.properties
obp-api/src/main/scripts/kafka/kafka_*

View File

@ -33,8 +33,7 @@ import net.liftweb.json.{JArray, JValue}
import scala.meta._
object APIBuilderSwagger
{
object APIBuilderSwagger {
def main(args: Array[String]): Unit = overwriteApiCode(apiSource,jsonFactorySource)
val jsonJValueFromFile: JValue = APIUtil.getJValueFromFile("src/main/scala/code/api/APIBuilder/swagger/swaggerResource.json")

View File

@ -21,5 +21,8 @@ object ScannedApis {
/**
* this map value are all scanned objects those extends ScannedApiVersion, the key is it apiVersion field
*/
lazy val versionMapScannedApis: Map[ScannedApiVersion, ScannedApis] = ClassScanUtils.getSubTypeObjects(classOf[ScannedApis]).map(it=> (it.apiVersion, it)).toMap
lazy val versionMapScannedApis: Map[ScannedApiVersion, ScannedApis] =
ClassScanUtils.getSubTypeObjects[ScannedApis]
.map(it=> (it.apiVersion, it))
.toMap
}

View File

@ -0,0 +1,15 @@
package code.api.util
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc}
import scala.collection.mutable.ArrayBuffer
trait VersionedOBPApis {
def version : ApiVersion
def versionStatus: String
def allResourceDocs: ArrayBuffer[ResourceDoc]
def routes: List[OBPEndpoint]
}

View File

@ -28,12 +28,12 @@ package code.api.v1_2_1
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.ApiVersion
import code.api.util.{ApiVersion, VersionedOBPApis}
import code.util.Helper.MdcLoggable
// Added so we can add resource docs for this version of the API
object OBPAPI1_2_1 extends OBPRestHelper with APIMethods121 with MdcLoggable {
object OBPAPI1_2_1 extends OBPRestHelper with APIMethods121 with MdcLoggable with VersionedOBPApis{
val version : ApiVersion = ApiVersion.v1_2_1 // "1.2.1"

View File

@ -2,7 +2,7 @@ package code.api.v1_3_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.ApiVersion
import code.api.util.{ApiVersion, VersionedOBPApis}
import code.api.v1_2_1.APIMethods121
import code.util.Helper.MdcLoggable
@ -11,7 +11,7 @@ import code.util.Helper.MdcLoggable
//has APIMethods121 as all api calls that went unchanged from 1.2.1 to 1.3.0 will use the old
//implementation
object OBPAPI1_3_0 extends OBPRestHelper with APIMethods130 with APIMethods121 with MdcLoggable {
object OBPAPI1_3_0 extends OBPRestHelper with APIMethods130 with APIMethods121 with MdcLoggable with VersionedOBPApis{
val version : ApiVersion = ApiVersion.v1_3_0 // "1.3.0"
val versionStatus = "STABLE"

View File

@ -2,11 +2,11 @@ package code.api.v1_4_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.ApiVersion
import code.api.util.{ApiVersion, VersionedOBPApis}
import code.util.Helper.MdcLoggable
object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with MdcLoggable {
object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with MdcLoggable with VersionedOBPApis{
val version : ApiVersion = ApiVersion.v1_4_0 //"1.4.0"
val versionStatus = "STABLE"

View File

@ -28,12 +28,12 @@ package code.api.v2_0_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.ApiVersion
import code.api.util.{ApiVersion, VersionedOBPApis}
import code.api.v1_3_0.APIMethods130
import code.api.v1_4_0.APIMethods140
import code.util.Helper.MdcLoggable
object OBPAPI2_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with MdcLoggable {
object OBPAPI2_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with MdcLoggable with VersionedOBPApis{
val version : ApiVersion = ApiVersion.v2_0_0 // "2.0.0"

View File

@ -28,7 +28,7 @@ package code.api.v2_1_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.{APIUtil, ApiVersion}
import code.api.util.{APIUtil, ApiVersion, VersionedOBPApis}
import code.api.v1_3_0.APIMethods130
import code.api.v1_4_0.APIMethods140
import code.api.v2_0_0.APIMethods200
@ -36,7 +36,7 @@ import code.util.Helper.MdcLoggable
import scala.collection.immutable.Nil
object OBPAPI2_1_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with MdcLoggable {
object OBPAPI2_1_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with MdcLoggable with VersionedOBPApis{

View File

@ -3,7 +3,7 @@ package code.api.v2_2_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.{APIUtil, ApiVersion}
import code.api.util.{APIUtil, ApiVersion, VersionedOBPApis}
import code.api.v1_3_0.APIMethods130
import code.api.v1_4_0.APIMethods140
import code.api.v2_0_0.APIMethods200
@ -12,7 +12,7 @@ import code.util.Helper.MdcLoggable
import scala.collection.immutable.Nil
object OBPAPI2_2_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with APIMethods220 with MdcLoggable {
object OBPAPI2_2_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with APIMethods220 with MdcLoggable with VersionedOBPApis{
val version : ApiVersion = ApiVersion.v2_2_0 // "2.2.0"
val versionStatus = "STABLE"

View File

@ -28,7 +28,7 @@ package code.api.v3_0_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.ApiVersion
import code.api.util.{ApiVersion, VersionedOBPApis}
import code.api.v1_3_0.APIMethods130
import code.api.v1_4_0.APIMethods140
import code.api.v2_0_0.APIMethods200
@ -46,7 +46,7 @@ This file defines which endpoints from all the versions are available in v3.0.0
*/
object OBPAPI3_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with APIMethods220 with APIMethods300 with CustomAPIMethods300 with MdcLoggable {
object OBPAPI3_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with APIMethods220 with APIMethods300 with CustomAPIMethods300 with MdcLoggable with VersionedOBPApis{

View File

@ -28,7 +28,7 @@ package code.api.v3_1_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.ApiVersion
import code.api.util.{ApiVersion, VersionedOBPApis}
import code.api.v1_3_0.APIMethods130
import code.api.v1_4_0.APIMethods140
import code.api.v2_0_0.APIMethods200
@ -47,7 +47,7 @@ This file defines which endpoints from all the versions are available in v3.0.0
*/
object OBPAPI3_1_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with APIMethods220 with APIMethods300 with CustomAPIMethods300 with APIMethods310 with MdcLoggable {
object OBPAPI3_1_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with APIMethods220 with APIMethods300 with CustomAPIMethods300 with APIMethods310 with MdcLoggable with VersionedOBPApis{
val version : ApiVersion = ApiVersion.v3_1_0

View File

@ -28,8 +28,8 @@ package code.api.v4_0_0
import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.ApiVersion
import code.api.v1_3_0.APIMethods130
import code.api.util.{ApiVersion, VersionedOBPApis}
import code.api.v1_3_0.{APIMethods130}
import code.api.v1_4_0.APIMethods140
import code.api.v2_0_0.APIMethods200
import code.api.v2_1_0.APIMethods210
@ -48,7 +48,7 @@ This file defines which endpoints from all the versions are available in v4.0.0
*/
object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with APIMethods220 with APIMethods300 with CustomAPIMethods300 with APIMethods310 with APIMethods400 with MdcLoggable {
object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 with APIMethods200 with APIMethods210 with APIMethods220 with APIMethods300 with CustomAPIMethods300 with APIMethods310 with APIMethods400 with MdcLoggable with VersionedOBPApis{
val version : ApiVersion = ApiVersion.v4_0_0

View File

@ -5,6 +5,7 @@ import java.io.File
import com.openbankproject.commons.model.Bank
import org.apache.commons.lang3.StringUtils
import org.clapper.classutil.{ClassFinder, ClassInfo}
import com.openbankproject.commons.util.ReflectUtils
import scala.reflect.runtime.universe.TypeTag
@ -29,11 +30,11 @@ object ClassScanUtils {
/**
* scan classpath to get all companion objects or singleton objects those implements given trait
* @param clazz a trait type for filter object
* @tparam T the trait type parameter
* @return all companion objects or singleton object those implements given clazz
*/
def getSubTypeObjects[T:TypeTag](clazz: Class[T]) = {
def getSubTypeObjects[T:TypeTag]: List[T] = {
val clazz = ReflectUtils.typeTagToClass[T]
finder.getClasses().filter(_.implements(clazz.getName)).map(_.name).map(companion[T](_)).toList
}

Binary file not shown.

View File

@ -26,7 +26,6 @@ TESOBE (http://www.tesobe.com/)
*/
import code.api.util.APIUtil
import net.liftweb.util.Props
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.webapp.WebAppContext

View File

@ -0,0 +1,81 @@
package code.util
import code.api.util.ApiVersion
import code.setup.ServerSetup
import org.scalatest.Tag
class FrozenClassTest extends ServerSetup {
object FrozenClassTag extends Tag("Frozen_Classes")
val (persistedVersionToEndpointNames, persistedTypeNameToTypeValFields) = FrozenClassUtil.readPersistedFrozenApiInfo
val (versionToEndpointNames, typeNameToTypeValFields) = FrozenClassUtil.getFrozenApiInfo
feature("Frozen version apis not changed") {
scenario(s"count of STABLE OBPAPIxxxx should not be reduce, if pretty sure need modify it, please run ${FrozenClassUtil.sourceName}", FrozenClassTag) {
val persistedStableVersions = persistedVersionToEndpointNames.map(_._1).toSet
val currentStableVersions = versionToEndpointNames.map(_._1).toSet
val increasedVersions = persistedStableVersions.diff(currentStableVersions)
increasedVersions should equal(Set.empty[ApiVersion])
}
scenario(s"count of STABLE OBPAPIxxxx should not be increased, if pretty sure need modify it, please run ${FrozenClassUtil.sourceName}", FrozenClassTag) {
val persistedStableVersions = persistedVersionToEndpointNames.map(_._1).toSet
val currentStableVersions = versionToEndpointNames.map(_._1).toSet
val reducedVersions = currentStableVersions.diff(persistedStableVersions)
reducedVersions should equal(Set.empty[ApiVersion])
}
scenario(s"api count of STABLE value of OBPAPIxxxx#versionStatus should not be reduce, if pretty sure need modify it, please run ${FrozenClassUtil.sourceName}", FrozenClassTag) {
val reducedApis = for {
(pVersion, pEndpointNames) <- persistedVersionToEndpointNames
(version, endpointNames) <- versionToEndpointNames
if (pVersion == version)
reducedApisOfVersion = pEndpointNames.diff(endpointNames).mkString(",")
if (reducedApisOfVersion.size > 0)
} yield {
s"$version reduced apis: $reducedApisOfVersion"
}
reducedApis should equal(Nil)
}
scenario(s"api count of STABLE value of OBPAPIxxxx#versionStatus should not be increased, if pretty sure need modify it, please run ${FrozenClassUtil.sourceName}", FrozenClassTag) {
val increasedApis = for {
(pVersion, pEndpointNames) <- persistedVersionToEndpointNames
(version, endpointNames) <- versionToEndpointNames
if (pVersion == version)
increasedApis = endpointNames.diff(pEndpointNames).mkString(",")
if (increasedApis.size > 0)
} yield {
s"$version increased apis: $increasedApis"
}
increasedApis should equal(Nil)
}
}
feature("Frozen type structure not be modified") {
scenario(s"frozen class structure should not be modified, if pretty sure need modify it, please run ${FrozenClassUtil.sourceName}", FrozenClassTag) {
val changedTypes = for {
(pTypeName, pFields) <- persistedTypeNameToTypeValFields.toList
(typeName, fields) <- typeNameToTypeValFields.toList
if(pTypeName == typeName && pFields != fields)
} yield {
val expectStructure = pFields.map(pair => s"${pair._1}:${pair._2}").mkString("(", ", ", ")")
val actualStructure = fields.map(pair => s"${pair._1}:${pair._2}").mkString("(", ", ", ")")
s"""
|{
| typeName: $typeName
| expectStructure: $expectStructure
| actualStructure: $actualStructure
|}
|""".stripMargin
}
changedTypes should equal (Nil)
}
}
}

View File

@ -0,0 +1,96 @@
package code.util
import java.io._
import java.net.URI
import code.TestServer
import code.api.util.{ApiVersion, VersionedOBPApis}
import com.openbankproject.commons.util.ReflectUtils
import org.apache.commons.io.IOUtils
import scala.reflect.runtime.universe._
/**
* this util is for persist metadata of frozen type, those frozen type is versionStatus = "STABLE" related example classes,
* after persist the metadata, the FrozenClassTest can check whether there some modify change any frozen type, the test will fail when there are some changes in the frozen type
*/
object FrozenClassUtil {
val sourceName = s"""${this.getClass.getName.replace("$", "")}.scala"""
// current project absolute path
val basePath = this.getClass.getResource("/").toString .replaceFirst("target[/\\\\].*$", "")
val persistFilePath = new URI(s"${basePath}/src/test/resources/frozen_type_meta_data").getPath
def main(args: Array[String]): Unit = {
System.setProperty("run.mode", "test") // make sure this Props.mode is the same as unit test Props.mode
val server = TestServer
val out = new ObjectOutputStream(new FileOutputStream(persistFilePath))
try {
out.writeObject(getFrozenApiInfo)
} finally {
IOUtils.closeQuietly(out)
// there is no graceful way to shutdown jetty server, so here use brutal way to shutdown it.
server.server.stop()
System.exit(0)
}
}
/**
* get frozen api information by scan classes
* @return frozen api information, include api names of given api version and frozen class metadata
*/
def getFrozenApiInfo: (List[(ApiVersion, Set[String])], Map[String, Map[String, String]]) = {
val versionedOBPApisList: List[VersionedOBPApis] = ClassScanUtils.getSubTypeObjects[VersionedOBPApis]
.filter(_.versionStatus == "STABLE")
val versionToEndpointNames: List[(ApiVersion, Set[String])] = versionedOBPApisList
.map(it => {
val version = it.version
val currentVersionApis = it.allResourceDocs.filter(version == _.implementedInApiVersion).toSet
(version, currentVersionApis.map(_.partialFunctionName))
})
val allFreezingTypes: Set[Type] = versionedOBPApisList
.flatMap(_.allResourceDocs)
.flatMap(it => it.exampleRequestBody :: it.successResponseBody :: Nil)
.filter(ReflectUtils.isObpObject(_))
.map(ReflectUtils.getType(_))
.toSet
.flatMap(getNestedOBPType(_))
val typeNameToTypeValFields: Map[String, Map[String, String]] = allFreezingTypes
.map(it => {
val valNameToTypeName = ReflectUtils.getConstructorParamInfo(it).map(pair => (pair._1, pair._2.toString))
(it.typeSymbol.asClass.fullName, valNameToTypeName)
})
.toMap
(versionToEndpointNames, typeNameToTypeValFields)
}
/**
* read persisted frozen api info from persist file
* @return persisted frozen api information, include api names of given api version and frozen class metadata
*/
def readPersistedFrozenApiInfo: (List[(ApiVersion, Set[String])], Map[String, Map[String, String]]) = {
assume(new File(persistFilePath).exists(), s"freeze type not persisted yet, please run ${this.sourceName}")
val input = new ObjectInputStream(new FileInputStream(persistFilePath))
try {
input.readObject().asInstanceOf[(List[(ApiVersion, Set[String])], Map[String, Map[String, String]])]
} finally {
IOUtils.closeQuietly(input)
}
}
private def getNestedOBPType(tp: Type): Set[Type] = {
ReflectUtils.getConstructorParamInfo(tp)
.values
.map(it => ReflectUtils.getDeepGenericType(it).head)
.toSet
.filter(ReflectUtils.isObpType)
.filterNot(tp ==) // avoid infinite recursive
match {
case set if(set.size > 0) => set.flatMap(getNestedOBPType) + tp
case _ => Set(tp)
}
}
}

View File

@ -15,7 +15,7 @@ object ReflectUtils {
def isObpObject(any: Any): Boolean = any != null && OBP_TYPE_REGEX.findFirstIn(any.getClass.getName).isDefined
def isObpType(tp: Type): Boolean = tp != null && OBP_TYPE_REGEX.findFirstIn(tp.typeSymbol.fullName).isDefined
def isObpType(tp: Type): Boolean = tp != null && tp.typeSymbol.isClass && OBP_TYPE_REGEX.findFirstIn(tp.typeSymbol.fullName).isDefined
/**
* get all val and var name to values of given object
@ -278,6 +278,11 @@ object ReflectUtils {
})
}
def typeTagToClass[T: TypeTag]: Class[_] = {
val tt = implicitly[TypeTag[T]]
tt.mirror.runtimeClass(tt.tpe.typeSymbol.asClass)
}
def classToSymbol(clazz: Class[_]): ru.ClassSymbol = ru.runtimeMirror(clazz.getClassLoader).classSymbol(clazz)
/**
@ -285,10 +290,16 @@ object ReflectUtils {
* @param tp to do extract type
* @return a map of constructor parameter name to type, if tp is abstract, return empty amp
*/
def getConstructorParamInfo(tp: ru.Type): Map[String, ru.Type] = tp.typeSymbol.isClass match {
def getConstructorParamInfo(tp: ru.Type): Map[String, ru.Type] =
tp.typeSymbol.isClass && !tp.typeSymbol.asClass.isTrait match {
case false => Map.empty[String, ru.Type]
case true => {
ReflectUtils.getPrimaryConstructor(tp).paramLists.headOption.getOrElse(Nil).map(it => (it.name.toString, it.info)).toMap
ReflectUtils.getPrimaryConstructor(tp)
.paramLists
.headOption
.getOrElse(Nil)
.map(it => (it.name.toString, it.info))
.toMap
}
}
/**