content: module Feature.Query.ComputedRelsSpec where
  
  import Network.Wai (Application)
  
  import Network.HTTP.Types
  import Test.Hspec
  import Test.Hspec.Wai
  import Test.Hspec.Wai.JSON
  
  import Protolude  hiding (get)
  import SpecHelper
  
  spec :: SpecWith ((), Application)
  spec = describe "computed relationships" $ do
    it "can define a many-to-one relationship with SETOF and ROWS 1 and embed" $
      get "/videogames?select=name,designer:computed_designers(name)"
      `shouldRespondWith`
        [json|[
          {"name":"Civilization I","designer":{"name":"Sid Meier"}},
          {"name":"Civilization II","designer":{"name":"Sid Meier"}},
          {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}},
          {"name":"Final Fantasy II","designer":{"name":"Hironobu Sakaguchi"}}
        ]|] { matchHeaders = [matchContentTypeJson] }
  
    it "can define a many-to-one relationship without SETOF and embed" $
      get "/videogames?select=name,designer:computed_designers_noset(name)"
      `shouldRespondWith`
        [json|[
          {"name":"Civilization I","designer":{"name":"Sid Meier"}},
          {"name":"Civilization II","designer":{"name":"Sid Meier"}},
          {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}},
          {"name":"Final Fantasy II","designer":{"name":"Hironobu Sakaguchi"}}
        ]|] { matchHeaders = [matchContentTypeJson] }
  
    it "can define a one-to-many relationship and embed" $
      get "/designers?select=name,videogames:computed_videogames(name)"
      `shouldRespondWith`
        [json|[
          {"name":"Sid Meier","videogames":[{"name":"Civilization I"}, {"name":"Civilization II"}]},
          {"name":"Hironobu Sakaguchi","videogames":[{"name":"Final Fantasy I"}, {"name":"Final Fantasy II"}]}
        ]|] { matchHeaders = [matchContentTypeJson] }
  
    it "works with !inner and count=exact" $ do
      request methodGet "/designers?select=name,videogames:computed_videogames!inner(name)&videogames.name=eq.Civilization%20I"
        [("Prefer", "count=exact")] ""
        `shouldRespondWith`
          [json|[{"name":"Sid Meier","videogames":[{"name":"Civilization I"}]}]|]
          { matchStatus  = 200
          , matchHeaders = ["Content-Range" <:> "0-0/1"]
          }
      request methodGet "/videogames?select=name,designer:computed_designers!inner(name)&designer.name=like.*Hironobu*"
        [("Prefer", "count=exact")] ""
        `shouldRespondWith`
          [json|[
            {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}},
            {"name":"Final Fantasy II","designer":{"name":"Hironobu Sakaguchi"}}
          ]|]
          { matchStatus  = 200
          , matchHeaders = ["Content-Range" <:> "0-1/2"]
          }
      request methodGet "/videogames?select=name,designer:computed_designers_noset!inner(name)&designer.name=like.*Hironobu*"
        [("Prefer", "count=exact")] ""
        `shouldRespondWith`
          [json|[
            {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}},
            {"name":"Final Fantasy II","designer":{"name":"Hironobu Sakaguchi"}}
          ]|]
          { matchStatus  = 200
          , matchHeaders = ["Content-Range" <:> "0-1/2"]
          }
  
    it "works with rpc" $ do
      get "/rpc/getallvideogames?select=name,designer:computed_designers(name)"
        `shouldRespondWith`
        [json|[
          {"name":"Civilization I","designer":{"name":"Sid Meier"}},
          {"name":"Civilization II","designer":{"name":"Sid Meier"}},
          {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}},
          {"name":"Final Fantasy II","designer":{"name":"Hironobu Sakaguchi"}}
        ]|] { matchHeaders = [matchContentTypeJson] }
      get "/rpc/getalldesigners?select=name,videogames:computed_videogames(name)"
        `shouldRespondWith`
        [json|[
          {"name":"Sid Meier","videogames":[{"name":"Civilization I"}, {"name":"Civilization II"}]},
          {"name":"Hironobu Sakaguchi","videogames":[{"name":"Final Fantasy I"}, {"name":"Final Fantasy II"}]}
        ]|] { matchHeaders = [matchContentTypeJson] }
  
    it "works with mutations" $ do
      request methodPost "/videogames?select=name,designer:computed_designers(name)"
        [("Prefer", "return=representation")]
        [json| {"id": 5, "name": "Chrono Trigger", "designer_id": 2} |]
        `shouldRespondWith`
          [json|[ {"name":"Chrono Trigger","designer":{"name":"Hironobu Sakaguchi"}} ]|]
          { matchStatus  = 201 }
      request methodPatch "/designers?select=name,videogames:computed_videogames(name)&id=eq.1"
        [("Prefer", "return=representation")]
        [json| {"name": "Sidney K. Meier"} |]
        `shouldRespondWith`
          [json|[ { "name": "Sidney K. Meier", "videogames": [{"name":"Civilization I"}, {"name":"Civilization II"}] } ]|]
          { matchStatus  = 200 }
      request methodDelete "/videogames?select=name,designer:computed_designers(name)&id=eq.3"
        [("Prefer", "return=representation")] ""
        `shouldRespondWith`
          [json|[ {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}} ]|]
          { matchStatus  = 200 }
  
    it "applies data representations to response" $ do
      -- A smoke test for data reps in the presence of computed relations.
  
      -- The data rep here title cases the designer name before presentation. So here the lowercase version will be saved,
      -- but the title case version returned. Pulling in a computed relation should not confuse this.
      request methodPatch "/designers?select=name,videogames:computed_videogames(name)&id=eq.1"
        [("Prefer", "return=representation"), ("Prefer", "tx=commit")]
        [json| {"name": "sidney k. meier"} |]
        `shouldRespondWith`
        [json|[{"name":"Sidney K. Meier","videogames":[{"name":"Civilization I"}, {"name":"Civilization II"}]}]|]
        { matchStatus = 200 }
  
      -- Verify it was saved the way we requested (there's no text data rep for this column, so if we select with the wrong casing, it should fail.)
      get "/designers?select=id&name=eq.Sidney%20K.%20Meier"
        `shouldRespondWith`
        [json|[]|]
        { matchStatus = 200, matchHeaders = [matchContentTypeJson] }
      -- But with the right casing it works.
      get "/designers?select=id,name&name=eq.sidney%20k.%20meier"
        `shouldRespondWith`
        [json|[{"id": 1, "name":"Sidney K. Meier"}]|]
        { matchStatus = 200, matchHeaders = [matchContentTypeJson] }
  
      -- Most importantly, if you read it back even via a computed relation, the data rep should be applied.
      get "/videogames?select=name,designer:computed_designers(*)&id=eq.1"
        `shouldRespondWith`
        [json|[
          {"name":"Civilization I","designer":{"id": 1, "name":"Sidney K. Meier"}}
        ]|] { matchHeaders = [matchContentTypeJson] }
  
      -- reset the test fixture
      request methodPatch "/designers?id=eq.1"
        [("Prefer", "tx=commit")]
        [json| {"name": "Sid Meier"} |]
        `shouldRespondWith` 204
      -- need to poke the second one too to prevent inherent ordering from changing
      request methodPatch "/designers?id=eq.2"
        [("Prefer", "tx=commit")]
        [json| {"name": "Hironobu Sakaguchi"} |]
        `shouldRespondWith` 204
  
    it "works with self joins" $
      get "/web_content?select=name,child_web_content(name),parent_web_content(name)&id=in.(0,1)"
      `shouldRespondWith`
        [json|[
          {"name":"tardis","child_web_content":[{"name":"fezz"}, {"name":"foo"}, {"name":"bar"}],"parent_web_content":{"name":"wat"}},
          {"name":"fezz","child_web_content":[{"name":"wut"}],"parent_web_content":{"name":"tardis"}}
        ]|] { matchHeaders = [matchContentTypeJson] }
  
    it "can override many-to-one and one-to-many relationships" $ do
      get "/videogames?select=*,designers!inner(*)"
        `shouldRespondWith`
        [json|[]|] { matchHeaders = [matchContentTypeJson] }
      get "/designers?select=*,videogames!inner(*)"
        `shouldRespondWith`
        [json|[]|] { matchHeaders = [matchContentTypeJson] }
  
    it "can override one-to-one relationships(would give disambiguation errors otherwise)" $ do
      get "/first_1?select=*,second_1(*)"
        `shouldRespondWith`
        [json|[]|] { matchHeaders = [matchContentTypeJson] }
      get "/second_1?select=*,first_1(*)"
        `shouldRespondWith`
        [json|[]|] { matchHeaders = [matchContentTypeJson] }
  
    -- https://github.com/PostgREST/postgrest/issues/2455
    it "creates queries with the right aliasing" $ do
      get "/fee?select=*,jsbaz(*,janedoe(*))"
        `shouldRespondWith`
        [json|[]|] { matchHeaders = [matchContentTypeJson] }
      get "/fee?select=*,jsbaz(*,johnsmith(*, fee(*)))"
        `shouldRespondWith`
        [json|[]|] { matchHeaders = [matchContentTypeJson] }
  
    it "creates queries with the right aliasing when following a normal embed" $ do
      get "/projects?select=name,clients(name,computed_projects(name))&limit=1"
        `shouldRespondWith`
        [json|
          [{"name":"Windows 7","clients":{"name":"Microsoft","computed_projects":{"name":"Windows 7"}}}]
        |] { matchHeaders = [matchContentTypeJson] }
      get "/clients?select=name,projects(name,computed_clients(name))&limit=1"
        `shouldRespondWith`
        [json|[
          {"name":"Microsoft","projects":[
            {"name":"Windows 7","computed_clients":{"name":"Microsoft"}},
            {"name":"Windows 10","computed_clients":{"name":"Microsoft"}}
          ]}
        ]|] { matchHeaders = [matchContentTypeJson] }
  
    -- https://github.com/PostgREST/postgrest/issues/2963
    context "can be defined using overloaded functions" $ do
      it "tables" $ do
        get "/items?select=*,computed_rel_overload(*)&limit=1"
          `shouldRespondWith`
          [json|
            [{"id":1,"computed_rel_overload":[{"id":1}]}]
          |] { matchHeaders = [matchContentTypeJson] }
        get "/items2?select=*,computed_rel_overload(*)&limit=1"
          `shouldRespondWith`
          [json|
            [{"id":1,"computed_rel_overload":[{"id":1},{"id":2}]}]
          |] { matchHeaders = [matchContentTypeJson] }
  
      it "rpc" $ do
        get "/rpc/search?id=1&select=*,computed_rel_overload(*)"
          `shouldRespondWith`
          [json|
            [{"id":1,"computed_rel_overload":[{"id":1}]}]
          |] { matchHeaders = [matchContentTypeJson] }
        get "/rpc/search2?id=1&select=*,computed_rel_overload(*)"
          `shouldRespondWith`
          [json|
            [{"id":1,"computed_rel_overload":[{"id":1},{"id":2}]}]
          |] { matchHeaders = [matchContentTypeJson] }
  