test: add tests for batch upserts (#3005)
This commit is contained in:
@@ -194,6 +194,62 @@ spec actualPgVersion = do
|
|||||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||||
totalCost `shouldBe` 1.29
|
totalCost `shouldBe` 1.29
|
||||||
|
|
||||||
|
it "outputs the total cost for 2 upserts" $ do
|
||||||
|
r <- request methodPost "/tiobe_pls"
|
||||||
|
[("Prefer","handling=strict; resolution=merge-duplicates"), ("Accept","application/vnd.pgrst.plan+json")]
|
||||||
|
[json| [ { "name": "Python", "rank": 19 }, { "name": "Go", "rank": 20} ]|]
|
||||||
|
|
||||||
|
let totalCost = planCost r
|
||||||
|
resStatus = simpleStatus r
|
||||||
|
resHeaders = simpleHeaders r
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||||
|
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||||
|
totalCost `shouldBe` 3.27
|
||||||
|
|
||||||
|
it "outputs the total cost for an upsert with 10 rows" $ do
|
||||||
|
r <- request methodPost "/tiobe_pls"
|
||||||
|
[("Prefer","handling=strict; resolution=merge-duplicates"), ("Accept","application/vnd.pgrst.plan+json")]
|
||||||
|
(getInsertDataForTiobePlsTable 10)
|
||||||
|
|
||||||
|
let totalCost = planCost r
|
||||||
|
resStatus = simpleStatus r
|
||||||
|
resHeaders = simpleHeaders r
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||||
|
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||||
|
totalCost `shouldBe` 3.27
|
||||||
|
|
||||||
|
it "outputs the total cost for an upsert with 100 rows" $ do
|
||||||
|
r <- request methodPost "/tiobe_pls"
|
||||||
|
[("Prefer","handling=strict; resolution=merge-duplicates"), ("Accept","application/vnd.pgrst.plan+json")]
|
||||||
|
(getInsertDataForTiobePlsTable 100)
|
||||||
|
|
||||||
|
let totalCost = planCost r
|
||||||
|
resStatus = simpleStatus r
|
||||||
|
resHeaders = simpleHeaders r
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||||
|
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||||
|
totalCost `shouldBe` 3.27
|
||||||
|
|
||||||
|
it "outputs the total cost for an upsert with 1000 rows" $ do
|
||||||
|
r <- request methodPost "/tiobe_pls"
|
||||||
|
[("Prefer","handling=strict; resolution=merge-duplicates"), ("Accept","application/vnd.pgrst.plan+json")]
|
||||||
|
(getInsertDataForTiobePlsTable 1000)
|
||||||
|
|
||||||
|
let totalCost = planCost r
|
||||||
|
resStatus = simpleStatus r
|
||||||
|
resHeaders = simpleHeaders r
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||||
|
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||||
|
totalCost `shouldBe` 3.27
|
||||||
|
|
||||||
it "outputs the plan for application/vnd.pgrst.object" $ do
|
it "outputs the plan for application/vnd.pgrst.object" $ do
|
||||||
r <- request methodDelete "/projects?id=eq.6"
|
r <- request methodDelete "/projects?id=eq.6"
|
||||||
[("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.plan+json; for=\"application/vnd.pgrst.object\"; options=verbose")] ""
|
[("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.plan+json; for=\"application/vnd.pgrst.object\"; options=verbose")] ""
|
||||||
|
|||||||
+26
-11
@@ -1,6 +1,7 @@
|
|||||||
module SpecHelper where
|
module SpecHelper where
|
||||||
|
|
||||||
import Control.Lens ((^?))
|
import Control.Lens ((^?))
|
||||||
|
import qualified Data.Aeson as JSON
|
||||||
import Data.Aeson.Lens
|
import Data.Aeson.Lens
|
||||||
import qualified Data.ByteString.Base64 as B64 (decodeLenient)
|
import qualified Data.ByteString.Base64 as B64 (decodeLenient)
|
||||||
import qualified Data.ByteString.Char8 as BS
|
import qualified Data.ByteString.Char8 as BS
|
||||||
@@ -9,7 +10,7 @@ import qualified Data.Map.Strict as M
|
|||||||
import Data.Scientific (toRealFloat)
|
import Data.Scientific (toRealFloat)
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
|
||||||
import Data.Aeson (Value (..), decode, encode)
|
import Data.Aeson ((.=))
|
||||||
import Data.CaseInsensitive (CI (..), mk, original)
|
import Data.CaseInsensitive (CI (..), mk, original)
|
||||||
import Data.List (lookup)
|
import Data.List (lookup)
|
||||||
import Data.List.NonEmpty (fromList)
|
import Data.List.NonEmpty (fromList)
|
||||||
@@ -79,14 +80,14 @@ validateOpenApiResponse headers = do
|
|||||||
let respHeaders = simpleHeaders r in
|
let respHeaders = simpleHeaders r in
|
||||||
respHeaders `shouldSatisfy`
|
respHeaders `shouldSatisfy`
|
||||||
\hs -> ("Content-Type", "application/openapi+json; charset=utf-8") `elem` hs
|
\hs -> ("Content-Type", "application/openapi+json; charset=utf-8") `elem` hs
|
||||||
Just body <- pure $ decode (simpleBody r)
|
Just body <- pure $ JSON.decode (simpleBody r)
|
||||||
Just schema <- liftIO $ decode <$> BL.readFile "test/spec/fixtures/openapi.json"
|
Just schema <- liftIO $ JSON.decode <$> BL.readFile "test/spec/fixtures/openapi.json"
|
||||||
let args :: M.Map Text Value
|
let args :: M.Map Text JSON.Value
|
||||||
args = M.fromList
|
args = M.fromList
|
||||||
[ ( "schema", schema )
|
[ ( "schema", schema )
|
||||||
, ( "data", body ) ]
|
, ( "data", body ) ]
|
||||||
hdrs = acceptHdrs "application/json"
|
hdrs = acceptHdrs "application/json"
|
||||||
request methodPost "/rpc/validate_json_schema" hdrs (encode args)
|
request methodPost "/rpc/validate_json_schema" hdrs (JSON.encode args)
|
||||||
`shouldRespondWith` "true"
|
`shouldRespondWith` "true"
|
||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
, matchHeaders = []
|
, matchHeaders = []
|
||||||
@@ -284,7 +285,7 @@ isErrorFormat s =
|
|||||||
"message" `S.member` keys &&
|
"message" `S.member` keys &&
|
||||||
S.null (S.difference keys validKeys)
|
S.null (S.difference keys validKeys)
|
||||||
where
|
where
|
||||||
obj = decode s :: Maybe (M.Map Text Value)
|
obj = JSON.decode s :: Maybe (M.Map Text JSON.Value)
|
||||||
keys = maybe S.empty M.keysSet obj
|
keys = maybe S.empty M.keysSet obj
|
||||||
validKeys = S.fromList ["message", "details", "hint", "code"]
|
validKeys = S.fromList ["message", "details", "hint", "code"]
|
||||||
|
|
||||||
@@ -309,7 +310,7 @@ mutatesWith = MutationCheck
|
|||||||
|
|
||||||
-- | The original table data before it is modified.
|
-- | The original table data before it is modified.
|
||||||
-- The column order is needed for an accurate comparison after the mutation
|
-- The column order is needed for an accurate comparison after the mutation
|
||||||
baseTable :: ByteString -> ByteString -> Value -> BaseTable
|
baseTable :: ByteString -> ByteString -> JSON.Value -> BaseTable
|
||||||
baseTable = BaseTable
|
baseTable = BaseTable
|
||||||
|
|
||||||
-- | The mutation (update/delete) that will be applied to the base table
|
-- | The mutation (update/delete) that will be applied to the base table
|
||||||
@@ -317,7 +318,7 @@ requestMutation :: Method -> ByteString -> [Header] -> BL.ByteString -> WaiExpec
|
|||||||
requestMutation method path headers body =
|
requestMutation method path headers body =
|
||||||
request method path (("Prefer", "tx=commit") : headers) body `shouldRespondWith` 204
|
request method path (("Prefer", "tx=commit") : headers) body `shouldRespondWith` 204
|
||||||
|
|
||||||
data BaseTable = BaseTable ByteString ByteString Value
|
data BaseTable = BaseTable ByteString ByteString JSON.Value
|
||||||
data MutationCheck = MutationCheck BaseTable (WaiExpectation ())
|
data MutationCheck = MutationCheck BaseTable (WaiExpectation ())
|
||||||
|
|
||||||
planCost :: SResponse -> Float
|
planCost :: SResponse -> Float
|
||||||
@@ -326,6 +327,20 @@ planCost resp =
|
|||||||
-- big value in case parsing fails
|
-- big value in case parsing fails
|
||||||
fromMaybe 1000000000.0 $ unbox =<< res
|
fromMaybe 1000000000.0 $ unbox =<< res
|
||||||
where
|
where
|
||||||
unbox :: Value -> Maybe Float
|
unbox :: JSON.Value -> Maybe Float
|
||||||
unbox (Number n) = Just $ toRealFloat n
|
unbox (JSON.Number n) = Just $ toRealFloat n
|
||||||
unbox _ = Nothing
|
unbox _ = Nothing
|
||||||
|
|
||||||
|
data TiobePlsRow = TiobePlsRow {
|
||||||
|
name' :: Text,
|
||||||
|
rank :: Int
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
instance JSON.ToJSON TiobePlsRow where
|
||||||
|
toJSON (TiobePlsRow name'' rank') = JSON.object ["name" .= name'', "rank" .= rank']
|
||||||
|
|
||||||
|
getInsertDataForTiobePlsTable :: Int -> BL.ByteString
|
||||||
|
getInsertDataForTiobePlsTable rows =
|
||||||
|
JSON.encode $ fromList $ [TiobePlsRow {name' = nm, rank = rk} | (nm,rk) <- nameRankList]
|
||||||
|
where
|
||||||
|
nameRankList = [("Lang " <> show i, i) | i <- [20..(rows+20)] ] :: [(Text, Int)]
|
||||||
|
|||||||
Reference in New Issue
Block a user