feat: add Content-Length response header
This commit is contained in:
committed by
Steve Chavez
parent
98fcbedca5
commit
57ef9988a5
+9
-9
@@ -964,45 +964,45 @@ def test_log_level(level, defaultenv):
|
||||
assert len(output) == 0
|
||||
elif level == "error":
|
||||
assert re.match(
|
||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 - "" "python-requests/.+"',
|
||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
||||
output[0],
|
||||
)
|
||||
assert len(output) == 1
|
||||
elif level == "warn":
|
||||
assert re.match(
|
||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 - "" "python-requests/.+"',
|
||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
||||
output[0],
|
||||
)
|
||||
assert re.match(
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 - "" "python-requests/.+"',
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 \d+ "" "python-requests/.+"',
|
||||
output[1],
|
||||
)
|
||||
assert len(output) == 2
|
||||
elif level == "info":
|
||||
assert re.match(
|
||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 - "" "python-requests/.+"',
|
||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
||||
output[0],
|
||||
)
|
||||
assert re.match(
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET / HTTP/1.1" 200 - "" "python-requests/.+"',
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET / HTTP/1.1" 200 \d+ "" "python-requests/.+"',
|
||||
output[1],
|
||||
)
|
||||
assert re.match(
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 - "" "python-requests/.+"',
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 \d+ "" "python-requests/.+"',
|
||||
output[2],
|
||||
)
|
||||
assert len(output) == 3
|
||||
elif level == "debug":
|
||||
assert re.match(
|
||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 - "" "python-requests/.+"',
|
||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
||||
output[0],
|
||||
)
|
||||
assert re.match(
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET / HTTP/1.1" 200 - "" "python-requests/.+"',
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET / HTTP/1.1" 200 \d+ "" "python-requests/.+"',
|
||||
output[1],
|
||||
)
|
||||
assert re.match(
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 - "" "python-requests/.+"',
|
||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 \d+ "" "python-requests/.+"',
|
||||
output[2],
|
||||
)
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ spec = describe "authorization" $ do
|
||||
"code":"42501",
|
||||
"message":"permission denied for table authors_only"} |]
|
||||
{ matchStatus = 401
|
||||
, matchHeaders = ["WWW-Authenticate" <:> "Bearer"]
|
||||
, matchHeaders = [ "WWW-Authenticate" <:> "Bearer"
|
||||
, "Content-Length" <:> "96" ]
|
||||
}
|
||||
|
||||
it "denies access to tables that postgrest_test_author does not own" $
|
||||
@@ -35,7 +36,7 @@ spec = describe "authorization" $ do
|
||||
"code":"42501",
|
||||
"message":"permission denied for table private_table"} |]
|
||||
{ matchStatus = 403
|
||||
, matchHeaders = []
|
||||
, matchHeaders = ["Content-Length" <:> "97"]
|
||||
}
|
||||
|
||||
it "denies execution on functions that anonymous does not own" $
|
||||
@@ -92,7 +93,8 @@ spec = describe "authorization" $ do
|
||||
{ matchStatus = 401
|
||||
, matchHeaders = [
|
||||
"WWW-Authenticate" <:>
|
||||
"Bearer error=\"invalid_token\", error_description=\"Empty JWT is sent in Authorization header\""
|
||||
"Bearer error=\"invalid_token\", error_description=\"Empty JWT is sent in Authorization header\"",
|
||||
"Content-Length" <:> "100"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -17,4 +17,5 @@ spec =
|
||||
[("Accept","application/openapi+json")] ""
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"PGRST126","details":null,"hint":null,"message":"Root endpoint metadata is disabled"} |]
|
||||
{ matchStatus = 404 }
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = ["Content-Length" <:> "93"]}
|
||||
|
||||
@@ -25,7 +25,8 @@ spec = describe "OpenAPI Ignore Privileges" $ do
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [ "Content-Type" <:> "application/openapi+json; charset=utf-8" ]
|
||||
, matchHeaders = [ "Content-Type" <:> "application/openapi+json; charset=utf-8"
|
||||
, matchHeaderAbsent hContentLength]
|
||||
}
|
||||
|
||||
describe "table" $ do
|
||||
|
||||
@@ -24,7 +24,8 @@ spec = describe "OpenAPI" $ do
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"]
|
||||
, matchHeaders = [ "Content-Type" <:> "application/openapi+json; charset=utf-8"
|
||||
, matchHeaderAbsent hContentLength ]
|
||||
}
|
||||
|
||||
it "should respond to openapi request on none root path with 406" $
|
||||
@@ -38,11 +39,14 @@ spec = describe "OpenAPI" $ do
|
||||
`shouldRespondWith` 406
|
||||
|
||||
it "includes postgrest.org current version api docs" $ do
|
||||
r <- simpleBody <$> get "/"
|
||||
r <- get "/"
|
||||
|
||||
let docsUrl = r ^? key "externalDocs" . key "url"
|
||||
let headers = simpleHeaders r
|
||||
docsUrl = simpleBody r ^? key "externalDocs" . key "url"
|
||||
|
||||
liftIO $ docsUrl `shouldBe` Just (String ("https://postgrest.org/en/" <> docsVersion <> "/references/api.html"))
|
||||
liftIO $ do
|
||||
headers `shouldSatisfy` notZeroContentLength
|
||||
docsUrl `shouldBe` Just (String ("https://postgrest.org/en/" <> docsVersion <> "/references/api.html"))
|
||||
|
||||
describe "schema" $ do
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ spec = describe "Allow header" $ do
|
||||
context "a table" $ do
|
||||
it "includes read/write methods for writeable table" $ do
|
||||
r <- request methodOptions "/items" [] ""
|
||||
liftIO $
|
||||
simpleHeaders r `shouldSatisfy`
|
||||
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
|
||||
liftIO $ do
|
||||
simpleHeaders r `shouldSatisfy` matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
|
||||
simpleHeaders r `shouldSatisfy` matchHeader "Content-Length" "0"
|
||||
|
||||
it "fails with 404 for an unknown table" $
|
||||
request methodOptions "/unknown" [] "" `shouldRespondWith` 404
|
||||
|
||||
@@ -20,6 +20,7 @@ spec = describe "custom media types" $ do
|
||||
liftIO $ do
|
||||
simpleBody r `shouldBe` readFixtureFile "lines.twkb"
|
||||
simpleHeaders r `shouldContain` [("Content-Type", "application/vnd.twkb")]
|
||||
simpleHeaders r `shouldContain` [("Content-Length", "30")]
|
||||
|
||||
it "can query by id if there's an aggregate defined for the table" $ do
|
||||
r <- request methodGet "/lines?id=eq.1" (acceptHdrs "application/vnd.twkb") ""
|
||||
@@ -32,7 +33,8 @@ spec = describe "custom media types" $ do
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"PGRST107","details":null,"hint":null,"message":"None of these media types are available: text/plain"} |]
|
||||
{ matchStatus = 406
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [ matchContentTypeJson
|
||||
, "Content-Length" <:> "110" ]
|
||||
}
|
||||
|
||||
it "can get raw xml output with Accept: text/xml if there's an aggregate defined" $ do
|
||||
@@ -40,7 +42,8 @@ spec = describe "custom media types" $ do
|
||||
`shouldRespondWith`
|
||||
"<myxml>foo</myxml>bar<foobar><baz/></foobar>"
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "text/xml; charset=utf-8"]
|
||||
, matchHeaders = [ "Content-Type" <:> "text/xml; charset=utf-8"
|
||||
, "Content-Length" <:> "44"]
|
||||
}
|
||||
|
||||
-- TODO SOH (start of heading) is being added to results
|
||||
@@ -50,7 +53,8 @@ spec = describe "custom media types" $ do
|
||||
`shouldRespondWith`
|
||||
"\SOH{\"type\": \"FeatureCollection\", \"hello\": \"world\"}"
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "application/vnd.geo2+json"]
|
||||
, matchHeaders = [ "Content-Type" <:> "application/vnd.geo2+json"
|
||||
, "Content-Length" <:> "48" ]
|
||||
}
|
||||
|
||||
it "will use the more specific application/vnd.geo2 handler for this table" $ do
|
||||
@@ -83,14 +87,16 @@ spec = describe "custom media types" $ do
|
||||
|</html>
|
||||
|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "text/html"]
|
||||
, matchHeaders = [ "Content-Type" <:> "text/html"
|
||||
, "Content-Length" <:> "117" ]
|
||||
}
|
||||
|
||||
it "can get raw output with Accept: text/plain" $ do
|
||||
request methodGet "/rpc/welcome" (acceptHdrs "text/plain") ""
|
||||
`shouldRespondWith` "Welcome to PostgREST"
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "text/plain; charset=utf-8"]
|
||||
, matchHeaders = [ "Content-Type" <:> "text/plain; charset=utf-8"
|
||||
, "Content-Length" <:> "20" ]
|
||||
}
|
||||
|
||||
it "can get raw xml output with Accept: text/xml" $ do
|
||||
@@ -98,7 +104,8 @@ spec = describe "custom media types" $ do
|
||||
`shouldRespondWith`
|
||||
"<my-xml-tag/>"
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "text/xml; charset=utf-8"]
|
||||
, matchHeaders = [ "Content-Type" <:> "text/xml; charset=utf-8"
|
||||
, "Content-Length" <:> "13" ]
|
||||
}
|
||||
|
||||
it "can get raw xml output with Accept: text/xml" $ do
|
||||
@@ -132,6 +139,7 @@ spec = describe "custom media types" $ do
|
||||
liftIO $ do
|
||||
simpleBody r `shouldBe` readFixtureFile "A.png"
|
||||
simpleHeaders r `shouldContain` [("Content-Type", "image/png")]
|
||||
simpleHeaders r `shouldContain` [("Content-Length", "138")]
|
||||
|
||||
context "Proc that returns set of scalars and Accept: text/plain" $
|
||||
it "will err because only scalars work with media type domains" $ do
|
||||
@@ -141,7 +149,8 @@ spec = describe "custom media types" $ do
|
||||
`shouldRespondWith`
|
||||
[json|{"code":"PGRST107","details":null,"hint":null,"message":"None of these media types are available: text/plain"}|]
|
||||
{ matchStatus = 406
|
||||
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
|
||||
, matchHeaders = [ "Content-Type" <:> "application/json; charset=utf-8"
|
||||
, "Content-Length" <:> "110"]
|
||||
}
|
||||
|
||||
context "Proc that returns rows and accepts custom media type" $ do
|
||||
@@ -173,7 +182,8 @@ spec = describe "custom media types" $ do
|
||||
`shouldRespondWith`
|
||||
[json| {"overridden": "true"} |]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
|
||||
, matchHeaders = [ "Content-Type" <:> "application/json; charset=utf-8"
|
||||
, "Content-Length" <:> "22" ]
|
||||
}
|
||||
|
||||
-- TODO SOH (start of heading) is being added to results
|
||||
@@ -182,7 +192,8 @@ spec = describe "custom media types" $ do
|
||||
`shouldRespondWith`
|
||||
"\SOH{\"crs\": {\"type\": \"name\", \"properties\": {\"name\": \"EPSG:4326\"}}, \"type\": \"FeatureCollection\", \"features\": [{\"type\": \"Feature\", \"geometry\": {\"type\": \"LineString\", \"coordinates\": [[1, 1], [5, 5]]}, \"properties\": {\"id\": 1, \"name\": \"line-1\"}}]}"
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"]
|
||||
, matchHeaders = [ "Content-Type" <:> "application/geo+json; charset=utf-8"
|
||||
, "Content-Length" <:> "239" ]
|
||||
}
|
||||
|
||||
it "will not override vendored media types like application/vnd.pgrst.object" $
|
||||
@@ -190,7 +201,8 @@ spec = describe "custom media types" $ do
|
||||
`shouldRespondWith`
|
||||
[json|{"id":1,"name":"Windows 7","client_id":1}|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"]
|
||||
, matchHeaders = [ "Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"
|
||||
, "Content-Length" <:> "41" ]
|
||||
}
|
||||
|
||||
context "matches requested media type correctly" $ do
|
||||
@@ -200,11 +212,13 @@ spec = describe "custom media types" $ do
|
||||
liftIO $ do
|
||||
simpleBody r1 `shouldBe` readFixtureFile "A.png"
|
||||
simpleHeaders r1 `shouldContain` [("Content-Type", "image/png")]
|
||||
simpleHeaders r1 `shouldContain` [("Content-Length", "138")]
|
||||
|
||||
r2 <- request methodGet "/rpc/ret_image" (acceptHdrs "text/html,application/xhtml+xml,application/xml;q=0.9,image/png,*/*;q=0.8") ""
|
||||
liftIO $ do
|
||||
simpleBody r2 `shouldBe` readFixtureFile "A.png"
|
||||
simpleHeaders r2 `shouldContain` [("Content-Type", "image/png")]
|
||||
simpleHeaders r2 `shouldContain` [("Content-Length", "138")]
|
||||
|
||||
-- https://github.com/PostgREST/postgrest/issues/2170
|
||||
it "will match json in presence of text/plain" $ do
|
||||
@@ -219,7 +233,8 @@ spec = describe "custom media types" $ do
|
||||
`shouldRespondWith`
|
||||
"id\tname\tclient_id\n1\tWindows 7\t1\n2\tWindows 10\t1\n"
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "text/tab-separated-values"]
|
||||
, matchHeaders = [ "Content-Type" <:> "text/tab-separated-values"
|
||||
, "Content-Length" <:> "47" ]
|
||||
}
|
||||
|
||||
-- https://github.com/PostgREST/postgrest/issues/1371#issuecomment-519248984
|
||||
@@ -229,6 +244,7 @@ spec = describe "custom media types" $ do
|
||||
simpleBody r `shouldBe` readFixtureFile "lines.csv"
|
||||
simpleHeaders r `shouldContain` [("Content-Type", "text/csv; charset=utf-8")]
|
||||
simpleHeaders r `shouldContain` [("Content-Disposition", "attachment; filename=\"lines.csv\"")]
|
||||
simpleHeaders r `shouldContain` [("Content-Length", "216")]
|
||||
|
||||
-- https://github.com/PostgREST/postgrest/issues/3160
|
||||
context "using select query parameter" $ do
|
||||
@@ -239,7 +255,8 @@ spec = describe "custom media types" $ do
|
||||
|(2,"Windows 10",1)
|
||||
|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "pg/outfunc"]
|
||||
, matchHeaders = [ "Content-Type" <:> "pg/outfunc"
|
||||
, "Content-Length" <:> "37" ]
|
||||
}
|
||||
|
||||
it "with fewer columns selected" $ do
|
||||
|
||||
@@ -22,6 +22,7 @@ spec =
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ spec =
|
||||
`shouldRespondWith` [json|[{"id":2}]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Range" <:> "*/1"
|
||||
, "Content-Length" <:> "10"
|
||||
, "Preference-Applied" <:> "return=representation, count=exact"]
|
||||
}
|
||||
|
||||
@@ -41,6 +43,7 @@ spec =
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
request methodDelete "/items?id=eq.3&select=id"
|
||||
@@ -50,6 +53,7 @@ spec =
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "*/*"
|
||||
, "Preference-Applied" <:> "return=minimal"]
|
||||
}
|
||||
@@ -58,7 +62,8 @@ spec =
|
||||
request methodDelete "/complex_items?id=eq.2&select=id,name" [("Prefer", "return=representation")] ""
|
||||
`shouldRespondWith` [json|[{"id":2,"name":"Two"}]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
, matchHeaders = [ "Content-Range" <:> "*/*"
|
||||
, "Content-Length" <:> "23" ]
|
||||
}
|
||||
|
||||
it "can rename and cast the selected columns" $
|
||||
@@ -137,6 +142,7 @@ spec =
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Preference-Applied" <:> "return=minimal" ]
|
||||
}
|
||||
|
||||
@@ -147,7 +153,8 @@ spec =
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [matchHeaderAbsent hContentType]
|
||||
, matchHeaders = [matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength]
|
||||
}
|
||||
|
||||
context "with ordering" $
|
||||
|
||||
@@ -40,7 +40,8 @@ spec =
|
||||
}
|
||||
|]
|
||||
{ matchStatus = 300
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [ matchContentTypeJson
|
||||
, "Content-Length" <:> "828" ]
|
||||
}
|
||||
|
||||
it "errs on an ambiguous embed that has a circular reference" $
|
||||
|
||||
@@ -26,7 +26,8 @@ pgErrorCodeMapping = do
|
||||
"details": null,
|
||||
"hint": "Increase the configuration parameter \"max_stack_depth\" (currently 2048kB), after ensuring the platform's stack depth limit is adequate.",
|
||||
"message": "stack depth limit exceeded"}|]
|
||||
{ matchStatus = 500 }
|
||||
{ matchStatus = 500
|
||||
, matchHeaders = ["Content-Length" <:> "217"] }
|
||||
|
||||
context "includes the proxy-status header on the response" $ do
|
||||
it "works with ApiRequest error" $
|
||||
@@ -34,7 +35,8 @@ pgErrorCodeMapping = do
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"PGRST125","details":null,"hint":null,"message":"Invalid path specified in request URL"} |]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = ["Proxy-Status" <:> "PostgREST; error=PGRST125"]
|
||||
, matchHeaders = [ "Proxy-Status" <:> "PostgREST; error=PGRST125"
|
||||
, "Content-Length" <:> "96" ]
|
||||
}
|
||||
|
||||
it "works with SchemaCache error" $
|
||||
@@ -42,7 +44,8 @@ pgErrorCodeMapping = do
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"PGRST205","details":null,"hint":"Perhaps you meant the table 'test.json_table'","message":"Could not find the table 'test.non_existent_table' in the schema cache"} |]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = ["Proxy-Status" <:> "PostgREST; error=PGRST205"]
|
||||
, matchHeaders = [ "Proxy-Status" <:> "PostgREST; error=PGRST205"
|
||||
, "Content-Length" <:> "172" ]
|
||||
}
|
||||
|
||||
it "works with Jwt error" $ do
|
||||
@@ -51,7 +54,8 @@ pgErrorCodeMapping = do
|
||||
`shouldRespondWith`
|
||||
[json| {"message":"Expected 3 parts in JWT; got 2","code":"PGRST301","hint":null,"details":null} |]
|
||||
{ matchStatus = 401
|
||||
, matchHeaders = ["Proxy-Status" <:> "PostgREST; error=PGRST301"]
|
||||
, matchHeaders = [ "Proxy-Status" <:> "PostgREST; error=PGRST301"
|
||||
, "Content-Length" <:> "89" ]
|
||||
}
|
||||
|
||||
it "works with raise sqlstate custom error" $
|
||||
@@ -59,7 +63,8 @@ pgErrorCodeMapping = do
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"PT402","details":"Quota exceeded","hint":"Upgrade your plan","message":"Payment Required"} |]
|
||||
{ matchStatus = 402
|
||||
, matchHeaders = ["Proxy-Status" <:> "PostgREST; error=PT402"]
|
||||
, matchHeaders = [ "Proxy-Status" <:> "PostgREST; error=PT402"
|
||||
, "Content-Length" <:> "99" ]
|
||||
}
|
||||
|
||||
it "works with sqlstate PGRST custom error" $
|
||||
@@ -67,5 +72,6 @@ pgErrorCodeMapping = do
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"123","details":"DEF","hint":"XYZ","message":"ABC"} |]
|
||||
{ matchStatus = 332
|
||||
, matchHeaders = ["Proxy-Status" <:> "PostgREST; error=123"]
|
||||
, matchHeaders = [ "Proxy-Status" <:> "PostgREST; error=123"
|
||||
, "Content-Length" <:> "59" ]
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ spec actualPgVersion = do
|
||||
} |] `shouldRespondWith` ""
|
||||
{ matchStatus = 201
|
||||
-- should not have content type set when body is empty
|
||||
, matchHeaders = [matchHeaderAbsent hContentType]
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, "Content-Length" <:> "0"]
|
||||
}
|
||||
|
||||
it "filters columns in result using &select" $
|
||||
@@ -42,6 +43,7 @@ spec actualPgVersion = do
|
||||
}] |] `shouldRespondWith` [json|[{"integer":14,"varchar":"testing!"}]|]
|
||||
{ matchStatus = 201
|
||||
, matchHeaders = [matchContentTypeJson
|
||||
, "Content-Length" <:> "37"
|
||||
, "Preference-Applied" <:> "return=representation"]
|
||||
}
|
||||
|
||||
@@ -187,7 +189,8 @@ spec actualPgVersion = do
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType ]
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength ]
|
||||
}
|
||||
|
||||
request methodPost "/auto_incrementing_pk"
|
||||
@@ -211,7 +214,7 @@ spec actualPgVersion = do
|
||||
[json|{"hint":null,"details":"Failing row contains (null, foo).","code":"23502","message":"null value in column \"k\" violates not-null constraint"}|]
|
||||
)
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [ matchContentTypeJson]
|
||||
}
|
||||
|
||||
context "into a table with no pk" $ do
|
||||
@@ -286,7 +289,8 @@ spec actualPgVersion = do
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"Empty or invalid json","code":"PGRST102","details":null,"hint":null}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [ matchContentTypeJson,
|
||||
"Content-Length" <:> "80" ]
|
||||
}
|
||||
|
||||
context "with no payload" $
|
||||
@@ -370,7 +374,8 @@ spec actualPgVersion = do
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType ]
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength ]
|
||||
}
|
||||
|
||||
request methodPost "/items2"
|
||||
@@ -388,7 +393,8 @@ spec actualPgVersion = do
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType ]
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength ]
|
||||
}
|
||||
|
||||
request methodPost "/items3?select=id"
|
||||
@@ -623,7 +629,8 @@ spec actualPgVersion = do
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [matchHeaderAbsent hContentType]
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength ]
|
||||
}
|
||||
|
||||
describe "CSV insert" $ do
|
||||
|
||||
@@ -33,6 +33,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||
totalCost `shouldBe` (if actualPgVersion >= pgVersion170 then 11.32 else 15.63)
|
||||
|
||||
@@ -140,6 +141,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||
totalCost `shouldBe` 0.06
|
||||
|
||||
@@ -153,6 +155,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||
totalCost `shouldBe` 8.23
|
||||
|
||||
@@ -166,6 +169,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||
totalCost `shouldBe` (if actualPgVersion >= pgVersion170 then 11.37 else 15.68)
|
||||
|
||||
@@ -180,6 +184,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||
totalCost `shouldBe` 3.55
|
||||
|
||||
@@ -194,6 +199,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||
totalCost `shouldBe` 5.53
|
||||
|
||||
@@ -248,6 +254,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/vnd.pgrst.object+json\"; options=verbose; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
aggCol `shouldBe` Just [aesonQQ| "COALESCE((json_agg(ROW(projects.id, projects.name, projects.client_id)) -> 0), 'null'::json)" |]
|
||||
|
||||
describe "function plan" $ do
|
||||
@@ -261,6 +268,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||
totalCost `shouldBe` 68.56
|
||||
|
||||
@@ -275,6 +283,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+text; for=\"application/json\"; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||
resBody `shouldSatisfy` (\t -> LBS.take 9 t == "Aggregate")
|
||||
|
||||
@@ -288,6 +297,7 @@ spec actualPgVersion = do
|
||||
|
||||
liftIO $ do
|
||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+text; for=\"application/json\"; charset=utf-8")
|
||||
resHeaders `shouldSatisfy` notZeroContentLength
|
||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||
resBody `shouldSatisfy` (\t -> LBS.take 9 t == "Aggregate")
|
||||
|
||||
|
||||
@@ -28,14 +28,17 @@ spec = do
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"PGRST205","details":null,"hint":"Perhaps you meant the table 'test.private_table'","message":"Could not find the table 'test.faketable' in the schema cache"} |]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = []
|
||||
, matchHeaders = ["Content-Length" <:> "166"]
|
||||
}
|
||||
|
||||
describe "Filtering response" $ do
|
||||
it "matches with equality" $
|
||||
get "/items?id=eq.5"
|
||||
`shouldRespondWith` [json| [{"id":5}] |]
|
||||
{ matchHeaders = ["Content-Range" <:> "0-0/*"] }
|
||||
{ matchHeaders = [
|
||||
"Content-Range" <:> "0-0/*",
|
||||
"Content-Length" <:> "10"
|
||||
] }
|
||||
|
||||
it "matches with equality using not operator" $
|
||||
get "/items?id=not.eq.5&order=id"
|
||||
@@ -511,7 +514,10 @@ spec = do
|
||||
"code":"PGRST108",
|
||||
"message":"'non_existent_projects' is not an embedded resource in this request"}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [
|
||||
"Content-Length" <:> "204",
|
||||
matchContentTypeJson
|
||||
]
|
||||
}
|
||||
get "/clients?select=*,amiga_projects:projects(*)&amiga_projectsss.name=ilike.*Amiga*" `shouldRespondWith`
|
||||
[json|
|
||||
@@ -590,7 +596,7 @@ spec = do
|
||||
get "/complex_items?select=id::fakecolumntype"
|
||||
`shouldRespondWith` [json| {"hint":null,"details":null,"code":"42704","message":"type \"fakecolumntype\" does not exist"} |]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = []
|
||||
, matchHeaders = ["Content-Length" <:> "94"]
|
||||
}
|
||||
|
||||
it "can cast types with underscore and numbers" $
|
||||
@@ -603,7 +609,11 @@ spec = do
|
||||
it "requesting parents and children" $
|
||||
get "/projects?id=eq.1&select=id, name, clients(*), tasks(id, name)" `shouldRespondWith`
|
||||
[json|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
{ matchHeaders = [
|
||||
"Content-Length" <:> "141",
|
||||
matchContentTypeJson
|
||||
]
|
||||
}
|
||||
|
||||
it "requesting parent and renaming primary key" $
|
||||
get "/projects?select=name,client:clients(clientId:id,name)" `shouldRespondWith`
|
||||
@@ -1145,7 +1155,10 @@ spec = do
|
||||
get "/items?order=id.asc.nullslasttt" `shouldRespondWith`
|
||||
[json|{"details":"unexpected 't' expecting \",\" or end of input","message":"\"failed to parse order (id.asc.nullslasttt)\" (line 1, column 17)","code":"PGRST100","hint":null}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [
|
||||
"Content-Length" <:> "169",
|
||||
matchContentTypeJson
|
||||
]
|
||||
}
|
||||
|
||||
describe "Accept headers" $ do
|
||||
@@ -1155,7 +1168,10 @@ spec = do
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"None of these media types are available: text/unknowntype","code":"PGRST107","details":null,"hint":null}|]
|
||||
{ matchStatus = 406
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [
|
||||
"Content-Length" <:> "116",
|
||||
matchContentTypeJson
|
||||
]
|
||||
}
|
||||
|
||||
it "should respond correctly to */* in accept header" $
|
||||
@@ -1194,7 +1210,10 @@ spec = do
|
||||
(acceptHdrs "text/csv; version=1") ""
|
||||
`shouldRespondWith` "k,extra\nxyyx,u\nxYYx,v"
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Type" <:> "text/csv; charset=utf-8"]
|
||||
, matchHeaders = [
|
||||
"Content-Type" <:> "text/csv; charset=utf-8",
|
||||
"Content-Length" <:> "21"
|
||||
]
|
||||
}
|
||||
|
||||
describe "Canonical location" $ do
|
||||
@@ -1512,7 +1531,10 @@ spec = do
|
||||
get "/datarep_todos?select=id,label_color,banana" `shouldRespondWith`
|
||||
[json| {"code":"42703","details":null,"hint":null,"message":"column datarep_todos.banana does not exist"} |]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [
|
||||
"Content-Length" <:> "98",
|
||||
matchContentTypeJson
|
||||
]
|
||||
}
|
||||
it "formats columns in views including computed columns" $
|
||||
get "/datarep_todos_computed?select=id,label_color,dark_color" `shouldRespondWith`
|
||||
@@ -1548,8 +1570,8 @@ spec = do
|
||||
get "/datarep_todos?id=lt.4" `shouldRespondWith`
|
||||
[json| [
|
||||
{"id":1,"name":"Report","label_color":"#000000","due_at":"2018-01-02T00:00:00Z","icon_image":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAAABBJREFUeJxiYAEAAAAA//8DAAAABgAFBXv6vUAAAAAASUVORK5CYII=","created_at":1513213350,"budget":"12.50"},
|
||||
{"id":2,"name":"Essay","label_color":"#000100","due_at":"2018-01-03T00:00:00Z","icon_image":null,"created_at":1513213350,"budget":"100000000000000.13"},
|
||||
{"id":3,"name":"Algebra","label_color":"#01E240","due_at":"2018-01-01T14:12:34.123456Z","icon_image":null,"created_at":1513213350,"budget":"0.00"}
|
||||
{"id":2,"name":"Essay","label_color":"#000100","due_at":"2018-01-03T00:00:00Z","icon_image":null,"created_at":1513213350,"budget":"100000000000000.13"},
|
||||
{"id":3,"name":"Algebra","label_color":"#01E240","due_at":"2018-01-01T14:12:34.123456Z","icon_image":null,"created_at":1513213350,"budget":"0.00"}
|
||||
] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
it "formats star and explicit mix" $
|
||||
@@ -1580,7 +1602,10 @@ spec = do
|
||||
-- we prove the parser is not used because it'd replace the Z with `+00:00` and a different error message.
|
||||
[json| {"code":"22007","details":null,"hint":null,"message":"invalid input syntax for type timestamp with time zone: \"Z\""} |]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [
|
||||
"Content-Length" <:> "117",
|
||||
matchContentTypeJson
|
||||
]
|
||||
}
|
||||
it "uses text parser for filter with 'IN' predicates" $
|
||||
get "/datarep_todos?select=id,due_at&label_color=in.(000100,01E240)" `shouldRespondWith`
|
||||
@@ -1607,7 +1632,10 @@ spec = do
|
||||
{"code":"42883","details":null,"hint":"No operator matches the given name and argument types. You might need to add explicit type casts.","message":"operator does not exist: public.color ~~* unknown"}
|
||||
|]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [
|
||||
"Content-Length" <:> "200",
|
||||
matchContentTypeJson
|
||||
]
|
||||
}
|
||||
|
||||
context "searching for an empty string" $ do
|
||||
@@ -1624,11 +1652,13 @@ spec = do
|
||||
it "return http status 500" $
|
||||
get "/infinite_recursion?select=*" `shouldRespondWith`
|
||||
[json|{"code":"42P17","message":"infinite recursion detected in rules for relation \"infinite_recursion\"","details":null,"hint":null}|]
|
||||
{ matchStatus = 500 }
|
||||
{ matchStatus = 500
|
||||
, matchHeaders = ["Content-Length" <:> "128"] }
|
||||
|
||||
context "invalid resource path" $ do
|
||||
it "return http status 404" $
|
||||
get "/first/second/third?select=*"
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"PGRST125","details":null,"hint":null,"message":"Invalid path specified in request URL"} |]
|
||||
{ matchStatus = 404 }
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = ["Content-Length" <:> "96"]}
|
||||
|
||||
@@ -22,7 +22,10 @@ spec = do
|
||||
context "when I don't want the count" $ do
|
||||
it "returns range Content-Range with */* for empty range" $
|
||||
get "/rpc/getitemrange?min=2&max=2"
|
||||
`shouldRespondWith` [json| [] |] {matchHeaders = ["Content-Range" <:> "*/*"]}
|
||||
`shouldRespondWith` [json| [] |]
|
||||
{ matchHeaders = [ "Content-Range" <:> "*/*"
|
||||
, "Content-Length" <:> "2" ]
|
||||
}
|
||||
|
||||
it "returns range Content-Range with range/*" $
|
||||
get "/rpc/getitemrange?order=id&min=0&max=15"
|
||||
@@ -42,7 +45,8 @@ spec = do
|
||||
"hint":null
|
||||
}|]
|
||||
{ matchStatus = 416
|
||||
, matchHeaders = ["Content-Range" <:> "*/0"]
|
||||
, matchHeaders = [ "Content-Range" <:> "*/0"
|
||||
, "Content-Length" <:> "144"]
|
||||
}
|
||||
|
||||
it "refuses a range requesting start past last item" $
|
||||
@@ -65,8 +69,8 @@ spec = do
|
||||
r <- request methodGet "/rpc/getitemrange?min=0&max=15"
|
||||
(rangeHdrs $ ByteRangeFromTo 0 1) mempty
|
||||
liftIO $ do
|
||||
simpleHeaders r `shouldSatisfy`
|
||||
matchHeader "Content-Range" "0-1/*"
|
||||
simpleHeaders r `shouldSatisfy` matchHeader "Content-Range" "0-1/*"
|
||||
simpleHeaders r `shouldSatisfy` matchHeader "Content-Length" "22"
|
||||
simpleStatus r `shouldBe` ok200
|
||||
|
||||
it "understands open-ended ranges" $
|
||||
|
||||
@@ -31,18 +31,21 @@ spec =
|
||||
post "/rpc/getitemrange?limit=1&offset=1" [json| { "min": 2, "max": 4 } |]
|
||||
`shouldRespondWith` [json| [{"id":4}] |]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Range" <:> "1-1/*"]
|
||||
, matchHeaders = [ "Content-Range" <:> "1-1/*"
|
||||
, "Content-Length" <:> "10" ]
|
||||
}
|
||||
get "/rpc/getitemrange?min=2&max=4&limit=1&offset=1"
|
||||
`shouldRespondWith` [json| [{"id":4}] |]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Range" <:> "1-1/*"]
|
||||
, matchHeaders = [ "Content-Range" <:> "1-1/*"
|
||||
, "Content-Length" <:> "10"]
|
||||
}
|
||||
request methodHead "/rpc/getitemrange?min=2&max=4&limit=1&offset=1" mempty mempty
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = [ matchContentTypeJson
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "1-1/*" ]
|
||||
}
|
||||
|
||||
@@ -210,7 +213,8 @@ spec =
|
||||
"code":"PGRST202",
|
||||
"details":"Searched for the function test.sayhell without parameters, but no matches were found in the schema cache."} |]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [ "Content-Length" <:> "291"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
it "should fail with 404 on unknown proc args" $ do
|
||||
@@ -268,7 +272,8 @@ spec =
|
||||
"code":"PGRST203",
|
||||
"details":null} |]
|
||||
{ matchStatus = 300
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [ "Content-Length" <:> "353"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
it "works when having uppercase identifiers" $ do
|
||||
@@ -465,7 +470,8 @@ spec =
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [matchHeaderAbsent hContentType]
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength]
|
||||
}
|
||||
|
||||
it "returns null for an integer with null value" $
|
||||
@@ -613,7 +619,8 @@ spec =
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"Cannot use the DELETE method on RPC","code":"PGRST101","details":null,"hint":null}|]
|
||||
{ matchStatus = 405
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [ "Content-Length" <:> "94"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
it "PATCH fails" $
|
||||
request methodPatch "/rpc/sayhello" [] ""
|
||||
@@ -628,7 +635,8 @@ spec =
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType ]
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength]
|
||||
}
|
||||
|
||||
-- now the test
|
||||
@@ -761,7 +769,8 @@ spec =
|
||||
get "/rpc/raise_pt402"
|
||||
`shouldRespondWith` [json|{ "hint": "Upgrade your plan", "details": "Quota exceeded", "code": "PT402", "message": "Payment Required" }|]
|
||||
{ matchStatus = 402
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [ "Content-Length" <:> "99"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
it "defaults to status 500 if RAISE code is PT not followed by a number" $
|
||||
@@ -769,7 +778,8 @@ spec =
|
||||
`shouldRespondWith`
|
||||
[json|{"hint": null, "details": null, "code": "PT40A", "message": "Wrong"}|]
|
||||
{ matchStatus = 500
|
||||
, matchHeaders = [ matchContentTypeJson ]
|
||||
, matchHeaders = [ "Content-Length" <:> "61"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
context "should work with an overloaded function" $ do
|
||||
@@ -1024,7 +1034,8 @@ spec =
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"response.headers guc must be a JSON array composed of objects with a single key and a string value","code":"PGRST111","details":null,"hint":null}|]
|
||||
{ matchStatus = 500
|
||||
, matchHeaders = [ matchContentTypeJson ]
|
||||
, matchHeaders = [ "Content-Length" <:> "157"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
get "/rpc/bad_guc_headers_2"
|
||||
`shouldRespondWith`
|
||||
@@ -1051,6 +1062,7 @@ spec =
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Set-Cookie" <:> "sessionid=38afes7a8; HttpOnly; Path=/"
|
||||
, "Set-Cookie" <:> "id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly" ]
|
||||
}
|
||||
@@ -1113,7 +1125,8 @@ spec =
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"response.status guc must be a valid status code","code":"PGRST112","details":null,"hint":null}|]
|
||||
{ matchStatus = 500
|
||||
, matchHeaders = [ matchContentTypeJson ]
|
||||
, matchHeaders = [ "Content-Length" <:> "106"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
context "single unnamed param" $ do
|
||||
@@ -1291,7 +1304,8 @@ spec =
|
||||
"details":"line 1: StartTag: invalid element name\n<\n ^"
|
||||
}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
, matchHeaders = [ "Content-Length" <:> "118"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
-- https://github.com/PostgREST/postgrest/issues/1586#issuecomment-696345442
|
||||
@@ -1304,7 +1318,10 @@ spec =
|
||||
post "/rpc/char_param_insert" [json| { "char_": "abcdefg", "char_arr": "{abc,abcdefg}" } |]
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"22001","details":null,"hint":null,"message":"value too long for type character(5)"} |]
|
||||
{ matchStatus = 400 }
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [ "Content-Length" <:> "92"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
it "modifies the param type from bit to bit varying" $ do
|
||||
get "/rpc/bit_param_select?bit_=101010&bit_arr={101,101010}" `shouldRespondWith`
|
||||
@@ -1314,7 +1331,10 @@ spec =
|
||||
post "/rpc/bit_param_insert" [json| { "bit_": "101010", "bit_arr": "{101,101010}" } |]
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"22026","details":null,"hint":null,"message":"bit string length 6 does not match type bit(5)"} |]
|
||||
{ matchStatus = 400 }
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [ "Content-Length" <:> "102"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
context "get message and details from raise sqlstate" $ do
|
||||
it "gets message and details from raise sqlstate PGRST" $ do
|
||||
@@ -1369,7 +1389,10 @@ spec =
|
||||
"message":"Could not parse JSON in the \"RAISE SQLSTATE 'PGRST'\" error",
|
||||
"details":"Invalid JSON value for MESSAGE: 'INVALID'",
|
||||
"hint":"MESSAGE must be a JSON object with obligatory keys: 'code', 'message' and optional keys: 'details', 'hint'."}|]
|
||||
{ matchStatus = 500 }
|
||||
{ matchStatus = 500
|
||||
, matchHeaders = [ "Content-Length" <:> "263"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
it "returns error for invalid JSON in the DETAIL option of the RAISE statement" $
|
||||
get "/rpc/raise_sqlstate_invalid_json_details" `shouldRespondWith`
|
||||
@@ -1395,4 +1418,7 @@ spec =
|
||||
it "should return http status 500" $
|
||||
request methodGet "/rpc/temp_file_limit" [auth] "" `shouldRespondWith`
|
||||
[json|{"code":"53400","message":"temporary file size exceeds temp_file_limit (1kB)","details":null,"hint":null}|]
|
||||
{ matchStatus = 500 }
|
||||
{ matchStatus = 500
|
||||
, matchHeaders = [ "Content-Length" <:> "105"
|
||||
, matchContentTypeJson ]
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ spec = do
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"PGRST205","details":null,"hint":"Perhaps you meant the table 'test.factories'","message":"Could not find the table 'test.fake' in the schema cache"} |]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = []
|
||||
, matchHeaders = ["Content-Length" <:> "157"]
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ spec = do
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204,
|
||||
matchHeaders = [matchHeaderAbsent hContentType]
|
||||
matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength ]
|
||||
}
|
||||
|
||||
context "with invalid json payload" $
|
||||
@@ -40,7 +41,8 @@ spec = do
|
||||
`shouldRespondWith`
|
||||
[json|{"message":"Empty or invalid json","code":"PGRST102","details":null,"hint":null}|]
|
||||
{ matchStatus = 400,
|
||||
matchHeaders = [matchContentTypeJson]
|
||||
matchHeaders = [ matchContentTypeJson
|
||||
, "Content-Length" <:> "80"]
|
||||
}
|
||||
|
||||
context "with no payload" $
|
||||
@@ -59,6 +61,7 @@ spec = do
|
||||
"\t \n \r { \"id\": 99 } \t \n \r "
|
||||
`shouldRespondWith` [json|[{"id":99}]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Length" <:> "11"]
|
||||
}
|
||||
|
||||
context "in a nonempty table" $ do
|
||||
@@ -69,6 +72,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "0-0/*" ]
|
||||
}
|
||||
|
||||
@@ -78,7 +82,8 @@ spec = do
|
||||
`shouldRespondWith` "[]"
|
||||
{
|
||||
matchStatus = 200,
|
||||
matchHeaders = ["Preference-Applied" <:> "return=representation"]
|
||||
matchHeaders = [ "Preference-Applied" <:> "return=representation"
|
||||
, "Content-Length" <:> "2"]
|
||||
}
|
||||
|
||||
it "returns status code 200 when no rows updated" $
|
||||
@@ -107,6 +112,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "0-1/*"
|
||||
, "Preference-Applied" <:> "tx=commit" ]
|
||||
}
|
||||
@@ -123,7 +129,8 @@ spec = do
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [matchHeaderAbsent hContentType]
|
||||
, matchHeaders = [matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength ]
|
||||
}
|
||||
|
||||
it "can set a column to NULL" $ do
|
||||
@@ -207,6 +214,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "0-0/*"
|
||||
, "Preference-Applied" <:> "return=minimal"]
|
||||
}
|
||||
@@ -217,6 +225,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "0-0/*"
|
||||
, "Preference-Applied" <:> "return=minimal"]
|
||||
}
|
||||
@@ -230,6 +239,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
@@ -240,6 +250,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
@@ -250,6 +261,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
@@ -261,6 +273,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
@@ -271,6 +284,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
@@ -281,6 +295,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
@@ -350,7 +365,7 @@ spec = do
|
||||
`shouldRespondWith`
|
||||
[json| {"code":"PGRST205","details":null,"hint":"Perhaps you meant the table 'test.articles'","message":"Could not find the table 'test.garlic' in the schema cache"} |]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = []
|
||||
, matchHeaders = ["Content-Length" <:> "158"]
|
||||
}
|
||||
|
||||
context "apply defaults on missing values" $ do
|
||||
@@ -613,6 +628,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Preference-Applied" <:> "return=minimal"]
|
||||
}
|
||||
|
||||
@@ -623,7 +639,8 @@ spec = do
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [matchHeaderAbsent hContentType]
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength ]
|
||||
}
|
||||
|
||||
-- Data representations for payload parsing requires Postgres 10 or above.
|
||||
@@ -636,6 +653,7 @@ spec = do
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "Content-Range" <:> "0-0/*" ]
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ deleteItems =
|
||||
`shouldRespondWith`
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [matchHeaderAbsent hContentType] }
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength ]
|
||||
}
|
||||
|
||||
preferDefault = [("Prefer", "return=representation")]
|
||||
preferCommit = [("Prefer", "return=representation"), ("Prefer", "tx=commit")]
|
||||
@@ -262,4 +264,3 @@ forced = describe "tx-rollback-all = true, tx-allow-override = false" $ do
|
||||
preferRollback `shouldRespondToReads` withoutPreferenceApplied
|
||||
preferRollback `shouldNotPersistMutations` withoutPreferenceApplied
|
||||
preferRollback `shouldRaiseExceptions` withoutPreferenceApplied
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ spec =
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "X-Custom-Header" <:> "mykey=myval" ]
|
||||
}
|
||||
|
||||
@@ -66,6 +67,7 @@ spec =
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "X-Custom-Header" <:> "mykey=myval" ]
|
||||
}
|
||||
|
||||
@@ -75,6 +77,7 @@ spec =
|
||||
""
|
||||
{ matchStatus = 204
|
||||
, matchHeaders = [ matchHeaderAbsent hContentType
|
||||
, matchHeaderAbsent hContentLength
|
||||
, "X-Custom-Header" <:> "mykey=myval" ]
|
||||
}
|
||||
it "can override the Content-Type header" $ do
|
||||
|
||||
@@ -281,6 +281,9 @@ noBlankHeader = notElem mempty
|
||||
noProfileHeader :: [Header] -> Bool
|
||||
noProfileHeader headers = isNothing $ find ((== "Content-Profile") . fst) headers
|
||||
|
||||
notZeroContentLength :: [Header] -> Bool
|
||||
notZeroContentLength headers = maybe False (/= "0") $ lookup hContentLength headers
|
||||
|
||||
authHeader :: BS.ByteString -> BS.ByteString -> Header
|
||||
authHeader typ creds =
|
||||
(hAuthorization, typ <> " " <> creds)
|
||||
|
||||
Reference in New Issue
Block a user