mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 18:47:29 +03:00
Add tests for update mutations to the Data Connector agent test suite
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8054 GitOrigin-RevId: 6869e8938e7a70128770f959bf34e3a291074d14
This commit is contained in:
parent
7dfd2d60f9
commit
72e319e486
@ -96,3 +96,12 @@ source-repository-package
|
||||
location: https://github.com/hasura/text.git
|
||||
tag: ba0fd2bf256c996a6c85dbdc8590a6fcde41b8f8
|
||||
|
||||
-- This is a version of Sandwich 0.1.2.0 but including
|
||||
-- this PR https://github.com/codedownio/sandwich/pull/69
|
||||
-- which adds the ability to skip tests from the command line
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/hasura/sandwich.git
|
||||
tag: v0.1.2.0-incl-skipping.0
|
||||
subdir: sandwich
|
||||
|
||||
|
@ -110,20 +110,25 @@ Datasets support is enabled via the ENV variables:
|
||||
* `DATASET_TEMPLATES`
|
||||
* `DATASET_CLONES`
|
||||
|
||||
Templates will be looked up at `${DATASET_TEMPLATES}/${template_name}.db`.
|
||||
Templates will be looked up at `${DATASET_TEMPLATES}/${template_name}.sqlite` or `${DATASET_TEMPLATES}/${template_name}.sql`. The `.sqlite` templates are just SQLite database files that will be copied as a clone. The `.sql` templates are SQL script files that will be run against a blank SQLite database in order to create a clone.
|
||||
|
||||
Clones will be copied to `${DATASET_CLONES}/${clone_name}.db`.
|
||||
Clones will be copied to `${DATASET_CLONES}/${clone_name}.sqlite`.
|
||||
|
||||
## Testing Changes to the Agent
|
||||
|
||||
Run:
|
||||
Ensure you run the agent with `DATASETS=1 DATASET_DELETE=1 MUTATIONS=1` in order to enable testing of mutations.
|
||||
|
||||
Then run:
|
||||
|
||||
```sh
|
||||
cabal run dc-api:test:tests-dc-api -- test --agent-base-url http://localhost:8100 --agent-config '{"db": "db.chinook2.sqlite"}'
|
||||
cabal run dc-api:test:tests-dc-api -- test --agent-base-url http://localhost:8100 sandwich --tui
|
||||
```
|
||||
|
||||
From the HGE repo.
|
||||
|
||||
## Known Issues
|
||||
* Using "returning" in update mutations where you join across relationships that are affected by the update mutation itself may return inconsistent results. This is because of this issue with SQLite: https://sqlite.org/forum/forumpost/9470611066
|
||||
|
||||
## TODO
|
||||
|
||||
* [x] Prometheus metrics hosted at `/metrics`
|
||||
|
@ -55,6 +55,7 @@ export async function withConnection<Result>(config: Config, mode: number, sqlLo
|
||||
* $name: "bar"
|
||||
* });
|
||||
*/
|
||||
sqlLogger(query);
|
||||
db_.all(query, params || {}, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
@ -67,6 +68,7 @@ export async function withConnection<Result>(config: Config, mode: number, sqlLo
|
||||
|
||||
const exec = (sql: string): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
sqlLogger(sql);
|
||||
db_.exec(sql, err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
|
@ -196,14 +196,11 @@ if(DATASETS) {
|
||||
return cloneDataset(sqlLogger, request.params.clone_name, request.body);
|
||||
});
|
||||
|
||||
// Only allow deletion if this is explicitly supported by ENV configuration
|
||||
if(DATASET_DELETE) {
|
||||
// TODO: The name param here should be a DatasetCloneName, but this isn't being code-generated.
|
||||
server.delete<{ Params: { clone_name: string, }, Reply: DatasetDeleteCloneResponse }>("/datasets/clones/:clone_name", async (request, _response) => {
|
||||
server.log.info({ headers: request.headers, query: request.body, }, "datasets.clones.delete");
|
||||
return deleteDataset(request.params.clone_name);
|
||||
});
|
||||
}
|
||||
// TODO: The name param here should be a DatasetCloneName, but this isn't being code-generated.
|
||||
server.delete<{ Params: { clone_name: string, }, Reply: DatasetDeleteCloneResponse }>("/datasets/clones/:clone_name", async (request, _response) => {
|
||||
server.log.info({ headers: request.headers, query: request.body, }, "datasets.clones.delete");
|
||||
return deleteDataset(request.params.clone_name);
|
||||
});
|
||||
}
|
||||
|
||||
server.get("/", async (request, response) => {
|
||||
|
@ -147,7 +147,7 @@ function insertString(relationships: Array<TableRelationships>, op: InsertMutati
|
||||
|
||||
function deleteString(relationships: Array<TableRelationships>, op: DeleteMutationOperation): string {
|
||||
return `
|
||||
DELETE FROM ${op.table}
|
||||
DELETE FROM ${escapeTableName(op.table)}
|
||||
WHERE ${whereString(relationships, op.where || EMPTY_AND, op.table)}
|
||||
RETURNING
|
||||
${returningString(relationships, op.returning_fields || {}, op.table)} as row,
|
||||
@ -157,7 +157,7 @@ function deleteString(relationships: Array<TableRelationships>, op: DeleteMutati
|
||||
|
||||
function updateString(relationships: Array<TableRelationships>, op: UpdateMutationOperation, info: Array<UpdateInfo>): string {
|
||||
const result = `
|
||||
UPDATE ${op.table}
|
||||
UPDATE ${escapeTableName(op.table)}
|
||||
SET ${setString(info)}
|
||||
WHERE ${whereString(relationships, op.where || EMPTY_AND, op.table)}
|
||||
RETURNING
|
||||
|
@ -66,7 +66,7 @@ function validateTableName(tableName: TableName): TableName {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ts
|
||||
* @param ts
|
||||
* @returns last section of a qualified table array. E.g. [a,b] -> [b]
|
||||
*/
|
||||
export function getTableNameSansSchema(ts: Array<string>): Array<string> {
|
||||
@ -83,9 +83,9 @@ export function escapeTableName(tableName: TableName): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tableName
|
||||
* @param tableName
|
||||
* @returns escaped tableName string with schema qualification removed
|
||||
*
|
||||
*
|
||||
* This is useful in where clauses in returning statements where a qualified table name is invalid SQLite SQL.
|
||||
*/
|
||||
export function escapeTableNameSansSchema(tableName: TableName): string {
|
||||
@ -253,18 +253,18 @@ function generateIdentifierAlias(identifier: string): string {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ts Array of Table Relationships
|
||||
* @param t Table Name
|
||||
* @param allTableRelationships Array of Table Relationships
|
||||
* @param tableName Table Name
|
||||
* @returns Relationships matching table-name
|
||||
*/
|
||||
function find_table_relationship(ts: TableRelationships[], t: TableName): TableRelationships {
|
||||
for(var i = 0; i < ts.length; i++) {
|
||||
const r = ts[i];
|
||||
if(tableNameEquals(r.source_table)(t)) {
|
||||
function find_table_relationship(allTableRelationships: TableRelationships[], tableName: TableName): TableRelationships {
|
||||
for(var i = 0; i < allTableRelationships.length; i++) {
|
||||
const r = allTableRelationships[i];
|
||||
if(tableNameEquals(r.source_table)(tableName)) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
throw new Error(`Couldn't find relationship ${ts}, ${t.join(".")} - This shouldn't happen.`);
|
||||
throw new Error(`Couldn't find table relationships for table ${tableName} - This shouldn't happen.`);
|
||||
}
|
||||
|
||||
function cast_aggregate_function(f: string): string {
|
||||
|
@ -162,6 +162,7 @@ test-suite tests-dc-api
|
||||
Test.Specs.MetricsSpec
|
||||
Test.Specs.MutationSpec
|
||||
Test.Specs.MutationSpec.InsertSpec
|
||||
Test.Specs.MutationSpec.UpdateSpec
|
||||
Test.Specs.QuerySpec
|
||||
Test.Specs.QuerySpec.AggregatesSpec
|
||||
Test.Specs.QuerySpec.BasicSpec
|
||||
|
@ -31,6 +31,11 @@ module Hasura.Backends.DataConnector.API.V0.Mutations
|
||||
_ObjectRelationInsertFieldValue,
|
||||
_ArrayRelationInsertFieldValue,
|
||||
UpdateMutationOperation (..),
|
||||
umoTable,
|
||||
umoWhere,
|
||||
umoUpdates,
|
||||
umoPostUpdateCheck,
|
||||
umoReturningFields,
|
||||
RowUpdate (..),
|
||||
RowColumnOperatorValue (..),
|
||||
DeleteMutationOperation (..),
|
||||
@ -506,5 +511,6 @@ instance HasCodec MutationOperationResults where
|
||||
|
||||
$(makeLenses ''MutationRequest)
|
||||
$(makeLenses ''InsertMutationOperation)
|
||||
$(makeLenses ''UpdateMutationOperation)
|
||||
$(makeLenses ''MutationResponse)
|
||||
$(makeLenses ''MutationOperationResults)
|
||||
|
@ -17,6 +17,7 @@ module Hasura.Backends.DataConnector.API.V0.Table
|
||||
tiDeletable,
|
||||
TableType (..),
|
||||
ForeignKeys (..),
|
||||
unForeignKeys,
|
||||
ConstraintName (..),
|
||||
Constraint (..),
|
||||
cForeignTable,
|
||||
@ -111,13 +112,13 @@ instance HasCodec TableType where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
newtype ForeignKeys = ForeignKeys {unForeignKeys :: HashMap ConstraintName Constraint}
|
||||
newtype ForeignKeys = ForeignKeys {_unForeignKeys :: HashMap ConstraintName Constraint}
|
||||
deriving stock (Eq, Ord, Show, Generic, Data)
|
||||
deriving anyclass (NFData, Hashable)
|
||||
deriving (FromJSON, ToJSON) via Autodocodec ForeignKeys
|
||||
|
||||
instance HasCodec ForeignKeys where
|
||||
codec = dimapCodec ForeignKeys unForeignKeys $ codec @(HashMap ConstraintName Constraint)
|
||||
codec = dimapCodec ForeignKeys _unForeignKeys $ codec @(HashMap ConstraintName Constraint)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -145,3 +146,4 @@ instance HasCodec Constraint where
|
||||
|
||||
$(makeLenses ''TableInfo)
|
||||
$(makeLenses ''Constraint)
|
||||
$(makeLenses ''ForeignKeys)
|
||||
|
@ -39,7 +39,7 @@ where
|
||||
import Codec.Compression.GZip qualified as GZip
|
||||
import Command (NameCasing (..), TestConfig (..))
|
||||
import Control.Arrow (first, (>>>))
|
||||
import Control.Lens (Index, IxValue, Ixed, Traversal', at, ix, lens, (%~), (&), (?~), (^.), (^..), (^?), _Just)
|
||||
import Control.Lens (Index, IxValue, Ixed, Traversal', at, ix, (%~), (&), (?~), (^.), (^..), (^?), _Just)
|
||||
import Data.Aeson (eitherDecodeStrict)
|
||||
import Data.Aeson qualified as J
|
||||
import Data.Aeson.Lens (_Bool, _Null, _Number, _String)
|
||||
@ -211,12 +211,49 @@ invoicesTableName = mkTableName "Invoice"
|
||||
invoicesRows :: [HashMap API.FieldName API.FieldValue]
|
||||
invoicesRows = sortBy (API.FieldName "InvoiceId") $ readTableFromXmlIntoRows invoicesTableName
|
||||
|
||||
invoicesRowsById :: HashMap Scientific (HashMap API.FieldName API.FieldValue)
|
||||
invoicesRowsById =
|
||||
HashMap.fromList $ mapMaybe (\invoice -> (,invoice) <$> invoice ^? field "InvoiceId" . _ColumnFieldNumber) invoicesRows
|
||||
|
||||
invoicesTableRelationships :: API.TableRelationships
|
||||
invoicesTableRelationships =
|
||||
let invoiceLinesJoinFieldMapping = HashMap.fromList [(API.ColumnName "InvoiceId", API.ColumnName "InvoiceId")]
|
||||
customersJoinFieldMapping = HashMap.fromList [(API.ColumnName "CustomerId", API.ColumnName "CustomerId")]
|
||||
in API.TableRelationships
|
||||
invoicesTableName
|
||||
( HashMap.fromList
|
||||
[ (invoiceLinesRelationshipName, API.Relationship invoiceLinesTableName API.ArrayRelationship invoiceLinesJoinFieldMapping),
|
||||
(customerRelationshipName, API.Relationship customersTableName API.ObjectRelationship customersJoinFieldMapping)
|
||||
]
|
||||
)
|
||||
|
||||
customerRelationshipName :: API.RelationshipName
|
||||
customerRelationshipName = API.RelationshipName "Customer"
|
||||
|
||||
invoiceLinesTableName :: API.TableName
|
||||
invoiceLinesTableName = mkTableName "InvoiceLine"
|
||||
|
||||
invoiceLinesRows :: [HashMap API.FieldName API.FieldValue]
|
||||
invoiceLinesRows = sortBy (API.FieldName "InvoiceLineId") $ readTableFromXmlIntoRows invoiceLinesTableName
|
||||
|
||||
invoiceLinesTableRelationships :: API.TableRelationships
|
||||
invoiceLinesTableRelationships =
|
||||
let invoiceJoinFieldMapping = HashMap.fromList [(API.ColumnName "InvoiceId", API.ColumnName "InvoiceId")]
|
||||
tracksJoinFieldMapping = HashMap.fromList [(API.ColumnName "TrackId", API.ColumnName "TrackId")]
|
||||
in API.TableRelationships
|
||||
invoiceLinesTableName
|
||||
( HashMap.fromList
|
||||
[ (invoiceRelationshipName, API.Relationship invoicesTableName API.ObjectRelationship invoiceJoinFieldMapping),
|
||||
(trackRelationshipName, API.Relationship tracksTableName API.ObjectRelationship tracksJoinFieldMapping)
|
||||
]
|
||||
)
|
||||
|
||||
invoiceRelationshipName :: API.RelationshipName
|
||||
invoiceRelationshipName = API.RelationshipName "Invoice"
|
||||
|
||||
trackRelationshipName :: API.RelationshipName
|
||||
trackRelationshipName = API.RelationshipName "Track"
|
||||
|
||||
mediaTypesTableName :: API.TableName
|
||||
mediaTypesTableName = mkTableName "MediaType"
|
||||
|
||||
@ -229,6 +266,10 @@ tracksTableName = mkTableName "Track"
|
||||
tracksRows :: [HashMap API.FieldName API.FieldValue]
|
||||
tracksRows = sortBy (API.FieldName "TrackId") $ readTableFromXmlIntoRows tracksTableName
|
||||
|
||||
tracksRowsById :: HashMap Scientific (HashMap API.FieldName API.FieldValue)
|
||||
tracksRowsById =
|
||||
HashMap.fromList $ mapMaybe (\track -> (,track) <$> track ^? field "TrackId" . _ColumnFieldNumber) tracksRows
|
||||
|
||||
tracksTableRelationships :: API.TableRelationships
|
||||
tracksTableRelationships =
|
||||
let invoiceLinesJoinFieldMapping = HashMap.fromList [(API.ColumnName "TrackId", API.ColumnName "TrackId")]
|
||||
@ -331,15 +372,22 @@ data TestData = TestData
|
||||
-- = Invoices table
|
||||
_tdInvoicesTableName :: API.TableName,
|
||||
_tdInvoicesRows :: [HashMap API.FieldName API.FieldValue],
|
||||
_tdInvoicesRowsById :: HashMap Scientific (HashMap API.FieldName API.FieldValue),
|
||||
_tdInvoicesTableRelationships :: API.TableRelationships,
|
||||
_tdCustomerRelationshipName :: API.RelationshipName,
|
||||
-- = InvoiceLines table
|
||||
_tdInvoiceLinesTableName :: API.TableName,
|
||||
_tdInvoiceLinesRows :: [HashMap API.FieldName API.FieldValue],
|
||||
_tdInvoiceLinesTableRelationships :: API.TableRelationships,
|
||||
_tdInvoiceRelationshipName :: API.RelationshipName,
|
||||
_tdTrackRelationshipName :: API.RelationshipName,
|
||||
-- = MediaTypes table
|
||||
_tdMediaTypesTableName :: API.TableName,
|
||||
_tdMediaTypesRows :: [HashMap API.FieldName API.FieldValue],
|
||||
-- = Tracks table
|
||||
_tdTracksTableName :: API.TableName,
|
||||
_tdTracksRows :: [HashMap API.FieldName API.FieldValue],
|
||||
_tdTracksRowsById :: HashMap Scientific (HashMap API.FieldName API.FieldValue),
|
||||
_tdTracksTableRelationships :: API.TableRelationships,
|
||||
_tdInvoiceLinesRelationshipName :: API.RelationshipName,
|
||||
_tdMediaTypeRelationshipName :: API.RelationshipName,
|
||||
@ -355,6 +403,7 @@ data TestData = TestData
|
||||
_tdColumnName :: Text -> API.ColumnName,
|
||||
_tdColumnField :: API.TableName -> Text -> API.Field,
|
||||
_tdColumnInsertSchema :: API.TableName -> Text -> API.ColumnInsertSchema,
|
||||
_tdRowColumnOperatorValue :: API.TableName -> Text -> J.Value -> API.RowColumnOperatorValue,
|
||||
_tdQueryComparisonColumn :: Text -> API.ScalarType -> API.ComparisonColumn,
|
||||
_tdCurrentComparisonColumn :: Text -> API.ScalarType -> API.ComparisonColumn,
|
||||
_tdOrderByColumn :: [API.RelationshipName] -> Text -> API.OrderDirection -> API.OrderByElement
|
||||
@ -387,12 +436,19 @@ mkTestData schemaResponse testConfig =
|
||||
_tdSupportRepForCustomersRelationshipName = supportRepForCustomersRelationshipName,
|
||||
_tdInvoicesTableName = formatTableName testConfig invoicesTableName,
|
||||
_tdInvoicesRows = invoicesRows,
|
||||
_tdInvoicesRowsById = invoicesRowsById,
|
||||
_tdInvoicesTableRelationships = formatTableRelationships invoicesTableRelationships,
|
||||
_tdCustomerRelationshipName = customerRelationshipName,
|
||||
_tdInvoiceLinesTableName = formatTableName testConfig invoiceLinesTableName,
|
||||
_tdInvoiceLinesRows = invoiceLinesRows,
|
||||
_tdInvoiceLinesTableRelationships = formatTableRelationships invoiceLinesTableRelationships,
|
||||
_tdInvoiceRelationshipName = invoiceRelationshipName,
|
||||
_tdTrackRelationshipName = trackRelationshipName,
|
||||
_tdMediaTypesTableName = formatTableName testConfig mediaTypesTableName,
|
||||
_tdMediaTypesRows = mediaTypesRows,
|
||||
_tdTracksTableName = formatTableName testConfig tracksTableName,
|
||||
_tdTracksRows = tracksRows,
|
||||
_tdTracksRowsById = tracksRowsById,
|
||||
_tdTracksTableRelationships = formatTableRelationships tracksTableRelationships,
|
||||
_tdInvoiceLinesRelationshipName = invoiceLinesRelationshipName,
|
||||
_tdMediaTypeRelationshipName = mediaTypeRelationshipName,
|
||||
@ -404,6 +460,7 @@ mkTestData schemaResponse testConfig =
|
||||
_tdColumnName = formatColumnName testConfig . API.ColumnName,
|
||||
_tdColumnField = columnField schemaResponse testConfig,
|
||||
_tdColumnInsertSchema = columnInsertSchema schemaResponse testConfig,
|
||||
_tdRowColumnOperatorValue = rowColumnOperatorValue schemaResponse testConfig,
|
||||
_tdFindColumnScalarType = \tableName name -> findColumnScalarType schemaResponse tableName (formatColumnName testConfig $ API.ColumnName name),
|
||||
_tdQueryComparisonColumn = API.ComparisonColumn API.QueryTable . formatColumnName testConfig . API.ColumnName,
|
||||
_tdCurrentComparisonColumn = API.ComparisonColumn API.CurrentTable . formatColumnName testConfig . API.ColumnName,
|
||||
@ -425,7 +482,7 @@ mkTestData schemaResponse testConfig =
|
||||
API.tiName %~ formatTableName testConfig
|
||||
>>> API.tiColumns . traverse . API.ciName %~ formatColumnName testConfig
|
||||
>>> API.tiPrimaryKey . traverse %~ formatColumnName testConfig
|
||||
>>> API.tiForeignKeys . lens API.unForeignKeys (const API.ForeignKeys) . traverse
|
||||
>>> API.tiForeignKeys . API.unForeignKeys . traverse
|
||||
%~ ( API.cForeignTable %~ formatTableName testConfig
|
||||
>>> API.cColumnMapping %~ (HashMap.toList >>> fmap (bimap (formatColumnName testConfig) (formatColumnName testConfig)) >>> HashMap.fromList)
|
||||
)
|
||||
@ -443,7 +500,9 @@ data EdgeCasesTestData = EdgeCasesTestData
|
||||
-- = Utility functions
|
||||
_ectdTableExists :: API.TableName -> Bool,
|
||||
_ectdColumnField :: API.TableName -> Text -> API.Field,
|
||||
_ectdColumnInsertSchema :: API.TableName -> Text -> API.ColumnInsertSchema
|
||||
_ectdColumnInsertSchema :: API.TableName -> Text -> API.ColumnInsertSchema,
|
||||
_ectdRowColumnOperatorValue :: API.TableName -> Text -> J.Value -> API.RowColumnOperatorValue,
|
||||
_ectdCurrentComparisonColumn :: Text -> API.ScalarType -> API.ComparisonColumn
|
||||
}
|
||||
|
||||
mkEdgeCasesTestData :: TestConfig -> API.SchemaResponse -> EdgeCasesTestData
|
||||
@ -455,7 +514,9 @@ mkEdgeCasesTestData testConfig schemaResponse =
|
||||
_ectdFindColumnScalarType = \tableName name -> findColumnScalarType schemaResponse tableName (formatColumnName testConfig $ API.ColumnName name),
|
||||
_ectdTableExists = tableExists,
|
||||
_ectdColumnField = columnField schemaResponse testConfig,
|
||||
_ectdColumnInsertSchema = columnInsertSchema schemaResponse testConfig
|
||||
_ectdColumnInsertSchema = columnInsertSchema schemaResponse testConfig,
|
||||
_ectdRowColumnOperatorValue = rowColumnOperatorValue schemaResponse testConfig,
|
||||
_ectdCurrentComparisonColumn = API.ComparisonColumn API.CurrentTable . formatColumnName testConfig . API.ColumnName
|
||||
}
|
||||
where
|
||||
tableExists :: API.TableName -> Bool
|
||||
@ -497,6 +558,13 @@ columnInsertSchema schemaResponse testConfig tableName columnName =
|
||||
columnName' = formatColumnName testConfig $ API.ColumnName columnName
|
||||
scalarType = findColumnScalarType schemaResponse tableName columnName'
|
||||
|
||||
rowColumnOperatorValue :: API.SchemaResponse -> TestConfig -> API.TableName -> Text -> J.Value -> API.RowColumnOperatorValue
|
||||
rowColumnOperatorValue schemaResponse testConfig tableName columnName value =
|
||||
API.RowColumnOperatorValue columnName' value scalarType
|
||||
where
|
||||
columnName' = formatColumnName testConfig $ API.ColumnName columnName
|
||||
scalarType = findColumnScalarType schemaResponse tableName columnName'
|
||||
|
||||
findColumnScalarType :: API.SchemaResponse -> API.TableName -> API.ColumnName -> API.ScalarType
|
||||
findColumnScalarType API.SchemaResponse {..} tableName columnName =
|
||||
maybe (error $ "Can't find the scalar type of column " <> show columnName <> " in table " <> show tableName) API._ciType columnInfo
|
||||
|
@ -8,11 +8,13 @@ import Hasura.Backends.DataConnector.API
|
||||
import Test.Data (EdgeCasesTestData, TestData)
|
||||
import Test.Sandwich (describe)
|
||||
import Test.Specs.MutationSpec.InsertSpec qualified as InsertSpec
|
||||
import Test.Specs.MutationSpec.UpdateSpec qualified as UpdateSpec
|
||||
import Test.TestHelpers (AgentTestSpec)
|
||||
import Prelude
|
||||
|
||||
spec :: TestData -> Maybe EdgeCasesTestData -> Capabilities -> AgentTestSpec
|
||||
spec testData edgeCasesTestData capabilities@Capabilities {..} = do
|
||||
describe "mutation API" do
|
||||
for_ (_cMutations >>= _mcInsertCapabilities) $ \_insertCapabilities ->
|
||||
for_ (_cMutations >>= _mcInsertCapabilities) $ \_insertCapabilities -> do
|
||||
InsertSpec.spec testData edgeCasesTestData capabilities
|
||||
UpdateSpec.spec testData edgeCasesTestData capabilities
|
||||
|
@ -3,9 +3,6 @@ module Test.Specs.MutationSpec.InsertSpec (spec) where
|
||||
import Control.Arrow ((>>>))
|
||||
import Control.Lens (ix, (&), (.~), (?~), (^?), _Just)
|
||||
import Control.Monad (when)
|
||||
import Control.Monad.Catch (MonadThrow)
|
||||
import Control.Monad.Free (Free)
|
||||
import Control.Monad.IO.Class (MonadIO)
|
||||
import Data.Aeson qualified as J
|
||||
import Data.Foldable (for_)
|
||||
import Data.Functor ((<&>))
|
||||
@ -15,19 +12,15 @@ import Data.List (sortOn)
|
||||
import Data.List.NonEmpty (NonEmpty (..))
|
||||
import Data.Maybe (fromMaybe, isJust, maybeToList)
|
||||
import Data.Scientific (Scientific)
|
||||
import Data.Text qualified as Text
|
||||
import GHC.Stack (HasCallStack)
|
||||
import Hasura.Backends.DataConnector.API
|
||||
import Test.AgentAPI (mutationExpectError, mutationGuarded)
|
||||
import Test.AgentClient (AgentClientT, HasAgentClient)
|
||||
import Test.AgentDatasets (HasDatasetContext, chinookTemplate, testingEdgeCasesTemplate, usesDataset)
|
||||
import Test.AgentTestContext
|
||||
import Test.AgentDatasets (chinookTemplate, usesDataset)
|
||||
import Test.Data (EdgeCasesTestData (..), TestData (..))
|
||||
import Test.Data qualified as Data
|
||||
import Test.Expectations (mutationResponseShouldBe)
|
||||
import Test.Sandwich (ExampleT, HasBaseContext, describe, pendingWith, shouldBe)
|
||||
import Test.Sandwich.Internal (SpecCommand)
|
||||
import Test.Sandwich (describe, shouldBe)
|
||||
import Test.TestHelpers (AgentTestSpec, it)
|
||||
import Test.TestHelpers qualified as Test
|
||||
import Prelude
|
||||
|
||||
spec :: TestData -> Maybe EdgeCasesTestData -> Capabilities -> AgentTestSpec
|
||||
@ -522,21 +515,7 @@ spec TestData {..} edgeCasesTestData Capabilities {..} = describe "Insert Mutati
|
||||
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
where
|
||||
edgeCaseTest ::
|
||||
(HasCallStack, HasAgentClient context, HasAgentTestContext context, HasBaseContext context, MonadThrow m, MonadIO m) =>
|
||||
(EdgeCasesTestData -> TableName) ->
|
||||
String ->
|
||||
(forall testContext. (HasBaseContext testContext, HasAgentClient testContext, HasAgentTestContext testContext, HasDatasetContext testContext) => EdgeCasesTestData -> AgentClientT (ExampleT testContext m) ()) ->
|
||||
Free (SpecCommand context m) ()
|
||||
edgeCaseTest expectedTable name test = do
|
||||
case edgeCasesTestData of
|
||||
Nothing -> it name $ pendingWith (testingEdgeCasesTemplateName <> " dataset template does not exist")
|
||||
Just edgeCasesTestData'@EdgeCasesTestData {..} ->
|
||||
if _ectdTableExists (expectedTable edgeCasesTestData')
|
||||
then usesDataset testingEdgeCasesTemplate $ it name $ test edgeCasesTestData'
|
||||
else it name $ pendingWith (Text.unpack (tableNameToText (expectedTable edgeCasesTestData')) <> " table does not exist within the " <> testingEdgeCasesTemplateName <> " dataset")
|
||||
where
|
||||
testingEdgeCasesTemplateName = Text.unpack (_unDatasetTemplateName testingEdgeCasesTemplate)
|
||||
edgeCaseTest = Test.edgeCaseTest edgeCasesTestData
|
||||
|
||||
mkSubqueryResponse :: [HashMap FieldName FieldValue] -> FieldValue
|
||||
mkSubqueryResponse rows =
|
||||
|
698
server/lib/dc-api/test/Test/Specs/MutationSpec/UpdateSpec.hs
Normal file
698
server/lib/dc-api/test/Test/Specs/MutationSpec/UpdateSpec.hs
Normal file
@ -0,0 +1,698 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Test.Specs.MutationSpec.UpdateSpec (spec) where
|
||||
|
||||
import Control.Lens (ix, (%~), (&), (.~), (?~), (^?))
|
||||
import Control.Monad (when)
|
||||
import Data.Aeson qualified as J
|
||||
import Data.Functor ((<&>))
|
||||
import Data.HashMap.Strict (HashMap)
|
||||
import Data.List (sortOn)
|
||||
import Data.List.NonEmpty (NonEmpty (..))
|
||||
import Data.Maybe (catMaybes, isJust, mapMaybe, maybeToList)
|
||||
import Hasura.Backends.DataConnector.API
|
||||
import Language.GraphQL.Draft.Syntax.QQ qualified as G
|
||||
import Test.AgentAPI (mutationExpectError, mutationGuarded, queryGuarded)
|
||||
import Test.AgentDatasets (chinookTemplate, usesDataset)
|
||||
import Test.Data (EdgeCasesTestData (..), TestData (..))
|
||||
import Test.Data qualified as Data
|
||||
import Test.Expectations (mutationResponseShouldBe, rowsShouldBe)
|
||||
import Test.Sandwich (describe, shouldBe)
|
||||
import Test.TestHelpers (AgentTestSpec, it)
|
||||
import Test.TestHelpers qualified as Test
|
||||
import Prelude
|
||||
|
||||
spec :: TestData -> Maybe EdgeCasesTestData -> Capabilities -> AgentTestSpec
|
||||
spec TestData {..} edgeCasesTestData Capabilities {..} = describe "Update Mutations" $ do
|
||||
usesDataset chinookTemplate $ it "can set the value of a column on all rows" $ do
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "Uniformity")]
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedResult = MutationOperationResults 275 Nothing
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdArtistsRows
|
||||
& fmap (\artist -> artist & Data.field "Name" . Data._ColumnFieldString .~ "Uniformity")
|
||||
|
||||
receivedArtists <- Data.sortResponseRowsBy "ArtistId" <$> queryGuarded (artistsQueryRequest (And []))
|
||||
Data.responseRows receivedArtists `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
usesDataset chinookTemplate $ it "can set the value of a column on a specific row" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) (ScalarValue (J.Number 1) artistIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "AySeeDeeSee")]
|
||||
& umoWhere ?~ whereExp
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedResult = MutationOperationResults 1 Nothing
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdArtistsRowsById ^? ix 1
|
||||
& fmap (\artist -> artist & Data.field "Name" . Data._ColumnFieldString .~ "AySeeDeeSee")
|
||||
& maybeToList
|
||||
|
||||
receivedArtists <- Data.sortResponseRowsBy "ArtistId" <$> queryGuarded (artistsQueryRequest whereExp)
|
||||
Data.responseRows receivedArtists `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
usesDataset chinookTemplate $ it "can set the value of a column on a range of rows" $ do
|
||||
let whereExp =
|
||||
And
|
||||
[ ApplyBinaryComparisonOperator GreaterThan (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) (ScalarValue (J.Number 10) artistIdScalarType),
|
||||
ApplyBinaryComparisonOperator LessThanOrEqual (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) (ScalarValue (J.Number 20) artistIdScalarType)
|
||||
]
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "Nameless")]
|
||||
& umoWhere ?~ whereExp
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedResult = MutationOperationResults 10 Nothing
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdArtistsRows
|
||||
& filter (\artist -> artist ^? Data.field "ArtistId" . Data._ColumnFieldNumber > Just 10 && artist ^? Data.field "ArtistId" . Data._ColumnFieldNumber <= Just 20)
|
||||
& fmap (\artist -> artist & Data.field "Name" . Data._ColumnFieldString .~ "Nameless")
|
||||
|
||||
receivedArtists <- Data.sortResponseRowsBy "ArtistId" <$> queryGuarded (artistsQueryRequest whereExp)
|
||||
Data.responseRows receivedArtists `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
when ((_cComparisons >>= _ccSubqueryComparisonCapabilities <&> _ctccSupportsRelations) == Just True) $ do
|
||||
usesDataset chinookTemplate $ it "can set the value of a column on rows filtered by a related table" $ do
|
||||
let whereExp =
|
||||
Exists (RelatedTable _tdAlbumsRelationshipName) $
|
||||
ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "Title" albumTitleScalarType) (ScalarValue (J.String "Master Of Puppets") albumTitleScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "Metalika")]
|
||||
& umoWhere ?~ whereExp
|
||||
let tableRelationships = [Data.onlyKeepRelationships [_tdAlbumsRelationshipName] _tdArtistsTableRelationships]
|
||||
let mutationRequest =
|
||||
Data.emptyMutationRequest
|
||||
& mrOperations .~ [UpdateOperation updateOperation]
|
||||
& mrTableRelationships .~ tableRelationships
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedResult = MutationOperationResults 1 Nothing
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdArtistsRows
|
||||
& filter
|
||||
( \artist ->
|
||||
let artistId = artist ^? Data.field "ArtistId" . Data._ColumnFieldNumber
|
||||
in _tdAlbumsRows
|
||||
& any
|
||||
( \album ->
|
||||
album ^? Data.field "ArtistId" . Data._ColumnFieldNumber == artistId
|
||||
&& album ^? Data.field "Title" . Data._ColumnFieldString == Just "Master Of Puppets"
|
||||
)
|
||||
)
|
||||
& fmap (\artist -> artist & Data.field "Name" . Data._ColumnFieldString .~ "Metalika")
|
||||
|
||||
receivedArtists <- Data.sortResponseRowsBy "ArtistId" <$> queryGuarded (artistsQueryRequest whereExp & qrTableRelationships .~ tableRelationships)
|
||||
Data.responseRows receivedArtists `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
usesDataset chinookTemplate $ it "can set the value of a column differently using multiple operations" $ do
|
||||
let whereExp1 = ApplyBinaryArrayComparisonOperator In (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) [J.Number 50, J.Number 51] artistIdScalarType
|
||||
let updateOperation1 =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "Renamed 1")]
|
||||
& umoWhere ?~ whereExp1
|
||||
let whereExp2 = ApplyBinaryComparisonOperator LessThan (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) (ScalarValue (J.Number 6) artistIdScalarType)
|
||||
let updateOperation2 =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "Renamed 2")]
|
||||
& umoWhere ?~ whereExp2
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation1, UpdateOperation updateOperation2]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
response
|
||||
`mutationResponseShouldBe` MutationResponse
|
||||
[ MutationOperationResults 2 Nothing,
|
||||
MutationOperationResults 5 Nothing
|
||||
]
|
||||
|
||||
let expectedModifiedRows =
|
||||
[ [_tdArtistsRowsById ^? ix 50, _tdArtistsRowsById ^? ix 51]
|
||||
& catMaybes
|
||||
& fmap (\artist -> artist & Data.field "Name" . Data._ColumnFieldString .~ "Renamed 1"),
|
||||
_tdArtistsRows
|
||||
& filter (\artist -> artist ^? Data.field "ArtistId" . Data._ColumnFieldNumber < Just 6)
|
||||
& fmap (\artist -> artist & Data.field "Name" . Data._ColumnFieldString .~ "Renamed 2")
|
||||
]
|
||||
& concat
|
||||
& sortOn (^? Data.field "ArtistId")
|
||||
|
||||
receivedArtists <- Data.sortResponseRowsBy "ArtistId" <$> queryGuarded (artistsQueryRequest (Or [whereExp1, whereExp2]))
|
||||
Data.responseRows receivedArtists `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
usesDataset chinookTemplate $ it "multiple update operations are run sequentially" $ do
|
||||
-- This test performs two updates whose updated row sets overlap each other, so sequential updates should
|
||||
-- result in the second update applying to the overlapping rows
|
||||
let whereExp1 = ApplyBinaryArrayComparisonOperator In (_tdCurrentComparisonColumn "Name" artistNameScalarType) [J.String "AC/DC", J.String "Audioslave"] artistNameScalarType
|
||||
let updateOperation1 =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "Renamed 1")]
|
||||
& umoWhere ?~ whereExp1
|
||||
let whereExp2 = ApplyBinaryComparisonOperator LessThanOrEqual (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) (ScalarValue (J.Number 5) artistIdScalarType)
|
||||
let updateOperation2 =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "Renamed 2")]
|
||||
& umoWhere ?~ whereExp2
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation1, UpdateOperation updateOperation2]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
response
|
||||
`mutationResponseShouldBe` MutationResponse
|
||||
[ MutationOperationResults 2 Nothing,
|
||||
MutationOperationResults 5 Nothing
|
||||
]
|
||||
|
||||
let expectedWhereExp1 artist = artist ^? Data.field "Name" . Data._ColumnFieldString `elem` [Just "AC/DC", Just "Audioslave"]
|
||||
let expectedWhereExp2 artist = artist ^? Data.field "ArtistId" . Data._ColumnFieldNumber <= Just 5
|
||||
let expectedModifiedRows =
|
||||
[ _tdArtistsRows
|
||||
& filter (\artist -> expectedWhereExp1 artist && not (expectedWhereExp2 artist))
|
||||
& fmap (\artist -> artist & Data.field "Name" . Data._ColumnFieldString .~ "Renamed 1"),
|
||||
_tdArtistsRows
|
||||
& filter expectedWhereExp2
|
||||
& fmap (\artist -> artist & Data.field "Name" . Data._ColumnFieldString .~ "Renamed 2")
|
||||
]
|
||||
& concat
|
||||
& sortOn (^? Data.field "ArtistId")
|
||||
|
||||
-- We can't use whereExp1 since we've renamed the artists!
|
||||
let alternateWhereExp1 = ApplyBinaryArrayComparisonOperator In (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) [J.Number 1, J.Number 8] artistIdScalarType
|
||||
receivedArtists <- Data.sortResponseRowsBy "ArtistId" <$> queryGuarded (artistsQueryRequest (Or [alternateWhereExp1, whereExp2]))
|
||||
Data.responseRows receivedArtists `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
usesDataset chinookTemplate $ it "can increment the value of a column" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 2) invoiceIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates .~ [CustomUpdateColumnOperator incOperator $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "Quantity" (J.Number 3)]
|
||||
& umoWhere ?~ whereExp
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedResult = MutationOperationResults 4 Nothing
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 2)
|
||||
& fmap (\invoiceLine -> invoiceLine & Data.field "Quantity" . Data._ColumnFieldNumber %~ (+ 3))
|
||||
|
||||
receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded (invoiceLinesQueryRequest whereExp)
|
||||
Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
usesDataset chinookTemplate $ it "can make multiple updates in one operation" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 299) invoiceIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates
|
||||
.~ [ CustomUpdateColumnOperator incOperator $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "UnitPrice" (J.Number 3),
|
||||
SetColumn $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "Quantity" (J.Number 2)
|
||||
]
|
||||
& umoWhere ?~ whereExp
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedResult = MutationOperationResults 14 Nothing
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 299)
|
||||
& fmap
|
||||
( \invoiceLine ->
|
||||
invoiceLine
|
||||
& Data.field "Quantity" . Data._ColumnFieldNumber .~ 2
|
||||
& Data.field "UnitPrice" . Data._ColumnFieldNumber %~ (+ 3)
|
||||
)
|
||||
|
||||
receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded (invoiceLinesQueryRequest whereExp)
|
||||
Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
describe "post-update checks" $ do
|
||||
usesDataset chinookTemplate $ it "can update when the post-update check passes" $ do
|
||||
let whereExp =
|
||||
Or
|
||||
[ ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) (ScalarValue (J.Number 1) artistIdScalarType),
|
||||
ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) (ScalarValue (J.Number 2) artistIdScalarType)
|
||||
]
|
||||
let postUpdateExp =
|
||||
ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "Name" artistNameScalarType) (ScalarValue (J.String "Some other name") artistNameScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "Some other name")]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoPostUpdateCheck ?~ postUpdateExp
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedResult = MutationOperationResults 2 Nothing
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
let expectedModifiedRows =
|
||||
[_tdArtistsRowsById ^? ix 1, _tdArtistsRowsById ^? ix 2]
|
||||
& catMaybes
|
||||
& fmap (\artist -> artist & Data.field "Name" . Data._ColumnFieldString .~ "Some other name")
|
||||
|
||||
receivedArtists <- Data.sortResponseRowsBy "ArtistId" <$> queryGuarded (artistsQueryRequest whereExp)
|
||||
Data.responseRows receivedArtists `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
usesDataset chinookTemplate $ it "fails to update when the post-update check fails" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 299) invoiceIdScalarType)
|
||||
let postUpdateExp = ApplyBinaryComparisonOperator LessThanOrEqual (_tdCurrentComparisonColumn "UnitPrice" invoiceLineUnitPriceScalarType) (ScalarValue (J.Number 1.99) invoiceLineUnitPriceScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates .~ [CustomUpdateColumnOperator incOperator $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "UnitPrice" (J.Number 1)]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoPostUpdateCheck ?~ postUpdateExp
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationExpectError mutationRequest
|
||||
|
||||
_crType response `shouldBe` MutationPermissionCheckFailure
|
||||
|
||||
let expectedUnchangedRows =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 299)
|
||||
|
||||
receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded (invoiceLinesQueryRequest whereExp)
|
||||
Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedUnchangedRows
|
||||
|
||||
when (isJust $ _cComparisons >>= _ccSubqueryComparisonCapabilities) $ do
|
||||
usesDataset chinookTemplate $ it "can update when post update check against unrelated table passes" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 299) invoiceIdScalarType)
|
||||
let postUpdateExp =
|
||||
Exists (UnrelatedTable _tdCustomersTableName) $
|
||||
ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "CustomerId" customerIdScalarType) (ScalarValue (J.Number 2) customerIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates .~ [CustomUpdateColumnOperator incOperator $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "UnitPrice" (J.Number 1)]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoPostUpdateCheck ?~ postUpdateExp
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedResult = MutationOperationResults 14 Nothing
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 299)
|
||||
& fmap (\invoiceLine -> invoiceLine & Data.field "UnitPrice" . Data._ColumnFieldNumber %~ (+ 1))
|
||||
|
||||
receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded (invoiceLinesQueryRequest whereExp)
|
||||
Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
usesDataset chinookTemplate $ it "fails to update when post update check against unrelated table fails" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 299) invoiceIdScalarType)
|
||||
let postUpdateExp =
|
||||
Exists (UnrelatedTable _tdCustomersTableName) $
|
||||
ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "CustomerId" customerIdScalarType) (ScalarValue (J.Number 666) customerIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates .~ [CustomUpdateColumnOperator incOperator $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "UnitPrice" (J.Number 1)]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoPostUpdateCheck ?~ postUpdateExp
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationExpectError mutationRequest
|
||||
|
||||
_crType response `shouldBe` MutationPermissionCheckFailure
|
||||
|
||||
let expectedUnchangedRows =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 299)
|
||||
|
||||
receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded (invoiceLinesQueryRequest whereExp)
|
||||
Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedUnchangedRows
|
||||
|
||||
when ((_cComparisons >>= _ccSubqueryComparisonCapabilities <&> _ctccSupportsRelations) == Just True) $ do
|
||||
usesDataset chinookTemplate $ it "can update when post update check against related table passes" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 299) invoiceIdScalarType)
|
||||
let postUpdateExp =
|
||||
Exists (RelatedTable _tdInvoiceRelationshipName) $
|
||||
ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "CustomerId" customerIdScalarType) (ScalarValue (J.Number 17) customerIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "InvoiceId" (J.Number 298)]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoPostUpdateCheck ?~ postUpdateExp
|
||||
let tableRelationships = [Data.onlyKeepRelationships [_tdInvoiceRelationshipName] _tdInvoiceLinesTableRelationships]
|
||||
let mutationRequest =
|
||||
Data.emptyMutationRequest
|
||||
& mrOperations .~ [UpdateOperation updateOperation]
|
||||
& mrTableRelationships .~ tableRelationships
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedResult = MutationOperationResults 14 Nothing
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 299)
|
||||
& fmap (\invoiceLine -> invoiceLine & Data.field "InvoiceId" . Data._ColumnFieldNumber .~ 298)
|
||||
|
||||
-- Need a different where expression for querying the modified rows because the invoice lines have moved
|
||||
-- to a different invoice and therefore we can't re-query them by the original invoice id any more
|
||||
let invoiceLineIds = expectedModifiedRows & mapMaybe (^? Data.field "InvoiceLineId" . Data._ColumnFieldNumber) & fmap J.Number
|
||||
let alternateWhereExp = ApplyBinaryArrayComparisonOperator In (_tdCurrentComparisonColumn "InvoiceLineId" invoiceLineIdScalarType) invoiceLineIds invoiceLineIdScalarType
|
||||
|
||||
receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded (invoiceLinesQueryRequest alternateWhereExp & qrTableRelationships .~ tableRelationships)
|
||||
Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedModifiedRows
|
||||
|
||||
usesDataset chinookTemplate $ it "fails to update when post update check against related table fails" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 299) invoiceIdScalarType)
|
||||
let postUpdateExp =
|
||||
Exists (RelatedTable _tdInvoiceRelationshipName) $
|
||||
ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "CustomerId" customerIdScalarType) (ScalarValue (J.Number 26) customerIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "InvoiceId" (J.Number 298)]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoPostUpdateCheck ?~ postUpdateExp
|
||||
let tableRelationships = [Data.onlyKeepRelationships [_tdInvoiceRelationshipName] _tdInvoiceLinesTableRelationships]
|
||||
let mutationRequest =
|
||||
Data.emptyMutationRequest
|
||||
& mrOperations .~ [UpdateOperation updateOperation]
|
||||
& mrTableRelationships .~ tableRelationships
|
||||
|
||||
response <- mutationExpectError mutationRequest
|
||||
|
||||
_crType response `shouldBe` MutationPermissionCheckFailure
|
||||
|
||||
let expectedUnchangedRows =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 299)
|
||||
|
||||
receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded (invoiceLinesQueryRequest whereExp)
|
||||
Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedUnchangedRows
|
||||
|
||||
describe "returning" $ do
|
||||
usesDataset chinookTemplate $ it "returns updated rows" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 299) invoiceIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates
|
||||
.~ [ SetColumn $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "InvoiceId" (J.Number 298),
|
||||
CustomUpdateColumnOperator incOperator $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "UnitPrice" (J.Number 1)
|
||||
]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoReturningFields .~ invoiceLinesFields
|
||||
let mutationRequest =
|
||||
Data.emptyMutationRequest
|
||||
& mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 299)
|
||||
& fmap
|
||||
( \invoiceLine ->
|
||||
invoiceLine
|
||||
& Data.field "InvoiceId" . Data._ColumnFieldNumber .~ 298
|
||||
& Data.field "UnitPrice" . Data._ColumnFieldNumber %~ (+ 1)
|
||||
)
|
||||
|
||||
let expectedResult = MutationOperationResults 14 (Just expectedModifiedRows)
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
usesDataset chinookTemplate $ it "can return rows from an object relationship" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 299) invoiceIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates
|
||||
.~ [ SetColumn $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "InvoiceId" (J.Number 298),
|
||||
CustomUpdateColumnOperator incOperator $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "UnitPrice" (J.Number 1)
|
||||
]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoReturningFields
|
||||
.~ invoiceLinesFields
|
||||
<> Data.mkFieldsMap
|
||||
[ ( "Track",
|
||||
( RelField
|
||||
( RelationshipField _tdTrackRelationshipName $
|
||||
Data.emptyQuery
|
||||
& qFields
|
||||
?~ Data.mkFieldsMap
|
||||
[ ("TrackId", _tdColumnField _tdTracksTableName "TrackId"),
|
||||
("Name", _tdColumnField _tdTracksTableName "Name")
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
let tableRelationships = [Data.onlyKeepRelationships [_tdTrackRelationshipName] _tdInvoiceLinesTableRelationships]
|
||||
let mutationRequest =
|
||||
Data.emptyMutationRequest
|
||||
& mrOperations .~ [UpdateOperation updateOperation]
|
||||
& mrTableRelationships .~ tableRelationships
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let joinInTrack (invoiceLine :: HashMap FieldName FieldValue) =
|
||||
let track = (invoiceLine ^? Data.field "TrackId" . Data._ColumnFieldNumber) >>= \trackId -> _tdTracksRowsById ^? ix trackId
|
||||
trackTrimmed = Data.filterColumns ["TrackId", "Name"] $ maybeToList track
|
||||
in Data.insertField "Track" (mkSubqueryResponse trackTrimmed) invoiceLine
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 299)
|
||||
& fmap
|
||||
( \invoiceLine ->
|
||||
invoiceLine
|
||||
& Data.field "InvoiceId" . Data._ColumnFieldNumber .~ 298
|
||||
& Data.field "UnitPrice" . Data._ColumnFieldNumber %~ (+ 1)
|
||||
& joinInTrack
|
||||
)
|
||||
|
||||
let expectedResult = MutationOperationResults 14 (Just expectedModifiedRows)
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
usesDataset chinookTemplate $ it "can return rows from an array relationship" $ do
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "ArtistId" artistIdScalarType) (ScalarValue (J.Number 1) artistIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdArtistsTableName
|
||||
& umoUpdates .~ [SetColumn $ _tdRowColumnOperatorValue _tdArtistsTableName "Name" (J.String "AySeeDeeSee")]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoReturningFields
|
||||
.~ artistsFields
|
||||
<> Data.mkFieldsMap
|
||||
[ ( "Albums",
|
||||
( RelField
|
||||
( RelationshipField _tdAlbumsRelationshipName $
|
||||
Data.emptyQuery
|
||||
& qFields
|
||||
?~ Data.mkFieldsMap
|
||||
[ ("AlbumId", _tdColumnField _tdAlbumsTableName "AlbumId"),
|
||||
("ArtistId", _tdColumnField _tdAlbumsTableName "ArtistId"),
|
||||
("Title", _tdColumnField _tdAlbumsTableName "Title")
|
||||
]
|
||||
& qOrderBy ?~ OrderBy mempty (_tdOrderByColumn [] "AlbumId" Ascending :| [])
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
let tableRelationships = [Data.onlyKeepRelationships [_tdAlbumsRelationshipName] _tdArtistsTableRelationships]
|
||||
let mutationRequest =
|
||||
Data.emptyMutationRequest
|
||||
& mrOperations .~ [UpdateOperation updateOperation]
|
||||
& mrTableRelationships .~ tableRelationships
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let joinInAlbums (artist :: HashMap FieldName FieldValue) =
|
||||
let artistId = (artist ^? Data.field "ArtistId" . Data._ColumnFieldNumber)
|
||||
albums = _tdAlbumsRows & filter (\album -> album ^? Data.field "ArtistId" . Data._ColumnFieldNumber == artistId)
|
||||
in Data.insertField "Albums" (mkSubqueryResponse albums) artist
|
||||
|
||||
let expectedModifiedRows =
|
||||
_tdArtistsRowsById ^? ix 1
|
||||
& fmap
|
||||
( \artist ->
|
||||
artist
|
||||
& Data.field "Name" . Data._ColumnFieldString .~ "AySeeDeeSee"
|
||||
& joinInAlbums
|
||||
)
|
||||
& maybeToList
|
||||
|
||||
let expectedResult = MutationOperationResults 1 (Just expectedModifiedRows)
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
usesDataset chinookTemplate $ it "updated rows are returned even when returned again from across a relationship" $ do
|
||||
-- In this scenario we move a set of invoice lines from one invoice to another
|
||||
-- and then return the invoice lines, joined to the invoice and then back to the invoice lines.
|
||||
-- We expect to see both the existing invoice lines and the moved invoice lines on the invoice.
|
||||
let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 299) invoiceIdScalarType)
|
||||
let updateOperation =
|
||||
mkUpdateOperation _tdInvoiceLinesTableName
|
||||
& umoUpdates
|
||||
.~ [ SetColumn $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "InvoiceId" (J.Number 298),
|
||||
CustomUpdateColumnOperator incOperator $ _tdRowColumnOperatorValue _tdInvoiceLinesTableName "UnitPrice" (J.Number 1)
|
||||
]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoReturningFields
|
||||
.~ invoiceLinesFields
|
||||
<> Data.mkFieldsMap
|
||||
[ ( "Invoice",
|
||||
( RelField
|
||||
( RelationshipField _tdInvoiceRelationshipName $
|
||||
Data.emptyQuery
|
||||
& qFields
|
||||
?~ Data.mkFieldsMap
|
||||
[ ("InvoiceId", _tdColumnField _tdInvoicesTableName "InvoiceId"),
|
||||
("Total", _tdColumnField _tdInvoicesTableName "Total"),
|
||||
( "InvoiceLines",
|
||||
( RelField
|
||||
( RelationshipField _tdInvoiceLinesRelationshipName $
|
||||
Data.emptyQuery
|
||||
& qFields ?~ invoiceLinesFields
|
||||
& qOrderBy ?~ OrderBy mempty (_tdOrderByColumn [] "InvoiceLineId" Ascending :| [])
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
let tableRelationships =
|
||||
[ Data.onlyKeepRelationships [_tdInvoiceRelationshipName] _tdInvoiceLinesTableRelationships,
|
||||
Data.onlyKeepRelationships [_tdInvoiceLinesRelationshipName] _tdInvoicesTableRelationships
|
||||
]
|
||||
let mutationRequest =
|
||||
Data.emptyMutationRequest
|
||||
& mrOperations .~ [UpdateOperation updateOperation]
|
||||
& mrTableRelationships .~ tableRelationships
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let modifiedInvoiceLines =
|
||||
_tdInvoiceLinesRows
|
||||
& filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 299)
|
||||
& fmap
|
||||
( \invoiceLine ->
|
||||
invoiceLine
|
||||
& Data.field "InvoiceId" . Data._ColumnFieldNumber .~ 298
|
||||
& Data.field "UnitPrice" . Data._ColumnFieldNumber %~ (+ 1)
|
||||
)
|
||||
|
||||
let joinInInvoiceLines (invoice :: HashMap FieldName FieldValue) =
|
||||
let invoiceId = invoice ^? Data.field "InvoiceId" . Data._ColumnFieldNumber
|
||||
invoiceLines = (_tdInvoiceLinesRows <> modifiedInvoiceLines) & filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == invoiceId)
|
||||
in Data.insertField "InvoiceLines" (mkSubqueryResponse invoiceLines) invoice
|
||||
|
||||
let joinInInvoice (invoiceLine :: HashMap FieldName FieldValue) =
|
||||
let invoice = (invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber) >>= \invoiceId -> _tdInvoicesRowsById ^? ix invoiceId
|
||||
invoiceTrimmed = Data.filterColumns ["InvoiceId", "Total"] $ maybeToList invoice
|
||||
in Data.insertField "Invoice" (mkSubqueryResponse (joinInInvoiceLines <$> invoiceTrimmed)) invoiceLine
|
||||
|
||||
let expectedModifiedRows = joinInInvoice <$> modifiedInvoiceLines
|
||||
|
||||
let expectedResult = MutationOperationResults 14 (Just expectedModifiedRows)
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
|
||||
describe "edge cases" $
|
||||
edgeCaseTest _ectdNoPrimaryKeyTableName "can update rows in a table with no primary key" $ \EdgeCasesTestData {..} -> do
|
||||
let firstNameScalarType = _ectdFindColumnScalarType _ectdNoPrimaryKeyTableName "FirstName"
|
||||
let lastNameScalarType = _ectdFindColumnScalarType _ectdNoPrimaryKeyTableName "LastName"
|
||||
let whereExp =
|
||||
And
|
||||
[ ApplyBinaryComparisonOperator Equal (_ectdCurrentComparisonColumn "FirstName" firstNameScalarType) (ScalarValue (J.String "Will") firstNameScalarType),
|
||||
ApplyBinaryComparisonOperator Equal (_ectdCurrentComparisonColumn "LastName" lastNameScalarType) (ScalarValue (J.String "Riker") lastNameScalarType)
|
||||
]
|
||||
let returning =
|
||||
Data.mkFieldsMap
|
||||
[ ("FirstName", _ectdColumnField _ectdNoPrimaryKeyTableName "FirstName"),
|
||||
("LastName", _ectdColumnField _ectdNoPrimaryKeyTableName "LastName")
|
||||
]
|
||||
let updateOperation =
|
||||
mkUpdateOperation _ectdNoPrimaryKeyTableName
|
||||
& umoUpdates .~ [SetColumn $ _ectdRowColumnOperatorValue _ectdNoPrimaryKeyTableName "FirstName" (J.String "William")]
|
||||
& umoWhere ?~ whereExp
|
||||
& umoReturningFields .~ returning
|
||||
let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [UpdateOperation updateOperation]
|
||||
|
||||
response <- mutationGuarded mutationRequest
|
||||
|
||||
let expectedModifiedRows =
|
||||
[ Data.mkFieldsMap $
|
||||
[ ("FirstName", mkColumnFieldValue $ J.String "William"),
|
||||
("LastName", mkColumnFieldValue $ J.String "Riker")
|
||||
]
|
||||
]
|
||||
|
||||
let expectedResult = MutationOperationResults 1 (Just expectedModifiedRows)
|
||||
response `mutationResponseShouldBe` MutationResponse [expectedResult]
|
||||
where
|
||||
edgeCaseTest = Test.edgeCaseTest edgeCasesTestData
|
||||
|
||||
mkUpdateOperation :: TableName -> UpdateMutationOperation
|
||||
mkUpdateOperation tableName = UpdateMutationOperation tableName Nothing [] Nothing mempty
|
||||
|
||||
mkSubqueryResponse :: [HashMap FieldName FieldValue] -> FieldValue
|
||||
mkSubqueryResponse rows =
|
||||
mkRelationshipFieldValue $ QueryResponse (Just rows) Nothing
|
||||
|
||||
artistsFields :: HashMap FieldName Field
|
||||
artistsFields =
|
||||
Data.mkFieldsMap
|
||||
[ ("ArtistId", _tdColumnField _tdArtistsTableName "ArtistId"),
|
||||
("Name", _tdColumnField _tdArtistsTableName "Name")
|
||||
]
|
||||
|
||||
artistsQueryRequest :: Expression -> QueryRequest
|
||||
artistsQueryRequest whereExp =
|
||||
let query = Data.emptyQuery & qFields ?~ artistsFields & qWhere ?~ whereExp
|
||||
in QueryRequest _tdArtistsTableName [] query
|
||||
|
||||
invoiceLinesFields :: HashMap FieldName Field
|
||||
invoiceLinesFields =
|
||||
Data.mkFieldsMap
|
||||
[ ("InvoiceId", _tdColumnField _tdInvoiceLinesTableName "InvoiceId"),
|
||||
("InvoiceLineId", _tdColumnField _tdInvoiceLinesTableName "InvoiceLineId"),
|
||||
("TrackId", _tdColumnField _tdInvoiceLinesTableName "TrackId"),
|
||||
("UnitPrice", _tdColumnField _tdInvoiceLinesTableName "UnitPrice"),
|
||||
("Quantity", _tdColumnField _tdInvoiceLinesTableName "Quantity")
|
||||
]
|
||||
|
||||
invoiceLinesQueryRequest :: Expression -> QueryRequest
|
||||
invoiceLinesQueryRequest whereExp =
|
||||
let query = Data.emptyQuery & qFields ?~ invoiceLinesFields & qWhere ?~ whereExp
|
||||
in QueryRequest _tdInvoiceLinesTableName [] query
|
||||
|
||||
incOperator :: UpdateColumnOperatorName
|
||||
incOperator = UpdateColumnOperatorName $ [G.name|inc|]
|
||||
|
||||
albumTitleScalarType = _tdFindColumnScalarType _tdAlbumsTableName "Title"
|
||||
artistIdScalarType = _tdFindColumnScalarType _tdArtistsTableName "ArtistId"
|
||||
artistNameScalarType = _tdFindColumnScalarType _tdArtistsTableName "Name"
|
||||
customerIdScalarType = _tdFindColumnScalarType _tdCustomersTableName "CustomerId"
|
||||
invoiceLineIdScalarType = _tdFindColumnScalarType _tdInvoiceLinesTableName "InvoiceLineId"
|
||||
invoiceIdScalarType = _tdFindColumnScalarType _tdInvoiceLinesTableName "InvoiceId"
|
||||
invoiceLineUnitPriceScalarType = _tdFindColumnScalarType _tdInvoiceLinesTableName "UnitPrice"
|
@ -119,7 +119,7 @@ spec TestData {..} API.Capabilities {..} = describe "schema API" $ do
|
||||
-- We compare only the constraints and ignore the constraint names since some agents will have
|
||||
-- different constraint names
|
||||
let extractConstraintsForComparison table =
|
||||
sort . HashMap.elems . API.unForeignKeys $ API._tiForeignKeys table
|
||||
sort . HashMap.elems . API._unForeignKeys $ API._tiForeignKeys table
|
||||
let actualConstraints = extractConstraintsForComparison <$> tables
|
||||
let expectedConstraints = Just $ extractConstraintsForComparison expectedTable
|
||||
|
||||
|
@ -3,15 +3,21 @@ module Test.TestHelpers
|
||||
AgentClientTestSpec,
|
||||
AgentDatasetTestSpec,
|
||||
it,
|
||||
edgeCaseTest,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad.Catch (MonadThrow)
|
||||
import Control.Monad.Free (Free)
|
||||
import Control.Monad.IO.Class (MonadIO)
|
||||
import Data.Text qualified as Text
|
||||
import GHC.Stack (HasCallStack)
|
||||
import Hasura.Backends.DataConnector.API qualified as API
|
||||
import Test.AgentClient (AgentClientT, HasAgentClient, runAgentClientT)
|
||||
import Test.AgentDatasets (HasDatasetContext)
|
||||
import Test.AgentDatasets (HasDatasetContext, testingEdgeCasesTemplate, usesDataset)
|
||||
import Test.AgentTestContext (HasAgentTestContext)
|
||||
import Test.Sandwich (ExampleT, HasBaseContext, SpecFree)
|
||||
import Test.Data (EdgeCasesTestData (..))
|
||||
import Test.Sandwich (ExampleT, HasBaseContext, SpecFree, pendingWith)
|
||||
import Test.Sandwich qualified as Sandwich
|
||||
import Test.Sandwich.Internal (SpecCommand)
|
||||
import Prelude
|
||||
@ -24,3 +30,20 @@ type AgentDatasetTestSpec = forall context. (HasBaseContext context, HasAgentTes
|
||||
|
||||
it :: (HasCallStack, HasAgentClient context, Monad m) => String -> AgentClientT (ExampleT context m) () -> Free (SpecCommand context m) ()
|
||||
it label test = Sandwich.it label $ runAgentClientT Nothing test
|
||||
|
||||
edgeCaseTest ::
|
||||
(HasCallStack, HasAgentClient context, HasAgentTestContext context, HasBaseContext context, MonadThrow m, MonadIO m) =>
|
||||
Maybe EdgeCasesTestData ->
|
||||
(EdgeCasesTestData -> API.TableName) ->
|
||||
String ->
|
||||
(forall testContext. (HasBaseContext testContext, HasAgentClient testContext, HasAgentTestContext testContext, HasDatasetContext testContext) => EdgeCasesTestData -> AgentClientT (ExampleT testContext m) ()) ->
|
||||
Free (SpecCommand context m) ()
|
||||
edgeCaseTest edgeCasesTestData expectedTable name test = do
|
||||
case edgeCasesTestData of
|
||||
Nothing -> it name $ pendingWith (testingEdgeCasesTemplateName <> " dataset template does not exist")
|
||||
Just edgeCasesTestData'@EdgeCasesTestData {..} ->
|
||||
if _ectdTableExists (expectedTable edgeCasesTestData')
|
||||
then usesDataset testingEdgeCasesTemplate $ it name $ test edgeCasesTestData'
|
||||
else it name $ pendingWith (Text.unpack (API.tableNameToText (expectedTable edgeCasesTestData')) <> " table does not exist within the " <> testingEdgeCasesTemplateName <> " dataset")
|
||||
where
|
||||
testingEdgeCasesTemplateName = Text.unpack (API._unDatasetTemplateName testingEdgeCasesTemplate)
|
||||
|
Loading…
Reference in New Issue
Block a user