graphql-engine/server/tests-hspec/Test/BigQuery/ComputedFieldSpec.hs
Samir Talwar eab4f75212 An ErrorMessage type, to encapsulate.
This introduces an `ErrorMessage` newtype which wraps `Text` in a manner which is designed to be easy to construct, and difficult to deconstruct.

It provides functionality similar to `Data.Text.Extended`, but designed _only_ for error messages. Error messages are constructed through `fromString`, concatenation, or the `toErrorValue` function, which is designed to be overridden for all meaningful domain types that might show up in an error message. Notably, there are not and should never be instances of `ToErrorValue` for `String`, `Text`, `Int`, etc. This is so that we correctly represent the value in a way that is specific to its type. For example, all `Name` values (from the _graphql-parser-hs_ library) are single-quoted now; no exceptions.

I have mostly had to add `instance ToErrorValue` for various backend types (and also add newtypes where necessary). Some of these are not strictly necessary for this changeset, as I had bigger aspirations when I started. These aspirations have been tempered by trying and failing twice.

As such, in this changeset, I have started by introducing this type to the `parseError` and `parseErrorWith` functions. In the future, I would like to extend this to the `QErr` record and the various `throwError` functions, but this is a much larger task and should probably be done in stages.

For now, `toErrorMessage` and `fromErrorMessage` are provided for conversion to and from `Text`, but the intent is to stop exporting these once all error messages are converted to the new type.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5018
GitOrigin-RevId: 84b37e238992e4312255a87ca44f41af65e2d89a
2022-07-18 20:27:06 +00:00

459 lines
10 KiB
Haskell

{-# LANGUAGE QuasiQuotes #-}
-- | All tests related to computed fields in a BigQuery source
module Test.BigQuery.ComputedFieldSpec (spec) where
import Data.Text qualified as T
import Harness.Backend.BigQuery qualified as BigQuery
import Harness.Constants qualified as Constants
import Harness.Exceptions (finally)
import Harness.GraphqlEngine qualified as GraphqlEngine
import Harness.Quoter.Graphql (graphql)
import Harness.Quoter.Yaml (shouldReturnYaml, yaml)
import Harness.Test.Context qualified as Context
import Harness.Test.Schema (Table (..), table)
import Harness.Test.Schema qualified as Schema
import Harness.TestEnvironment (TestEnvironment)
import Test.Hspec (SpecWith, it)
import Prelude
-- ** Preamble
spec :: SpecWith TestEnvironment
spec =
Context.run
[ Context.Context
{ name = Context.Backend Context.BigQuery,
mkLocalTestEnvironment = Context.noLocalTestEnvironment,
setup = bigquerySetup,
teardown = bigqueryTeardown,
customOptions = Nothing
}
]
tests
-- ** Setup and teardown
bigquerySetup :: (TestEnvironment, ()) -> IO ()
bigquerySetup (testEnv, ()) = do
BigQuery.setup [authorTable, articleTable] (testEnv, ())
-- Create functions in BigQuery
BigQuery.runSql_ createFunctionsSQL
-- Add computed fields and define select permissions
let dataset = Constants.bigqueryDataset
GraphqlEngine.postMetadata_
testEnv
[yaml|
type: bulk
args:
- type: bigquery_add_computed_field
args:
source: bigquery
name: search_articles_1
table:
dataset: *dataset
name: author
definition:
function:
dataset: *dataset
name: fetch_articles_returns_table
argument_mapping:
a_id: id
- type: bigquery_add_computed_field
args:
source: bigquery
name: search_articles_2
table:
dataset: *dataset
name: author
definition:
function:
dataset: *dataset
name: fetch_articles
argument_mapping:
a_id: id
return_table:
name: article
dataset: *dataset
# Role user_1 has select permissions on author and article tables.
# user_1 can query search_articles_1 computed field.
- type: bigquery_create_select_permission
args:
source: bigquery
table:
dataset: *dataset
name: author
role: user_1
permission:
columns: '*'
filter: {}
computed_fields:
- search_articles_1
- type: bigquery_create_select_permission
args:
source: bigquery
table:
dataset: *dataset
name: article
role: user_1
permission:
columns: '*'
filter: {}
# Role user_2 has select permissions only on author table.
- type: bigquery_create_select_permission
args:
source: bigquery
table:
dataset: *dataset
name: author
role: user_2
permission:
columns: '*'
filter: {}
|]
bigqueryTeardown :: (TestEnvironment, ()) -> IO ()
bigqueryTeardown (testEnv, ()) = do
-- Drop permissions and computed fields metadata
let dataset = Constants.bigqueryDataset
dropComputedFieldsYaml =
[yaml|
type: bulk
args:
- type: bigquery_drop_select_permission
args:
source: bigquery
table:
dataset: *dataset
name: author
role: user_1
- type: bigquery_drop_select_permission
args:
source: bigquery
table:
dataset: *dataset
name: article
role: user_1
- type: bigquery_drop_select_permission
args:
source: bigquery
table:
dataset: *dataset
name: author
role: user_2
- type: bigquery_drop_computed_field
args:
source: bigquery
name: search_articles_1
table:
dataset: *dataset
name: author
- type: bigquery_drop_computed_field
args:
source: bigquery
name: search_articles_2
table:
dataset: *dataset
name: author
|]
finally
(GraphqlEngine.postMetadata_ testEnv dropComputedFieldsYaml)
( finally
-- Drop functions in BigQuery database
(BigQuery.runSql_ dropFunctionsSQL)
-- Teardown schema
(BigQuery.teardown [authorTable, articleTable] (testEnv, ()))
)
authorTable :: Table
authorTable =
(table "author")
{ tableColumns =
[ Schema.column "id" Schema.TInt,
Schema.column "name" Schema.TStr
],
tablePrimaryKey = ["id"],
tableData =
[ [ Schema.VInt 1,
Schema.VStr "Author 1"
],
[ Schema.VInt 2,
Schema.VStr "Author 2"
]
]
}
articleTable :: Table
articleTable =
(table "article")
{ tableColumns =
[ Schema.column "id" Schema.TInt,
Schema.column "title" Schema.TStr,
Schema.column "content" Schema.TStr,
Schema.column "author_id" Schema.TInt
],
tablePrimaryKey = ["id"],
tableData =
[ [ Schema.VInt 1,
Schema.VStr "Article 1 Title",
Schema.VStr "Article 1 by Author 1",
Schema.VInt 1
],
[ Schema.VInt 2,
Schema.VStr "Article 2 Title",
Schema.VStr "Article 2 by Author 2",
Schema.VInt 2
],
[ Schema.VInt 3,
Schema.VStr "Article 3 Title",
Schema.VStr "Article 3 by Author 2, has search keyword",
Schema.VInt 2
]
]
}
fetch_articles_returns_table :: T.Text
fetch_articles_returns_table =
T.pack Constants.bigqueryDataset <> ".fetch_articles_returns_table"
fetch_articles :: T.Text
fetch_articles =
T.pack Constants.bigqueryDataset <> ".fetch_articles"
createFunctionsSQL :: String
createFunctionsSQL =
T.unpack $
T.unwords $
[ "CREATE TABLE FUNCTION ",
fetch_articles_returns_table,
"(a_id INT64, search STRING)",
"RETURNS TABLE<id INT64, title STRING, content STRING>",
"AS (",
"SELECT t.id, t.title, t.content FROM",
articleTableSQL,
"AS t WHERE t.author_id = a_id and (t.title LIKE `search` OR t.content LIKE `search`)",
");"
]
<> [ "CREATE TABLE FUNCTION ",
fetch_articles,
"(a_id INT64, search STRING)",
"AS (",
"SELECT t.* FROM",
articleTableSQL,
"AS t WHERE t.author_id = a_id and (t.title LIKE `search` OR t.content LIKE `search`)",
");"
]
where
articleTableSQL = T.pack Constants.bigqueryDataset <> ".article"
dropFunctionsSQL :: String
dropFunctionsSQL =
T.unpack $
T.unwords $
[ "DROP TABLE FUNCTION " <> fetch_articles_returns_table <> ";",
"DROP TABLE FUNCTION " <> fetch_articles <> ";"
]
-- * Tests
tests :: Context.Options -> SpecWith TestEnvironment
tests opts = do
it "Query with computed fields" $ \testEnv ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnv
[graphql|
query {
hasura_author(order_by: {id: asc}){
id
name
search_articles_1(args: {search: "%1%"}){
id
title
content
}
search_articles_2(args: {search: "%keyword%"}){
id
title
content
author_id
}
}
}
|]
)
[yaml|
data:
hasura_author:
- id: '1'
name: Author 1
search_articles_1:
- id: '1'
title: Article 1 Title
content: Article 1 by Author 1
search_articles_2: []
- id: '2'
name: Author 2
search_articles_1: []
search_articles_2:
- id: '3'
title: Article 3 Title
content: Article 3 by Author 2, has search keyword
author_id: '2'
|]
it "Query with computed fields using limit and order_by" $ \testEnv ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnv
[graphql|
query {
hasura_author(order_by: {id: asc}){
id
name
search_articles_2(args: {search: "%by%"} limit: 1 order_by: {id: asc}){
id
title
content
author_id
}
}
}
|]
)
[yaml|
data:
hasura_author:
- id: '1'
name: Author 1
search_articles_2:
- author_id: '1'
content: Article 1 by Author 1
id: '1'
title: Article 1 Title
- id: '2'
name: Author 2
search_articles_2:
- author_id: '2'
content: Article 2 by Author 2
id: '2'
title: Article 2 Title
|]
it "Query with computed fields as user_1 role" $ \testEnv ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphqlWithHeaders
testEnv
[("X-Hasura-Role", "user_1")]
[graphql|
query {
hasura_author(order_by: {id: asc}){
id
name
search_articles_1(args: {search: "%1%"}){
id
title
content
}
search_articles_2(args: {search: "%keyword%"}){
id
title
content
author_id
}
}
}
|]
)
[yaml|
data:
hasura_author:
- id: '1'
name: Author 1
search_articles_1:
- id: '1'
title: Article 1 Title
content: Article 1 by Author 1
search_articles_2: []
- id: '2'
name: Author 2
search_articles_1: []
search_articles_2:
- id: '3'
title: Article 3 Title
content: Article 3 by Author 2, has search keyword
author_id: '2'
|]
it "Query with computed field search_articles_1 as user_2 role" $ \testEnv ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphqlWithHeaders
testEnv
[("X-Hasura-Role", "user_2")]
[graphql|
query {
hasura_author(order_by: {id: asc}){
id
name
search_articles_1(args: {search: "%1%"}){
id
title
content
}
}
}
|]
)
[yaml|
errors:
- extensions:
path: "$.selectionSet.hasura_author.selectionSet.search_articles_1"
code: validation-failed
message: |-
field 'search_articles_1' not found in type: 'hasura_author'
|]
it "Query with computed field search_articles_2 as user_2 role" $ \testEnv ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphqlWithHeaders
testEnv
[("X-Hasura-Role", "user_2")]
[graphql|
query {
hasura_author(order_by: {id: asc}){
id
name
search_articles_2(args: {search: "%keyword%"}){
id
title
content
author_id
}
}
}
|]
)
[yaml|
errors:
- extensions:
path: "$.selectionSet.hasura_author.selectionSet.search_articles_2"
code: validation-failed
message: |-
field 'search_articles_2' not found in type: 'hasura_author'
|]