github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/kvledger/txmgmt/statedb/statecouchdb/couchdb_test.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package statecouchdb
     8  
     9  import (
    10  	"context"
    11  	"encoding/json"
    12  	"net/http"
    13  	"net/url"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  	"unicode/utf8"
    18  
    19  	"github.com/hechain20/hechain/common/metrics/disabled"
    20  	"github.com/hechain20/hechain/core/ledger"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  const (
    25  	badConnectURL                = "couchdb:5990"
    26  	badParseConnectURL           = "http://host.com|5432"
    27  	updateDocumentConflictError  = "conflict"
    28  	updateDocumentConflictReason = "Document update conflict."
    29  )
    30  
    31  type Asset struct {
    32  	ID        string `json:"_id"`
    33  	Rev       string `json:"_rev"`
    34  	AssetName string `json:"asset_name"`
    35  	Color     string `json:"color"`
    36  	Size      string `json:"size"`
    37  	Owner     string `json:"owner"`
    38  }
    39  
    40  var assetJSON = []byte(`{"asset_name":"marble1","color":"blue","size":"35","owner":"jerry"}`)
    41  
    42  func testConfig() *ledger.CouchDBConfig {
    43  	return &ledger.CouchDBConfig{
    44  		Address:               "",
    45  		Username:              "admin",
    46  		Password:              "adminpw",
    47  		MaxRetries:            3,
    48  		MaxRetriesOnStartup:   20,
    49  		RequestTimeout:        35 * time.Second,
    50  		CreateGlobalChangesDB: false,
    51  	}
    52  }
    53  
    54  func TestDBBadConnectionDef(t *testing.T) {
    55  	config := &ledger.CouchDBConfig{
    56  		Address:             badParseConnectURL,
    57  		Username:            "admin",
    58  		Password:            "adminpw",
    59  		MaxRetries:          3,
    60  		MaxRetriesOnStartup: 3,
    61  		RequestTimeout:      35 * time.Second,
    62  	}
    63  	_, err := createCouchInstance(config, &disabled.Provider{})
    64  	require.Error(t, err, "Did not receive error when trying to create database connection definition with a bad hostname")
    65  }
    66  
    67  func TestEncodePathElement(t *testing.T) {
    68  	encodedString := encodePathElement("testelement")
    69  	require.Equal(t, "testelement", encodedString)
    70  
    71  	encodedString = encodePathElement("test element")
    72  	require.Equal(t, "test%20element", encodedString)
    73  
    74  	encodedString = encodePathElement("/test element")
    75  	require.Equal(t, "%2Ftest%20element", encodedString)
    76  
    77  	encodedString = encodePathElement("/test element:")
    78  	require.Equal(t, "%2Ftest%20element:", encodedString)
    79  
    80  	encodedString = encodePathElement("/test+ element:")
    81  	require.Equal(t, "%2Ftest%2B%20element:", encodedString)
    82  }
    83  
    84  func TestHealthCheck(t *testing.T) {
    85  	config := testConfig()
    86  	couchDBEnv.startCouchDB(t)
    87  	config.Address = couchDBEnv.couchAddress
    88  	defer couchDBEnv.cleanup(config)
    89  
    90  	configWithIncorrectAddress := testConfig()
    91  	client := &http.Client{}
    92  	badCouchDBInstance := couchInstance{
    93  		conf:   configWithIncorrectAddress,
    94  		client: client,
    95  		stats:  newStats(&disabled.Provider{}),
    96  	}
    97  	err := badCouchDBInstance.healthCheck(context.Background())
    98  	require.Error(t, err, "Health check should result in an error if unable to connect to couch db")
    99  	require.Contains(t, err.Error(), "failed to connect to couch db")
   100  
   101  	// Create a good couchdb instance
   102  	goodCouchDBInstance := couchInstance{
   103  		conf:   config,
   104  		client: client,
   105  		stats:  newStats(&disabled.Provider{}),
   106  	}
   107  	err = goodCouchDBInstance.healthCheck(context.Background())
   108  	require.NoError(t, err)
   109  }
   110  
   111  func TestBadCouchDBInstance(t *testing.T) {
   112  	client := &http.Client{}
   113  
   114  	// Create a bad couchdb instance
   115  	badCouchDBInstance := couchInstance{
   116  		conf: &ledger.CouchDBConfig{
   117  			Address:             badParseConnectURL,
   118  			Username:            "admin",
   119  			Password:            "adminpw",
   120  			MaxRetries:          3,
   121  			MaxRetriesOnStartup: 10,
   122  			RequestTimeout:      30 * time.Second,
   123  		},
   124  		client: client,
   125  		stats:  newStats(&disabled.Provider{}),
   126  	}
   127  
   128  	// Create a bad CouchDatabase
   129  	badDB := couchDatabase{&badCouchDBInstance, "baddb"}
   130  
   131  	// Test createCouchDatabase with bad connection
   132  	_, err := createCouchDatabase(&badCouchDBInstance, "baddbtest")
   133  	require.Error(t, err, "Error should have been thrown with createCouchDatabase and invalid connection")
   134  
   135  	// Test createSystemDatabasesIfNotExist with bad connection
   136  	err = createSystemDatabasesIfNotExist(&badCouchDBInstance)
   137  	require.Error(t, err, "Error should have been thrown with createSystemDatabasesIfNotExist and invalid connection")
   138  
   139  	// Test createDatabaseIfNotExist with bad connection
   140  	err = badDB.createDatabaseIfNotExist()
   141  	require.Error(t, err, "Error should have been thrown with createDatabaseIfNotExist and invalid connection")
   142  
   143  	// Test getDatabaseInfo with bad connection
   144  	_, _, err = badDB.getDatabaseInfo()
   145  	require.Error(t, err, "Error should have been thrown with getDatabaseInfo and invalid connection")
   146  
   147  	// Test verifyCouchConfig with bad connection
   148  	_, _, err = badCouchDBInstance.verifyCouchConfig()
   149  	require.Error(t, err, "Error should have been thrown with verifyCouchConfig and invalid connection")
   150  
   151  	// Test dropDatabase with bad connection
   152  	err = badDB.dropDatabase()
   153  	require.Error(t, err, "Error should have been thrown with dropDatabase and invalid connection")
   154  
   155  	// Test readDoc with bad connection
   156  	_, _, err = badDB.readDoc("1")
   157  	require.Error(t, err, "Error should have been thrown with readDoc and invalid connection")
   158  
   159  	// Test saveDoc with bad connection
   160  	_, err = badDB.saveDoc("1", "1", nil)
   161  	require.Error(t, err, "Error should have been thrown with saveDoc and invalid connection")
   162  
   163  	// Test deleteDoc with bad connection
   164  	err = badDB.deleteDoc("1", "1")
   165  	require.Error(t, err, "Error should have been thrown with deleteDoc and invalid connection")
   166  
   167  	// Test readDocRange with bad connection
   168  	_, _, err = badDB.readDocRange("1", "2", 1000)
   169  	require.Error(t, err, "Error should have been thrown with readDocRange and invalid connection")
   170  
   171  	// Test queryDocuments with bad connection
   172  	_, _, err = badDB.queryDocuments("1")
   173  	require.Error(t, err, "Error should have been thrown with queryDocuments and invalid connection")
   174  
   175  	// Test batchRetrieveDocumentMetadata with bad connection
   176  	_, err = badDB.batchRetrieveDocumentMetadata(nil)
   177  	require.Error(t, err, "Error should have been thrown with batchRetrieveDocumentMetadata and invalid connection")
   178  
   179  	// Test batchUpdateDocuments with bad connection
   180  	_, err = badDB.batchUpdateDocuments(nil)
   181  	require.Error(t, err, "Error should have been thrown with batchUpdateDocuments and invalid connection")
   182  
   183  	// Test listIndex with bad connection
   184  	_, err = badDB.listIndex()
   185  	require.Error(t, err, "Error should have been thrown with listIndex and invalid connection")
   186  
   187  	// Test createIndex with bad connection
   188  	_, err = badDB.createIndex("")
   189  	require.Error(t, err, "Error should have been thrown with createIndex and invalid connection")
   190  
   191  	// Test deleteIndex with bad connection
   192  	err = badDB.deleteIndex("", "")
   193  	require.Error(t, err, "Error should have been thrown with deleteIndex and invalid connection")
   194  }
   195  
   196  func TestDBCreateSaveWithoutRevision(t *testing.T) {
   197  	config := testConfig()
   198  	couchDBEnv.startCouchDB(t)
   199  	config.Address = couchDBEnv.couchAddress
   200  	defer couchDBEnv.cleanup(config)
   201  	database := "testdbcreatesavewithoutrevision"
   202  
   203  	// create a new instance and database object
   204  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   205  	require.NoError(t, err, "Error when trying to create couch instance")
   206  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   207  
   208  	// create a new database
   209  	errdb := db.createDatabaseIfNotExist()
   210  	require.NoError(t, errdb, "Error when trying to create database")
   211  
   212  	// Save the test document
   213  	_, saveerr := db.saveDoc("2", "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   214  	require.NoError(t, saveerr, "Error when trying to save a document")
   215  }
   216  
   217  func TestDBCreateEnsureFullCommit(t *testing.T) {
   218  	config := testConfig()
   219  	couchDBEnv.startCouchDB(t)
   220  	config.Address = couchDBEnv.couchAddress
   221  	defer couchDBEnv.cleanup(config)
   222  	database := "testdbensurefullcommit"
   223  
   224  	// create a new instance and database object
   225  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   226  	require.NoError(t, err, "Error when trying to create couch instance")
   227  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   228  
   229  	// create a new database
   230  	errdb := db.createDatabaseIfNotExist()
   231  	require.NoError(t, errdb, "Error when trying to create database")
   232  
   233  	// Save the test document
   234  	_, saveerr := db.saveDoc("2", "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   235  	require.NoError(t, saveerr, "Error when trying to save a document")
   236  }
   237  
   238  func TestIsEmpty(t *testing.T) {
   239  	config := testConfig()
   240  	couchDBEnv.startCouchDB(t)
   241  	config.Address = couchDBEnv.couchAddress
   242  	defer couchDBEnv.cleanup(config)
   243  
   244  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   245  	require.NoError(t, err)
   246  
   247  	ignore := []string{"_global_changes", "_replicator", "_users", "fabric__internal"}
   248  	isEmpty, err := couchInstance.isEmpty(ignore)
   249  	require.NoError(t, err)
   250  	require.True(t, isEmpty)
   251  
   252  	testdbs := []string{"testdb1", "testdb2"}
   253  	couchDBEnv.cleanup(config)
   254  
   255  	for _, d := range testdbs {
   256  		db := couchDatabase{couchInstance: couchInstance, dbName: d}
   257  		require.NoError(t, db.createDatabaseIfNotExist())
   258  	}
   259  	isEmpty, err = couchInstance.isEmpty(ignore)
   260  	require.NoError(t, err)
   261  	require.False(t, isEmpty)
   262  
   263  	ignore = append(ignore, "testdb1")
   264  	isEmpty, err = couchInstance.isEmpty(ignore)
   265  	require.NoError(t, err)
   266  	require.False(t, isEmpty)
   267  
   268  	ignore = append(ignore, "testdb2")
   269  	isEmpty, err = couchInstance.isEmpty(ignore)
   270  	require.NoError(t, err)
   271  	require.True(t, isEmpty)
   272  
   273  	configCopy := *config
   274  	configCopy.Address = "address-and-port.invalid:0"
   275  	configCopy.MaxRetries = 0
   276  	couchInstance.conf = &configCopy
   277  	_, err = couchInstance.isEmpty(ignore)
   278  	require.Error(t, err)
   279  	require.Regexp(t, `unable to connect to CouchDB, check the hostname and port: http error calling couchdb: Get "?http://address-and-port.invalid:0/_all_dbs"?`, err.Error())
   280  }
   281  
   282  func TestDBBadDatabaseName(t *testing.T) {
   283  	config := testConfig()
   284  	couchDBEnv.startCouchDB(t)
   285  	config.Address = couchDBEnv.couchAddress
   286  	defer couchDBEnv.cleanup(config)
   287  	// create a new instance and database object using a valid database name mixed case
   288  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   289  	require.NoError(t, err, "Error when trying to create couch instance")
   290  	_, dberr := createCouchDatabase(couchInstance, "testDB")
   291  	require.Error(t, dberr, "Error should have been thrown for an invalid db name")
   292  
   293  	// create a new instance and database object using a valid database name letters and numbers
   294  	couchInstance, err = createCouchInstance(config, &disabled.Provider{})
   295  	require.NoError(t, err, "Error when trying to create couch instance")
   296  	_, dberr = createCouchDatabase(couchInstance, "test132")
   297  	require.NoError(t, dberr, "Error when testing a valid database name")
   298  
   299  	// create a new instance and database object using a valid database name - special characters
   300  	couchInstance, err = createCouchInstance(config, &disabled.Provider{})
   301  	require.NoError(t, err, "Error when trying to create couch instance")
   302  	_, dberr = createCouchDatabase(couchInstance, "test1234~!@#$%^&*()[]{}.")
   303  	require.Error(t, dberr, "Error should have been thrown for an invalid db name")
   304  
   305  	// create a new instance and database object using a invalid database name - too long	/*
   306  	couchInstance, err = createCouchInstance(config, &disabled.Provider{})
   307  	require.NoError(t, err, "Error when trying to create couch instance")
   308  	_, dberr = createCouchDatabase(couchInstance, "a12345678901234567890123456789012345678901234"+
   309  		"56789012345678901234567890123456789012345678901234567890123456789012345678901234567890"+
   310  		"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456"+
   311  		"78901234567890123456789012345678901234567890")
   312  	require.Error(t, dberr, "Error should have been thrown for invalid database name")
   313  }
   314  
   315  func TestDBBadConnection(t *testing.T) {
   316  	// create a new instance and database object
   317  	// Limit the maxRetriesOnStartup to 3 in order to reduce time for the failure
   318  	config := &ledger.CouchDBConfig{
   319  		Address:             badConnectURL,
   320  		Username:            "admin",
   321  		Password:            "adminpw",
   322  		MaxRetries:          3,
   323  		MaxRetriesOnStartup: 3,
   324  		RequestTimeout:      35 * time.Second,
   325  	}
   326  	_, err := createCouchInstance(config, &disabled.Provider{})
   327  	require.Error(t, err, "Error should have been thrown for a bad connection")
   328  }
   329  
   330  func TestBadDBCredentials(t *testing.T) {
   331  	config := testConfig()
   332  	couchDBEnv.startCouchDB(t)
   333  	config.Address = couchDBEnv.couchAddress
   334  	defer couchDBEnv.cleanup(config)
   335  
   336  	badConfig := testConfig()
   337  	badConfig.Address = config.Address
   338  	badConfig.Username = "fred"
   339  	badConfig.Password = "fred"
   340  	// create a new instance and database object
   341  	_, err := createCouchInstance(badConfig, &disabled.Provider{})
   342  	require.Error(t, err, "Error should have been thrown for bad credentials")
   343  }
   344  
   345  func TestDBCreateDatabaseAndPersist(t *testing.T) {
   346  	config := testConfig()
   347  	couchDBEnv.startCouchDB(t)
   348  	config.Address = couchDBEnv.couchAddress
   349  	defer couchDBEnv.cleanup(config)
   350  
   351  	// Test create and persist with default configured maxRetries
   352  	testDBCreateDatabaseAndPersist(t, config)
   353  	couchDBEnv.cleanup(config)
   354  
   355  	// Test create and persist with 0 retries
   356  	configCopy := *config
   357  	configCopy.MaxRetries = 0
   358  	testDBCreateDatabaseAndPersist(t, &configCopy)
   359  	couchDBEnv.cleanup(config)
   360  
   361  	// Test batch operations with default configured maxRetries
   362  	testBatchBatchOperations(t, config)
   363  	couchDBEnv.cleanup(config)
   364  
   365  	// Test batch operations with 0 retries
   366  	testBatchBatchOperations(t, config)
   367  }
   368  
   369  func testDBCreateDatabaseAndPersist(t *testing.T, config *ledger.CouchDBConfig) {
   370  	database := "testdbcreatedatabaseandpersist"
   371  
   372  	// create a new instance and database object
   373  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   374  	require.NoError(t, err, "Error when trying to create couch instance")
   375  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   376  
   377  	// create a new database
   378  	errdb := db.createDatabaseIfNotExist()
   379  	require.NoError(t, errdb, "Error when trying to create database")
   380  
   381  	// Retrieve the info for the new database and make sure the name matches
   382  	dbResp, _, errdb := db.getDatabaseInfo()
   383  	require.NoError(t, errdb, "Error when trying to retrieve database information")
   384  	require.Equal(t, database, dbResp.DbName)
   385  
   386  	// Save the test document
   387  	_, saveerr := db.saveDoc("idWith/slash", "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   388  	require.NoError(t, saveerr, "Error when trying to save a document")
   389  
   390  	// Retrieve the test document
   391  	dbGetResp, _, geterr := db.readDoc("idWith/slash")
   392  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   393  
   394  	// Unmarshal the document to Asset structure
   395  	assetResp := &Asset{}
   396  	geterr = json.Unmarshal(dbGetResp.jsonValue, &assetResp)
   397  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   398  
   399  	// Verify the owner retrieved matches
   400  	require.Equal(t, "jerry", assetResp.Owner)
   401  
   402  	// Save the test document
   403  	_, saveerr = db.saveDoc("1", "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   404  	require.NoError(t, saveerr, "Error when trying to save a document")
   405  
   406  	// Retrieve the test document
   407  	dbGetResp, _, geterr = db.readDoc("1")
   408  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   409  
   410  	// Unmarshal the document to Asset structure
   411  	assetResp = &Asset{}
   412  	geterr = json.Unmarshal(dbGetResp.jsonValue, &assetResp)
   413  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   414  
   415  	// Verify the owner retrieved matches
   416  	require.Equal(t, "jerry", assetResp.Owner)
   417  
   418  	// Change owner to bob
   419  	assetResp.Owner = "bob"
   420  
   421  	// create a byte array of the JSON
   422  	assetDocUpdated, _ := json.Marshal(assetResp)
   423  
   424  	// Save the updated test document
   425  	_, saveerr = db.saveDoc("1", "", &couchDoc{jsonValue: assetDocUpdated, attachments: nil})
   426  	require.NoError(t, saveerr, "Error when trying to save the updated document")
   427  
   428  	// Retrieve the updated test document
   429  	dbGetResp, _, geterr = db.readDoc("1")
   430  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   431  
   432  	// Unmarshal the document to Asset structure
   433  	assetResp = &Asset{}
   434  	require.NoError(t, json.Unmarshal(dbGetResp.jsonValue, &assetResp))
   435  
   436  	// Assert that the update was saved and retrieved
   437  	require.Equal(t, "bob", assetResp.Owner)
   438  
   439  	testBytes2 := []byte(`test attachment 2`)
   440  
   441  	attachment2 := &attachmentInfo{}
   442  	attachment2.AttachmentBytes = testBytes2
   443  	attachment2.ContentType = "application/octet-stream"
   444  	attachment2.Name = "data"
   445  	attachments2 := []*attachmentInfo{}
   446  	attachments2 = append(attachments2, attachment2)
   447  
   448  	// Save the test document with an attachment
   449  	_, saveerr = db.saveDoc("2", "", &couchDoc{jsonValue: nil, attachments: attachments2})
   450  	require.NoError(t, saveerr, "Error when trying to save a document")
   451  
   452  	// Retrieve the test document with attachments
   453  	dbGetResp, _, geterr = db.readDoc("2")
   454  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   455  
   456  	// verify the text from the attachment is correct
   457  	testattach := dbGetResp.attachments[0].AttachmentBytes
   458  	require.Equal(t, testBytes2, testattach)
   459  
   460  	testBytes3 := []byte{}
   461  
   462  	attachment3 := &attachmentInfo{}
   463  	attachment3.AttachmentBytes = testBytes3
   464  	attachment3.ContentType = "application/octet-stream"
   465  	attachment3.Name = "data"
   466  	attachments3 := []*attachmentInfo{}
   467  	attachments3 = append(attachments3, attachment3)
   468  
   469  	// Save the test document with a zero length attachment
   470  	_, saveerr = db.saveDoc("3", "", &couchDoc{jsonValue: nil, attachments: attachments3})
   471  	require.NoError(t, saveerr, "Error when trying to save a document")
   472  
   473  	// Retrieve the test document with attachments
   474  	dbGetResp, _, geterr = db.readDoc("3")
   475  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   476  
   477  	// verify the text from the attachment is correct,  zero bytes
   478  	testattach = dbGetResp.attachments[0].AttachmentBytes
   479  	require.Equal(t, testBytes3, testattach)
   480  
   481  	testBytes4a := []byte(`test attachment 4a`)
   482  	attachment4a := &attachmentInfo{}
   483  	attachment4a.AttachmentBytes = testBytes4a
   484  	attachment4a.ContentType = "application/octet-stream"
   485  	attachment4a.Name = "data1"
   486  
   487  	testBytes4b := []byte(`test attachment 4b`)
   488  	attachment4b := &attachmentInfo{}
   489  	attachment4b.AttachmentBytes = testBytes4b
   490  	attachment4b.ContentType = "application/octet-stream"
   491  	attachment4b.Name = "data2"
   492  
   493  	attachments4 := []*attachmentInfo{}
   494  	attachments4 = append(attachments4, attachment4a)
   495  	attachments4 = append(attachments4, attachment4b)
   496  
   497  	// Save the updated test document with multiple attachments
   498  	_, saveerr = db.saveDoc("4", "", &couchDoc{jsonValue: assetJSON, attachments: attachments4})
   499  	require.NoError(t, saveerr, "Error when trying to save the updated document")
   500  
   501  	// Retrieve the test document with attachments
   502  	dbGetResp, _, geterr = db.readDoc("4")
   503  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   504  
   505  	for _, attach4 := range dbGetResp.attachments {
   506  
   507  		currentName := attach4.Name
   508  		if currentName == "data1" {
   509  			require.Equal(t, testBytes4a, attach4.AttachmentBytes)
   510  		}
   511  		if currentName == "data2" {
   512  			require.Equal(t, testBytes4b, attach4.AttachmentBytes)
   513  		}
   514  
   515  	}
   516  
   517  	testBytes5a := []byte(`test attachment 5a`)
   518  	attachment5a := &attachmentInfo{}
   519  	attachment5a.AttachmentBytes = testBytes5a
   520  	attachment5a.ContentType = "application/octet-stream"
   521  	attachment5a.Name = "data1"
   522  
   523  	testBytes5b := []byte{}
   524  	attachment5b := &attachmentInfo{}
   525  	attachment5b.AttachmentBytes = testBytes5b
   526  	attachment5b.ContentType = "application/octet-stream"
   527  	attachment5b.Name = "data2"
   528  
   529  	attachments5 := []*attachmentInfo{}
   530  	attachments5 = append(attachments5, attachment5a)
   531  	attachments5 = append(attachments5, attachment5b)
   532  
   533  	// Save the updated test document with multiple attachments and zero length attachments
   534  	_, saveerr = db.saveDoc("5", "", &couchDoc{jsonValue: assetJSON, attachments: attachments5})
   535  	require.NoError(t, saveerr, "Error when trying to save the updated document")
   536  
   537  	// Retrieve the test document with attachments
   538  	dbGetResp, _, geterr = db.readDoc("5")
   539  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   540  
   541  	for _, attach5 := range dbGetResp.attachments {
   542  
   543  		currentName := attach5.Name
   544  		if currentName == "data1" {
   545  			require.Equal(t, testBytes5a, attach5.AttachmentBytes)
   546  		}
   547  		if currentName == "data2" {
   548  			require.Equal(t, testBytes5b, attach5.AttachmentBytes)
   549  		}
   550  
   551  	}
   552  
   553  	// Attempt to save the document with an invalid id
   554  	_, saveerr = db.saveDoc(string([]byte{0xff, 0xfe, 0xfd}), "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   555  	require.Error(t, saveerr, "Error should have been thrown when saving a document with an invalid ID")
   556  
   557  	// Attempt to read a document with an invalid id
   558  	_, _, readerr := db.readDoc(string([]byte{0xff, 0xfe, 0xfd}))
   559  	require.Error(t, readerr, "Error should have been thrown when reading a document with an invalid ID")
   560  
   561  	// Drop the database
   562  	errdbdrop := db.dropDatabase()
   563  	require.NoError(t, errdbdrop, "Error dropping database")
   564  
   565  	// Make sure an error is thrown for getting info for a missing database
   566  	_, _, errdbinfo := db.getDatabaseInfo()
   567  	require.Error(t, errdbinfo, "Error should have been thrown for missing database")
   568  
   569  	// Attempt to save a document to a deleted database
   570  	_, saveerr = db.saveDoc("6", "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   571  	require.Error(t, saveerr, "Error should have been thrown while attempting to save to a deleted database")
   572  
   573  	// Attempt to read from a deleted database
   574  	_, _, geterr = db.readDoc("6")
   575  	require.NoError(t, geterr, "Error should not have been thrown for a missing database, nil value is returned")
   576  }
   577  
   578  func TestDBRequestTimeout(t *testing.T) {
   579  	config := testConfig()
   580  	couchDBEnv.startCouchDB(t)
   581  	config.Address = couchDBEnv.couchAddress
   582  	defer couchDBEnv.cleanup(config)
   583  
   584  	// create a new instance and database object with a timeout that will fail
   585  	// Also use a maxRetriesOnStartup=3 to reduce the number of retries
   586  	configCopy := *config
   587  	configCopy.MaxRetriesOnStartup = 3
   588  	// create an impossibly short timeout
   589  	impossibleTimeout := time.Nanosecond
   590  	configCopy.RequestTimeout = impossibleTimeout
   591  	_, err := createCouchInstance(&configCopy, &disabled.Provider{})
   592  	require.Error(t, err, "Error should have been thown while trying to create a couchdb instance with a connection timeout")
   593  
   594  	// create a new instance and database object
   595  	configCopy.MaxRetries = -1
   596  	configCopy.MaxRetriesOnStartup = 3
   597  	_, err = createCouchInstance(&configCopy, &disabled.Provider{})
   598  	require.Error(t, err, "Error should have been thrown while attempting to create a database")
   599  }
   600  
   601  func TestDBTimeoutConflictRetry(t *testing.T) {
   602  	config := testConfig()
   603  	couchDBEnv.startCouchDB(t)
   604  	config.Address = couchDBEnv.couchAddress
   605  	defer couchDBEnv.cleanup(config)
   606  	database := "testdbtimeoutretry"
   607  
   608  	// create a new instance and database object
   609  	configCopy := *config
   610  	configCopy.MaxRetriesOnStartup = 3
   611  	couchInstance, err := createCouchInstance(&configCopy, &disabled.Provider{})
   612  	require.NoError(t, err, "Error when trying to create couch instance")
   613  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   614  
   615  	// create a new database
   616  	errdb := db.createDatabaseIfNotExist()
   617  	require.NoError(t, errdb, "Error when trying to create database")
   618  
   619  	// Retrieve the info for the new database and make sure the name matches
   620  	dbResp, _, errdb := db.getDatabaseInfo()
   621  	require.NoError(t, errdb, "Error when trying to retrieve database information")
   622  	require.Equal(t, database, dbResp.DbName)
   623  
   624  	// Save the test document
   625  	_, saveerr := db.saveDoc("1", "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   626  	require.NoError(t, saveerr, "Error when trying to save a document")
   627  
   628  	// Retrieve the test document
   629  	_, _, geterr := db.readDoc("1")
   630  	require.NoError(t, geterr, "Error when trying to retrieve a document")
   631  
   632  	// Save the test document with an invalid rev.  This should cause a retry
   633  	_, saveerr = db.saveDoc("1", "1-11111111111111111111111111111111", &couchDoc{jsonValue: assetJSON, attachments: nil})
   634  	require.NoError(t, saveerr, "Error when trying to save a document with a revision conflict")
   635  
   636  	// Delete the test document with an invalid rev.  This should cause a retry
   637  	deleteerr := db.deleteDoc("1", "1-11111111111111111111111111111111")
   638  	require.NoError(t, deleteerr, "Error when trying to delete a document with a revision conflict")
   639  }
   640  
   641  func TestDBBadNumberOfRetries(t *testing.T) {
   642  	config := testConfig()
   643  	couchDBEnv.startCouchDB(t)
   644  	config.Address = couchDBEnv.couchAddress
   645  	defer couchDBEnv.cleanup(config)
   646  
   647  	// create a new instance and database object
   648  	configCopy := *config
   649  	configCopy.MaxRetries = -1
   650  	configCopy.MaxRetriesOnStartup = 3
   651  	_, err := createCouchInstance(&configCopy, &disabled.Provider{})
   652  	require.Error(t, err, "Error should have been thrown while attempting to create a database")
   653  }
   654  
   655  func TestDBBadJSON(t *testing.T) {
   656  	config := testConfig()
   657  	couchDBEnv.startCouchDB(t)
   658  	config.Address = couchDBEnv.couchAddress
   659  	defer couchDBEnv.cleanup(config)
   660  	database := "testdbbadjson"
   661  
   662  	// create a new instance and database object
   663  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   664  	require.NoError(t, err, "Error when trying to create couch instance")
   665  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   666  
   667  	// create a new database
   668  	errdb := db.createDatabaseIfNotExist()
   669  	require.NoError(t, errdb, "Error when trying to create database")
   670  
   671  	// Retrieve the info for the new database and make sure the name matches
   672  	dbResp, _, errdb := db.getDatabaseInfo()
   673  	require.NoError(t, errdb, "Error when trying to retrieve database information")
   674  	require.Equal(t, database, dbResp.DbName)
   675  
   676  	badJSON := []byte(`{"asset_name"}`)
   677  
   678  	// Save the test document
   679  	_, saveerr := db.saveDoc("1", "", &couchDoc{jsonValue: badJSON, attachments: nil})
   680  	require.Error(t, saveerr, "Error should have been thrown for a bad JSON")
   681  }
   682  
   683  func TestPrefixScan(t *testing.T) {
   684  	config := testConfig()
   685  	couchDBEnv.startCouchDB(t)
   686  	config.Address = couchDBEnv.couchAddress
   687  	defer couchDBEnv.cleanup(config)
   688  	database := "testprefixscan"
   689  
   690  	// create a new instance and database object
   691  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   692  	require.NoError(t, err, "Error when trying to create couch instance")
   693  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   694  
   695  	// create a new database
   696  	errdb := db.createDatabaseIfNotExist()
   697  	require.NoError(t, errdb, "Error when trying to create database")
   698  
   699  	// Retrieve the info for the new database and make sure the name matches
   700  	dbResp, _, errdb := db.getDatabaseInfo()
   701  	require.NoError(t, errdb, "Error when trying to retrieve database information")
   702  	require.Equal(t, database, dbResp.DbName)
   703  
   704  	// Save documents
   705  	for i := rune(0); i < 20; i++ {
   706  		id1 := string([]rune{0, i, 0})
   707  		id2 := string([]rune{0, i, 1})
   708  		id3 := string([]rune{0, i, utf8.MaxRune - 1})
   709  		_, saveerr := db.saveDoc(id1, "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   710  		require.NoError(t, saveerr, "Error when trying to save a document")
   711  		_, saveerr = db.saveDoc(id2, "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   712  		require.NoError(t, saveerr, "Error when trying to save a document")
   713  		_, saveerr = db.saveDoc(id3, "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   714  		require.NoError(t, saveerr, "Error when trying to save a document")
   715  
   716  	}
   717  	startKey := string([]rune{0, 10})
   718  	endKey := startKey + string(utf8.MaxRune)
   719  	_, _, geterr := db.readDoc(endKey)
   720  	require.NoError(t, geterr, "Error when trying to get lastkey")
   721  
   722  	resultsPtr, _, geterr := db.readDocRange(startKey, endKey, 1000)
   723  	require.NoError(t, geterr, "Error when trying to perform a range scan")
   724  	require.NotNil(t, resultsPtr)
   725  	results := resultsPtr
   726  	require.Equal(t, 3, len(results))
   727  	require.Equal(t, string([]rune{0, 10, 0}), results[0].id)
   728  	require.Equal(t, string([]rune{0, 10, 1}), results[1].id)
   729  	require.Equal(t, string([]rune{0, 10, utf8.MaxRune - 1}), results[2].id)
   730  
   731  	// Drop the database
   732  	errdbdrop := db.dropDatabase()
   733  	require.NoError(t, errdbdrop, "Error dropping database")
   734  
   735  	// Drop again is not an error
   736  	require.NoError(t, db.dropDatabase())
   737  
   738  	// Retrieve the info for the new database and make sure the name matches
   739  	_, _, errdbinfo := db.getDatabaseInfo()
   740  	require.Error(t, errdbinfo, "Error should have been thrown for missing database")
   741  }
   742  
   743  func TestDBSaveAttachment(t *testing.T) {
   744  	config := testConfig()
   745  	couchDBEnv.startCouchDB(t)
   746  	config.Address = couchDBEnv.couchAddress
   747  	defer couchDBEnv.cleanup(config)
   748  	database := "testdbsaveattachment"
   749  
   750  	byteText := []byte(`This is a test document.  This is only a test`)
   751  
   752  	attachment := &attachmentInfo{}
   753  	attachment.AttachmentBytes = byteText
   754  	attachment.ContentType = "text/plain"
   755  	attachment.Length = uint64(len(byteText))
   756  	attachment.Name = "valueBytes"
   757  
   758  	attachments := []*attachmentInfo{}
   759  	attachments = append(attachments, attachment)
   760  
   761  	// create a new instance and database object
   762  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   763  	require.NoError(t, err, "Error when trying to create couch instance")
   764  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   765  
   766  	// create a new database
   767  	errdb := db.createDatabaseIfNotExist()
   768  	require.NoError(t, errdb, "Error when trying to create database")
   769  
   770  	// Save the test document
   771  	_, saveerr := db.saveDoc("10", "", &couchDoc{jsonValue: nil, attachments: attachments})
   772  	require.NoError(t, saveerr, "Error when trying to save a document")
   773  
   774  	// Attempt to retrieve the updated test document with attachments
   775  	couchDoc, _, geterr2 := db.readDoc("10")
   776  	require.NoError(t, geterr2, "Error when trying to retrieve a document with attachment")
   777  	require.NotNil(t, couchDoc.attachments)
   778  	require.Equal(t, byteText, couchDoc.attachments[0].AttachmentBytes)
   779  	require.Equal(t, attachment.Length, couchDoc.attachments[0].Length)
   780  }
   781  
   782  func TestDBDeleteDocument(t *testing.T) {
   783  	config := testConfig()
   784  	couchDBEnv.startCouchDB(t)
   785  	config.Address = couchDBEnv.couchAddress
   786  	defer couchDBEnv.cleanup(config)
   787  	database := "testdbdeletedocument"
   788  
   789  	// create a new instance and database object
   790  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   791  	require.NoError(t, err, "Error when trying to create couch instance")
   792  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   793  
   794  	// create a new database
   795  	errdb := db.createDatabaseIfNotExist()
   796  	require.NoError(t, errdb, "Error when trying to create database")
   797  
   798  	// Save the test document
   799  	_, saveerr := db.saveDoc("2", "", &couchDoc{jsonValue: assetJSON, attachments: nil})
   800  	require.NoError(t, saveerr, "Error when trying to save a document")
   801  
   802  	// Attempt to retrieve the test document
   803  	_, _, readErr := db.readDoc("2")
   804  	require.NoError(t, readErr, "Error when trying to retrieve a document with attachment")
   805  
   806  	// Delete the test document
   807  	deleteErr := db.deleteDoc("2", "")
   808  	require.NoError(t, deleteErr, "Error when trying to delete a document")
   809  
   810  	// Attempt to retrieve the test document
   811  	readValue, _, _ := db.readDoc("2")
   812  	require.Nil(t, readValue)
   813  }
   814  
   815  func TestDBDeleteNonExistingDocument(t *testing.T) {
   816  	config := testConfig()
   817  	couchDBEnv.startCouchDB(t)
   818  	config.Address = couchDBEnv.couchAddress
   819  	defer couchDBEnv.cleanup(config)
   820  	database := "testdbdeletenonexistingdocument"
   821  
   822  	// create a new instance and database object
   823  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   824  	require.NoError(t, err, "Error when trying to create couch instance")
   825  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   826  
   827  	// create a new database
   828  	errdb := db.createDatabaseIfNotExist()
   829  	require.NoError(t, errdb, "Error when trying to create database")
   830  
   831  	// Save the test document
   832  	deleteErr := db.deleteDoc("2", "")
   833  	require.NoError(t, deleteErr, "Error when trying to delete a non existing document")
   834  }
   835  
   836  func TestCouchDBVersion(t *testing.T) {
   837  	config := testConfig()
   838  	couchDBEnv.startCouchDB(t)
   839  	config.Address = couchDBEnv.couchAddress
   840  	defer couchDBEnv.cleanup(config)
   841  
   842  	err := checkCouchDBVersion("2.0.0")
   843  	require.NoError(t, err, "Error should not have been thrown for valid version")
   844  
   845  	err = checkCouchDBVersion("4.5.0")
   846  	require.NoError(t, err, "Error should not have been thrown for valid version")
   847  
   848  	err = checkCouchDBVersion("1.6.5.4")
   849  	require.Error(t, err, "Error should have been thrown for invalid version")
   850  
   851  	err = checkCouchDBVersion("0.0.0.0")
   852  	require.Error(t, err, "Error should have been thrown for invalid version")
   853  }
   854  
   855  func TestIndexOperations(t *testing.T) {
   856  	config := testConfig()
   857  	couchDBEnv.startCouchDB(t)
   858  	config.Address = couchDBEnv.couchAddress
   859  	defer couchDBEnv.cleanup(config)
   860  	database := "testindexoperations"
   861  
   862  	byteJSON1 := []byte(`{"_id":"1", "asset_name":"marble1","color":"blue","size":1,"owner":"jerry"}`)
   863  	byteJSON2 := []byte(`{"_id":"2", "asset_name":"marble2","color":"red","size":2,"owner":"tom"}`)
   864  	byteJSON3 := []byte(`{"_id":"3", "asset_name":"marble3","color":"green","size":3,"owner":"jerry"}`)
   865  	byteJSON4 := []byte(`{"_id":"4", "asset_name":"marble4","color":"purple","size":4,"owner":"tom"}`)
   866  	byteJSON5 := []byte(`{"_id":"5", "asset_name":"marble5","color":"blue","size":5,"owner":"jerry"}`)
   867  	byteJSON6 := []byte(`{"_id":"6", "asset_name":"marble6","color":"white","size":6,"owner":"tom"}`)
   868  	byteJSON7 := []byte(`{"_id":"7", "asset_name":"marble7","color":"white","size":7,"owner":"tom"}`)
   869  	byteJSON8 := []byte(`{"_id":"8", "asset_name":"marble8","color":"white","size":8,"owner":"tom"}`)
   870  	byteJSON9 := []byte(`{"_id":"9", "asset_name":"marble9","color":"white","size":9,"owner":"tom"}`)
   871  	byteJSON10 := []byte(`{"_id":"10", "asset_name":"marble10","color":"white","size":10,"owner":"tom"}`)
   872  
   873  	// create a new instance and database object   --------------------------------------------------------
   874  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
   875  	require.NoError(t, err, "Error when trying to create couch instance")
   876  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
   877  
   878  	// create a new database
   879  	errdb := db.createDatabaseIfNotExist()
   880  	require.NoError(t, errdb, "Error when trying to create database")
   881  
   882  	batchUpdateDocs := []*couchDoc{}
   883  
   884  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON1, attachments: nil})
   885  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON2, attachments: nil})
   886  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON3, attachments: nil})
   887  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON4, attachments: nil})
   888  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON5, attachments: nil})
   889  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON6, attachments: nil})
   890  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON7, attachments: nil})
   891  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON8, attachments: nil})
   892  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON9, attachments: nil})
   893  	batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: byteJSON10, attachments: nil})
   894  
   895  	_, err = db.batchUpdateDocuments(batchUpdateDocs)
   896  	require.NoError(t, err, "Error adding batch of documents")
   897  
   898  	// Create an index definition
   899  	indexDefSize := `{"index":{"fields":[{"size":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortName","type":"json"}`
   900  
   901  	// Create the index
   902  	_, err = db.createIndex(indexDefSize)
   903  	require.NoError(t, err, "Error thrown while creating an index")
   904  
   905  	// Retrieve the list of indexes
   906  	// Delay for 100ms since CouchDB index list is updated async after index create/drop
   907  	time.Sleep(100 * time.Millisecond)
   908  	listResult, err := db.listIndex()
   909  	require.NoError(t, err, "Error thrown while retrieving indexes")
   910  
   911  	// There should only be one item returned
   912  	require.Equal(t, 1, len(listResult))
   913  
   914  	// Verify the returned definition
   915  	for _, elem := range listResult {
   916  		require.Equal(t, "indexSizeSortDoc", elem.DesignDocument)
   917  		require.Equal(t, "indexSizeSortName", elem.Name)
   918  		// ensure the index definition is correct,  CouchDB 2.1.1 will also return "partial_filter_selector":{}
   919  		require.Equal(t, true, strings.Contains(elem.Definition, `"fields":[{"size":"desc"}]`))
   920  	}
   921  
   922  	// Create an index definition with no DesignDocument or name
   923  	indexDefColor := `{"index":{"fields":[{"color":"desc"}]}}`
   924  
   925  	// Create the index
   926  	_, err = db.createIndex(indexDefColor)
   927  	require.NoError(t, err, "Error thrown while creating an index")
   928  
   929  	// Retrieve the list of indexes
   930  	// Delay for 100ms since CouchDB index list is updated async after index create/drop
   931  	time.Sleep(100 * time.Millisecond)
   932  	listResult, err = db.listIndex()
   933  	require.NoError(t, err, "Error thrown while retrieving indexes")
   934  
   935  	// There should be two indexes returned
   936  	require.Equal(t, 2, len(listResult))
   937  
   938  	// Delete the named index
   939  	err = db.deleteIndex("indexSizeSortDoc", "indexSizeSortName")
   940  	require.NoError(t, err, "Error thrown while deleting an index")
   941  
   942  	// Retrieve the list of indexes
   943  	// Delay for 100ms since CouchDB index list is updated async after index create/drop
   944  	time.Sleep(100 * time.Millisecond)
   945  	listResult, err = db.listIndex()
   946  	require.NoError(t, err, "Error thrown while retrieving indexes")
   947  
   948  	// There should be one index returned
   949  	require.Equal(t, 1, len(listResult))
   950  
   951  	// Delete the unnamed index
   952  	for _, elem := range listResult {
   953  		err = db.deleteIndex(elem.DesignDocument, elem.Name)
   954  		require.NoError(t, err, "Error thrown while deleting an index")
   955  	}
   956  
   957  	// Retrieve the list of indexes
   958  	// Delay for 100ms since CouchDB index list is updated async after index create/drop
   959  	time.Sleep(100 * time.Millisecond)
   960  	listResult, err = db.listIndex()
   961  	require.NoError(t, err, "Error thrown while retrieving indexes")
   962  	require.Equal(t, 0, len(listResult))
   963  
   964  	// Create a query string with a descending sort, this will require an index
   965  	queryString := `{"selector":{"size": {"$gt": 0}},"fields": ["_id", "_rev", "owner", "asset_name", "color", "size"], "sort":[{"size":"desc"}], "limit": 10,"skip": 0}`
   966  
   967  	// Execute a query with a sort, this should throw the exception
   968  	_, _, err = db.queryDocuments(queryString)
   969  	require.Error(t, err, "Error should have thrown while querying without a valid index")
   970  
   971  	// Create the index
   972  	_, err = db.createIndex(indexDefSize)
   973  	require.NoError(t, err, "Error thrown while creating an index")
   974  
   975  	// Delay for 100ms since CouchDB index list is updated async after index create/drop
   976  	time.Sleep(100 * time.Millisecond)
   977  
   978  	// Execute a query with an index,  this should succeed
   979  	_, _, err = db.queryDocuments(queryString)
   980  	require.NoError(t, err, "Error thrown while querying with an index")
   981  
   982  	// Create another index definition
   983  	indexDefSize = `{"index":{"fields":[{"data.size":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeOwnerSortDoc", "name":"indexSizeOwnerSortName","type":"json"}`
   984  
   985  	// Create the index
   986  	dbResp, err := db.createIndex(indexDefSize)
   987  	require.NoError(t, err, "Error thrown while creating an index")
   988  
   989  	// verify the response is "created" for an index creation
   990  	require.Equal(t, "created", dbResp.Result)
   991  
   992  	// Delay for 100ms since CouchDB index list is updated async after index create/drop
   993  	time.Sleep(100 * time.Millisecond)
   994  
   995  	// Update the index
   996  	dbResp, err = db.createIndex(indexDefSize)
   997  	require.NoError(t, err, "Error thrown while creating an index")
   998  
   999  	// verify the response is "exists" for an update
  1000  	require.Equal(t, "exists", dbResp.Result)
  1001  
  1002  	// Retrieve the list of indexes
  1003  	// Delay for 100ms since CouchDB index list is updated async after index create/drop
  1004  	time.Sleep(100 * time.Millisecond)
  1005  	listResult, err = db.listIndex()
  1006  	require.NoError(t, err, "Error thrown while retrieving indexes")
  1007  
  1008  	// There should only be two definitions
  1009  	require.Equal(t, 2, len(listResult))
  1010  
  1011  	// Create an invalid index definition with an invalid JSON
  1012  	indexDefSize = `{"index"{"fields":[{"data.size":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeOwnerSortDoc", "name":"indexSizeOwnerSortName","type":"json"}`
  1013  
  1014  	// Create the index
  1015  	_, err = db.createIndex(indexDefSize)
  1016  	require.Error(t, err, "Error should have been thrown for an invalid index JSON")
  1017  
  1018  	// Create an invalid index definition with a valid JSON and an invalid index definition
  1019  	indexDefSize = `{"index":{"fields2":[{"data.size":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeOwnerSortDoc", "name":"indexSizeOwnerSortName","type":"json"}`
  1020  
  1021  	// Create the index
  1022  	_, err = db.createIndex(indexDefSize)
  1023  	require.Error(t, err, "Error should have been thrown for an invalid index definition")
  1024  }
  1025  
  1026  func TestRichQuery(t *testing.T) {
  1027  	config := testConfig()
  1028  	couchDBEnv.startCouchDB(t)
  1029  	config.Address = couchDBEnv.couchAddress
  1030  	defer couchDBEnv.cleanup(config)
  1031  	byteJSON01 := []byte(`{"asset_name":"marble01","color":"blue","size":1,"owner":"jerry"}`)
  1032  	byteJSON02 := []byte(`{"asset_name":"marble02","color":"red","size":2,"owner":"tom"}`)
  1033  	byteJSON03 := []byte(`{"asset_name":"marble03","color":"green","size":3,"owner":"jerry"}`)
  1034  	byteJSON04 := []byte(`{"asset_name":"marble04","color":"purple","size":4,"owner":"tom"}`)
  1035  	byteJSON05 := []byte(`{"asset_name":"marble05","color":"blue","size":5,"owner":"jerry"}`)
  1036  	byteJSON06 := []byte(`{"asset_name":"marble06","color":"white","size":6,"owner":"tom"}`)
  1037  	byteJSON07 := []byte(`{"asset_name":"marble07","color":"white","size":7,"owner":"tom"}`)
  1038  	byteJSON08 := []byte(`{"asset_name":"marble08","color":"white","size":8,"owner":"tom"}`)
  1039  	byteJSON09 := []byte(`{"asset_name":"marble09","color":"white","size":9,"owner":"tom"}`)
  1040  	byteJSON10 := []byte(`{"asset_name":"marble10","color":"white","size":10,"owner":"tom"}`)
  1041  	byteJSON11 := []byte(`{"asset_name":"marble11","color":"green","size":11,"owner":"tom"}`)
  1042  	byteJSON12 := []byte(`{"asset_name":"marble12","color":"green","size":12,"owner":"frank"}`)
  1043  
  1044  	attachment1 := &attachmentInfo{}
  1045  	attachment1.AttachmentBytes = []byte(`marble01 - test attachment`)
  1046  	attachment1.ContentType = "application/octet-stream"
  1047  	attachment1.Name = "data"
  1048  	attachments1 := []*attachmentInfo{}
  1049  	attachments1 = append(attachments1, attachment1)
  1050  
  1051  	attachment2 := &attachmentInfo{}
  1052  	attachment2.AttachmentBytes = []byte(`marble02 - test attachment`)
  1053  	attachment2.ContentType = "application/octet-stream"
  1054  	attachment2.Name = "data"
  1055  	attachments2 := []*attachmentInfo{}
  1056  	attachments2 = append(attachments2, attachment2)
  1057  
  1058  	attachment3 := &attachmentInfo{}
  1059  	attachment3.AttachmentBytes = []byte(`marble03 - test attachment`)
  1060  	attachment3.ContentType = "application/octet-stream"
  1061  	attachment3.Name = "data"
  1062  	attachments3 := []*attachmentInfo{}
  1063  	attachments3 = append(attachments3, attachment3)
  1064  
  1065  	attachment4 := &attachmentInfo{}
  1066  	attachment4.AttachmentBytes = []byte(`marble04 - test attachment`)
  1067  	attachment4.ContentType = "application/octet-stream"
  1068  	attachment4.Name = "data"
  1069  	attachments4 := []*attachmentInfo{}
  1070  	attachments4 = append(attachments4, attachment4)
  1071  
  1072  	attachment5 := &attachmentInfo{}
  1073  	attachment5.AttachmentBytes = []byte(`marble05 - test attachment`)
  1074  	attachment5.ContentType = "application/octet-stream"
  1075  	attachment5.Name = "data"
  1076  	attachments5 := []*attachmentInfo{}
  1077  	attachments5 = append(attachments5, attachment5)
  1078  
  1079  	attachment6 := &attachmentInfo{}
  1080  	attachment6.AttachmentBytes = []byte(`marble06 - test attachment`)
  1081  	attachment6.ContentType = "application/octet-stream"
  1082  	attachment6.Name = "data"
  1083  	attachments6 := []*attachmentInfo{}
  1084  	attachments6 = append(attachments6, attachment6)
  1085  
  1086  	attachment7 := &attachmentInfo{}
  1087  	attachment7.AttachmentBytes = []byte(`marble07 - test attachment`)
  1088  	attachment7.ContentType = "application/octet-stream"
  1089  	attachment7.Name = "data"
  1090  	attachments7 := []*attachmentInfo{}
  1091  	attachments7 = append(attachments7, attachment7)
  1092  
  1093  	attachment8 := &attachmentInfo{}
  1094  	attachment8.AttachmentBytes = []byte(`marble08 - test attachment`)
  1095  	attachment8.ContentType = "application/octet-stream"
  1096  	attachment7.Name = "data"
  1097  	attachments8 := []*attachmentInfo{}
  1098  	attachments8 = append(attachments8, attachment8)
  1099  
  1100  	attachment9 := &attachmentInfo{}
  1101  	attachment9.AttachmentBytes = []byte(`marble09 - test attachment`)
  1102  	attachment9.ContentType = "application/octet-stream"
  1103  	attachment9.Name = "data"
  1104  	attachments9 := []*attachmentInfo{}
  1105  	attachments9 = append(attachments9, attachment9)
  1106  
  1107  	attachment10 := &attachmentInfo{}
  1108  	attachment10.AttachmentBytes = []byte(`marble10 - test attachment`)
  1109  	attachment10.ContentType = "application/octet-stream"
  1110  	attachment10.Name = "data"
  1111  	attachments10 := []*attachmentInfo{}
  1112  	attachments10 = append(attachments10, attachment10)
  1113  
  1114  	attachment11 := &attachmentInfo{}
  1115  	attachment11.AttachmentBytes = []byte(`marble11 - test attachment`)
  1116  	attachment11.ContentType = "application/octet-stream"
  1117  	attachment11.Name = "data"
  1118  	attachments11 := []*attachmentInfo{}
  1119  	attachments11 = append(attachments11, attachment11)
  1120  
  1121  	attachment12 := &attachmentInfo{}
  1122  	attachment12.AttachmentBytes = []byte(`marble12 - test attachment`)
  1123  	attachment12.ContentType = "application/octet-stream"
  1124  	attachment12.Name = "data"
  1125  	attachments12 := []*attachmentInfo{}
  1126  	attachments12 = append(attachments12, attachment12)
  1127  
  1128  	database := "testrichquery"
  1129  
  1130  	// create a new instance and database object   --------------------------------------------------------
  1131  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
  1132  	require.NoError(t, err, "Error when trying to create couch instance")
  1133  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
  1134  
  1135  	// create a new database
  1136  	errdb := db.createDatabaseIfNotExist()
  1137  	require.NoError(t, errdb, "Error when trying to create database")
  1138  
  1139  	// Save the test document
  1140  	_, saveerr := db.saveDoc("marble01", "", &couchDoc{jsonValue: byteJSON01, attachments: attachments1})
  1141  	require.NoError(t, saveerr, "Error when trying to save a document")
  1142  
  1143  	// Save the test document
  1144  	_, saveerr = db.saveDoc("marble02", "", &couchDoc{jsonValue: byteJSON02, attachments: attachments2})
  1145  	require.NoError(t, saveerr, "Error when trying to save a document")
  1146  
  1147  	// Save the test document
  1148  	_, saveerr = db.saveDoc("marble03", "", &couchDoc{jsonValue: byteJSON03, attachments: attachments3})
  1149  	require.NoError(t, saveerr, "Error when trying to save a document")
  1150  
  1151  	// Save the test document
  1152  	_, saveerr = db.saveDoc("marble04", "", &couchDoc{jsonValue: byteJSON04, attachments: attachments4})
  1153  	require.NoError(t, saveerr, "Error when trying to save a document")
  1154  
  1155  	// Save the test document
  1156  	_, saveerr = db.saveDoc("marble05", "", &couchDoc{jsonValue: byteJSON05, attachments: attachments5})
  1157  	require.NoError(t, saveerr, "Error when trying to save a document")
  1158  
  1159  	// Save the test document
  1160  	_, saveerr = db.saveDoc("marble06", "", &couchDoc{jsonValue: byteJSON06, attachments: attachments6})
  1161  	require.NoError(t, saveerr, "Error when trying to save a document")
  1162  
  1163  	// Save the test document
  1164  	_, saveerr = db.saveDoc("marble07", "", &couchDoc{jsonValue: byteJSON07, attachments: attachments7})
  1165  	require.NoError(t, saveerr, "Error when trying to save a document")
  1166  
  1167  	// Save the test document
  1168  	_, saveerr = db.saveDoc("marble08", "", &couchDoc{jsonValue: byteJSON08, attachments: attachments8})
  1169  	require.NoError(t, saveerr, "Error when trying to save a document")
  1170  
  1171  	// Save the test document
  1172  	_, saveerr = db.saveDoc("marble09", "", &couchDoc{jsonValue: byteJSON09, attachments: attachments9})
  1173  	require.NoError(t, saveerr, "Error when trying to save a document")
  1174  
  1175  	// Save the test document
  1176  	_, saveerr = db.saveDoc("marble10", "", &couchDoc{jsonValue: byteJSON10, attachments: attachments10})
  1177  	require.NoError(t, saveerr, "Error when trying to save a document")
  1178  
  1179  	// Save the test document
  1180  	_, saveerr = db.saveDoc("marble11", "", &couchDoc{jsonValue: byteJSON11, attachments: attachments11})
  1181  	require.NoError(t, saveerr, "Error when trying to save a document")
  1182  
  1183  	// Save the test document
  1184  	_, saveerr = db.saveDoc("marble12", "", &couchDoc{jsonValue: byteJSON12, attachments: attachments12})
  1185  	require.NoError(t, saveerr, "Error when trying to save a document")
  1186  
  1187  	// Test query with invalid JSON -------------------------------------------------------------------
  1188  	queryString := `{"selector":{"owner":}}`
  1189  
  1190  	_, _, err = db.queryDocuments(queryString)
  1191  	require.Error(t, err, "Error should have been thrown for bad json")
  1192  
  1193  	// Test query with object  -------------------------------------------------------------------
  1194  	queryString = `{"selector":{"owner":{"$eq":"jerry"}}}`
  1195  
  1196  	queryResult, _, err := db.queryDocuments(queryString)
  1197  	require.NoError(t, err, "Error when attempting to execute a query")
  1198  
  1199  	// There should be 3 results for owner="jerry"
  1200  	require.Equal(t, 3, len(queryResult))
  1201  
  1202  	// Test query with implicit operator   --------------------------------------------------------------
  1203  	queryString = `{"selector":{"owner":"jerry"}}`
  1204  
  1205  	queryResult, _, err = db.queryDocuments(queryString)
  1206  	require.NoError(t, err, "Error when attempting to execute a query")
  1207  
  1208  	// There should be 3 results for owner="jerry"
  1209  	require.Equal(t, 3, len(queryResult))
  1210  
  1211  	// Test query with specified fields   -------------------------------------------------------------------
  1212  	queryString = `{"selector":{"owner":{"$eq":"jerry"}},"fields": ["owner","asset_name","color","size"]}`
  1213  
  1214  	queryResult, _, err = db.queryDocuments(queryString)
  1215  	require.NoError(t, err, "Error when attempting to execute a query")
  1216  
  1217  	// There should be 3 results for owner="jerry"
  1218  	require.Equal(t, 3, len(queryResult))
  1219  
  1220  	// Test query with a leading operator   -------------------------------------------------------------------
  1221  	queryString = `{"selector":{"$or":[{"owner":{"$eq":"jerry"}},{"owner": {"$eq": "frank"}}]}}`
  1222  
  1223  	queryResult, _, err = db.queryDocuments(queryString)
  1224  	require.NoError(t, err, "Error when attempting to execute a query")
  1225  
  1226  	// There should be 4 results for owner="jerry" or owner="frank"
  1227  	require.Equal(t, 4, len(queryResult))
  1228  
  1229  	// Test query implicit and explicit operator   ------------------------------------------------------------------
  1230  	queryString = `{"selector":{"color":"green","$or":[{"owner":"tom"},{"owner":"frank"}]}}`
  1231  
  1232  	queryResult, _, err = db.queryDocuments(queryString)
  1233  	require.NoError(t, err, "Error when attempting to execute a query")
  1234  
  1235  	// There should be 2 results for color="green" and (owner="jerry" or owner="frank")
  1236  	require.Equal(t, 2, len(queryResult))
  1237  
  1238  	// Test query with a leading operator  -------------------------------------------------------------------------
  1239  	queryString = `{"selector":{"$and":[{"size":{"$gte":2}},{"size":{"$lte":5}}]}}`
  1240  
  1241  	queryResult, _, err = db.queryDocuments(queryString)
  1242  	require.NoError(t, err, "Error when attempting to execute a query")
  1243  
  1244  	// There should be 4 results for size >= 2 and size <= 5
  1245  	require.Equal(t, 4, len(queryResult))
  1246  
  1247  	// Test query with leading and embedded operator  -------------------------------------------------------------
  1248  	queryString = `{"selector":{"$and":[{"size":{"$gte":3}},{"size":{"$lte":10}},{"$not":{"size":7}}]}}`
  1249  
  1250  	queryResult, _, err = db.queryDocuments(queryString)
  1251  	require.NoError(t, err, "Error when attempting to execute a query")
  1252  
  1253  	// There should be 7 results for size >= 3 and size <= 10 and not 7
  1254  	require.Equal(t, 7, len(queryResult))
  1255  
  1256  	// Test query with leading operator and array of objects ----------------------------------------------------------
  1257  	queryString = `{"selector":{"$and":[{"size":{"$gte":2}},{"size":{"$lte":10}},{"$nor":[{"size":3},{"size":5},{"size":7}]}]}}`
  1258  
  1259  	queryResult, _, err = db.queryDocuments(queryString)
  1260  	require.NoError(t, err, "Error when attempting to execute a query")
  1261  
  1262  	// There should be 6 results for size >= 2 and size <= 10 and not 3,5 or 7
  1263  	require.Equal(t, 6, len(queryResult))
  1264  
  1265  	// Test a range query ---------------------------------------------------------------------------------------------
  1266  	queryResult, _, err = db.readDocRange("marble02", "marble06", 10000)
  1267  	require.NoError(t, err, "Error when attempting to execute a range query")
  1268  
  1269  	// There should be 4 results
  1270  	require.Equal(t, 4, len(queryResult))
  1271  
  1272  	// Attachments retrieved should be correct
  1273  	require.Equal(t, attachment2.AttachmentBytes, queryResult[0].attachments[0].AttachmentBytes)
  1274  	require.Equal(t, attachment3.AttachmentBytes, queryResult[1].attachments[0].AttachmentBytes)
  1275  	require.Equal(t, attachment4.AttachmentBytes, queryResult[2].attachments[0].AttachmentBytes)
  1276  	require.Equal(t, attachment5.AttachmentBytes, queryResult[3].attachments[0].AttachmentBytes)
  1277  
  1278  	// Test query with for tom  -------------------------------------------------------------------
  1279  	queryString = `{"selector":{"owner":{"$eq":"tom"}}}`
  1280  
  1281  	queryResult, _, err = db.queryDocuments(queryString)
  1282  	require.NoError(t, err, "Error when attempting to execute a query")
  1283  
  1284  	// There should be 8 results for owner="tom"
  1285  	require.Equal(t, 8, len(queryResult))
  1286  
  1287  	// Test query with for tom with limit  -------------------------------------------------------------------
  1288  	queryString = `{"selector":{"owner":{"$eq":"tom"}},"limit":2}`
  1289  
  1290  	queryResult, _, err = db.queryDocuments(queryString)
  1291  	require.NoError(t, err, "Error when attempting to execute a query")
  1292  
  1293  	// There should be 2 results for owner="tom" with a limit of 2
  1294  	require.Equal(t, 2, len(queryResult))
  1295  
  1296  	// Create an index definition
  1297  	indexDefSize := `{"index":{"fields":[{"size":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortName","type":"json"}`
  1298  
  1299  	// Create the index
  1300  	_, err = db.createIndex(indexDefSize)
  1301  	require.NoError(t, err, "Error thrown while creating an index")
  1302  
  1303  	// Delay for 100ms since CouchDB index list is updated async after index create/drop
  1304  	time.Sleep(100 * time.Millisecond)
  1305  
  1306  	// Test query with valid index  -------------------------------------------------------------------
  1307  	queryString = `{"selector":{"size":{"$gt":0}}, "use_index":["indexSizeSortDoc","indexSizeSortName"]}`
  1308  
  1309  	_, _, err = db.queryDocuments(queryString)
  1310  	require.NoError(t, err, "Error when attempting to execute a query with a valid index")
  1311  }
  1312  
  1313  func testBatchBatchOperations(t *testing.T, config *ledger.CouchDBConfig) {
  1314  	byteJSON01 := []byte(`{"_id":"marble01","asset_name":"marble01","color":"blue","size":"1","owner":"jerry"}`)
  1315  	byteJSON02 := []byte(`{"_id":"marble02","asset_name":"marble02","color":"red","size":"2","owner":"tom"}`)
  1316  	byteJSON03 := []byte(`{"_id":"marble03","asset_name":"marble03","color":"green","size":"3","owner":"jerry"}`)
  1317  	byteJSON04 := []byte(`{"_id":"marble04","asset_name":"marble04","color":"purple","size":"4","owner":"tom"}`)
  1318  	byteJSON05 := []byte(`{"_id":"marble05","asset_name":"marble05","color":"blue","size":"5","owner":"jerry"}`)
  1319  	byteJSON06 := []byte(`{"_id":"marble06#$&'()*+,/:;=?@[]","asset_name":"marble06#$&'()*+,/:;=?@[]","color":"blue","size":"6","owner":"jerry"}`)
  1320  
  1321  	attachment1 := &attachmentInfo{}
  1322  	attachment1.AttachmentBytes = []byte(`marble01 - test attachment`)
  1323  	attachment1.ContentType = "application/octet-stream"
  1324  	attachment1.Name = "data"
  1325  	attachments1 := []*attachmentInfo{}
  1326  	attachments1 = append(attachments1, attachment1)
  1327  
  1328  	attachment2 := &attachmentInfo{}
  1329  	attachment2.AttachmentBytes = []byte(`marble02 - test attachment`)
  1330  	attachment2.ContentType = "application/octet-stream"
  1331  	attachment2.Name = "data"
  1332  	attachments2 := []*attachmentInfo{}
  1333  	attachments2 = append(attachments2, attachment2)
  1334  
  1335  	attachment3 := &attachmentInfo{}
  1336  	attachment3.AttachmentBytes = []byte(`marble03 - test attachment`)
  1337  	attachment3.ContentType = "application/octet-stream"
  1338  	attachment3.Name = "data"
  1339  	attachments3 := []*attachmentInfo{}
  1340  	attachments3 = append(attachments3, attachment3)
  1341  
  1342  	attachment4 := &attachmentInfo{}
  1343  	attachment4.AttachmentBytes = []byte(`marble04 - test attachment`)
  1344  	attachment4.ContentType = "application/octet-stream"
  1345  	attachment4.Name = "data"
  1346  	attachments4 := []*attachmentInfo{}
  1347  	attachments4 = append(attachments4, attachment4)
  1348  
  1349  	attachment5 := &attachmentInfo{}
  1350  	attachment5.AttachmentBytes = []byte(`marble05 - test attachment`)
  1351  	attachment5.ContentType = "application/octet-stream"
  1352  	attachment5.Name = "data"
  1353  	attachments5 := []*attachmentInfo{}
  1354  	attachments5 = append(attachments5, attachment5)
  1355  
  1356  	attachment6 := &attachmentInfo{}
  1357  	attachment6.AttachmentBytes = []byte(`marble06#$&'()*+,/:;=?@[] - test attachment`)
  1358  	attachment6.ContentType = "application/octet-stream"
  1359  	attachment6.Name = "data"
  1360  	attachments6 := []*attachmentInfo{}
  1361  	attachments6 = append(attachments6, attachment6)
  1362  
  1363  	database := "testbatch"
  1364  
  1365  	// create a new instance and database object   --------------------------------------------------------
  1366  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
  1367  	require.NoError(t, err, "Error when trying to create couch instance")
  1368  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
  1369  
  1370  	// create a new database
  1371  	errdb := db.createDatabaseIfNotExist()
  1372  	require.NoError(t, errdb, "Error when trying to create database")
  1373  
  1374  	batchUpdateDocs := []*couchDoc{}
  1375  
  1376  	value1 := &couchDoc{jsonValue: byteJSON01, attachments: attachments1}
  1377  	value2 := &couchDoc{jsonValue: byteJSON02, attachments: attachments2}
  1378  	value3 := &couchDoc{jsonValue: byteJSON03, attachments: attachments3}
  1379  	value4 := &couchDoc{jsonValue: byteJSON04, attachments: attachments4}
  1380  	value5 := &couchDoc{jsonValue: byteJSON05, attachments: attachments5}
  1381  	value6 := &couchDoc{jsonValue: byteJSON06, attachments: attachments6}
  1382  
  1383  	batchUpdateDocs = append(batchUpdateDocs, value1)
  1384  	batchUpdateDocs = append(batchUpdateDocs, value2)
  1385  	batchUpdateDocs = append(batchUpdateDocs, value3)
  1386  	batchUpdateDocs = append(batchUpdateDocs, value4)
  1387  	batchUpdateDocs = append(batchUpdateDocs, value5)
  1388  	batchUpdateDocs = append(batchUpdateDocs, value6)
  1389  
  1390  	batchUpdateResp, err := db.batchUpdateDocuments(batchUpdateDocs)
  1391  	require.NoError(t, err, "Error when attempting to update a batch of documents")
  1392  
  1393  	// check to make sure each batch update response was successful
  1394  	for _, updateDoc := range batchUpdateResp {
  1395  		require.Equal(t, true, updateDoc.Ok)
  1396  	}
  1397  
  1398  	//----------------------------------------------
  1399  	//Test Retrieve JSON
  1400  	dbGetResp, _, geterr := db.readDoc("marble01")
  1401  	require.NoError(t, geterr, "Error when attempting read a document")
  1402  
  1403  	assetResp := &Asset{}
  1404  	geterr = json.Unmarshal(dbGetResp.jsonValue, &assetResp)
  1405  	require.NoError(t, geterr, "Error when trying to retrieve a document")
  1406  	// Verify the owner retrieved matches
  1407  	require.Equal(t, "jerry", assetResp.Owner)
  1408  
  1409  	//----------------------------------------------
  1410  	// Test Retrieve JSON using ID with URL special characters,
  1411  	// this will confirm that batch document IDs and URL IDs are consistent, even if they include special characters
  1412  	dbGetResp, _, geterr = db.readDoc("marble06#$&'()*+,/:;=?@[]")
  1413  	require.NoError(t, geterr, "Error when attempting read a document")
  1414  
  1415  	assetResp = &Asset{}
  1416  	geterr = json.Unmarshal(dbGetResp.jsonValue, &assetResp)
  1417  	require.NoError(t, geterr, "Error when trying to retrieve a document")
  1418  	// Verify the owner retrieved matches
  1419  	require.Equal(t, "jerry", assetResp.Owner)
  1420  
  1421  	//----------------------------------------------
  1422  	//Test retrieve binary
  1423  	dbGetResp, _, geterr = db.readDoc("marble03")
  1424  	require.NoError(t, geterr, "Error when attempting read a document")
  1425  	// Retrieve the attachments
  1426  	attachments := dbGetResp.attachments
  1427  	// Only one was saved, so take the first
  1428  	retrievedAttachment := attachments[0]
  1429  	// Verify the text matches
  1430  	require.Equal(t, retrievedAttachment.AttachmentBytes, attachment3.AttachmentBytes)
  1431  	//----------------------------------------------
  1432  	//Test Bad Updates
  1433  	batchUpdateDocs = []*couchDoc{}
  1434  	batchUpdateDocs = append(batchUpdateDocs, value1)
  1435  	batchUpdateDocs = append(batchUpdateDocs, value2)
  1436  	batchUpdateResp, err = db.batchUpdateDocuments(batchUpdateDocs)
  1437  	require.NoError(t, err, "Error when attempting to update a batch of documents")
  1438  	// No revision was provided, so these two updates should fail
  1439  	// Verify that the "Ok" field is returned as false
  1440  	for _, updateDoc := range batchUpdateResp {
  1441  		require.Equal(t, false, updateDoc.Ok)
  1442  		require.Equal(t, updateDocumentConflictError, updateDoc.Error)
  1443  		require.Equal(t, updateDocumentConflictReason, updateDoc.Reason)
  1444  	}
  1445  
  1446  	//----------------------------------------------
  1447  	//Test Batch Retrieve Keys and Update
  1448  
  1449  	var keys []string
  1450  
  1451  	keys = append(keys, "marble01")
  1452  	keys = append(keys, "marble03")
  1453  
  1454  	batchRevs, err := db.batchRetrieveDocumentMetadata(keys)
  1455  	require.NoError(t, err, "Error when attempting retrieve revisions")
  1456  
  1457  	batchUpdateDocs = []*couchDoc{}
  1458  
  1459  	// iterate through the revision docs
  1460  	for _, revdoc := range batchRevs {
  1461  		if revdoc.ID == "marble01" {
  1462  			// update the json with the rev and add to the batch
  1463  			marble01Doc := addRevisionAndDeleteStatus(t, revdoc.Rev, byteJSON01, false)
  1464  			batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: marble01Doc, attachments: attachments1})
  1465  		}
  1466  
  1467  		if revdoc.ID == "marble03" {
  1468  			// update the json with the rev and add to the batch
  1469  			marble03Doc := addRevisionAndDeleteStatus(t, revdoc.Rev, byteJSON03, false)
  1470  			batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: marble03Doc, attachments: attachments3})
  1471  		}
  1472  	}
  1473  
  1474  	// Update couchdb with the batch
  1475  	batchUpdateResp, err = db.batchUpdateDocuments(batchUpdateDocs)
  1476  	require.NoError(t, err, "Error when attempting to update a batch of documents")
  1477  	// check to make sure each batch update response was successful
  1478  	for _, updateDoc := range batchUpdateResp {
  1479  		require.Equal(t, true, updateDoc.Ok)
  1480  	}
  1481  
  1482  	//----------------------------------------------
  1483  	//Test Batch Delete
  1484  
  1485  	keys = []string{}
  1486  
  1487  	keys = append(keys, "marble02")
  1488  	keys = append(keys, "marble04")
  1489  
  1490  	batchRevs, err = db.batchRetrieveDocumentMetadata(keys)
  1491  	require.NoError(t, err, "Error when attempting retrieve revisions")
  1492  
  1493  	batchUpdateDocs = []*couchDoc{}
  1494  
  1495  	// iterate through the revision docs
  1496  	for _, revdoc := range batchRevs {
  1497  		if revdoc.ID == "marble02" {
  1498  			// update the json with the rev and add to the batch
  1499  			marble02Doc := addRevisionAndDeleteStatus(t, revdoc.Rev, byteJSON02, true)
  1500  			batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: marble02Doc, attachments: attachments1})
  1501  		}
  1502  		if revdoc.ID == "marble04" {
  1503  			// update the json with the rev and add to the batch
  1504  			marble04Doc := addRevisionAndDeleteStatus(t, revdoc.Rev, byteJSON04, true)
  1505  			batchUpdateDocs = append(batchUpdateDocs, &couchDoc{jsonValue: marble04Doc, attachments: attachments3})
  1506  		}
  1507  	}
  1508  
  1509  	// Update couchdb with the batch
  1510  	batchUpdateResp, err = db.batchUpdateDocuments(batchUpdateDocs)
  1511  	require.NoError(t, err, "Error when attempting to update a batch of documents")
  1512  
  1513  	// check to make sure each batch update response was successful
  1514  	for _, updateDoc := range batchUpdateResp {
  1515  		require.Equal(t, true, updateDoc.Ok)
  1516  	}
  1517  
  1518  	// Retrieve the test document
  1519  	dbGetResp, _, geterr = db.readDoc("marble02")
  1520  	require.NoError(t, geterr, "Error when trying to retrieve a document")
  1521  
  1522  	// assert the value was deleted
  1523  	require.Nil(t, dbGetResp)
  1524  
  1525  	// Retrieve the test document
  1526  	dbGetResp, _, geterr = db.readDoc("marble04")
  1527  	require.NoError(t, geterr, "Error when trying to retrieve a document")
  1528  
  1529  	// assert the value was deleted
  1530  	require.Nil(t, dbGetResp)
  1531  }
  1532  
  1533  // addRevisionAndDeleteStatus adds keys for version and chaincodeID to the JSON value
  1534  func addRevisionAndDeleteStatus(t *testing.T, revision string, value []byte, deleted bool) []byte {
  1535  	// create a version mapping
  1536  	jsonMap := make(map[string]interface{})
  1537  
  1538  	require.NoError(t, json.Unmarshal(value, &jsonMap))
  1539  
  1540  	// add the revision
  1541  	if revision != "" {
  1542  		jsonMap["_rev"] = revision
  1543  	}
  1544  
  1545  	// If this record is to be deleted, set the "_deleted" property to true
  1546  	if deleted {
  1547  		jsonMap["_deleted"] = true
  1548  	}
  1549  	// marshal the data to a byte array
  1550  	returnJSON, _ := json.Marshal(jsonMap)
  1551  
  1552  	return returnJSON
  1553  }
  1554  
  1555  func TestDatabaseSecuritySettings(t *testing.T) {
  1556  	config := testConfig()
  1557  	couchDBEnv.startCouchDB(t)
  1558  	config.Address = couchDBEnv.couchAddress
  1559  	defer couchDBEnv.cleanup(config)
  1560  	database := "testdbsecuritysettings"
  1561  
  1562  	// create a new instance and database object   --------------------------------------------------------
  1563  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
  1564  	require.NoError(t, err, "Error when trying to create couch instance")
  1565  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
  1566  
  1567  	// create a new database
  1568  	errdb := db.createDatabaseIfNotExist()
  1569  	require.NoError(t, errdb, "Error when trying to create database")
  1570  
  1571  	// Create a database security object
  1572  	securityPermissions := &databaseSecurity{}
  1573  	securityPermissions.Admins.Names = append(securityPermissions.Admins.Names, "admin")
  1574  	securityPermissions.Members.Names = append(securityPermissions.Members.Names, "admin")
  1575  
  1576  	// Apply security
  1577  	err = db.applyDatabaseSecurity(securityPermissions)
  1578  	require.NoError(t, err, "Error when trying to apply database security")
  1579  
  1580  	// Retrieve database security
  1581  	dbSecurity, err := db.getDatabaseSecurity()
  1582  	require.NoError(t, err, "Error when retrieving database security")
  1583  
  1584  	// Verify retrieval of admins
  1585  	require.Equal(t, "admin", dbSecurity.Admins.Names[0])
  1586  
  1587  	// Verify retrieval of members
  1588  	require.Equal(t, "admin", dbSecurity.Members.Names[0])
  1589  
  1590  	// Create an empty database security object
  1591  	securityPermissions = &databaseSecurity{}
  1592  
  1593  	// Apply the security
  1594  	err = db.applyDatabaseSecurity(securityPermissions)
  1595  	require.NoError(t, err, "Error when trying to apply database security")
  1596  
  1597  	// Retrieve database security
  1598  	dbSecurity, err = db.getDatabaseSecurity()
  1599  	require.NoError(t, err, "Error when retrieving database security")
  1600  
  1601  	// Verify retrieval of admins, should be an empty array
  1602  	require.Equal(t, 0, len(dbSecurity.Admins.Names))
  1603  
  1604  	// Verify retrieval of members, should be an empty array
  1605  	require.Equal(t, 0, len(dbSecurity.Members.Names))
  1606  }
  1607  
  1608  func TestURLWithSpecialCharacters(t *testing.T) {
  1609  	config := testConfig()
  1610  	couchDBEnv.startCouchDB(t)
  1611  	config.Address = couchDBEnv.couchAddress
  1612  	defer couchDBEnv.cleanup(config)
  1613  	database := "testdb+with+plus_sign"
  1614  
  1615  	// parse a contructed URL
  1616  	finalURL, err := url.Parse("http://127.0.0.1:5984")
  1617  	require.NoError(t, err, "error thrown while parsing couchdb url")
  1618  
  1619  	// test the constructCouchDBUrl function with multiple path elements
  1620  	couchdbURL := constructCouchDBUrl(finalURL, database, "_index", "designdoc", "json", "indexname")
  1621  	require.Equal(t, "http://127.0.0.1:5984/testdb%2Bwith%2Bplus_sign/_index/designdoc/json/indexname", couchdbURL.String())
  1622  
  1623  	// create a new instance and database object   --------------------------------------------------------
  1624  	couchInstance, err := createCouchInstance(config, &disabled.Provider{})
  1625  	require.NoError(t, err, "Error when trying to create couch instance")
  1626  	db := couchDatabase{couchInstance: couchInstance, dbName: database}
  1627  
  1628  	// create a new database
  1629  	errdb := db.createDatabaseIfNotExist()
  1630  	require.NoError(t, errdb, "Error when trying to create database")
  1631  
  1632  	dbInfo, _, errInfo := db.getDatabaseInfo()
  1633  	require.NoError(t, errInfo, "Error when trying to get database info")
  1634  
  1635  	require.Equal(t, database, dbInfo.DbName)
  1636  }
  1637  
  1638  func TestCouchDocKey(t *testing.T) {
  1639  	m := make(jsonValue)
  1640  	m[idField] = "key-1"
  1641  	m[revField] = "rev-1"
  1642  	m["a"] = "b"
  1643  	json, err := json.Marshal(m)
  1644  	require.NoError(t, err)
  1645  	doc := &couchDoc{jsonValue: json}
  1646  	actualKey, err := doc.key()
  1647  	require.NoError(t, err)
  1648  	require.Equal(t, "key-1", actualKey)
  1649  
  1650  	doc = &couchDoc{jsonValue: []byte("random")}
  1651  	_, err = doc.key()
  1652  	require.Error(t, err)
  1653  }
  1654  
  1655  func TestCouchDocLength(t *testing.T) {
  1656  	testData := []struct {
  1657  		description    string
  1658  		doc            *couchDoc
  1659  		expectedLength int
  1660  	}{
  1661  		{
  1662  			description: "doc has a json value and attachments",
  1663  			doc: &couchDoc{
  1664  				jsonValue: []byte("7length"),
  1665  				attachments: []*attachmentInfo{
  1666  					{
  1667  						Name:            "5leng",
  1668  						ContentType:     "3le",
  1669  						AttachmentBytes: []byte("8length."),
  1670  					},
  1671  					{
  1672  						AttachmentBytes: []byte("8length."),
  1673  					},
  1674  					{},
  1675  				},
  1676  			},
  1677  			expectedLength: 31, // 7 + 5 + 3 + 8 + 8
  1678  		},
  1679  		{
  1680  			description: "doc has only json value",
  1681  			doc: &couchDoc{
  1682  				jsonValue: []byte("7length"),
  1683  			},
  1684  			expectedLength: 7,
  1685  		},
  1686  		{
  1687  			description:    "doc is empty",
  1688  			doc:            &couchDoc{},
  1689  			expectedLength: 0,
  1690  		},
  1691  		{
  1692  			description:    "doc is nil",
  1693  			doc:            nil,
  1694  			expectedLength: 0,
  1695  		},
  1696  	}
  1697  
  1698  	for _, tData := range testData {
  1699  		t.Run(tData.description, func(t *testing.T) {
  1700  			require.Equal(t, tData.expectedLength, tData.doc.len())
  1701  		})
  1702  	}
  1703  }