github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/core/ledger/kvledger/txmgmt/statedb/statecouchdb/couchdb_test.go (about)

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