1
Fork 0
mirror of https://github.com/Airsequel/AirGQL.git synced 2025-09-18 19:34:32 +02: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 , Introspection.typeNameResolver
, resolvers , 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 <&> wrapResolver requireRead
} }
@ -1074,94 +1076,73 @@ mutationType connection maxRowsPerTable accessMode dbId tables = do
numOfChanges <- SS.changes connection numOfChanges <- SS.changes connection
mutationByPKResponse table numOfChanges $ P.head deletedRows mutationByPKResponse table numOfChanges $ P.head deletedRows
getMutationResolvers :: IO (HashMap.HashMap Text (Resolver IO)) getInsertTableTuple :: TableEntry -> IO (Text, Resolver IO)
getMutationResolvers = do getInsertTableTuple table =
let makeResolver
getInsertTableTuple :: TableEntry -> IO (Text, Resolver IO) (Introspection.tableInsertField accessMode table)
getInsertTableTuple table = (executeDbInserts table)
makeResolver
(Introspection.tableInsertField accessMode table)
(executeDbInserts table)
getUpdateTableTuple :: TableEntry -> IO (Text, Resolver IO) getUpdateTableTuple :: TableEntry -> IO (Text, Resolver IO)
getUpdateTableTuple table = getUpdateTableTuple table =
makeResolver makeResolver
(Introspection.tableUpdateField accessMode table) (Introspection.tableUpdateField accessMode table)
(executeDbUpdates table) (executeDbUpdates table)
getUpdateByPKTableTuple :: TableEntry -> IO (Maybe (Text, Resolver IO)) getUpdateByPKTableTuple :: TableEntry -> IO (Maybe (Text, Resolver IO))
getUpdateByPKTableTuple table = getUpdateByPKTableTuple table =
P.for (Introspection.tableUpdateFieldByPk accessMode tables table) $ P.for (Introspection.tableUpdateFieldByPk accessMode tables table) $
\field -> makeResolver field (executeDbUpdatesByPK table) \field -> makeResolver field (executeDbUpdatesByPK table)
getDeleteTableTuple :: TableEntry -> IO (Text, Resolver IO) getDeleteTableTuple :: TableEntry -> IO (Text, Resolver IO)
getDeleteTableTuple table = getDeleteTableTuple table =
makeResolver makeResolver
(Introspection.tableDeleteField accessMode table) (Introspection.tableDeleteField accessMode table)
(executeDbDeletions table) (executeDbDeletions table)
getDeleteByPKTableTuple :: TableEntry -> IO (Maybe (Text, Resolver IO)) getDeleteByPKTableTuple :: TableEntry -> IO (Maybe (Text, Resolver IO))
getDeleteByPKTableTuple table = getDeleteByPKTableTuple table =
P.for (Introspection.tableDeleteFieldByPK accessMode tables table) $ P.for (Introspection.tableDeleteFieldByPK accessMode tables table) $
\field -> makeResolver field (executeDbDeletionsByPK table) \field -> makeResolver field (executeDbDeletionsByPK table)
tablesWithoutViews :: [TableEntry] tablesWithoutViews :: [TableEntry]
tablesWithoutViews = tablesWithoutViews =
List.filter List.filter
(\table -> table.object_type == Table) (\table -> table.object_type == Table)
tables tables
insertTuples <- insertTuples <-
P.fold P.fold
[ P.for tablesWithoutViews getInsertTableTuple [ P.for tablesWithoutViews getInsertTableTuple
] ]
writeTuples <- writeTuples <-
P.fold P.fold
[ P.for tablesWithoutViews getUpdateTableTuple [ P.for tablesWithoutViews getUpdateTableTuple
, P.for tablesWithoutViews getDeleteTableTuple , P.for tablesWithoutViews getDeleteTableTuple
, P.for tablesWithoutViews getUpdateByPKTableTuple , P.for tablesWithoutViews getUpdateByPKTableTuple
<&> P.catMaybes <&> P.catMaybes
, P.for tablesWithoutViews getDeleteByPKTableTuple , P.for tablesWithoutViews getDeleteByPKTableTuple
<&> P.catMaybes <&> P.catMaybes
] ]
let let
requireWrite :: Out.Resolve IO -> Out.Resolve IO insertResolvers =
requireWrite resolve = do if canInsert accessMode
when (P.not $ canWrite accessMode) $ do then HashMap.fromList insertTuples
throw $ else mempty
ResolverException $
userError "Cannot write field using the provided token"
resolve
requireInsert :: Out.Resolve IO -> Out.Resolve IO writeResolvers =
requireInsert resolve = do if canWrite accessMode
when (P.not $ canInsert accessMode) $ do then HashMap.fromList writeTuples
throw $ else mempty
ResolverException $
userError "Cannot insert entries using the provided token"
resolve
insertResolvers = pure
HashMap.fromList insertTuples $ Just
<&> wrapResolver requireInsert $ Out.ObjectType
"Mutation"
writeResolvers = Nothing
HashMap.fromList writeTuples []
<&> wrapResolver requireWrite $ insertResolvers <> writeResolvers
pure $ insertResolvers <> writeResolvers
if canWrite accessMode
then
Just
. Out.ObjectType
"Mutation"
Nothing
[]
<$> getMutationResolvers
else pure Nothing
-- | Automatically generated schema derived from the SQLite database -- | 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 Test.Hspec (Spec, describe, it, shouldBe)
import AirGQL.GraphQL (getDerivedSchema) import AirGQL.GraphQL (getDerivedSchema)
import AirGQL.Lib (getEnrichedTables, writeOnly) import AirGQL.Lib (getEnrichedTables, insertOnly, writeOnly)
import AirGQL.Raw (raw) import AirGQL.Raw (raw)
import AirGQL.Types.SchemaConf (SchemaConf (accessMode), defaultSchemaConf) import AirGQL.Types.SchemaConf (SchemaConf (accessMode), defaultSchemaConf)
import AirGQL.Utils (withRetryConn) import AirGQL.Utils (withRetryConn)
@ -181,7 +181,7 @@ main = void $ do
"data": null, "data": null,
"errors": [{ "errors": [{
"locations": [{ "column":3, "line":2 }], "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"] "path": ["__schema"]
}] }]
} }
@ -644,41 +644,88 @@ main = void $ do
) )
|] |]
let let
query :: Text query :: Text
query = query =
[gql| [gql|
mutation items { mutation items {
update_items(filter: { id: { eq: 0 }}, set: { id: 0 }) { update_items(filter: { id: { eq: 0 }}, set: { id: 0 }) {
returning { id } returning { id }
} }
}
|]
expected =
rmSpaces
[raw|
{
"data": null,
"errors": [{
"locations": [{ "column":3, "line":2 }],
"message": "Cannot query field \"update_items\" on type \"Mutation\"."
}]
} }
|] |]
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 Right tables <- getEnrichedTables conn
getDerivedSchema schema <-
defaultSchemaConf{accessMode = writeOnly} 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 conn
fixtureDbId [sql|
tables CREATE TABLE items (
id INTEGER PRIMARY KEY
)
|]
Right response <- let
graphql schema Nothing mempty query 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 describe "Naming conflicts" $ do
it "appends _ at the end of queries to avoid conflicts with table names" $ do it "appends _ at the end of queries to avoid conflicts with table names" $ do
@ -747,32 +794,32 @@ main = void $ do
"__schema": { "__schema": {
"mutationType": { "mutationType": {
"fields": [ "fields": [
{ {
"name": "insert_foo", "name": "insert_foo",
"args": [ "args": [
{ "name": "objects" }, { "name": "objects" },
{ "name": "on_conflict" } { "name": "on_conflict" }
] ]
}, },
{ {
"name": "update_foo", "name": "update_foo",
"args": [ "args": [
{ "name": "set" }, { "name": "set" },
{ "name": "filter" } { "name": "filter" }
] ]
}, },
{ {
"name": "update_foo_by_pk", "name": "update_foo_by_pk",
"args": [ "args": [
{ "name": "set" }, { "name": "set" },
{ "name": "set_" } { "name": "set_" }
] ]
}, },
{ {
"name": "delete_foo", "name": "delete_foo",
"args": [{ "name": "filter" }] "args": [{ "name": "filter" }]
}, },
{ {
"name": "delete_foo_by_pk", "name": "delete_foo_by_pk",
"args": [{ "name": "set" }] "args": [{ "name": "set" }]
} }

View file

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