packages feed

poppy-codegen-1.0.0: test/Poppy/Codegen/ValidateSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Poppy.Codegen.ValidateSpec
  ( validateSpec,
  )
where

import Poppy.Codegen.IR
import qualified Poppy.Codegen.Schema as Builder
import Poppy.Codegen.Spec.Editor (editorSchema)
import Poppy.Codegen.Spec.Example (exampleSchema)
import Poppy.Codegen.Spec.Flag (flagSchema)
import Poppy.Codegen.Spec.Packet (packetSchema)
import Poppy.Codegen.Spec.Shelf (shelfSchema)
import Poppy.Codegen.Validate
import Test.Hspec

emptySchema :: [Model] -> Schema
emptySchema models =
  Schema {schemaEnums = [], schemaModels = models, schemaUniques = []}

validateSpec :: Spec
validateSpec =
  describe "Poppy.Codegen.Validate" $ do
    describe "example specs" $ do
      it "accepts the Shelf schema" $
        validateSchema shelfSchema `shouldBe` []

      it "accepts the canonical Example schema" $
        validateSchema exampleSchema `shouldBe` []

      it "accepts a Schema with a boolean field" $
        validateSchema flagSchema `shouldBe` []

      it "accepts a Schema with numeric and jsonb fields" $
        validateSchema packetSchema `shouldBe` []

      it "rejects a builder model with no primary key" $
        validateSchema (Builder.schema [] [Builder.model "Widget" [text "name"] []] [])
          `shouldBe` [ModelMissingPrimaryKey "Widget"]

    describe "primary keys" $ do
      it "rejects a model with no primary key" $ do
        let schema =
              emptySchema
                [ Model
                    { modelName = "Widget",
                      modelTable = "widget",
                      modelFields = [text "name"],
                      modelRelations = []
                    }
                ]
        validateSchema schema
          `shouldBe` [ModelMissingPrimaryKey "Widget"]

      it "rejects a model with multiple primary keys" $ do
        let schema =
              emptySchema
                [ Model
                    { modelName = "Widget",
                      modelTable = "widget",
                      modelFields = [pk (uuid "id"), pk (uuid "otherId")],
                      modelRelations = []
                    }
                ]
        validateSchema schema
          `shouldBe` [ModelMultiplePrimaryKeys "Widget"]

    describe "enums" $ do
      it "rejects a field referencing an unknown enum" $ do
        let schema =
              emptySchema
                [ Model
                    { modelName = "Item",
                      modelTable = "item",
                      modelFields = [pk (uuid "id"), enumField "unit" "Unit"],
                      modelRelations = []
                    }
                ]
        validateSchema schema
          `shouldBe` [UnknownEnumType "Item" "unit" "Unit"]

      it "rejects an enum with no variants" $ do
        let schema =
              Schema
                { schemaEnums = [enum_ "Unit" []],
                  schemaModels =
                    [ Model
                        { modelName = "Item",
                          modelTable = "item",
                          modelFields = [pk (uuid "id")],
                          modelRelations = []
                        }
                    ],
                  schemaUniques = []
                }
        validateSchema schema
          `shouldBe` [EnumHasNoVariants "Unit"]

      it "rejects duplicate enum variant names" $ do
        let schema =
              Schema
                { schemaEnums = [enum_ "Unit" [variant "G", variant "G"]],
                  schemaModels =
                    [ Model
                        { modelName = "Item",
                          modelTable = "item",
                          modelFields = [pk (uuid "id")],
                          modelRelations = []
                        }
                    ],
                  schemaUniques = []
                }
        validateSchema schema
          `shouldBe` [DuplicateEnumVariant "Unit" "G"]

    describe "relations" $ do
      it "rejects a relation pointing at an unknown model" $ do
        let schema =
              emptySchema
                [ Model
                    { modelName = "Shelf",
                      modelTable = "test_shelf",
                      modelFields = [pk (uuid "id")],
                      modelRelations =
                        [ hasMany "shelfBooks" "Shelf" "Missing" "id" "shelfId"
                        ]
                    }
                ]
        validateSchema schema
          `shouldBe` [UnknownRelationModel "shelfBooks" "to" "Missing"]

      it "rejects a relation with an unknown field" $ do
        let schema =
              emptySchema
                [ Model
                    { modelName = "Shelf",
                      modelTable = "test_shelf",
                      modelFields = [pk (uuid "id")],
                      modelRelations =
                        [ hasMany "shelfBooks" "Shelf" "Book" "id" "shelfId"
                        ]
                    },
                  Model
                    { modelName = "Book",
                      modelTable = "test_book",
                      modelFields = [pk (uuid "id"), text "title"],
                      modelRelations = []
                    }
                ]
        validateSchema schema
          `shouldBe` [UnknownRelationField "shelfBooks" "Book" "shelfId"]

      it "rejects a relation name used twice on one model" $ do
        let schema =
              Builder.schema
                []
                [ Builder.model
                    "Author"
                    [pk (uuid "id"), text "name"]
                    [ Builder.hasMany "posts" "Post" "authorId",
                      Builder.hasMany "posts" "Post" "authorId"
                    ],
                  Builder.model
                    "Post"
                    [pk (uuid "id"), uuid "authorId", text "title"]
                    []
                ]
                []
        validateSchema schema
          `shouldBe` [ DuplicateRelationName "Author" "posts"
                     ]

      it "rejects a relation name equal to a scalar field" $ do
        let schema =
              Builder.schema
                []
                [ Builder.model
                    "Author"
                    [pk (uuid "id"), text "name"]
                    [Builder.hasMany "name" "Post" "authorId"],
                  Builder.model
                    "Post"
                    [pk (uuid "id"), uuid "authorId"]
                    []
                ]
                []
        validateSchema schema
          `shouldBe` [RelationNameClashesWithField "Author" "name"]

      it "accepts two relations from one model to the same model" $ do
        validateSchema editorSchema `shouldBe` []

    describe "uniques" $ do
      it "rejects a unique constraint on an unknown model" $ do
        let schema =
              emptySchema
                [ Model
                    { modelName = "Widget",
                      modelTable = "widget",
                      modelFields = [pk (uuid "id")],
                      modelRelations = []
                    }
                ]
            drifted = schema {schemaUniques = [unique_ "Nope" ["id"]]}
        validateSchema drifted
          `shouldBe` [UnknownUniqueModel "Nope"]

      it "rejects a unique constraint that references an unknown field" $ do
        let schema =
              emptySchema
                [ Model
                    { modelName = "Widget",
                      modelTable = "widget",
                      modelFields = [pk (uuid "id")],
                      modelRelations = []
                    }
                ]
            drifted = schema {schemaUniques = [unique_ "Widget" ["name"]]}
        validateSchema drifted
          `shouldBe` [UnknownUniqueField "Widget" "name"]