packages feed

api-tools-0.9.0.0: tests/Data/API/Test/MigrationData.hs

{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE OverloadedStrings          #-}

-- This module provides data for Data.API.Test.Migration; it is
-- separated out because of the Template Haskell staging restriction.

module Data.API.Test.MigrationData where

import           Data.API.Changes
import           Data.API.JSON
import           Data.API.Parse
import           Data.API.Types
import           Data.API.Utils

import qualified Data.Aeson as JS
import qualified Data.Set as Set
import qualified Data.Text as T
import           Data.Version


startSchema :: API
startSchema = [api|

id :: Id
    = basic integer

idId :: IdId
    = Id

an_enum :: AnEnum
    = enum
      | foo
      | bar

another_enum :: AnotherEnum
    = enum
      | foo
      | woo

a_union :: AUnion
    = union
      | bar :: Bar

another_union :: AnotherUnion
    = union
      | foo :: Foo
      | woo :: Id

dbs :: DatabaseSnapshot
    = record
        foo   :: ? [Foo]        nothing
        bar   :: ? [Bar]        []
        recur :: ? [Recursive]

fooPrefix :: Foo
    = record
        id   :: Id
        nest :: Nested
        en   :: AnEnum
        un   :: AUnion
        quux :: ? IdId

barPrefix :: Bar
    = record
       id :: Id

nestedPrefix :: Nested
    = record
       id :: Id

recursive :: Recursive
    = record
      id    :: Id
      recur :: ? Recursive

|]


endSchema :: API
changelog :: APIChangelog
(endSchema, changelog) = [apiWithChangelog|

id :: Id
    = basic integer

idId :: IdId
    = Id

bin :: Binary
    = basic binary

an_enum :: AnEnum
    = enum
      | foofoo
      | bar
      | baz

another_enum :: AnotherEnum
    = enum
      | foo
      | woo
      | barbar

a_union :: AUnion
    = union
      | barbar :: RenamedBar
      | baz :: Baz

another_union :: AnotherUnion
    = union
      | foo :: Foo
      | woo :: Id
      | barbar :: RenamedBar

dbs :: DatabaseSnapshot
    = record
        foo   :: ? [Foo]
        bar2  :: ? [Bar2]
        boz   :: ? [Baz]
        recur :: ? [Recursive]
        recur2 :: ? [DuplicateRecursive]

fooPrefix :: Foo
    = record
        id   :: Id
        nest :: RenamedNested
        en   :: AnEnum
        un   :: AUnion
        c    :: string
        nolist  :: [string]
        nomaybe :: ? string

barPrefix :: RenamedBar
    = record
       id :: Id

bar2Prefix :: Bar2
    = record
       id :: Id

nestedPrefix :: RenamedNested
    = record
       id :: Id
       new :: string

bazPrefix :: Baz
    = record
       id :: Id
       yy :: string

recursive :: Recursive
    = record
        renamed_id :: Id
        recur      :: ? Recursive
        new        :: string
        newnew     :: string

duplicaterecursive :: DuplicateRecursive
    = record
        renamed_id :: Id
        recur      :: ? Recursive
        new        :: string

new :: New
    = basic integer

changes

version "development"
  // changes since last release
  added New basic integer

version "2.6"
  // add fields with implicit default values
  changed record Foo
    field added nolist  :: [string]
    field added nomaybe :: ? string

version "2.5"
  // schema-changing custom record migration
  migration record Recursive DuplicateNew

version "2.4"
  // schema-changing custom database migration
  migration DuplicateRecursive

version "2.3"
  // changing a recursively defined record
  changed record Recursive
    field renamed id to renamed_id
    field added new :: string default "hello"

version "2.2"
  // changing a nested union
  changed union AUnion
    alternative renamed bar to barbar

version "2.1"
  // Changing a nested enum
  changed enum AnEnum
    alternative renamed foo to foofoo

version "2.0"
  // Changing a nested record
  changed record RenamedNested
    field added new :: string default "hello"

version "1.9"
  // Deleting a table
  changed record DatabaseSnapshot
    field removed bar

version "1.8"
  // Renaming a table
  changed record DatabaseSnapshot
    field renamed baz to boz

version "1.7"
  // Adding a table
  changed record DatabaseSnapshot
    field added baz :: ? [Baz] default []

version "1.6"
  // Custom change to the whole database
  migration DuplicateBar
  changed record DatabaseSnapshot
    field added bar2 :: ? [Bar2] default nothing
  added Bar2 record
    id :: Id

version "1.5"
  // Custom change to a record
  migration record Foo CopyIDtoC

version "1.4"
  // Renaming enum values
  changed enum AnotherEnum
    alternative renamed bar to barbar

version "1.3"
  // Deleting enum values
  changed enum AnotherEnum
    alternative removed baz

version "1.2"
  // Adding enum values
  changed enum AnEnum
    alternative added baz
  changed enum AnotherEnum
    alternative added bar
    alternative added baz

version "1.1"
  // Renaming union alternatives
  changed union AnotherUnion
    alternative renamed bar to barbar

version "1.0"
  // Deleting union alternatives
  changed union AnotherUnion
    alternative removed baz

version "0.9"
  // Adding union alternatives
  changed union AUnion
    alternative added baz :: Baz
  changed union AnotherUnion
    alternative added bar :: RenamedBar
    alternative added baz :: Baz

version "0.8"
  // Custom migration on fields
  changed record Foo
    field changed c :: string migration ConvertBinaryToString

version "0.7"
  // Renaming fields
  changed record Foo
    field renamed b to c
  changed record Baz
    field renamed y to yy

version "0.6"
  // Deleting fields
  changed record Foo
    field removed quux
  changed record Baz
    field removed x

version "0.5"
  // Adding fields
  changed record Foo
    field added b :: Binary default "Zm9vYmFy"
  changed record Baz
    field added x :: integer
    field added y :: string

version "0.4"
  // Renaming types
  renamed Nested to RenamedNested
  renamed Bar to RenamedBar

version "0.3"
  // Deleting types
  removed ASynonym

version "0.2"
  // Adding types
  added ASynonym Binary
  added Binary basic binary
  added Baz record
    id :: Id

version "0.1"
  // No changes

version "0"

|]


badChangelog :: APIChangelog
(_, badChangelog) = [apiWithChangelog|
changes

version "3.1"
  // Adding a table without a default
  changed record DatabaseSnapshot
    field added badtable :: Nested

version "3.0"
  // Renaming to an existing val
  changed enum AnotherEnum
    alternative renamed foo to woo

version "2.9"
  // Renaming a missing val
  changed enum AnotherEnum
    alternative renamed missing to also_missing

version "2.8"
  // Deleting a missing val
  changed enum AnotherEnum
    alternative removed missing

version "2.7"
  // Adding a val that already exists
  changed enum AnEnum
    alternative added bar

version "2.6"
  // Removing vals from a type in use
  changed enum AnEnum
    alternative removed foo

version "2.5"
  // Adding vals to a non-existent type
  changed enum NotThere
    alternative added oops

version "2.4"
  // Adding vals to a non-enum
  changed enum Id
    alternative added oops

version "2.3"
  // Renaming to an existing alt
  changed union AnotherUnion
    alternative renamed foo to woo

version "2.2"
  // Renaming a missing alt
  changed union AnotherUnion
    alternative renamed missing to also_missing

version "2.1"
  // Deleting a missing alt
  changed union AnotherUnion
    alternative removed missing

version "2.0"
  // Adding an alt that already exists
  changed union AUnion
    alternative added bar :: Bar

version "1.9"
  // Adding an alt with a malformed type
  changed union AUnion
    alternative added oops :: Missing

version "1.8"
  // Removing alts from a type in use
  changed union AUnion
    alternative removed foo

version "1.7"
  // Adding alts to a non-existent type
  changed union NotThere
    alternative added oops :: Id

version "1.6"
  // Adding alts to a non-union
  changed union Id
    alternative added oops :: Id

version "1.5"
  // Renaming to an existing field
  changed record Foo
    field renamed en to un

version "1.4"
  // Renaming a missing field
  changed record Foo
    field renamed missing to also_missing

version "1.3"
  // Deleting a missing field
  changed record Foo
    field removed missing

version "1.2"
  // Adding a field with an ill-typed default value
  changed record Foo
    field added oops :: Id default "hello"

version "1.1"
  // Adding a field without a default value
  changed record Foo
    field added oops :: Id

version "1.0"
  // Adding a field that already exists
  changed record Foo
    field added id :: Id

version "0.9"
  // Adding a field with a malformed type
  changed record Foo
    field added oops :: Missing

// // This is now legal:
// version "0.8"
//  // Adding fields to a type in use
//  changed record Nested
//    field added oops :: Id

version "0.7"
  // Adding fields to a non-existent type
  changed record NotThere
    field added oops :: Id

version "0.6"
  // Adding fields to a non-record
  changed record Id
    field added oops :: Id

version "0.5"
  // Renaming to an existing type
  renamed Id to AnEnum

version "0.4"
  // Renaming a missing type
  renamed NotThere to SomethingElse

version "0.3"
  // Deleting a missing type
  removed NotThere

version "0.2"
  // Adding an ill-formed type
  added New Missing

version "0.1"
  // Adding a type that already exists
  added Id basic binary

version "0"
|]

expectedApplyFailures :: [(Version, Version, ApplyFailure)]
expectedApplyFailures = map toVersions $
  [ ("0",   "0.1", TypeExists (TypeName "Id"))
  , ("0.1", "0.2", DeclMalformed (TypeName "New")
                       (NTypeSynonym (TyName (TypeName "Missing")))
                       (Set.singleton (TypeName "Missing")))
  , ("0.2", "0.3", TypeDoesNotExist (TypeName "NotThere"))
  , ("0.3", "0.4", TypeDoesNotExist (TypeName "NotThere"))
  , ("0.4", "0.5", TypeExists (TypeName "AnEnum"))
  , ("0.5", "0.6", TypeWrongKind (TypeName "Id") TKRecord)
  , ("0.6", "0.7", TypeDoesNotExist (TypeName "NotThere"))
--  , ("0.7", "0.8", TypeInUse (TypeName "Nested"))
  , ("0.8", "0.9", TypeMalformed
                       (TyName (TypeName "Missing"))
                       (Set.singleton (TypeName "Missing")))
  , ("0.9", "1.0", FieldExists (TypeName "Foo") TKRecord (FieldName "id"))
  , ("1.0", "1.1", DefaultMissing (TypeName "Foo") (FieldName "oops"))
  , ("1.1", "1.2", FieldBadDefaultValue (TypeName "Foo") (FieldName "oops")
                       (TyName (TypeName "Id")) (DefValString (T.pack "hello")))
  , ("1.2", "1.3", FieldDoesNotExist (TypeName "Foo") TKRecord (FieldName "missing"))
  , ("1.3", "1.4", FieldDoesNotExist (TypeName "Foo") TKRecord (FieldName "missing"))
  , ("1.4", "1.5", FieldExists (TypeName "Foo") TKRecord (FieldName "un"))
  , ("1.5", "1.6", TypeWrongKind (TypeName "Id") TKUnion)
  , ("1.6", "1.7", TypeDoesNotExist (TypeName "NotThere"))
  , ("1.7", "1.8", TypeInUse (TypeName "AUnion"))
  , ("1.8", "1.9", TypeMalformed
                       (TyName (TypeName "Missing"))
                       (Set.singleton (TypeName "Missing")))
  , ("1.9", "2.0", FieldExists (TypeName "AUnion") TKUnion (FieldName "bar"))
  , ("2.0", "2.1", FieldDoesNotExist (TypeName "AnotherUnion") TKUnion (FieldName "missing"))
  , ("2.1", "2.2", FieldDoesNotExist (TypeName "AnotherUnion") TKUnion (FieldName "missing"))
  , ("2.2", "2.3", FieldExists (TypeName "AnotherUnion") TKUnion (FieldName "woo"))
  , ("2.3", "2.4", TypeWrongKind (TypeName "Id") TKEnum)
  , ("2.4", "2.5", TypeDoesNotExist (TypeName "NotThere"))
  , ("2.5", "2.6", TypeInUse (TypeName "AnEnum"))
  , ("2.6", "2.7", FieldExists (TypeName "AnEnum") TKEnum (FieldName "bar"))
  , ("2.7", "2.8", FieldDoesNotExist (TypeName "AnotherEnum") TKEnum (FieldName "missing"))
  , ("2.8", "2.9", FieldDoesNotExist (TypeName "AnotherEnum") TKEnum (FieldName "missing"))
  , ("2.9", "3.0", FieldExists (TypeName "AnotherEnum") TKEnum (FieldName "woo"))
  , ("3.0", "3.1", DefaultMissing (TypeName "DatabaseSnapshot") (FieldName "badtable"))
  ]
  where
    toVersions (s, s', x)
      | Just v  <- simpleParseVersion s
      , Just v' <- simpleParseVersion s'
      , v < v'                    = (v, v', x)
      | otherwise = error $ "expectedApplyFailures: bad versions "
                            ++ show s ++ " and " ++ show s'

type MigrateFailureTest = ( String
                          , (API, Version)  -- Start version
                          , (API, VersionExtra)  -- End version
                          , APIChangelog
                          , JS.Value
                          , MigrateFailure -> Bool )

expectedMigrateFailures :: [MigrateFailureTest]
expectedMigrateFailures =
  [ ( "Out of order"
    , (startSchema, ver "0.1")
    , (endSchema, verRelease "0.2")
    , outOfOrder
    , startData
    , (== ValidateFailure (ChangelogOutOfOrder (verRelease "0.1") (verRelease "0.2")))
    )

  , ("Incomplete"
    , (startSchema, ver "0.1")
    , (endSchema, verRelease "2.5")
    , incomplete
    , startData
    , \ err -> case err of
                 ValidateFailure (ChangelogIncomplete _ _ _) -> True
                 _                                           -> False
    )

  , ( "Downgrade"
    , (startSchema, ver "0.2")
    , (startSchema, verRelease "0.1")
    , changelog
    , startData
    , (== ValidateFailure (CannotDowngrade (verRelease "0.2") (verRelease "0.1")))
    )

  , ("Bad custom migration"
    , (startSchema, ver "0.1")
    , (startSchema, verRelease "0.2")
    , badCustomMigration
    , startData
    , \ err -> case err of
                 ValueError (JSONError UnexpectedField) [InField t] -> t == "bar2"
                 _                                                  -> False
    )
  ]
  where
    ver s | Just v <- simpleParseVersion s = v
          | otherwise = error $ "expectedValidateFailures: bad version " ++ show s
    verRelease s = Release $ ver s

    outOfOrder = snd [apiWithChangelog|
changes
version "0.1"
version "0.2"
|]

    incomplete = snd [apiWithChangelog|
changes
version "0.1"
|]

    badCustomMigration = snd [apiWithChangelog|
changes
version "0.2"
  migration DuplicateBar
version "0.1"
|]


startData, endData :: JS.Value
Just startData = JS.decode "{ \"foo\": [ {\"id\": 42, \"nest\": { \"id\": 3 }, \"en\": \"foo\", \"un\": { \"bar\": { \"id\": 43 } }, \"quux\": null } ], \"bar\": [ { \"id\": 4 } ], \"recur\": [{ \"id\": 9, \"recur\": { \"id\": 8, \"recur\": null} }] }"
Just endData = JS.decode "{ \"foo\": [ {\"id\":42, \"nest\": { \"id\": 3, \"new\": \"hello\" }, \"c\": \"foobar42\", \"en\": \"foofoo\", \"un\": { \"barbar\": { \"id\": 43 } }, \"nolist\": [], \"nomaybe\": null } ], \"boz\": [], \"bar2\": [ {\"id\": 4 } ], \"recur\": [{ \"renamed_id\": 9, \"new\": \"hello\", \"newnew\": \"hello\", \"recur\": { \"renamed_id\": 8, \"new\": \"hello\", \"newnew\": \"hello\", \"recur\": null} }], \"recur2\": [{ \"renamed_id\": 9, \"new\": \"hello\", \"recur\": { \"renamed_id\": 8, \"new\": \"hello\", \"newnew\": \"hello\", \"recur\": null} }] }"

startVersion :: Version
startVersion = changelogStartVersion changelog

root_ :: TypeName
root_ = TypeName "DatabaseSnapshot"