1
Fork 0
mirror of https://github.com/Airsequel/AirGQL.git synced 2025-07-31 02:06:44 +03:00

Add insertonly test

Moreover, this fixes the test for writeonly tokens, which used to create
a table in a db, and then try querying from a separate db, which ended
up not testing what it was supposed to at all.

I also changed the way mutations are guarded (the end result is pretty
much the same)
This commit is contained in:
prescientmoon 2024-12-07 20:00:09 +01:00
commit 8b3b1bbe37
3 changed files with 150 additions and 117 deletions

View file

@ -36,7 +36,6 @@ import Protolude (
(&),
(&&),
(.),
(<$>),
(<&>),
(<=),
(>),
@ -814,6 +813,9 @@ queryType connection accessMode dbId tables = do
, Introspection.typeNameResolver
, resolvers
]
-- TODO: is it better to wrap the resolvers here,
-- or to just return an empty list of resolvers
-- when given a token that cannot read?
<&> wrapResolver requireRead
}
@ -1074,94 +1076,73 @@ mutationType connection maxRowsPerTable accessMode dbId tables = do
numOfChanges <- SS.changes connection
mutationByPKResponse table numOfChanges $ P.head deletedRows
getMutationResolvers :: IO (HashMap.HashMap Text (Resolver IO))
getMutationResolvers = do
let
getInsertTableTuple :: TableEntry -> IO (Text, Resolver IO)
getInsertTableTuple table =
makeResolver
(Introspection.tableInsertField accessMode table)
(executeDbInserts table)
getInsertTableTuple :: TableEntry -> IO (Text, Resolver IO)
getInsertTableTuple table =
makeResolver
(Introspection.tableInsertField accessMode table)
(executeDbInserts table)
getUpdateTableTuple :: TableEntry -> IO (Text, Resolver IO)
getUpdateTableTuple table =
makeResolver
(Introspection.tableUpdateField accessMode table)
(executeDbUpdates table)
getUpdateTableTuple :: TableEntry -> IO (Text, Resolver IO)
getUpdateTableTuple table =
makeResolver
(Introspection.tableUpdateField accessMode table)
(executeDbUpdates table)
getUpdateByPKTableTuple :: TableEntry -> IO (Maybe (Text, Resolver IO))
getUpdateByPKTableTuple table =
P.for (Introspection.tableUpdateFieldByPk accessMode tables table) $
\field -> makeResolver field (executeDbUpdatesByPK table)
getUpdateByPKTableTuple :: TableEntry -> IO (Maybe (Text, Resolver IO))
getUpdateByPKTableTuple table =
P.for (Introspection.tableUpdateFieldByPk accessMode tables table) $
\field -> makeResolver field (executeDbUpdatesByPK table)
getDeleteTableTuple :: TableEntry -> IO (Text, Resolver IO)
getDeleteTableTuple table =
makeResolver
(Introspection.tableDeleteField accessMode table)
(executeDbDeletions table)
getDeleteTableTuple :: TableEntry -> IO (Text, Resolver IO)
getDeleteTableTuple table =
makeResolver
(Introspection.tableDeleteField accessMode table)
(executeDbDeletions table)
getDeleteByPKTableTuple :: TableEntry -> IO (Maybe (Text, Resolver IO))
getDeleteByPKTableTuple table =
P.for (Introspection.tableDeleteFieldByPK accessMode tables table) $
\field -> makeResolver field (executeDbDeletionsByPK table)
getDeleteByPKTableTuple :: TableEntry -> IO (Maybe (Text, Resolver IO))
getDeleteByPKTableTuple table =
P.for (Introspection.tableDeleteFieldByPK accessMode tables table) $
\field -> makeResolver field (executeDbDeletionsByPK table)
tablesWithoutViews :: [TableEntry]
tablesWithoutViews =
List.filter
(\table -> table.object_type == Table)
tables
tablesWithoutViews :: [TableEntry]
tablesWithoutViews =
List.filter
(\table -> table.object_type == Table)
tables
insertTuples <-
P.fold
[ P.for tablesWithoutViews getInsertTableTuple
]
insertTuples <-
P.fold
[ P.for tablesWithoutViews getInsertTableTuple
]
writeTuples <-
P.fold
[ P.for tablesWithoutViews getUpdateTableTuple
, P.for tablesWithoutViews getDeleteTableTuple
, P.for tablesWithoutViews getUpdateByPKTableTuple
<&> P.catMaybes
, P.for tablesWithoutViews getDeleteByPKTableTuple
<&> P.catMaybes
]
writeTuples <-
P.fold
[ P.for tablesWithoutViews getUpdateTableTuple
, P.for tablesWithoutViews getDeleteTableTuple
, P.for tablesWithoutViews getUpdateByPKTableTuple
<&> P.catMaybes
, P.for tablesWithoutViews getDeleteByPKTableTuple
<&> P.catMaybes
]
let
requireWrite :: Out.Resolve IO -> Out.Resolve IO
requireWrite resolve = do
when (P.not $ canWrite accessMode) $ do
throw $
ResolverException $
userError "Cannot write field using the provided token"
resolve
let
insertResolvers =
if canInsert accessMode
then HashMap.fromList insertTuples
else mempty
requireInsert :: Out.Resolve IO -> Out.Resolve IO
requireInsert resolve = do
when (P.not $ canInsert accessMode) $ do
throw $
ResolverException $
userError "Cannot insert entries using the provided token"
resolve
writeResolvers =
if canWrite accessMode
then HashMap.fromList writeTuples
else mempty
insertResolvers =
HashMap.fromList insertTuples
<&> wrapResolver requireInsert
writeResolvers =
HashMap.fromList writeTuples
<&> wrapResolver requireWrite
pure $ insertResolvers <> writeResolvers
if canWrite accessMode
then
Just
. Out.ObjectType
"Mutation"
Nothing
[]
<$> getMutationResolvers
else pure Nothing
pure
$ Just
$ Out.ObjectType
"Mutation"
Nothing
[]
$ insertResolvers <> writeResolvers
-- | Automatically generated schema derived from the SQLite database

View file

@ -24,7 +24,7 @@ import System.FilePath ((</>))
import Test.Hspec (Spec, describe, it, shouldBe)
import AirGQL.GraphQL (getDerivedSchema)
import AirGQL.Lib (getEnrichedTables, writeOnly)
import AirGQL.Lib (getEnrichedTables, insertOnly, writeOnly)
import AirGQL.Raw (raw)
import AirGQL.Types.SchemaConf (SchemaConf (accessMode), defaultSchemaConf)
import AirGQL.Utils (withRetryConn)
@ -181,7 +181,7 @@ main = void $ do
"data": null,
"errors": [{
"locations": [{ "column":3, "line":2 }],
"message": "user error (Cannot read field using writeonly access code)",
"message": "user error (Cannot read field using the provided token)",
"path": ["__schema"]
}]
}
@ -644,41 +644,88 @@ main = void $ do
)
|]
let
query :: Text
query =
[gql|
mutation items {
update_items(filter: { id: { eq: 0 }}, set: { id: 0 }) {
returning { id }
}
}
|]
expected =
rmSpaces
[raw|
{
"data": null,
"errors": [{
"locations": [{ "column":3, "line":2 }],
"message": "Cannot query field \"update_items\" on type \"Mutation\"."
}]
let
query :: Text
query =
[gql|
mutation items {
update_items(filter: { id: { eq: 0 }}, set: { id: 0 }) {
returning { id }
}
}
|]
schema <- withRetryConn dbPath $ \conn -> do
expected =
rmSpaces
[raw|
{
"data": null,
"errors": [{
"locations": [{ "column":5, "line":3 }],
"message": "Cannot query field \"returning\" on type \"items_mutation_response\"."
}]
}
|]
Right tables <- getEnrichedTables conn
getDerivedSchema
defaultSchemaConf{accessMode = writeOnly}
schema <-
getDerivedSchema
defaultSchemaConf{accessMode = writeOnly}
conn
fixtureDbId
tables
Right response <-
graphql schema Nothing mempty query
Ae.encode response `shouldBe` expected
it "doesn't allow insertonly tokens to update data" $ do
let dbName = "no-insertonly-return.db"
withTestDbConn dbName $ \conn -> do
SS.execute_
conn
fixtureDbId
tables
[sql|
CREATE TABLE items (
id INTEGER PRIMARY KEY
)
|]
Right response <-
graphql schema Nothing mempty query
let
query :: Text
query =
[gql|
mutation items {
update_items_by_pk(id: 0, set: { id: 0 }) {
affected_rows
}
}
|]
Ae.encode response `shouldBe` expected
expected =
rmSpaces
[raw|
{
"data": null,
"errors": [{
"locations": [{ "column":3, "line":2 }],
"message": "Cannot query field \"update_items_by_pk\" on type \"Mutation\"."
}]
}
|]
Right tables <- getEnrichedTables conn
schema <-
getDerivedSchema
defaultSchemaConf{accessMode = insertOnly}
conn
fixtureDbId
tables
Right response <-
graphql schema Nothing mempty query
Ae.encode response `shouldBe` expected
describe "Naming conflicts" $ do
it "appends _ at the end of queries to avoid conflicts with table names" $ do
@ -747,32 +794,32 @@ main = void $ do
"__schema": {
"mutationType": {
"fields": [
{
{
"name": "insert_foo",
"args": [
{ "name": "objects" },
{ "name": "on_conflict" }
]
},
{
{
"name": "update_foo",
"args": [
{ "name": "set" },
{ "name": "filter" }
]
},
{
{
"name": "update_foo_by_pk",
"args": [
{ "name": "set" },
{ "name": "set_" }
]
},
{
{
"name": "delete_foo",
"args": [{ "name": "filter" }]
},
{
{
"name": "delete_foo_by_pk",
"args": [{ "name": "set" }]
}

View file

@ -24,11 +24,11 @@ import System.FilePath ((</>))
import Test.Hspec (Spec, describe, it, shouldBe)
import AirGQL.GraphQL (getDerivedSchema)
import AirGQL.Lib (SQLPost (SQLPost, query), getEnrichedTables)
import AirGQL.Lib (SQLPost (SQLPost, query), getEnrichedTables, insertOnly)
import AirGQL.Raw (raw)
import AirGQL.Servant.SqlQuery (sqlQueryPostHandler)
import AirGQL.Types.PragmaConf qualified as PragmaConf
import AirGQL.Types.SchemaConf (defaultSchemaConf)
import AirGQL.Types.SchemaConf (SchemaConf (accessMode), defaultSchemaConf)
import AirGQL.Types.SqlQueryPostResult (
SqlQueryPostResult (rows),
)
@ -75,7 +75,12 @@ main = void $ do
conn <- SS.open dbPath
Right tables <- getEnrichedTables conn
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
schema <-
getDerivedSchema
defaultSchemaConf{accessMode = insertOnly}
conn
fixtureDbId
tables
Right result <- graphql schema Nothing mempty query
Ae.encode result `shouldBe` expected