packages feed

openapi-hs-4.0.0: test/Data/OpenApi/MigrationSpec.hs

{-# LANGUAGE OverloadedStrings #-}
-- this spec intentionally exercises the deprecated migration helpers
{-# OPTIONS_GHC -Wno-deprecations #-}

-- | Tests for the 3.0 -> 3.1 'Data.Aeson.Value'-layer migration helpers (EP-7).
module Data.OpenApi.MigrationSpec where

import Data.Aeson (Value, decode, encode, object, (.=))
import Data.HashMap.Strict.InsOrd.Compat qualified as InsOrdHashMap
import Data.OpenApi
import Data.OpenApi.Internal
import Data.OpenApi.Migration
  ( migrate30ExclusiveBoundsValue,
    migrate30ItemsArrayValue,
    migrate30NullableValue,
    migrate30To31,
  )
import Data.Text (Text)
import Test.Hspec

spec :: Spec
spec = describe "3.0 to 3.1 migration" $ do
  it "converts nullable:true to a type array" $ do
    let v30 = object ["type" .= ("string" :: Text), "nullable" .= True]
    (decode (encode (migrate30NullableValue v30)) :: Maybe Schema)
      `shouldBe` Just (mempty {_schemaType = Just (OpenApiTypeArray [OpenApiString, OpenApiNull])})

  it "drops nullable:false without touching type" $ do
    let v30 = object ["type" .= ("string" :: Text), "nullable" .= False]
    (decode (encode (migrate30NullableValue v30)) :: Maybe Schema)
      `shouldBe` Just (mempty {_schemaType = Just (OpenApiTypeSingle OpenApiString)})

  it "rewrites boolean exclusiveMaximum to a numeric bound (dropping maximum)" $ do
    let v30 = object ["maximum" .= (100 :: Int), "exclusiveMaximum" .= True]
    (decode (encode (migrate30ExclusiveBoundsValue v30)) :: Maybe Schema)
      `shouldBe` Just (mempty {_schemaExclusiveMaximum = Just 100})

  it "keeps maximum when exclusiveMaximum:false" $ do
    let v30 = object ["maximum" .= (100 :: Int), "exclusiveMaximum" .= False]
    (decode (encode (migrate30ExclusiveBoundsValue v30)) :: Maybe Schema)
      `shouldBe` Just (mempty {_schemaMaximum = Just 100})

  it "rewrites a tuple items array into prefixItems + items:false" $ do
    let v30 =
          object
            [ "items"
                .= ( [ object ["type" .= ("string" :: Text)],
                       object ["type" .= ("number" :: Text)]
                     ] ::
                       [Value]
                   )
            ]
        expected =
          mempty
            { _schemaPrefixItems =
                Just
                  [ Inline (mempty {_schemaType = Just (OpenApiTypeSingle OpenApiString)}),
                    Inline (mempty {_schemaType = Just (OpenApiTypeSingle OpenApiNumber)})
                  ],
              _schemaItems = Just (OpenApiItemsBoolean False)
            }
    (decode (encode (migrate30ItemsArrayValue v30)) :: Maybe Schema) `shouldBe` Just expected

  it "migrate30To31 recurses into nested properties" $ do
    let v30 =
          object
            [ "type" .= ("object" :: Text),
              "properties"
                .= object
                  ["n" .= object ["type" .= ("string" :: Text), "nullable" .= True]]
            ]
        nestedType = do
          s <- decode (encode (migrate30To31 v30)) :: Maybe Schema
          pn <- InsOrdHashMap.lookup ("n" :: Text) (_schemaProperties s)
          case pn of
            Inline ns -> _schemaType ns
            _ -> Nothing
    -- the nested property schema's nullable became a 3.1 type array
    nestedType `shouldBe` Just (OpenApiTypeArray [OpenApiString, OpenApiNull])