github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/privatemessaging/privatemessaging_test.go (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package privatemessaging 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 24 "github.com/kaleido-io/firefly/internal/config" 25 "github.com/kaleido-io/firefly/mocks/batchmocks" 26 "github.com/kaleido-io/firefly/mocks/blockchainmocks" 27 "github.com/kaleido-io/firefly/mocks/databasemocks" 28 "github.com/kaleido-io/firefly/mocks/dataexchangemocks" 29 "github.com/kaleido-io/firefly/mocks/datamocks" 30 "github.com/kaleido-io/firefly/mocks/identitymocks" 31 "github.com/kaleido-io/firefly/pkg/blockchain" 32 "github.com/kaleido-io/firefly/pkg/fftypes" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/mock" 35 ) 36 37 func newTestPrivateMessaging(t *testing.T) (*privateMessaging, func()) { 38 config.Reset() 39 config.Set(config.NodeName, "node1") 40 config.Set(config.OrgIdentity, "localorg") 41 config.Set(config.GroupCacheTTL, "1m") 42 config.Set(config.GroupCacheSize, "1m") 43 44 mdi := &databasemocks.Plugin{} 45 mii := &identitymocks.Plugin{} 46 mdx := &dataexchangemocks.Plugin{} 47 mbi := &blockchainmocks.Plugin{} 48 mba := &batchmocks.Manager{} 49 mdm := &datamocks.Manager{} 50 51 mba.On("RegisterDispatcher", []fftypes.MessageType{fftypes.MessageTypeGroupInit, fftypes.MessageTypePrivate}, mock.Anything, mock.Anything).Return() 52 53 ctx, cancel := context.WithCancel(context.Background()) 54 pm, err := NewPrivateMessaging(ctx, mdi, mii, mdx, mbi, mba, mdm) 55 assert.NoError(t, err) 56 57 // Default mocks to save boilerplate in the tests 58 mdx.On("Name").Return("utdx").Maybe() 59 mbi.On("Name").Return("utblk").Maybe() 60 mii.On("Resolve", ctx, "org1").Return(&fftypes.Identity{ 61 Identifier: "org1", OnChain: "0x12345", 62 }, nil).Maybe() 63 mbi.On("VerifyIdentitySyntax", ctx, mock.MatchedBy(func(i *fftypes.Identity) bool { return i.OnChain == "0x12345" })).Return(nil).Maybe() 64 mii.On("Resolve", ctx, "org1").Return(&fftypes.Identity{ 65 Identifier: "org1", OnChain: "0x23456", 66 }, nil).Maybe() 67 mbi.On("VerifyIdentitySyntax", ctx, mock.MatchedBy(func(i *fftypes.Identity) bool { return i.OnChain == "0x23456" })).Return(nil).Maybe() 68 69 return pm.(*privateMessaging), cancel 70 } 71 72 func uuidMatches(id1 *fftypes.UUID) interface{} { 73 return mock.MatchedBy(func(id2 *fftypes.UUID) bool { return id1.Equals(id2) }) 74 } 75 76 func TestDispatchBatch(t *testing.T) { 77 78 pm, cancel := newTestPrivateMessaging(t) 79 defer cancel() 80 81 batchID := fftypes.NewUUID() 82 groupID := fftypes.NewRandB32() 83 pin1 := fftypes.NewRandB32() 84 pin2 := fftypes.NewRandB32() 85 node1 := fftypes.NewUUID() 86 node2 := fftypes.NewUUID() 87 txID := fftypes.NewUUID() 88 batchHash := fftypes.NewRandB32() 89 90 mdi := pm.database.(*databasemocks.Plugin) 91 mbi := pm.blockchain.(*blockchainmocks.Plugin) 92 mdx := pm.exchange.(*dataexchangemocks.Plugin) 93 94 rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything).Maybe() 95 rag.RunFn = func(a mock.Arguments) { 96 rag.ReturnArguments = mock.Arguments{ 97 a[1].(func(context.Context) error)(a[0].(context.Context)), 98 } 99 } 100 101 mdi.On("GetGroupByHash", pm.ctx, groupID).Return(&fftypes.Group{ 102 Hash: fftypes.NewRandB32(), 103 GroupIdentity: fftypes.GroupIdentity{ 104 Name: "group1", 105 Members: fftypes.Members{ 106 {Identity: "org1", Node: node1}, 107 {Identity: "org2", Node: node2}, 108 }, 109 }, 110 }, nil) 111 mdi.On("GetNodeByID", pm.ctx, uuidMatches(node1)).Return(&fftypes.Node{ 112 ID: node1, 113 DX: fftypes.DXInfo{ 114 Peer: "node1", 115 Endpoint: fftypes.JSONObject{"url": "https://node1.example.com"}, 116 }, 117 }, nil).Once() 118 mdi.On("GetNodeByID", pm.ctx, uuidMatches(node2)).Return(&fftypes.Node{ 119 ID: node2, 120 DX: fftypes.DXInfo{ 121 Peer: "node2", 122 Endpoint: fftypes.JSONObject{"url": "https://node2.example.com"}, 123 }, 124 }, nil).Once() 125 126 mdx.On("SendMessage", pm.ctx, mock.Anything, mock.Anything).Return("tracking1", nil).Once() 127 mdi.On("UpsertOperation", pm.ctx, mock.MatchedBy(func(op *fftypes.Operation) bool { 128 return op.BackendID == "tracking1" && op.Type == fftypes.OpTypeDataExchangeBatchSend 129 }), false).Return(nil, nil) 130 mdx.On("SendMessage", pm.ctx, mock.Anything, mock.Anything).Return("tracking2", nil).Once() 131 mdi.On("UpsertOperation", pm.ctx, mock.MatchedBy(func(op *fftypes.Operation) bool { 132 return op.BackendID == "tracking2" && op.Type == fftypes.OpTypeDataExchangeBatchSend 133 }), false).Return(nil, nil) 134 135 mdi.On("UpsertTransaction", pm.ctx, mock.MatchedBy(func(tx *fftypes.Transaction) bool { 136 return tx.Subject.Type == fftypes.TransactionTypeBatchPin && tx.ID.Equals(txID) 137 }), true, false).Return(nil, nil) 138 mbi.On("SubmitBatchPin", pm.ctx, mock.Anything, mock.Anything, mock.MatchedBy(func(bp *blockchain.BatchPin) bool { 139 assert.Equal(t, txID, bp.TransactionID) 140 assert.Equal(t, batchID, bp.BatchID) 141 assert.Equal(t, batchHash, bp.BatchHash) 142 assert.Equal(t, "ns1", bp.Namespace) 143 assert.Equal(t, []*fftypes.Bytes32{pin1, pin2}, bp.Contexts) 144 return true 145 })).Return("tracking3", nil) 146 mdi.On("UpsertOperation", pm.ctx, mock.MatchedBy(func(op *fftypes.Operation) bool { 147 return op.BackendID == "tracking3" && op.Type == fftypes.OpTypeBlockchainBatchPin 148 }), false).Return(nil, nil) 149 150 err := pm.dispatchBatch(pm.ctx, &fftypes.Batch{ 151 ID: batchID, 152 Author: "org1", 153 Group: groupID, 154 Namespace: "ns1", 155 Payload: fftypes.BatchPayload{ 156 TX: fftypes.TransactionRef{ 157 ID: txID, 158 }, 159 }, 160 Hash: batchHash, 161 }, []*fftypes.Bytes32{pin1, pin2}) 162 assert.NoError(t, err) 163 164 mdi.AssertExpectations(t) 165 mdx.AssertExpectations(t) 166 } 167 168 func TestNewPrivateMessagingMissingDeps(t *testing.T) { 169 _, err := NewPrivateMessaging(context.Background(), nil, nil, nil, nil, nil, nil) 170 assert.Regexp(t, "FF10128", err) 171 } 172 173 func TestDispatchBatchBadData(t *testing.T) { 174 pm, cancel := newTestPrivateMessaging(t) 175 defer cancel() 176 177 err := pm.dispatchBatch(pm.ctx, &fftypes.Batch{ 178 Payload: fftypes.BatchPayload{ 179 Data: []*fftypes.Data{ 180 {Value: fftypes.Byteable(`{!json}`)}, 181 }, 182 }, 183 }, []*fftypes.Bytes32{}) 184 assert.Regexp(t, "FF10137", err) 185 } 186 187 func TestDispatchErrorFindingGroup(t *testing.T) { 188 pm, cancel := newTestPrivateMessaging(t) 189 defer cancel() 190 191 mdi := pm.database.(*databasemocks.Plugin) 192 mdi.On("GetGroupByHash", pm.ctx, mock.Anything).Return(nil, fmt.Errorf("pop")) 193 194 err := pm.dispatchBatch(pm.ctx, &fftypes.Batch{}, []*fftypes.Bytes32{}) 195 assert.Regexp(t, "pop", err) 196 } 197 198 func TestSendAndSubmitBatchBadID(t *testing.T) { 199 pm, cancel := newTestPrivateMessaging(t) 200 defer cancel() 201 202 mdi := pm.database.(*databasemocks.Plugin) 203 mdi.On("GetGroupByHash", pm.ctx, mock.Anything).Return(nil, fmt.Errorf("pop")) 204 205 mii := pm.identity.(*identitymocks.Plugin) 206 mii.On("Resolve", pm.ctx, "badauthor").Return(&fftypes.Identity{OnChain: "!badaddress"}, nil) 207 208 mbi := pm.blockchain.(*blockchainmocks.Plugin) 209 mbi.On("VerifyIdentitySyntax", pm.ctx, mock.Anything).Return(fmt.Errorf("pop")) 210 211 err := pm.sendAndSubmitBatch(pm.ctx, &fftypes.Batch{ 212 Author: "badauthor", 213 }, []*fftypes.Node{}, fftypes.Byteable(`{}`), []*fftypes.Bytes32{}) 214 assert.Regexp(t, "pop", err) 215 } 216 217 func TestSendImmediateFail(t *testing.T) { 218 pm, cancel := newTestPrivateMessaging(t) 219 defer cancel() 220 221 mdx := pm.exchange.(*dataexchangemocks.Plugin) 222 mdx.On("SendMessage", pm.ctx, mock.Anything, mock.Anything).Return("", fmt.Errorf("pop")) 223 224 err := pm.sendAndSubmitBatch(pm.ctx, &fftypes.Batch{ 225 Author: "org1", 226 }, []*fftypes.Node{ 227 { 228 DX: fftypes.DXInfo{ 229 Peer: "node1", 230 Endpoint: fftypes.JSONObject{"url": "https://node1.example.com"}, 231 }, 232 }, 233 }, fftypes.Byteable(`{}`), []*fftypes.Bytes32{}) 234 assert.Regexp(t, "pop", err) 235 } 236 237 func TestSendSubmitUpsertOperationFail(t *testing.T) { 238 pm, cancel := newTestPrivateMessaging(t) 239 defer cancel() 240 241 mdx := pm.exchange.(*dataexchangemocks.Plugin) 242 mdx.On("SendMessage", pm.ctx, mock.Anything, mock.Anything).Return("tracking1", nil) 243 244 mdi := pm.database.(*databasemocks.Plugin) 245 mdi.On("UpsertOperation", pm.ctx, mock.Anything, false).Return(fmt.Errorf("pop")) 246 247 err := pm.sendAndSubmitBatch(pm.ctx, &fftypes.Batch{ 248 Author: "org1", 249 }, []*fftypes.Node{ 250 { 251 DX: fftypes.DXInfo{ 252 Peer: "node1", 253 Endpoint: fftypes.JSONObject{"url": "https://node1.example.com"}, 254 }, 255 }, 256 }, fftypes.Byteable(`{}`), []*fftypes.Bytes32{}) 257 assert.Regexp(t, "pop", err) 258 } 259 260 func TestWriteTransactionUpsertFail(t *testing.T) { 261 pm, cancel := newTestPrivateMessaging(t) 262 defer cancel() 263 264 mdi := pm.database.(*databasemocks.Plugin) 265 mdi.On("UpsertTransaction", pm.ctx, mock.Anything, true, false).Return(fmt.Errorf("pop")) 266 267 err := pm.writeTransaction(pm.ctx, &fftypes.Identity{OnChain: "0x12345"}, &fftypes.Batch{}, []*fftypes.Bytes32{}) 268 assert.Regexp(t, "pop", err) 269 } 270 271 func TestWriteTransactionSubmitBatchPinFail(t *testing.T) { 272 pm, cancel := newTestPrivateMessaging(t) 273 defer cancel() 274 275 mdi := pm.database.(*databasemocks.Plugin) 276 mdi.On("UpsertTransaction", pm.ctx, mock.Anything, true, false).Return(nil) 277 278 mbi := pm.blockchain.(*blockchainmocks.Plugin) 279 mbi.On("SubmitBatchPin", pm.ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", fmt.Errorf("pop")) 280 281 err := pm.writeTransaction(pm.ctx, &fftypes.Identity{OnChain: "0x12345"}, &fftypes.Batch{}, []*fftypes.Bytes32{}) 282 assert.Regexp(t, "pop", err) 283 } 284 285 func TestWriteTransactionUpsertOpFail(t *testing.T) { 286 pm, cancel := newTestPrivateMessaging(t) 287 defer cancel() 288 289 mdi := pm.database.(*databasemocks.Plugin) 290 mdi.On("UpsertTransaction", pm.ctx, mock.Anything, true, false).Return(nil) 291 292 mbi := pm.blockchain.(*blockchainmocks.Plugin) 293 mbi.On("SubmitBatchPin", pm.ctx, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("tracking1", nil) 294 295 mdi.On("UpsertOperation", pm.ctx, mock.Anything, false).Return(fmt.Errorf("pop")) 296 297 err := pm.writeTransaction(pm.ctx, &fftypes.Identity{OnChain: "0x12345"}, &fftypes.Batch{}, []*fftypes.Bytes32{}) 298 assert.Regexp(t, "pop", err) 299 } 300 301 func TestStart(t *testing.T) { 302 pm, cancel := newTestPrivateMessaging(t) 303 defer cancel() 304 305 mdx := pm.exchange.(*dataexchangemocks.Plugin) 306 mdx.On("Start").Return(nil) 307 308 err := pm.Start() 309 assert.NoError(t, err) 310 }