All Downloads are FREE. Search and download functionalities are using the official Maven repository.

munit.TapirGoldenOpenAPISuite.scala Maven / Gradle / Ivy

/*
 * Copyright 2022-2024 Alejandro Hernández 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package munit

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

import scala.jdk.CollectionConverters._

import sttp.apispec.openapi.Info
import sttp.apispec.openapi.OpenAPI
import sttp.apispec.openapi.circe.yaml._
import sttp.tapir._
import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
import sttp.tapir.docs.openapi.OpenAPIDocsOptions

/** Base class for golden testing the generation of OpenAPI documentation for Tapir endpoints.
  *
  * To use this class you just need to override the `val endpoints: List[AnyEndpoint]` definition with your list of
  * endpoints to test.
  *
  * There are a bunch of things that can be overriden when generating the documentation:
  *
  *   - `tapirGoldenOpenAPIInfo`: The info that will be used when generating the OpenAPI file.
  *   - `tapirGoldenOpenAPIOptions`: The options that will be used when generating the OpenAPI file.
  *   - `tapirGoldenOpenAPIHeader`: The value of this header that will be prepended to the generated YAML file.
  *   - `tapirGoldenOpenAPIFileName`: Name of the generated file with the OpenAPI documentation.
  *   - `tapirGoldenOpenAPIPath`: Folder where the OpenAPI file will be generated.
  *
  * @example
  *   {{{
  *   class EndpointsSuite extends munit.TapirGoldenOpenAPISuite {
  *
  *     // Here you will add your list of endpoints
  *     override val endpoints: List[AnyEndpoint] = ???
  *
  *   }
  *   }}}
  * @author
  *   Alejandro Hernández
  * @author
  *   Jose Gutierrez de Ory
  * @author
  *   Adrian Ramirez
  */
abstract class TapirGoldenOpenAPISuite extends FunSuite {

  /** The list of endpoints for which to generate open-api documentation. */
  val endpoints: List[AnyEndpoint]

  /** The info that will be used when generating the OpenAPI file.
    *
    * Defaults to empty `Info`.
    *
    * @example
    *   {{{
    *   override def tapirGoldenOpenAPIInfo: Info = Info("title", "version", Some("description"))
    *   }}}
    *
    * @see
    *   https://tapir.softwaremill.com/en/stable/docs/openapi.html#id1
    */
  def tapirGoldenOpenAPIInfo: Info = Info("", "")

  /** The options that will be used when generating the OpenAPI file.
    *
    * Defaults to `OpenAPIDocsOptions.default`
    *
    * @see
    *   https://tapir.softwaremill.com/en/stable/docs/openapi.html#options
    */
  def tapirGoldenOpenAPIOptions: OpenAPIDocsOptions = OpenAPIDocsOptions.default

  /** The value of this header will be prepended to the generated YAML file. If you want to disable this behaviour just
    * set this value to an empty string.
    */
  def tapirGoldenOpenAPIHeader: String = "# This file has been autogenerated, don't try to edit it manually"

  /** Name of the generated file with the OpenAPI documentation.
    *
    * Defaults to `openapi.yaml`
    */
  def tapirGoldenOpenAPIFileName: String = "openapi.yaml"

  /** Method that can be used to modify the `OpenAPI` object before transforming it to YAML */
  def tapirGoldenOpenAPIModifier(openApi: OpenAPI): OpenAPI = openApi

  /** Folder where the OpenAPI file will be generated.
    *
    * Defaults to `resources` folder.
    */
  def tapirGoldenOpenAPIPath: Path =
    Paths
      .get(getClass.getResource("/").toURI())
      .iterator()
      .asScala
      .toList
      .takeWhile(_.toString() != "target") // scalafix:ok
      .fold(Paths.get("/"))(_ resolve _)
      .resolve("src")
      .resolve("test")
      .resolve("resources")

  private def yaml: String = {
    val openAPI = OpenAPIDocsInterpreter(tapirGoldenOpenAPIOptions).toOpenAPI(endpoints, tapirGoldenOpenAPIInfo)

    val yamlString = tapirGoldenOpenAPIModifier(openAPI).toYaml

    tapirGoldenOpenAPIHeader match {
      case ""     => yamlString
      case header => s"$header\n\n$yamlString"
    }
  }

  if (Files.notExists(tapirGoldenOpenAPIPath.resolve(tapirGoldenOpenAPIFileName)))
    test(s"Generating $tapirGoldenOpenAPIFileName") {
      Files.createDirectories(tapirGoldenOpenAPIPath)
      Files.write(tapirGoldenOpenAPIPath.resolve(tapirGoldenOpenAPIFileName), yaml.getBytes())

      assert(
        !sys.env.contains("CI"),
        s"You forgot to commit the `${tapirGoldenOpenAPIPath.resolve(tapirGoldenOpenAPIFileName)}` file"
      )
    }
  else
    test(s"$tapirGoldenOpenAPIFileName is correct") {
      val content =
        Files.readAllLines(tapirGoldenOpenAPIPath.resolve(tapirGoldenOpenAPIFileName)).asScala.mkString("\n")

      assertNoDiff(yaml, content)
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy