From 46a339b29dbce44bdb9ed345284ad0918d581315 Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Fri, 12 Mar 2021 14:38:14 -0800 Subject: [PATCH 01/12] Add support to UpdateItemExpectation for additional properties - Adds ConditionExpression support - Adds ExpressionAttributeNames support - Adds ExpressionAttributeValues support - Adds UpdateExpression support --- types.go | 12 ++++++++---- update_item.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/types.go b/types.go index f898888..df580c5 100644 --- a/types.go +++ b/types.go @@ -43,10 +43,14 @@ type ( // UpdateItemExpectation struct hold expectation field, err, and result UpdateItemExpectation struct { - attributeUpdates map[string]*dynamodb.AttributeValueUpdate - key map[string]*dynamodb.AttributeValue - table *string - output *dynamodb.UpdateItemOutput + attributeUpdates map[string]*dynamodb.AttributeValueUpdate + key map[string]*dynamodb.AttributeValue + table *string + output *dynamodb.UpdateItemOutput + conditionExpression *string + expressionAttributeNames map[string]*string + expressionAttributeValues map[string]*dynamodb.AttributeValue + updateExpression *string } // PutItemExpectation struct hold expectation field, err, and result diff --git a/update_item.go b/update_item.go index 3876a29..985bd85 100644 --- a/update_item.go +++ b/update_item.go @@ -21,6 +21,30 @@ func (e *UpdateItemExpectation) WithKeys(keys map[string]*dynamodb.AttributeValu return e } +// WithConditionExpression - method for setting a ConditionExpression expectation +func (e *UpdateItemExpectation) WithConditionExpression(expr *string) *UpdateItemExpectation { + e.conditionExpression = expr + return e +} + +// WithExpressionAttributeNames - method for setting a ExpressionAttributeNames expectation +func (e *UpdateItemExpectation) WithExpressionAttributeNames(names map[string]*string) *UpdateItemExpectation { + e.expressionAttributeNames = names + return e +} + +// WithExpressionAttributeValues - method for setting a ExpressionAttributeValues expectation +func (e *UpdateItemExpectation) WithExpressionAttributeValues(attrs map[string]*dynamodb.AttributeValue) *UpdateItemExpectation { + e.expressionAttributeValues = attrs + return e +} + +// WithUpdateExpression - method for setting a UpdateExpression expectation +func (e *UpdateItemExpectation) WithUpdateExpression(expr *string) *UpdateItemExpectation { + e.updateExpression = expr + return e +} + // Updates - method for set Updates expectation func (e *UpdateItemExpectation) Updates(attrs map[string]*dynamodb.AttributeValueUpdate) *UpdateItemExpectation { e.attributeUpdates = attrs @@ -56,6 +80,30 @@ func (e *MockDynamoDB) UpdateItem(input *dynamodb.UpdateItemInput) (*dynamodb.Up } } + if x.conditionExpression != nil { + if !reflect.DeepEqual(x.conditionExpression, input.ConditionExpression) { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.conditionExpression, input.ConditionExpression) + } + } + + if x.expressionAttributeNames != nil { + if !reflect.DeepEqual(x.expressionAttributeNames, input.ExpressionAttributeNames) { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.expressionAttributeNames, input.ExpressionAttributeNames) + } + } + + if x.expressionAttributeValues != nil { + if !reflect.DeepEqual(x.expressionAttributeValues, input.ExpressionAttributeValues) { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.expressionAttributeValues, input.ExpressionAttributeValues) + } + } + + if x.updateExpression != nil { + if !reflect.DeepEqual(x.updateExpression, input.UpdateExpression) { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.updateExpression, input.UpdateExpression) + } + } + // delete first element of expectation e.dynaMock.UpdateItemExpect = append(e.dynaMock.UpdateItemExpect[:0], e.dynaMock.UpdateItemExpect[1:]...) From 393a4eb97440986004318b47d1177a43cd51cb38 Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Fri, 12 Mar 2021 14:57:43 -0800 Subject: [PATCH 02/12] Move package to fork --- examples/example_test.go | 2 +- go.mod | 2 +- go.sum | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/example_test.go b/examples/example_test.go index 2fa8525..674df90 100644 --- a/examples/example_test.go +++ b/examples/example_test.go @@ -1,7 +1,7 @@ package examples import ( - dynamock "github.com/gusaul/go-dynamock" + dynamock "github.com/caldwecr/go-dynamock" "testing" "github.com/aws/aws-sdk-go/aws" diff --git a/go.mod b/go.mod index 1c928b2..5e15f78 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/gusaul/go-dynamock +module github.com/caldwecr/go-dynamock go 1.15 diff --git a/go.sum b/go.sum index cdd1f24..22e9060 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,28 @@ github.com/aws/aws-sdk-go v1.36.22 h1:kkQdiotYI9RlGoAoMPbQyHKsl9oyT+vz/w2cN6EUZKs= github.com/aws/aws-sdk-go v1.36.22/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 6e7d97ff6d2d8c7dd2bb547104b560fcb38a5a0c Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Fri, 12 Mar 2021 17:47:45 -0800 Subject: [PATCH 03/12] Revert "Move package to fork" This reverts commit 393a4eb97440986004318b47d1177a43cd51cb38. --- examples/example_test.go | 2 +- go.mod | 2 +- go.sum | 7 ------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/examples/example_test.go b/examples/example_test.go index 674df90..2fa8525 100644 --- a/examples/example_test.go +++ b/examples/example_test.go @@ -1,7 +1,7 @@ package examples import ( - dynamock "github.com/caldwecr/go-dynamock" + dynamock "github.com/gusaul/go-dynamock" "testing" "github.com/aws/aws-sdk-go/aws" diff --git a/go.mod b/go.mod index 5e15f78..1c928b2 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/caldwecr/go-dynamock +module github.com/gusaul/go-dynamock go 1.15 diff --git a/go.sum b/go.sum index 22e9060..cdd1f24 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,21 @@ github.com/aws/aws-sdk-go v1.36.22 h1:kkQdiotYI9RlGoAoMPbQyHKsl9oyT+vz/w2cN6EUZKs= github.com/aws/aws-sdk-go v1.36.22/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 8af988697e61746a31b7b914cf07cb338b2c6064 Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Thu, 29 Apr 2021 10:20:21 -0700 Subject: [PATCH 04/12] Update UpdateItemWithContext to have same expectation checks as UpdateItem --- go.sum | 7 +++++++ update_item.go | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/go.sum b/go.sum index cdd1f24..22e9060 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,28 @@ github.com/aws/aws-sdk-go v1.36.22 h1:kkQdiotYI9RlGoAoMPbQyHKsl9oyT+vz/w2cN6EUZKs= github.com/aws/aws-sdk-go v1.36.22/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/update_item.go b/update_item.go index 985bd85..2e819e7 100644 --- a/update_item.go +++ b/update_item.go @@ -136,6 +136,29 @@ func (e *MockDynamoDB) UpdateItemWithContext(ctx aws.Context, input *dynamodb.Up } } + if x.conditionExpression != nil { + if !reflect.DeepEqual(x.conditionExpression, input.ConditionExpression) { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.conditionExpression, input.ConditionExpression) + } + } + + if x.expressionAttributeNames != nil { + if !reflect.DeepEqual(x.expressionAttributeNames, input.ExpressionAttributeNames) { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.expressionAttributeNames, input.ExpressionAttributeNames) + } + } + + if x.expressionAttributeValues != nil { + if !reflect.DeepEqual(x.expressionAttributeValues, input.ExpressionAttributeValues) { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.expressionAttributeValues, input.ExpressionAttributeValues) + } + } + + if x.updateExpression != nil { + if !reflect.DeepEqual(x.updateExpression, input.UpdateExpression) { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.updateExpression, input.UpdateExpression) + } + } // delete first element of expectation e.dynaMock.UpdateItemExpect = append(e.dynaMock.UpdateItemExpect[:0], e.dynaMock.UpdateItemExpect[1:]...) From 9dd469250dacb3e4cbdb90ffc3c231fd48070926 Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Thu, 29 Apr 2021 10:24:49 -0700 Subject: [PATCH 05/12] Fix module path to caldwecr/go-dynamock --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1c928b2..5e15f78 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/gusaul/go-dynamock +module github.com/caldwecr/go-dynamock go 1.15 From 11089eeefdd4704668699d9798e2e6dbea61a0e7 Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Fri, 30 Apr 2021 10:57:10 -0700 Subject: [PATCH 06/12] Add parseUpdateExpression and implement ADD extractor --- types.go | 17 ++++---- update_item.go | 100 +++++++++++++++++++++++++++++++++++++++++- update_item_test.go | 103 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 update_item_test.go diff --git a/types.go b/types.go index df580c5..63fe893 100644 --- a/types.go +++ b/types.go @@ -43,14 +43,15 @@ type ( // UpdateItemExpectation struct hold expectation field, err, and result UpdateItemExpectation struct { - attributeUpdates map[string]*dynamodb.AttributeValueUpdate - key map[string]*dynamodb.AttributeValue - table *string - output *dynamodb.UpdateItemOutput - conditionExpression *string - expressionAttributeNames map[string]*string - expressionAttributeValues map[string]*dynamodb.AttributeValue - updateExpression *string + attributeUpdates map[string]*dynamodb.AttributeValueUpdate + key map[string]*dynamodb.AttributeValue + table *string + output *dynamodb.UpdateItemOutput + conditionExpression *string + expressionAttributeNames map[string]*string + expressionAttributeValues map[string]*dynamodb.AttributeValue + updateExpression *string + setAttributeValueExpression *string } // PutItemExpectation struct hold expectation field, err, and result diff --git a/update_item.go b/update_item.go index 2e819e7..bc1e811 100644 --- a/update_item.go +++ b/update_item.go @@ -2,11 +2,13 @@ package dynamock import ( "fmt" - "reflect" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/dynamodb" + "reflect" + "regexp" + "sort" + "strings" ) // ToTable - method for set Table expectation @@ -45,6 +47,11 @@ func (e *UpdateItemExpectation) WithUpdateExpression(expr *string) *UpdateItemEx return e } +func (e *UpdateItemExpectation) WithSetAttributeValueExpression(expr *string) *UpdateItemExpectation { + e.setAttributeValueExpression = expr + return e +} + // Updates - method for set Updates expectation func (e *UpdateItemExpectation) Updates(attrs map[string]*dynamodb.AttributeValueUpdate) *UpdateItemExpectation { e.attributeUpdates = attrs @@ -104,6 +111,10 @@ func (e *MockDynamoDB) UpdateItem(input *dynamodb.UpdateItemInput) (*dynamodb.Up } } + if x.setAttributeValueExpression != nil { + + } + // delete first element of expectation e.dynaMock.UpdateItemExpect = append(e.dynaMock.UpdateItemExpect[:0], e.dynaMock.UpdateItemExpect[1:]...) @@ -113,6 +124,91 @@ func (e *MockDynamoDB) UpdateItem(input *dynamodb.UpdateItemInput) (*dynamodb.Up return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Update Item Expectation Not Found") } +type parsedUpdateExpression struct { + ADDExpressions []addExpression + DELETEExpressions []string + REMOVEExpressions []string + SETExpressions []string +} + +type operation string + +const ( + ADD operation = "ADD" + DELETE operation = "DELETE" + REMOVE operation = "REMOVE" + SET operation = "SET" +) + +type operationIndexTuple struct { + Index int + Operation operation +} + +type addExpression struct { + path string + value string +} + +func extractAddPathValuePairs(addExpr string) []addExpression { + re := regexp.MustCompile(`ADD\s+((\S+\s+[\w:]+\s*,?\s*)+)`) + subMatchRe := regexp.MustCompile(`(\S+)\s+([\w:]+)\s*,?\s*`) + subMatches := re.FindStringSubmatch(addExpr) + var result []addExpression + if subMatches == nil { + return result + } + + pairMatches := subMatchRe.FindAllStringSubmatch(subMatches[1], -1) + if pairMatches == nil { + return result + } + for _, subMatch := range pairMatches { + result = append(result, addExpression{subMatch[1], subMatch[2]}) + } + return result +} + +func parseUpdateExpression(updateExpression string) parsedUpdateExpression { + addOp := operationIndexTuple{strings.Index(updateExpression, "ADD"), ADD} + deleteOp := operationIndexTuple{strings.Index(updateExpression, "DELETE"), DELETE} + removeOp := operationIndexTuple{strings.Index(updateExpression, "REMOVE"), REMOVE} + setOp := operationIndexTuple{strings.Index(updateExpression, "SET"), SET} + + ops := []operationIndexTuple{ + addOp, + deleteOp, + removeOp, + setOp, + } + sort.Slice(ops, func(i, j int) bool { + return ops[i].Index < ops[j].Index + }) + + result := parsedUpdateExpression{} + for opIdx, op := range ops { + if op.Index < 0 { + // op.Index should be -1 for operations that are not present in an update expression + continue + } + // get the substring for the operation + var substr string + if opIdx+1 < len(ops) { + // We don't need to worry about the case where (opIdx+1).Index is -1, because we're iterating through a + // ascending sorted array. + substr = updateExpression[op.Index:ops[opIdx+1].Index] + } else { + substr = updateExpression[op.Index:] + } + // apply the operation specific parsing + switch op.Operation { + case ADD: + result.ADDExpressions = extractAddPathValuePairs(substr) + } + } + return result +} + // UpdateItemWithContext - this func will be invoked when test running matching expectation with actual input func (e *MockDynamoDB) UpdateItemWithContext(ctx aws.Context, input *dynamodb.UpdateItemInput, opt ...request.Option) (*dynamodb.UpdateItemOutput, error) { if len(e.dynaMock.UpdateItemExpect) > 0 { diff --git a/update_item_test.go b/update_item_test.go new file mode 100644 index 0000000..0e3e654 --- /dev/null +++ b/update_item_test.go @@ -0,0 +1,103 @@ +package dynamock + +import ( + "reflect" + "testing" +) + +func Test_extractAddPathValuePairs(t *testing.T) { + type args struct { + addExpr string + } + tests := []struct { + name string + args args + want []addExpression + }{ + { + "no ADD in expression produces empty result", + args{"SET foo = 3"}, + []addExpression{}, + }, + { + "ADD with single pair captures correct pair", + args{"ADD foobar 3"}, + []addExpression{{"foobar", "3"}}, + }, + { + "ADD with single pair and trailing comma captures correct pair", + args{"ADD foobar 3 ,"}, + []addExpression{{"foobar", "3"}}, + }, + { + "ADD with single pair and trailing whitespace captures correct pair", + args{"ADD foobar 3 "}, + []addExpression{{"foobar", "3"}}, + }, + { + "ADD with multiple pairs captures the pairs", + args{"ADD foobar 3, bazdog 7, chicken 8"}, + []addExpression{ + {"foobar", "3"}, + {"bazdog", "7"}, + {"chicken", "8"}, + }, + }, + { + "ADD with multiple pairs and curious whitespace", + args{"ADD foobar 3 , bazdog 7 ,chicken 8"}, + []addExpression{ + {"foobar", "3"}, + {"bazdog", "7"}, + {"chicken", "8"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := extractAddPathValuePairs(tt.args.addExpr) + if len(got) != len(tt.want) { + t.Errorf("extractAddPathValuePairs() len = %v, wanted len %v", len(got), len(tt.want)) + return + } + for idx, pair := range got { + if !reflect.DeepEqual(pair, tt.want[idx]) { + t.Errorf("extractAddPathValuePairs() %vth item got: %v, want: %v", idx, pair, tt.want[idx]) + return + } + } + }) + } +} + +func Test_parseUpdateExpression(t *testing.T) { + type args struct { + updateExpression string + } + tests := []struct { + name string + args args + want parsedUpdateExpression + }{ + { + "ADD expression only has only Add expressions", + args{"ADD foobar 3, dog 76"}, + parsedUpdateExpression{ + ADDExpressions: []addExpression{ + {"foobar", "3"}, + {"dog", "76"}, + }, + DELETEExpressions: nil, + REMOVEExpressions: nil, + SETExpressions: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseUpdateExpression(tt.args.updateExpression); !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseUpdateExpression() = %v, want %v", got, tt.want) + } + }) + } +} From 06b7a18161b49f62c528c135bb2b564cd7fcce1c Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Fri, 30 Apr 2021 11:20:59 -0700 Subject: [PATCH 07/12] Handle DELETE expressions --- update_item.go | 37 ++++++++++++++++++----- update_item_test.go | 71 +++++++++++++++++++++++++++++++++------------ 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/update_item.go b/update_item.go index bc1e811..1b8447f 100644 --- a/update_item.go +++ b/update_item.go @@ -125,8 +125,8 @@ func (e *MockDynamoDB) UpdateItem(input *dynamodb.UpdateItemInput) (*dynamodb.Up } type parsedUpdateExpression struct { - ADDExpressions []addExpression - DELETEExpressions []string + ADDExpressions []pathValueExpression + DELETEExpressions []pathValueExpression REMOVEExpressions []string SETExpressions []string } @@ -145,16 +145,27 @@ type operationIndexTuple struct { Operation operation } -type addExpression struct { +type pathValueExpression struct { path string value string } -func extractAddPathValuePairs(addExpr string) []addExpression { - re := regexp.MustCompile(`ADD\s+((\S+\s+[\w:]+\s*,?\s*)+)`) +func mustExtractPathValueExpressions(operation operation, expr string) []pathValueExpression { + var re *regexp.Regexp + var result []pathValueExpression + switch operation { + case ADD: + re = regexp.MustCompile(`ADD\s+((\S+\s+[\w:]+\s*,?\s*)+)`) + case DELETE: + re = regexp.MustCompile(`DELETE\s+((\S+\s+[\w:]+\s*,?\s*)+)`) + } + if re == nil { + return result + } + subMatchRe := regexp.MustCompile(`(\S+)\s+([\w:]+)\s*,?\s*`) - subMatches := re.FindStringSubmatch(addExpr) - var result []addExpression + subMatches := re.FindStringSubmatch(expr) + if subMatches == nil { return result } @@ -164,11 +175,19 @@ func extractAddPathValuePairs(addExpr string) []addExpression { return result } for _, subMatch := range pairMatches { - result = append(result, addExpression{subMatch[1], subMatch[2]}) + result = append(result, pathValueExpression{subMatch[1], subMatch[2]}) } return result } +func extractAddPathValuePairs(addExpr string) []pathValueExpression { + return mustExtractPathValueExpressions(ADD, addExpr) +} + +func extractDeletePathValuePairs(deleteExpr string) []pathValueExpression { + return mustExtractPathValueExpressions(DELETE, deleteExpr) +} + func parseUpdateExpression(updateExpression string) parsedUpdateExpression { addOp := operationIndexTuple{strings.Index(updateExpression, "ADD"), ADD} deleteOp := operationIndexTuple{strings.Index(updateExpression, "DELETE"), DELETE} @@ -204,6 +223,8 @@ func parseUpdateExpression(updateExpression string) parsedUpdateExpression { switch op.Operation { case ADD: result.ADDExpressions = extractAddPathValuePairs(substr) + case DELETE: + result.DELETEExpressions = extractDeletePathValuePairs(substr) } } return result diff --git a/update_item_test.go b/update_item_test.go index 0e3e654..0f08b7b 100644 --- a/update_item_test.go +++ b/update_item_test.go @@ -5,39 +5,40 @@ import ( "testing" ) -func Test_extractAddPathValuePairs(t *testing.T) { +func Test_mustExtractPathValueExpressions(t *testing.T) { type args struct { - addExpr string + operation operation + addExpr string } tests := []struct { name string args args - want []addExpression + want []pathValueExpression }{ { - "no ADD in expression produces empty result", - args{"SET foo = 3"}, - []addExpression{}, + "no ADD or DELETE in expression produces empty result", + args{SET, "SET foo = 3"}, + []pathValueExpression{}, }, { "ADD with single pair captures correct pair", - args{"ADD foobar 3"}, - []addExpression{{"foobar", "3"}}, + args{ADD, "ADD foobar 3"}, + []pathValueExpression{{"foobar", "3"}}, }, { "ADD with single pair and trailing comma captures correct pair", - args{"ADD foobar 3 ,"}, - []addExpression{{"foobar", "3"}}, + args{ADD, "ADD foobar 3 ,"}, + []pathValueExpression{{"foobar", "3"}}, }, { "ADD with single pair and trailing whitespace captures correct pair", - args{"ADD foobar 3 "}, - []addExpression{{"foobar", "3"}}, + args{ADD, "ADD foobar 3 "}, + []pathValueExpression{{"foobar", "3"}}, }, { "ADD with multiple pairs captures the pairs", - args{"ADD foobar 3, bazdog 7, chicken 8"}, - []addExpression{ + args{ADD, "ADD foobar 3, bazdog 7, chicken 8"}, + []pathValueExpression{ {"foobar", "3"}, {"bazdog", "7"}, {"chicken", "8"}, @@ -45,17 +46,22 @@ func Test_extractAddPathValuePairs(t *testing.T) { }, { "ADD with multiple pairs and curious whitespace", - args{"ADD foobar 3 , bazdog 7 ,chicken 8"}, - []addExpression{ + args{ADD, "ADD foobar 3 , bazdog 7 ,chicken 8"}, + []pathValueExpression{ {"foobar", "3"}, {"bazdog", "7"}, {"chicken", "8"}, }, }, + { + "DELETE with single pair and trailing comma captures correct pair", + args{DELETE, "DELETE foobar 3 ,"}, + []pathValueExpression{{"foobar", "3"}}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := extractAddPathValuePairs(tt.args.addExpr) + got := mustExtractPathValueExpressions(tt.args.operation, tt.args.addExpr) if len(got) != len(tt.want) { t.Errorf("extractAddPathValuePairs() len = %v, wanted len %v", len(got), len(tt.want)) return @@ -83,7 +89,7 @@ func Test_parseUpdateExpression(t *testing.T) { "ADD expression only has only Add expressions", args{"ADD foobar 3, dog 76"}, parsedUpdateExpression{ - ADDExpressions: []addExpression{ + ADDExpressions: []pathValueExpression{ {"foobar", "3"}, {"dog", "76"}, }, @@ -92,6 +98,35 @@ func Test_parseUpdateExpression(t *testing.T) { SETExpressions: nil, }, }, + { + "DELETE expression only has only Delete expressions", + args{"DELETE foobar 5, cat 6"}, + parsedUpdateExpression{ + ADDExpressions: nil, + DELETEExpressions: []pathValueExpression{ + {"foobar", "5"}, + {"cat", "6"}, + }, + REMOVEExpressions: nil, + SETExpressions: nil, + }, + }, + { + "ADD and DELETE expressions are extracted correctly", + args{"ADD abc 1, def 2 DELETE ghi 3, jkl 4"}, + parsedUpdateExpression{ + ADDExpressions: []pathValueExpression{ + {"abc", "1"}, + {"def", "2"}, + }, + DELETEExpressions: []pathValueExpression{ + {"ghi", "3"}, + {"jkl", "4"}, + }, + REMOVEExpressions: nil, + SETExpressions: nil, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From dcc2a779edf33d330787a6b40ae349baea727820 Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Fri, 30 Apr 2021 12:30:20 -0700 Subject: [PATCH 08/12] Implement SET expressions --- update_item.go | 24 +++++++++++++++++---- update_item_test.go | 52 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/update_item.go b/update_item.go index 1b8447f..972712d 100644 --- a/update_item.go +++ b/update_item.go @@ -128,7 +128,7 @@ type parsedUpdateExpression struct { ADDExpressions []pathValueExpression DELETEExpressions []pathValueExpression REMOVEExpressions []string - SETExpressions []string + SETExpressions []pathValueExpression } type operation string @@ -152,18 +152,28 @@ type pathValueExpression struct { func mustExtractPathValueExpressions(operation operation, expr string) []pathValueExpression { var re *regexp.Regexp + var subMatchRe *regexp.Regexp var result []pathValueExpression switch operation { case ADD: - re = regexp.MustCompile(`ADD\s+((\S+\s+[\w:]+\s*,?\s*)+)`) + re = regexp.MustCompile(`ADD\s+((\S+\s+[\w:#]+\s*,?\s*)+)`) + subMatchRe = regexp.MustCompile(`(\S+)\s+([\w:#]+)\s*,?\s*`) case DELETE: - re = regexp.MustCompile(`DELETE\s+((\S+\s+[\w:]+\s*,?\s*)+)`) + re = regexp.MustCompile(`DELETE\s+((\S+\s+[\w:#]+\s*,?\s*)+)`) + subMatchRe = regexp.MustCompile(`(\S+)\s+([\w:#]+)\s*,?\s*`) + case SET: + // SET operations allow the value to two operands with a + or - in between them + // SET operations allow the operand to be a function such as `SET #ri = list_append(#ri, :vals)` + re = regexp.MustCompile(`SET\s+((\S+\s*=\s*[\w:#\(\)\+-,\s=]+\s*,?\s*)+)`) + subMatchRe = regexp.MustCompile(`(\S+)\s*=\s*([\w:#\+-]+(\([\w\s,:#]*\))?)\s*,?\s*`) } if re == nil { return result } + if subMatchRe == nil { + return result + } - subMatchRe := regexp.MustCompile(`(\S+)\s+([\w:]+)\s*,?\s*`) subMatches := re.FindStringSubmatch(expr) if subMatches == nil { @@ -188,6 +198,10 @@ func extractDeletePathValuePairs(deleteExpr string) []pathValueExpression { return mustExtractPathValueExpressions(DELETE, deleteExpr) } +func extractSetPathValuePairs(setExpr string) []pathValueExpression { + return mustExtractPathValueExpressions(SET, setExpr) +} + func parseUpdateExpression(updateExpression string) parsedUpdateExpression { addOp := operationIndexTuple{strings.Index(updateExpression, "ADD"), ADD} deleteOp := operationIndexTuple{strings.Index(updateExpression, "DELETE"), DELETE} @@ -225,6 +239,8 @@ func parseUpdateExpression(updateExpression string) parsedUpdateExpression { result.ADDExpressions = extractAddPathValuePairs(substr) case DELETE: result.DELETEExpressions = extractDeletePathValuePairs(substr) + case SET: + result.SETExpressions = extractSetPathValuePairs(substr) } } return result diff --git a/update_item_test.go b/update_item_test.go index 0f08b7b..c25024a 100644 --- a/update_item_test.go +++ b/update_item_test.go @@ -8,7 +8,7 @@ import ( func Test_mustExtractPathValueExpressions(t *testing.T) { type args struct { operation operation - addExpr string + expr string } tests := []struct { name string @@ -16,8 +16,8 @@ func Test_mustExtractPathValueExpressions(t *testing.T) { want []pathValueExpression }{ { - "no ADD or DELETE in expression produces empty result", - args{SET, "SET foo = 3"}, + "no matching expression produces empty result", + args{ADD, "SET foo = 3"}, []pathValueExpression{}, }, { @@ -58,17 +58,44 @@ func Test_mustExtractPathValueExpressions(t *testing.T) { args{DELETE, "DELETE foobar 3 ,"}, []pathValueExpression{{"foobar", "3"}}, }, + { + "SET with single pair captures correct pair", + args{SET, "SET foobar = 3"}, + []pathValueExpression{{"foobar", "3"}}, + }, + { + "SET with multiple pairs captures the pairs", + args{SET, "SET foobar= 3, bazdog =7, chicken=8 "}, + []pathValueExpression{ + {"foobar", "3"}, + {"bazdog", "7"}, + {"chicken", "8"}, + }, + }, + { + "SET with single pair including function captures correct pair", + args{SET, "SET foobar = list_append(:vals, #ri)"}, + []pathValueExpression{{"foobar", "list_append(:vals, #ri)"}}, + }, + { + "SET with multiple pairs including function captures correct pair", + args{SET, "SET dog = :food, foobar = list_append(:vals, #ri)"}, + []pathValueExpression{ + {"dog", ":food"}, + {"foobar", "list_append(:vals, #ri)"}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := mustExtractPathValueExpressions(tt.args.operation, tt.args.addExpr) + got := mustExtractPathValueExpressions(tt.args.operation, tt.args.expr) if len(got) != len(tt.want) { - t.Errorf("extractAddPathValuePairs() len = %v, wanted len %v", len(got), len(tt.want)) + t.Errorf("mustExtractPathValueExpressions() len = %v, wanted len %v", len(got), len(tt.want)) return } for idx, pair := range got { if !reflect.DeepEqual(pair, tt.want[idx]) { - t.Errorf("extractAddPathValuePairs() %vth item got: %v, want: %v", idx, pair, tt.want[idx]) + t.Errorf("mustExtractPathValueExpressions() %vth item got: %v, want: %v", idx, pair, tt.want[idx]) return } } @@ -127,6 +154,19 @@ func Test_parseUpdateExpression(t *testing.T) { SETExpressions: nil, }, }, + { + "SET expression only has only Set expressions", + args{"SET foobar = 5, cat = list_append(:vals, #ri)"}, + parsedUpdateExpression{ + ADDExpressions: nil, + DELETEExpressions: nil, + REMOVEExpressions: nil, + SETExpressions: []pathValueExpression{ + {"foobar", "5"}, + {"cat", "list_append(:vals, #ri)"}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From c73befbd166b28406a28852dc0352e661b6842fa Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Fri, 30 Apr 2021 13:12:31 -0700 Subject: [PATCH 09/12] Implement REMOVE --- update_item.go | 26 +++++++++++++++++++++++++- update_item_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/update_item.go b/update_item.go index 972712d..e90167b 100644 --- a/update_item.go +++ b/update_item.go @@ -127,7 +127,7 @@ func (e *MockDynamoDB) UpdateItem(input *dynamodb.UpdateItemInput) (*dynamodb.Up type parsedUpdateExpression struct { ADDExpressions []pathValueExpression DELETEExpressions []pathValueExpression - REMOVEExpressions []string + REMOVEExpressions []pathExpression SETExpressions []pathValueExpression } @@ -150,6 +150,10 @@ type pathValueExpression struct { value string } +type pathExpression struct { + path string +} + func mustExtractPathValueExpressions(operation operation, expr string) []pathValueExpression { var re *regexp.Regexp var subMatchRe *regexp.Regexp @@ -198,6 +202,24 @@ func extractDeletePathValuePairs(deleteExpr string) []pathValueExpression { return mustExtractPathValueExpressions(DELETE, deleteExpr) } +func extractRemovePath(removeExpr string) []pathExpression { + re := regexp.MustCompile(`REMOVE\s+(([\w:#\[\]]+\s*,?\s*)+)`) + subMatchRe := regexp.MustCompile(`\s*([\w:#\[\]]+)\s*,?\s*`) + subMatches := re.FindStringSubmatch(removeExpr) + var result []pathExpression + if subMatches == nil { + return result + } + pairMatches := subMatchRe.FindAllStringSubmatch(subMatches[1], -1) + if pairMatches == nil { + return result + } + for _, subMatch := range pairMatches { + result = append(result, pathExpression{subMatch[1]}) + } + return result +} + func extractSetPathValuePairs(setExpr string) []pathValueExpression { return mustExtractPathValueExpressions(SET, setExpr) } @@ -239,6 +261,8 @@ func parseUpdateExpression(updateExpression string) parsedUpdateExpression { result.ADDExpressions = extractAddPathValuePairs(substr) case DELETE: result.DELETEExpressions = extractDeletePathValuePairs(substr) + case REMOVE: + result.REMOVEExpressions = extractRemovePath(substr) case SET: result.SETExpressions = extractSetPathValuePairs(substr) } diff --git a/update_item_test.go b/update_item_test.go index c25024a..ad08fba 100644 --- a/update_item_test.go +++ b/update_item_test.go @@ -167,6 +167,19 @@ func Test_parseUpdateExpression(t *testing.T) { }, }, }, + { + "REMOVE expression only has only Remove expressions", + args{"REMOVE RelatedItems[1], RelatedItems[2]"}, + parsedUpdateExpression{ + ADDExpressions: nil, + DELETEExpressions: nil, + REMOVEExpressions: []pathExpression{ + {"RelatedItems[1]"}, + {"RelatedItems[2]"}, + }, + SETExpressions: nil, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -176,3 +189,35 @@ func Test_parseUpdateExpression(t *testing.T) { }) } } + +func Test_extractRemovePath(t *testing.T) { + type args struct { + removeExpr string + } + tests := []struct { + name string + args args + want []pathExpression + }{ + { + "extracting a single Remove path works correctly", + args{"REMOVE RelatedItems[1]"}, + []pathExpression{{"RelatedItems[1]"}}, + }, + { + "extracting multiple Remove paths works correctly", + args{"REMOVE RelatedItems[1], RelatedItems[2]"}, + []pathExpression{ + {"RelatedItems[1]"}, + {"RelatedItems[2]"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := extractRemovePath(tt.args.removeExpr); !reflect.DeepEqual(got, tt.want) { + t.Errorf("extractRemovePath() = %v, want %v", got, tt.want) + } + }) + } +} From 95684b5ae4f9b995b731f32c3e7e3b0b50830035 Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Fri, 30 Apr 2021 13:34:34 -0700 Subject: [PATCH 10/12] Add support for equivalentUdpateExpressions --- types.go | 18 +++++++-------- update_item.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/types.go b/types.go index 63fe893..8847e84 100644 --- a/types.go +++ b/types.go @@ -43,15 +43,15 @@ type ( // UpdateItemExpectation struct hold expectation field, err, and result UpdateItemExpectation struct { - attributeUpdates map[string]*dynamodb.AttributeValueUpdate - key map[string]*dynamodb.AttributeValue - table *string - output *dynamodb.UpdateItemOutput - conditionExpression *string - expressionAttributeNames map[string]*string - expressionAttributeValues map[string]*dynamodb.AttributeValue - updateExpression *string - setAttributeValueExpression *string + attributeUpdates map[string]*dynamodb.AttributeValueUpdate + key map[string]*dynamodb.AttributeValue + table *string + output *dynamodb.UpdateItemOutput + conditionExpression *string + expressionAttributeNames map[string]*string + expressionAttributeValues map[string]*dynamodb.AttributeValue + updateExpression *string + equivalentUpdateExpression *parsedUpdateExpression } // PutItemExpectation struct hold expectation field, err, and result diff --git a/update_item.go b/update_item.go index e90167b..21b20d0 100644 --- a/update_item.go +++ b/update_item.go @@ -47,8 +47,9 @@ func (e *UpdateItemExpectation) WithUpdateExpression(expr *string) *UpdateItemEx return e } -func (e *UpdateItemExpectation) WithSetAttributeValueExpression(expr *string) *UpdateItemExpectation { - e.setAttributeValueExpression = expr +func (e *UpdateItemExpectation) WithEquivalentUpdateExpression(expr *string) *UpdateItemExpectation { + parsed := parseUpdateExpression(*expr) + e.equivalentUpdateExpression = &parsed return e } @@ -111,8 +112,12 @@ func (e *MockDynamoDB) UpdateItem(input *dynamodb.UpdateItemInput) (*dynamodb.Up } } - if x.setAttributeValueExpression != nil { - + if x.equivalentUpdateExpression != nil { + inputExpr := parseUpdateExpression(*input.UpdateExpression) + err := x.equivalentUpdateExpression.CheckIsEquivalentTo(&inputExpr) + if err != nil { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("non-equivalent update expressions found: %v", err) + } } // delete first element of expectation @@ -131,6 +136,46 @@ type parsedUpdateExpression struct { SETExpressions []pathValueExpression } +func (p *parsedUpdateExpression) CheckIsEquivalentTo(other *parsedUpdateExpression) error { + sort.Slice(p.ADDExpressions, func(i, j int) bool { + return p.ADDExpressions[i].path < p.ADDExpressions[j].path + }) + sort.Slice(other.ADDExpressions, func(i, j int) bool { + return other.ADDExpressions[i].path < other.ADDExpressions[j].path + }) + if !reflect.DeepEqual(p.ADDExpressions, other.ADDExpressions) { + return fmt.Errorf("ADDExpressions do not match, %v != %v", p.ADDExpressions, other.ADDExpressions) + } + sort.Slice(p.DELETEExpressions, func(i, j int) bool { + return p.DELETEExpressions[i].path < p.DELETEExpressions[j].path + }) + sort.Slice(other.DELETEExpressions, func(i, j int) bool { + return other.DELETEExpressions[i].path < other.DELETEExpressions[j].path + }) + if !reflect.DeepEqual(p.DELETEExpressions, other.DELETEExpressions) { + return fmt.Errorf("DELETEExpressions do not match, %v != %v", p.DELETEExpressions, other.DELETEExpressions) + } + sort.Slice(p.REMOVEExpressions, func(i, j int) bool { + return p.REMOVEExpressions[i].path < p.REMOVEExpressions[j].path + }) + sort.Slice(other.REMOVEExpressions, func(i, j int) bool { + return other.REMOVEExpressions[i].path < other.REMOVEExpressions[j].path + }) + if !reflect.DeepEqual(p.REMOVEExpressions, other.REMOVEExpressions) { + return fmt.Errorf("REMOVEExpressions do not match, %v != %v", p.REMOVEExpressions, other.REMOVEExpressions) + } + sort.Slice(p.SETExpressions, func(i, j int) bool { + return p.SETExpressions[i].path < p.SETExpressions[j].path + }) + sort.Slice(other.SETExpressions, func(i, j int) bool { + return other.SETExpressions[i].path < other.SETExpressions[j].path + }) + if !reflect.DeepEqual(p.SETExpressions, other.SETExpressions) { + return fmt.Errorf("SETExpressions do not match, %v != %v", p.SETExpressions, other.SETExpressions) + } + return nil +} + type operation string const ( @@ -316,6 +361,13 @@ func (e *MockDynamoDB) UpdateItemWithContext(ctx aws.Context, input *dynamodb.Up return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.updateExpression, input.UpdateExpression) } } + if x.equivalentUpdateExpression != nil { + inputExpr := parseUpdateExpression(*input.UpdateExpression) + err := x.equivalentUpdateExpression.CheckIsEquivalentTo(&inputExpr) + if err != nil { + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("non-equivalent update expressions found: %v", err) + } + } // delete first element of expectation e.dynaMock.UpdateItemExpect = append(e.dynaMock.UpdateItemExpect[:0], e.dynaMock.UpdateItemExpect[1:]...) From d7ffbd5118394838ae0400b6911121b641bdb5e5 Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Fri, 30 Apr 2021 13:45:33 -0700 Subject: [PATCH 11/12] Add test of CheckIsEquivalentTo --- update_item_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/update_item_test.go b/update_item_test.go index ad08fba..1eca176 100644 --- a/update_item_test.go +++ b/update_item_test.go @@ -221,3 +221,49 @@ func Test_extractRemovePath(t *testing.T) { }) } } + +func Test_parsedUpdateExpression_CheckIsEquivalentTo(t *testing.T) { + type args struct { + p string + other string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + "equivalent expressions do not return an error", + args{ + "ADD foobar 5, dog 7 ", + "ADD dog 7, foobar 5", + }, + false, + }, + { + "non-equivalent expressions return an error", + args{ + "ADD foobar 5, dog 7 ", + "ADD cat 7, foobar 5", + }, + true, + }, + { + "non-equivalent expressions with equivalent paths return an error", + args{ + "ADD foobar 5, dog 7 ", + "ADD dog 7, foobar 99", + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pExpr := parseUpdateExpression(tt.args.p) + otherExpr := parseUpdateExpression(tt.args.other) + if err := pExpr.CheckIsEquivalentTo(&otherExpr); (err != nil) != tt.wantErr { + t.Errorf("CheckIsEquivalentTo() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From 90a23c8c6e3d9d42dbdd7b1c7f7437a9c1efa182 Mon Sep 17 00:00:00 2001 From: Courtland Caldwell Date: Thu, 13 May 2021 16:02:20 -0700 Subject: [PATCH 12/12] Update Update expectations checker to give specific expectation type in error - When one of the various expectation checks fails for an update clause, the message that is shown to the user should be unique and differentiated from others. Doing this makes it easier to debug which part of a mock failed its expectation. --- examples/example_test.go | 2 +- update_item.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/example_test.go b/examples/example_test.go index 2fa8525..e8e0179 100644 --- a/examples/example_test.go +++ b/examples/example_test.go @@ -1,7 +1,7 @@ package examples import ( - dynamock "github.com/gusaul/go-dynamock" + "github.com/caldwecr/go-dynamock" "testing" "github.com/aws/aws-sdk-go/aws" diff --git a/update_item.go b/update_item.go index 21b20d0..285a229 100644 --- a/update_item.go +++ b/update_item.go @@ -72,43 +72,43 @@ func (e *MockDynamoDB) UpdateItem(input *dynamodb.UpdateItemInput) (*dynamodb.Up if x.table != nil { if *x.table != *input.TableName { - return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect table %s but found table %s", *x.table, *input.TableName) + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("expect table %s but found table %s", *x.table, *input.TableName) } } if x.key != nil { if !reflect.DeepEqual(x.key, input.Key) { - return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.key, input.Key) + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("expect key %+v but found key %+v", x.key, input.Key) } } if x.attributeUpdates != nil { if !reflect.DeepEqual(x.attributeUpdates, input.AttributeUpdates) { - return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.attributeUpdates, input.AttributeUpdates) + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("expect AttributeUpdates: %+v but got: %+v", x.attributeUpdates, input.AttributeUpdates) } } if x.conditionExpression != nil { if !reflect.DeepEqual(x.conditionExpression, input.ConditionExpression) { - return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.conditionExpression, input.ConditionExpression) + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("expect ConditionExpressions: %+v but got: %+v", x.conditionExpression, input.ConditionExpression) } } if x.expressionAttributeNames != nil { if !reflect.DeepEqual(x.expressionAttributeNames, input.ExpressionAttributeNames) { - return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.expressionAttributeNames, input.ExpressionAttributeNames) + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("expect ExpressionAttributeNames: %+v but got: %+v", x.expressionAttributeNames, input.ExpressionAttributeNames) } } if x.expressionAttributeValues != nil { if !reflect.DeepEqual(x.expressionAttributeValues, input.ExpressionAttributeValues) { - return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.expressionAttributeValues, input.ExpressionAttributeValues) + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("expect ExpressionAttributeValues: %+v but got: %+v", x.expressionAttributeValues, input.ExpressionAttributeValues) } } if x.updateExpression != nil { if !reflect.DeepEqual(x.updateExpression, input.UpdateExpression) { - return &dynamodb.UpdateItemOutput{}, fmt.Errorf("Expect key %+v but found key %+v", x.updateExpression, input.UpdateExpression) + return &dynamodb.UpdateItemOutput{}, fmt.Errorf("expect UpdateExpression: %+v but got: %+v", x.updateExpression, input.UpdateExpression) } }