Err embedding when multiple relationships found
When having one-to-many relationships like: person -< message[sender] person -< message[recipient] person_detail -< message[sender] person_detail -< message[recipient] Where person_detail is a view of person. This request: GET "/message?select=*,sender(*)" Is ambiguous. Both person or person_detail could be embedded. Until now we have returned the first detected relationship but now we return a 300 Multiple Choices error with a descriptive error message asking the user to disambiguate. This is more helpful for the user and also aids in cases of more complex relationships.
This commit is contained in:
committed by
Steve Chávez
parent
cb3d9ab625
commit
9847e60dca
@@ -37,7 +37,7 @@ spec =
|
||||
request methodDelete "/complex_items?id=eq.3&select=ciId:id::text,ciName:name" [("Prefer", "return=representation")] ""
|
||||
`shouldRespondWith` [str|[{"ciId":"3","ciName":"Three"}]|]
|
||||
it "can embed (parent) entities" $
|
||||
request methodDelete "/tasks?id=eq.8&select=id,name,project(id)" [("Prefer", "return=representation")] ""
|
||||
request methodDelete "/tasks?id=eq.8&select=id,name,project:projects(id)" [("Prefer", "return=representation")] ""
|
||||
`shouldRespondWith` [str|[{"id":8,"name":"Code OSX","project":{"id":4}}]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Range" <:> "*/*"]
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
module Feature.EmbedDisambiguationSpec where
|
||||
|
||||
import Network.Wai (Application)
|
||||
|
||||
import Test.Hspec hiding (pendingWith)
|
||||
import Test.Hspec.Wai
|
||||
import Test.Hspec.Wai.JSON
|
||||
import Text.Heredoc
|
||||
|
||||
import Protolude hiding (get)
|
||||
import SpecHelper
|
||||
|
||||
spec :: SpecWith Application
|
||||
spec =
|
||||
describe "resource embedding disambiguation" $ do
|
||||
|
||||
it "gives a 300 Multiple Choices error when the request is ambiguous" $ do
|
||||
get "/message?select=id,body,sender(name,sent)" `shouldRespondWith`
|
||||
[json|
|
||||
{
|
||||
"details": [
|
||||
{
|
||||
"cardinality": "Parent",
|
||||
"source": "test.person[id]",
|
||||
"target": "test.message[sender]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Parent",
|
||||
"source": "test.person_detail[id]",
|
||||
"target": "test.message[sender]"
|
||||
}
|
||||
],
|
||||
"hint": "Disambiguate by choosing a relationship from the `details` key",
|
||||
"message": "More than one relationship was found for message and sender"
|
||||
}
|
||||
|]
|
||||
{ matchStatus = 300
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
get "/users?select=*,id(*)" `shouldRespondWith`
|
||||
[json|
|
||||
{
|
||||
"details": [
|
||||
{
|
||||
"cardinality": "Child",
|
||||
"source": "test.articleStars[userId]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Child",
|
||||
"source": "test.limited_article_stars[user_id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Child",
|
||||
"source": "test.comments[commenter_id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Child",
|
||||
"source": "test.users_projects[user_id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Child",
|
||||
"source": "test.users_tasks[user_id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Many",
|
||||
"junction": "private.article_stars[article_id][user_id]",
|
||||
"source": "test.articles[id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Many",
|
||||
"junction": "test.articleStars[articleId][userId]",
|
||||
"source": "test.articles[id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Many",
|
||||
"junction": "test.limited_article_stars[article_id][user_id]",
|
||||
"source": "test.articles[id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Many",
|
||||
"junction": "test.users_projects[project_id][user_id]",
|
||||
"source": "test.projects[id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Many",
|
||||
"junction": "test.users_projects[project_id][user_id]",
|
||||
"source": "test.materialized_projects[id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Many",
|
||||
"junction": "test.users_projects[project_id][user_id]",
|
||||
"source": "test.projects_view[id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Many",
|
||||
"junction": "test.users_projects[project_id][user_id]",
|
||||
"source": "test.projects_view_alt[t_id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Many",
|
||||
"junction": "test.users_tasks[task_id][user_id]",
|
||||
"source": "test.tasks[id]",
|
||||
"target": "test.users[id]"
|
||||
},
|
||||
{
|
||||
"cardinality": "Many",
|
||||
"junction": "test.users_tasks[task_id][user_id]",
|
||||
"source": "test.filtered_tasks[myId]",
|
||||
"target": "test.users[id]"
|
||||
}
|
||||
],
|
||||
"hint": "Disambiguate by choosing a relationship from the `details` key",
|
||||
"message": "More than one relationship was found for users and id"
|
||||
}
|
||||
|]
|
||||
{ matchStatus = 300
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
|
||||
it "works when requesting children 2 levels" $
|
||||
get "/clients?id=eq.1&select=id,projects:projects!client_id(id,tasks(id))" `shouldRespondWith`
|
||||
[json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "works with parent relation" $
|
||||
get "/message?select=id,body,sender:person!sender(name),recipient:person!recipient(name)&id=lt.4" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"body":"Hello Jane","sender":{"name":"John"},"recipient":{"name":"Jane"}},
|
||||
{"id":2,"body":"Hi John","sender":{"name":"Jane"},"recipient":{"name":"John"}},
|
||||
{"id":3,"body":"How are you doing?","sender":{"name":"John"},"recipient":{"name":"Jane"}}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "fails with an unknown relation" $
|
||||
get "/message?select=id,sender:person!space(name)&id=lt.4" `shouldRespondWith`
|
||||
[json|{"message":"Could not find foreign keys between these entities, No relation found between message and person"}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "works with a parent view relation" $
|
||||
get "/message?select=id,body,sender:person_detail!sender(name,sent),recipient:person_detail!recipient(name,received)&id=lt.4" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"body":"Hello Jane","sender":{"name":"John","sent":2},"recipient":{"name":"Jane","received":2}},
|
||||
{"id":2,"body":"Hi John","sender":{"name":"Jane","sent":1},"recipient":{"name":"John","received":1}},
|
||||
{"id":3,"body":"How are you doing?","sender":{"name":"John","sent":2},"recipient":{"name":"Jane","received":2}}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "works with many<->many relation" $
|
||||
get "/tasks?select=id,users:users!users_tasks(id)" `shouldRespondWith`
|
||||
[json|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
context "using FK col to specify the relationship" $ do
|
||||
it "can embed by FK column name" $
|
||||
get "/projects?id=in.(1,3)&select=id,name,client_id(id,name)" `shouldRespondWith`
|
||||
[json|[{"id":1,"name":"Windows 7","client_id":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":{"id":2,"name":"Apple"}}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can embed by FK column name and select the FK value at the same time, if aliased" $
|
||||
get "/projects?id=in.(1,3)&select=id,name,client_id,client:client_id(id,name)" `shouldRespondWith`
|
||||
[json|[{"id":1,"name":"Windows 7","client_id":1,"client":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client":{"id":2,"name":"Apple"}}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "requests parents two levels up" $
|
||||
get "/tasks?id=eq.1&select=id,name,project:projects!project_id(id,name,client:client_id(id,name))" `shouldRespondWith`
|
||||
[str|[{"id":1,"name":"Design w7","project":{"id":1,"name":"Windows 7","client":{"id":1,"name":"Microsoft"}}}]|]
|
||||
|
||||
|
||||
context "tables with self reference foreign keys" $ do
|
||||
context "one self reference foreign key" $ do
|
||||
it "embeds parents recursively" $
|
||||
get "/family_tree?id=in.(3,4)&select=id,parent(id,name,parent(*))" `shouldRespondWith`
|
||||
[json|[
|
||||
{ "id": "3", "parent": { "id": "1", "name": "Parental Unit", "parent": null } },
|
||||
{ "id": "4", "parent": { "id": "2", "name": "Kid One", "parent": { "id": "1", "name": "Parental Unit", "parent": null } } }
|
||||
]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds childs recursively" $
|
||||
get "/family_tree?id=eq.1&select=id,name, childs:family_tree!parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": "1", "name": "Parental Unit", "childs": [
|
||||
{ "id": "2", "name": "Kid One", "childs": [ { "id": "4", "name": "Grandkid One" } ] },
|
||||
{ "id": "3", "name": "Kid Two", "childs": [ { "id": "5", "name": "Grandkid Two" } ] }
|
||||
]
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds parent and then embeds childs" $
|
||||
get "/family_tree?id=eq.2&select=id,name,parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": "2", "name": "Kid One", "parent": {
|
||||
"id": "1", "name": "Parental Unit", "childs": [ { "id": "2", "name": "Kid One" }, { "id": "3", "name": "Kid Two"} ]
|
||||
}
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
context "two self reference foreign keys" $ do
|
||||
it "embeds parents" $
|
||||
get "/organizations?select=id,name,referee(id,name),auditor(id,name)&id=eq.3" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": 3, "name": "Acme",
|
||||
"referee": {
|
||||
"id": 1,
|
||||
"name": "Referee Org"
|
||||
},
|
||||
"auditor": {
|
||||
"id": 2,
|
||||
"name": "Auditor Org"
|
||||
}
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds childs" $ do
|
||||
get "/organizations?select=id,name,refereeds:organizations!referee(id,name)&id=eq.1" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": 1, "name": "Referee Org",
|
||||
"refereeds": [
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Acme"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Umbrella"
|
||||
}
|
||||
]
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
get "/organizations?select=id,name,auditees:organizations!auditor(id,name)&id=eq.2" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": 2, "name": "Auditor Org",
|
||||
"auditees": [
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Acme"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Umbrella"
|
||||
}
|
||||
]
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds other relations(manager) besides the self reference" $ do
|
||||
get "/organizations?select=name,manager(name),referee(name,manager(name),auditor(name,manager(name))),auditor(name,manager(name),referee(name,manager(name)))&id=eq.5" `shouldRespondWith`
|
||||
[json|[{
|
||||
"name":"Cyberdyne",
|
||||
"manager":{"name":"Cyberdyne Manager"},
|
||||
"referee":{
|
||||
"name":"Acme",
|
||||
"manager":{"name":"Acme Manager"},
|
||||
"auditor":{
|
||||
"name":"Auditor Org",
|
||||
"manager":{"name":"Auditor Manager"}}},
|
||||
"auditor":{
|
||||
"name":"Umbrella",
|
||||
"manager":{"name":"Umbrella Manager"},
|
||||
"referee":{
|
||||
"name":"Referee Org",
|
||||
"manager":{"name":"Referee Manager"}}}
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
get "/organizations?select=name,manager(name),auditees:organizations!auditor(name,manager(name),refereeds:organizations!referee(name,manager(name)))&id=eq.2" `shouldRespondWith`
|
||||
[json|[{
|
||||
"name":"Auditor Org",
|
||||
"manager":{"name":"Auditor Manager"},
|
||||
"auditees":[
|
||||
{"name":"Acme",
|
||||
"manager":{"name":"Acme Manager"},
|
||||
"refereeds":[
|
||||
{"name":"Cyberdyne",
|
||||
"manager":{"name":"Cyberdyne Manager"}},
|
||||
{"name":"Oscorp",
|
||||
"manager":{"name":"Oscorp Manager"}}]},
|
||||
{"name":"Umbrella",
|
||||
"manager":{"name":"Umbrella Manager"},
|
||||
"refereeds":[]}]
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
-- TODO Remove in next major version(7.0)
|
||||
describe "old dot '.' symbol, deprecated" $
|
||||
it "still works" $ do
|
||||
get "/clients?id=eq.1&select=id,projects:projects.client_id(id,tasks(id))" `shouldRespondWith`
|
||||
[json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/tasks?select=id,users:users.users_tasks(id)" `shouldRespondWith`
|
||||
[json|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
@@ -37,7 +37,7 @@ spec =
|
||||
}
|
||||
|
||||
it "succeeds in getting parent embeds despite the limit, see #647" $
|
||||
get "/tasks?select=id,project(id)&id=gt.5"
|
||||
get "/tasks?select=id,project:projects(id)&id=gt.5"
|
||||
`shouldRespondWith` [json|[{"id":6,"project":{"id":3}},{"id":7,"project":{"id":4}}]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Content-Range" <:> "0-1/*"]
|
||||
|
||||
+4
-173
@@ -305,14 +305,6 @@ spec actualPgVersion = do
|
||||
[json|[{"myId":1,"name":"Windows 7","project_client":{"id":1,"name":"Microsoft"},"project_tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "requesting parents two levels up while using FK to specify the link" $
|
||||
get "/tasks?id=eq.1&select=id,name,project:project_id(id,name,client:client_id(id,name))" `shouldRespondWith`
|
||||
[str|[{"id":1,"name":"Design w7","project":{"id":1,"name":"Windows 7","client":{"id":1,"name":"Microsoft"}}}]|]
|
||||
|
||||
it "requesting parents two levels up while using FK to specify the link (with rename)" $
|
||||
get "/tasks?id=eq.1&select=id,name,project:project_id(id,name,client:client_id(id,name))" `shouldRespondWith`
|
||||
[str|[{"id":1,"name":"Design w7","project":{"id":1,"name":"Windows 7","client":{"id":1,"name":"Microsoft"}}}]|]
|
||||
|
||||
it "requesting parents and filtering parent columns" $
|
||||
get "/projects?id=eq.1&select=id, name, clients(id)" `shouldRespondWith`
|
||||
[str|[{"id":1,"name":"Windows 7","clients":{"id":1}}]|]
|
||||
@@ -351,16 +343,6 @@ spec actualPgVersion = do
|
||||
[json|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can embed by FK column name" $
|
||||
get "/projects?id=in.(1,3)&select=id,name,client_id(id,name)" `shouldRespondWith`
|
||||
[json|[{"id":1,"name":"Windows 7","client_id":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":{"id":2,"name":"Apple"}}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can embed by FK column name and select the FK value at the same time, if aliased" $
|
||||
get "/projects?id=in.(1,3)&select=id,name,client_id,client:client_id(id,name)" `shouldRespondWith`
|
||||
[json|[{"id":1,"name":"Windows 7","client_id":1,"client":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client":{"id":2,"name":"Apple"}}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can select by column name sans id" $
|
||||
get "/projects?id=in.(1,3)&select=id,name,client_id,client(id,name)" `shouldRespondWith`
|
||||
[json|[{"id":1,"name":"Windows 7","client_id":1,"client":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client":{"id":2,"name":"Apple"}}]|]
|
||||
@@ -394,16 +376,16 @@ spec actualPgVersion = do
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "detects parent relations when having many views of a private table" $ do
|
||||
get "/books?select=title,author(name)&id=eq.5" `shouldRespondWith`
|
||||
get "/books?select=title,author:authors(name)&id=eq.5" `shouldRespondWith`
|
||||
[json|[ { "title": "Farenheit 451", "author": { "name": "Ray Bradbury" } } ]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/forties_books?select=title,author(name)&limit=1" `shouldRespondWith`
|
||||
get "/forties_books?select=title,author:authors(name)&limit=1" `shouldRespondWith`
|
||||
[json|[ { "title": "1984", "author": { "name": "George Orwell" } } ]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/fifties_books?select=title,author(name)&limit=1" `shouldRespondWith`
|
||||
get "/fifties_books?select=title,author:authors(name)&limit=1" `shouldRespondWith`
|
||||
[json|[ { "title": "The Catcher in the Rye", "author": { "name": "J.D. Salinger" } } ]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/sixties_books?select=title,author(name)&limit=1" `shouldRespondWith`
|
||||
get "/sixties_books?select=title,author:authors(name)&limit=1" `shouldRespondWith`
|
||||
[json|[ { "title": "To Kill a Mockingbird", "author": { "name": "Harper Lee" } } ]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
@@ -478,49 +460,6 @@ spec actualPgVersion = do
|
||||
[json| [{"name":"George Orwell","entities":[3, 4],"books":[{"title":"1984"}]}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
describe "path fixed" $ do
|
||||
it "works when requesting children 2 levels" $
|
||||
get "/clients?id=eq.1&select=id,projects:projects!client_id(id,tasks(id))" `shouldRespondWith`
|
||||
[json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "works with parent relation" $
|
||||
get "/message?select=id,body,sender:person!sender(name),recipient:person!recipient(name)&id=lt.4" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"body":"Hello Jane","sender":{"name":"John"},"recipient":{"name":"Jane"}},
|
||||
{"id":2,"body":"Hi John","sender":{"name":"Jane"},"recipient":{"name":"John"}},
|
||||
{"id":3,"body":"How are you doing?","sender":{"name":"John"},"recipient":{"name":"Jane"}}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "fails with an unknown relation" $
|
||||
get "/message?select=id,sender:person.space(name)&id=lt.4" `shouldRespondWith`
|
||||
[json|{"message":"Could not find foreign keys between these entities, No relation found between message and person"}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "works with a parent view relation" $
|
||||
get "/message?select=id,body,sender:person_detail!sender(name,sent),recipient:person_detail!recipient(name,received)&id=lt.4" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"body":"Hello Jane","sender":{"name":"John","sent":2},"recipient":{"name":"Jane","received":2}},
|
||||
{"id":2,"body":"Hi John","sender":{"name":"Jane","sent":1},"recipient":{"name":"John","received":1}},
|
||||
{"id":3,"body":"How are you doing?","sender":{"name":"John","sent":2},"recipient":{"name":"Jane","received":2}}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "works with many<->many relation" $
|
||||
get "/tasks?select=id,users:users!users_tasks(id)" `shouldRespondWith`
|
||||
[json|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
-- TODO Remove in next major version(7.0)
|
||||
describe "old dot '.' symbol, deprecated" $
|
||||
it "still works" $ do
|
||||
get "/clients?id=eq.1&select=id,projects:projects.client_id(id,tasks(id))" `shouldRespondWith`
|
||||
[json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
get "/tasks?select=id,users:users.users_tasks(id)" `shouldRespondWith`
|
||||
[json|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
describe "aliased embeds" $ do
|
||||
it "works with child relation" $
|
||||
get "/space?select=id,zones:zone(id,name),stores:zone(id,name)&zones.zone_type_id=eq.2&stores.zone_type_id=eq.3" `shouldRespondWith`
|
||||
@@ -578,114 +517,6 @@ spec actualPgVersion = do
|
||||
{ "id":4,"childs":[]}
|
||||
]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
describe "tables with self reference foreign keys" $ do
|
||||
context "one self reference foreign key" $ do
|
||||
it "embeds parents recursively" $
|
||||
get "/family_tree?id=in.(3,4)&select=id,parent(id,name,parent(*))" `shouldRespondWith`
|
||||
[json|[
|
||||
{ "id": "3", "parent": { "id": "1", "name": "Parental Unit", "parent": null } },
|
||||
{ "id": "4", "parent": { "id": "2", "name": "Kid One", "parent": { "id": "1", "name": "Parental Unit", "parent": null } } }
|
||||
]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds childs recursively" $
|
||||
get "/family_tree?id=eq.1&select=id,name, childs:family_tree!parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": "1", "name": "Parental Unit", "childs": [
|
||||
{ "id": "2", "name": "Kid One", "childs": [ { "id": "4", "name": "Grandkid One" } ] },
|
||||
{ "id": "3", "name": "Kid Two", "childs": [ { "id": "5", "name": "Grandkid Two" } ] }
|
||||
]
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds parent and then embeds childs" $
|
||||
get "/family_tree?id=eq.2&select=id,name,parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": "2", "name": "Kid One", "parent": {
|
||||
"id": "1", "name": "Parental Unit", "childs": [ { "id": "2", "name": "Kid One" }, { "id": "3", "name": "Kid Two"} ]
|
||||
}
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
context "two self reference foreign keys" $ do
|
||||
it "embeds parents" $
|
||||
get "/organizations?select=id,name,referee(id,name),auditor(id,name)&id=eq.3" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": 3, "name": "Acme",
|
||||
"referee": {
|
||||
"id": 1,
|
||||
"name": "Referee Org"
|
||||
},
|
||||
"auditor": {
|
||||
"id": 2,
|
||||
"name": "Auditor Org"
|
||||
}
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds childs" $ do
|
||||
get "/organizations?select=id,name,refereeds:organizations!referee(id,name)&id=eq.1" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": 1, "name": "Referee Org",
|
||||
"refereeds": [
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Acme"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Umbrella"
|
||||
}
|
||||
]
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
get "/organizations?select=id,name,auditees:organizations!auditor(id,name)&id=eq.2" `shouldRespondWith`
|
||||
[json|[{
|
||||
"id": 2, "name": "Auditor Org",
|
||||
"auditees": [
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Acme"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Umbrella"
|
||||
}
|
||||
]
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "embeds other relations(manager) besides the self reference" $ do
|
||||
get "/organizations?select=name,manager(name),referee(name,manager(name),auditor(name,manager(name))),auditor(name,manager(name),referee(name,manager(name)))&id=eq.5" `shouldRespondWith`
|
||||
[json|[{
|
||||
"name":"Cyberdyne",
|
||||
"manager":{"name":"Cyberdyne Manager"},
|
||||
"referee":{
|
||||
"name":"Acme",
|
||||
"manager":{"name":"Acme Manager"},
|
||||
"auditor":{
|
||||
"name":"Auditor Org",
|
||||
"manager":{"name":"Auditor Manager"}}},
|
||||
"auditor":{
|
||||
"name":"Umbrella",
|
||||
"manager":{"name":"Umbrella Manager"},
|
||||
"referee":{
|
||||
"name":"Referee Org",
|
||||
"manager":{"name":"Referee Manager"}}}
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
get "/organizations?select=name,manager(name),auditees:organizations!auditor(name,manager(name),refereeds:organizations!referee(name,manager(name)))&id=eq.2" `shouldRespondWith`
|
||||
[json|[{
|
||||
"name":"Auditor Org",
|
||||
"manager":{"name":"Auditor Manager"},
|
||||
"auditees":[
|
||||
{"name":"Acme",
|
||||
"manager":{"name":"Acme Manager"},
|
||||
"refereeds":[
|
||||
{"name":"Cyberdyne",
|
||||
"manager":{"name":"Cyberdyne Manager"}},
|
||||
{"name":"Oscorp",
|
||||
"manager":{"name":"Oscorp Manager"}}]},
|
||||
{"name":"Umbrella",
|
||||
"manager":{"name":"Umbrella Manager"},
|
||||
"refereeds":[]}]
|
||||
}]|] { matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
describe "ordering response" $ do
|
||||
it "by a column asc" $
|
||||
get "/items?id=lte.2&order=id.asc"
|
||||
|
||||
+11
-9
@@ -26,6 +26,7 @@ import qualified Feature.BinaryJwtSecretSpec
|
||||
import qualified Feature.ConcurrentSpec
|
||||
import qualified Feature.CorsSpec
|
||||
import qualified Feature.DeleteSpec
|
||||
import qualified Feature.EmbedDisambiguationSpec
|
||||
import qualified Feature.ExtraSearchPathSpec
|
||||
import qualified Feature.HtmlRawOutputSpec
|
||||
import qualified Feature.InsertSpec
|
||||
@@ -91,15 +92,16 @@ main = do
|
||||
[("Feature.PgVersion96Spec", Feature.PgVersion96Spec.spec) | actualPgVersion >= pgVersion96]
|
||||
|
||||
specs = uncurry describe <$> [
|
||||
("Feature.AuthSpec" , Feature.AuthSpec.spec actualPgVersion)
|
||||
, ("Feature.RawOutputTypesSpec" , Feature.RawOutputTypesSpec.spec)
|
||||
, ("Feature.ConcurrentSpec" , Feature.ConcurrentSpec.spec)
|
||||
, ("Feature.CorsSpec" , Feature.CorsSpec.spec)
|
||||
, ("Feature.JsonOperatorSpec" , Feature.JsonOperatorSpec.spec actualPgVersion)
|
||||
, ("Feature.QuerySpec" , Feature.QuerySpec.spec actualPgVersion)
|
||||
, ("Feature.RpcSpec" , Feature.RpcSpec.spec actualPgVersion)
|
||||
, ("Feature.StructureSpec" , Feature.StructureSpec.spec)
|
||||
, ("Feature.AndOrParamsSpec" , Feature.AndOrParamsSpec.spec actualPgVersion)
|
||||
("Feature.AuthSpec" , Feature.AuthSpec.spec actualPgVersion)
|
||||
, ("Feature.RawOutputTypesSpec" , Feature.RawOutputTypesSpec.spec)
|
||||
, ("Feature.ConcurrentSpec" , Feature.ConcurrentSpec.spec)
|
||||
, ("Feature.CorsSpec" , Feature.CorsSpec.spec)
|
||||
, ("Feature.JsonOperatorSpec" , Feature.JsonOperatorSpec.spec actualPgVersion)
|
||||
, ("Feature.QuerySpec" , Feature.QuerySpec.spec actualPgVersion)
|
||||
, ("Feature.EmbedDisambiguationSpec" , Feature.EmbedDisambiguationSpec.spec)
|
||||
, ("Feature.RpcSpec" , Feature.RpcSpec.spec actualPgVersion)
|
||||
, ("Feature.StructureSpec" , Feature.StructureSpec.spec)
|
||||
, ("Feature.AndOrParamsSpec" , Feature.AndOrParamsSpec.spec actualPgVersion)
|
||||
] ++ extraSpecs
|
||||
|
||||
mutSpecs = uncurry describe <$> [
|
||||
|
||||
Reference in New Issue
Block a user