github.com/rohankumardubey/aresdb@v0.0.2-0.20190517170215-e54e3ca06b9c/api/debug_handler_test.go (about)

     1  //  Copyright (c) 2017-2018 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package api
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net/http"
    22  	"net/http/httptest"
    23  
    24  	"time"
    25  
    26  	"github.com/gorilla/mux"
    27  	"github.com/onsi/ginkgo"
    28  	. "github.com/onsi/gomega"
    29  	"github.com/uber/aresdb/diskstore"
    30  	"github.com/uber/aresdb/memstore"
    31  	memCom "github.com/uber/aresdb/memstore/common"
    32  	memMocks "github.com/uber/aresdb/memstore/mocks"
    33  	"github.com/uber/aresdb/metastore"
    34  	metaCom "github.com/uber/aresdb/metastore/common"
    35  	"github.com/uber/aresdb/utils"
    36  	utilsMocks "github.com/uber/aresdb/utils/mocks"
    37  
    38  	"path/filepath"
    39  
    40  	"strconv"
    41  
    42  	"bytes"
    43  	"github.com/pkg/errors"
    44  	"github.com/stretchr/testify/mock"
    45  	"github.com/uber/aresdb/common"
    46  	"github.com/uber/aresdb/query"
    47  	"sync"
    48  	"unsafe"
    49  )
    50  
    51  // convertToAPIError wraps up an error into APIError
    52  func convertToAPIError(err error) error {
    53  	if _, ok := err.(utils.APIError); ok {
    54  		return err
    55  	}
    56  
    57  	return utils.APIError{
    58  		Message: err.Error(),
    59  	}
    60  }
    61  
    62  var _ = ginkgo.Describe("DebugHandler", func() {
    63  
    64  	testFactory := memstore.TestFactoryT{
    65  		RootPath:   "../testing/data",
    66  		FileSystem: utils.OSFileSystem{},
    67  	}
    68  
    69  	testTableName := "test"
    70  	testTableShardID := 1
    71  	var batchID int32 = 1
    72  	testTable := metaCom.Table{
    73  		Name:        testTableName,
    74  		IsFactTable: true,
    75  		Columns: []metaCom.Column{
    76  			{
    77  				Name: "c0",
    78  			},
    79  			{
    80  				Name: "c1",
    81  			},
    82  			{
    83  				Name: "c2",
    84  			},
    85  			{
    86  				Name: "c3",
    87  			},
    88  			{
    89  				Name: "c4",
    90  			},
    91  			{
    92  				Name: "c5",
    93  				Type: metaCom.Bool,
    94  			},
    95  			{
    96  				Name: "c6",
    97  				Type: metaCom.SmallEnum,
    98  			},
    99  		},
   100  		Config: metaCom.TableConfig{
   101  			BatchSize:                10,
   102  			BackfillMaxBufferSize:    1 << 32,
   103  			BackfillThresholdInBytes: 1 << 21,
   104  		},
   105  	}
   106  	var testSchema *memstore.TableSchema
   107  
   108  	redoLogTableName := "abc"
   109  	redoLogShardID := 0
   110  	var redoLogFile int64 = 1501869573
   111  
   112  	var memStore *memMocks.MemStore
   113  	var testServer *httptest.Server
   114  	var debugHandler *DebugHandler
   115  	var scheduler *memMocks.Scheduler
   116  
   117  	ginkgo.BeforeEach(func() {
   118  		testBatch, _ := testFactory.ReadArchiveBatch("archiveBatch")
   119  		testArchiveBatch := memstore.ArchiveBatch{
   120  			Batch: memstore.Batch{
   121  				RWMutex: &sync.RWMutex{},
   122  				Columns: testBatch.Columns,
   123  			},
   124  			Size:    5,
   125  			Version: 0,
   126  		}
   127  		mockMetaStore := CreateMockMetaStore()
   128  		mockDiskStore := CreateMockDiskStore()
   129  		testRootPath := "../testing/data/integration/sample-ares-root"
   130  		testDiskStore := diskstore.NewLocalDiskStore(testRootPath)
   131  		testMetaStore, err := metastore.NewDiskMetaStore(filepath.Join(testRootPath, "metastore"))
   132  		Ω(err).Should(BeNil())
   133  
   134  		// test table
   135  		testSchema = memstore.NewTableSchema(&testTable)
   136  		for col := range testSchema.Schema.Columns {
   137  			testSchema.SetDefaultValue(col)
   138  		}
   139  
   140  		testSchema.EnumDicts["c6"] = memstore.EnumDict{}
   141  		enumDict, ok := testSchema.EnumDicts["c6"]
   142  		Ω(ok).Should(BeTrue())
   143  		enumDict.ReverseDict = append(enumDict.ReverseDict, "enum case1")
   144  		testSchema.EnumDicts["c6"] = enumDict
   145  		memStore = CreateMemStore(testSchema, testTableShardID, mockMetaStore, mockDiskStore)
   146  		testShard, _ := memStore.GetTableShard(testTableName, testTableShardID)
   147  		memstore.NewArchiveStoreVersion(100, testShard)
   148  		testShard.Schema = testSchema
   149  		testShard.ArchiveStore = &memstore.ArchiveStore{
   150  			CurrentVersion: memstore.NewArchiveStoreVersion(100, testShard),
   151  		}
   152  
   153  		testShard.ArchiveStore.CurrentVersion.Batches = map[int32]*memstore.ArchiveBatch{
   154  			batchID: &testArchiveBatch,
   155  		}
   156  
   157  		testShard.LiveStore = memstore.NewLiveStore(
   158  			10,
   159  			testShard,
   160  		)
   161  
   162  		testArchiveBatch.Shard = testShard
   163  
   164  		key := []byte{1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1}
   165  		recordID := memstore.RecordID{
   166  			BatchID: 1,
   167  			Index:   1,
   168  		}
   169  
   170  		testShard.LiveStore.PrimaryKey.FindOrInsert(key, recordID, 1)
   171  
   172  		// Create first batch.
   173  		testShard.LiveStore.AdvanceNextWriteRecord()
   174  		testShard.LiveStore.AdvanceLastReadRecord()
   175  		liveBatch := testShard.LiveStore.GetBatchForWrite(memstore.BaseBatchID)
   176  		liveBatch.Unlock()
   177  		vp := liveBatch.GetOrCreateVectorParty(5, false)
   178  		vp.SetDataValue(0, memCom.DataValue{Valid: true}, memstore.IgnoreCount)
   179  
   180  		var val uint8 = 0
   181  		vp = liveBatch.GetOrCreateVectorParty(6, false)
   182  		vp.SetDataValue(0, memCom.DataValue{Valid: true, OtherVal: unsafe.Pointer(&val)}, memstore.IgnoreCount)
   183  
   184  		// redolog table.
   185  		redoLogTable, err := testMetaStore.GetTable(redoLogTableName)
   186  		Ω(err).Should(BeNil())
   187  		redoLogTableSchema := &memstore.TableSchema{
   188  			Schema: *redoLogTable,
   189  		}
   190  		redoLogShard := memstore.NewTableShard(redoLogTableSchema, mockMetaStore, testDiskStore, CreateMockHostMemoryManger(), redoLogShardID)
   191  
   192  		mockShardNotExistErr := convertToAPIError(errors.New("Failed to get shard"))
   193  		memStore.On("GetTableShard", redoLogTableName, redoLogShardID).Return(redoLogShard, nil).
   194  			Run(func(arguments mock.Arguments) {
   195  				redoLogShard.Users.Add(1)
   196  			})
   197  		memStore.On("GetTableShard", redoLogTableName, testTableShardID).Return(nil, mockShardNotExistErr)
   198  		memStore.On("GetTableShard", testTableName, 2).Return(nil, mockShardNotExistErr)
   199  		memStore.On("GetSchema", redoLogTableName).Return(redoLogTableSchema, nil)
   200  
   201  		scheduler = new(memMocks.Scheduler)
   202  		memStore.On("GetScheduler").Return(scheduler)
   203  		utils.SetClockImplementation(func() time.Time {
   204  			return time.Unix(100, 0)
   205  		})
   206  
   207  		mockMetaStore.On(
   208  			"AddArchiveBatchVersion",
   209  			mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   210  		mockMetaStore.On(
   211  			"UpdateArchivingCutoff", mock.Anything, mock.Anything,
   212  			mock.Anything).Return(nil)
   213  		mockDiskStore.On(
   214  			"DeleteBatchVersions", mock.Anything, mock.Anything,
   215  			mock.Anything, mock.Anything, mock.Anything).Return(nil)
   216  		mockDiskStore.On(
   217  			"DeleteLogFile", mock.Anything, mock.Anything,
   218  			mock.Anything).Return(nil)
   219  
   220  		writer := new(utilsMocks.WriteCloser)
   221  		writer.On("Write", mock.Anything).Return(0, nil)
   222  		writer.On("Close").Return(nil)
   223  		mockDiskStore.On(
   224  			"OpenVectorPartyFileForWrite", mock.Anything,
   225  			mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil)
   226  
   227  		queryHandler := NewQueryHandler(memStore, common.QueryConfig{
   228  			DeviceMemoryUtilization: 0.9,
   229  			DeviceChoosingTimeout:   5,
   230  		})
   231  
   232  		healthCheckHandler := NewHealthCheckHandler()
   233  		debugHandler = NewDebugHandler(memStore, mockMetaStore, queryHandler, healthCheckHandler)
   234  		testRouter := mux.NewRouter()
   235  		debugHandler.Register(testRouter.PathPrefix("/debug").Subrouter())
   236  		testServer = httptest.NewUnstartedServer(testRouter)
   237  		testServer.Start()
   238  	})
   239  
   240  	ginkgo.AfterEach(func() {
   241  		utils.ResetClockImplementation()
   242  		testServer.Close()
   243  	})
   244  
   245  	ginkgo.It("Health", func() {
   246  		Ω(debugHandler.healthCheckHandler.disable).Should(BeFalse())
   247  		hostPort := testServer.Listener.Addr().String()
   248  
   249  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/health", hostPort))
   250  		Ω(err).Should(BeNil())
   251  		bs, err := ioutil.ReadAll(resp.Body)
   252  		Ω(resp.StatusCode).Should(Equal(200))
   253  		Ω(string(bs)).Should(Equal("on"))
   254  
   255  		debugHandler.healthCheckHandler.disable = true
   256  
   257  		resp, err = http.Get(fmt.Sprintf("http://%s/debug/health", hostPort))
   258  		Ω(err).Should(BeNil())
   259  		bs, err = ioutil.ReadAll(resp.Body)
   260  		Ω(resp.StatusCode).Should(Equal(200))
   261  		Ω(string(bs)).Should(Equal("off"))
   262  	})
   263  
   264  	ginkgo.It("HealthSwitch", func() {
   265  		Ω(debugHandler.healthCheckHandler.disable).Should(BeFalse())
   266  		hostPort := testServer.Listener.Addr().String()
   267  
   268  		resp, err := http.Post(fmt.Sprintf("http://%s/debug/health/off", hostPort), "", nil)
   269  		Ω(err).Should(BeNil())
   270  		Ω(resp.StatusCode).Should(Equal(200))
   271  		Ω(debugHandler.healthCheckHandler.disable).Should(BeTrue())
   272  
   273  		resp, err = http.Post(fmt.Sprintf("http://%s/debug/health/on", hostPort), "", nil)
   274  		Ω(err).Should(BeNil())
   275  		Ω(resp.StatusCode).Should(Equal(200))
   276  
   277  		Ω(debugHandler.healthCheckHandler.disable).Should(BeFalse())
   278  
   279  		resp, err = http.Post(fmt.Sprintf("http://%s/debug/health/os", hostPort), "", nil)
   280  		Ω(err).Should(BeNil())
   281  		Ω(resp.StatusCode).Should(Equal(400))
   282  		Ω(debugHandler.healthCheckHandler.disable).Should(BeFalse())
   283  	})
   284  	ginkgo.It("ShowBatch", func() {
   285  		hostPort := testServer.Listener.Addr().String()
   286  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/%s/%d/batches/%d?startRow=0&numRows=10", hostPort, testTableName, testTableShardID, batchID))
   287  		Ω(err).Should(BeNil())
   288  		Ω(resp.StatusCode).Should(Equal(200))
   289  		bs, err := ioutil.ReadAll(resp.Body)
   290  		var body ShowBatchResponse
   291  		json.Unmarshal(bs, &body.Body)
   292  		Ω(body.Body.Columns).Should(Equal([]string{"c0", "c1", "c2", "c3", "c4", "c5", "c6"}))
   293  		Ω(body.Body.NumRows).Should(Equal(5))
   294  		Ω(body.Body.Vectors[0].Counts).Should(Equal([]int{1, 2, 3, 4, 5}))
   295  		Ω(body.Body.Vectors[1].Counts).Should(Equal([]int{3, 4, 5}))
   296  		Ω(body.Body.Vectors[2].Counts).Should(Equal([]int{1, 2, 3, 4, 5}))
   297  		Ω(body.Body.Vectors[3].Counts).Should(Equal([]int{5}))
   298  		Ω(body.Body.Vectors[4].Counts).Should(Equal([]int{1, 2, 3, 4, 5}))
   299  		Ω(body.Body.Vectors[5].Counts).Should(Equal([]int{5}))
   300  
   301  		// startRow and numRows should be optional
   302  		resp, _ = http.Get(fmt.Sprintf("http://%s/debug/%s/%d/batches/%d", hostPort, testTableName, testTableShardID, batchID))
   303  		bs, _ = ioutil.ReadAll(resp.Body)
   304  		json.Unmarshal(bs, &body.Body)
   305  		Ω(resp.StatusCode).Should(Equal(200))
   306  		Ω(body.Body.Columns).Should(Equal([]string{"c0", "c1", "c2", "c3", "c4", "c5", "c6"}))
   307  		Ω(body.Body.NumRows).Should(Equal(5))
   308  	})
   309  
   310  	ginkgo.It("show live batch should work", func() {
   311  		hostPort := testServer.Listener.Addr().String()
   312  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/%s/%d/batches/%d?startRow=0&numRows=5",
   313  			hostPort, testTableName, testTableShardID, memstore.BaseBatchID))
   314  		Ω(err).Should(BeNil())
   315  		Ω(resp.StatusCode).Should(Equal(200))
   316  		bs, err := ioutil.ReadAll(resp.Body)
   317  		var body ShowBatchResponse
   318  		json.Unmarshal(bs, &body.Body)
   319  		Ω(body.Body.Columns).Should(Equal([]string{"c0", "c1", "c2", "c3", "c4", "c5", "c6"}))
   320  		Ω(body.Body.NumRows).Should(Equal(1))
   321  		Ω(body.Body.Vectors[0].Values).Should(Equal([]interface{}{nil}))
   322  		Ω(body.Body.Vectors[1].Values).Should(Equal([]interface{}{nil}))
   323  		Ω(body.Body.Vectors[2].Values).Should(Equal([]interface{}{nil}))
   324  		Ω(body.Body.Vectors[3].Values).Should(Equal([]interface{}{nil}))
   325  		Ω(body.Body.Vectors[4].Values).Should(Equal([]interface{}{nil}))
   326  		Ω(body.Body.Vectors[5].Values).Should(Equal([]interface{}{false}))
   327  		Ω(body.Body.Vectors[6].Values).Should(Equal([]interface{}{"enum case1"}))
   328  
   329  		// startRow and numRows should be optional
   330  		resp, _ = http.Get(fmt.Sprintf("http://%s/debug/%s/%d/batches/%d", hostPort, testTableName, testTableShardID, memstore.BaseBatchID))
   331  		bs, _ = ioutil.ReadAll(resp.Body)
   332  		json.Unmarshal(bs, &body.Body)
   333  		Ω(resp.StatusCode).Should(Equal(200))
   334  		Ω(body.Body.Columns).Should(Equal([]string{"c0", "c1", "c2", "c3", "c4", "c5", "c6"}))
   335  		Ω(body.Body.NumRows).Should(Equal(1))
   336  	})
   337  
   338  	ginkgo.It("LookupPrimaryKey", func() {
   339  		testSchema.PrimaryKeyBytes = 21
   340  		testSchema.PrimaryKeyColumnTypes = []memCom.DataType{memCom.UUID, memCom.Uint32, memCom.Bool}
   341  		hostPort := testServer.Listener.Addr().String()
   342  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/%s/%d/primary-keys?key=01000000000000000100000000000000,1,true", hostPort, testTableName, testTableShardID))
   343  		Ω(err).Should(BeNil())
   344  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   345  		bs, _ := ioutil.ReadAll(resp.Body)
   346  		var r memstore.RecordID
   347  		json.Unmarshal(bs, &r)
   348  		Ω(r).Should(Equal(memstore.RecordID{
   349  			BatchID: 1,
   350  			Index:   1,
   351  		}))
   352  	})
   353  
   354  	ginkgo.It("Archiving request should work", func() {
   355  		hostPort := testServer.Listener.Addr().String()
   356  		request := &ArchiveRequest{}
   357  		request.Body.Cutoff = 200
   358  		job := new(memMocks.Job)
   359  		scheduler.On("NewArchivingJob", mock.Anything, mock.Anything, mock.Anything).Return(job)
   360  		scheduler.On("SubmitJob", job).Return(nil, nil)
   361  		job.On("Run", mock.Anything).Return(nil)
   362  		correctURL := fmt.Sprintf("http://%s/debug/%s/%d/archive", hostPort, testTableName, testTableShardID)
   363  		contentType := "application/json"
   364  		resp, err := http.Post(correctURL, contentType, RequestToBody(&request.Body))
   365  		Ω(err).Should(BeNil())
   366  		bs, err := ioutil.ReadAll(resp.Body)
   367  		Ω(err).Should(BeNil())
   368  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   369  		Ω(string(bs)).Should(ContainSubstring("Archiving job submitted"))
   370  
   371  		// shard does not exist.
   372  		resp, err = http.Post(
   373  			fmt.Sprintf("http://%s/debug/%s/%d/archive", hostPort, testTableName, 2), contentType,
   374  			RequestToBody(&request.Body))
   375  		Ω(err).Should(BeNil())
   376  		bs, err = ioutil.ReadAll(resp.Body)
   377  		Ω(err).Should(BeNil())
   378  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   379  		Ω(string(bs)).Should(ContainSubstring("Failed to get shard"))
   380  	})
   381  
   382  	ginkgo.It("ListRedoLogs should work", func() {
   383  		hostPort := testServer.Listener.Addr().String()
   384  		resp, err := http.Get(
   385  			fmt.Sprintf("http://%s/debug/%s/%d/redologs", hostPort, redoLogTableName, redoLogShardID))
   386  		Ω(err).Should(BeNil())
   387  		bs, err := ioutil.ReadAll(resp.Body)
   388  		Ω(err).Should(BeNil())
   389  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   390  
   391  		var redoLogs []string
   392  		Ω(json.Unmarshal(bs, &redoLogs)).Should(BeNil())
   393  		Ω(redoLogs).Should(ConsistOf(strconv.FormatInt(redoLogFile, 10)))
   394  
   395  		// Fail to get shard.
   396  		resp, err = http.Get(
   397  			fmt.Sprintf("http://%s/debug/%s/%d/redologs", hostPort, redoLogTableName, testTableShardID))
   398  		Ω(err).Should(BeNil())
   399  		bs, err = ioutil.ReadAll(resp.Body)
   400  		Ω(err).Should(BeNil())
   401  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   402  		Ω(string(bs)).Should(ContainSubstring("Failed to get shard"))
   403  	})
   404  
   405  	ginkgo.It("ListUpsertBatches should work", func() {
   406  		hostPort := testServer.Listener.Addr().String()
   407  		resp, err := http.Get(
   408  			fmt.Sprintf("http://%s/debug/%s/%d/redologs/%d/upsertbatches", hostPort, redoLogTableName,
   409  				redoLogShardID, redoLogFile))
   410  		Ω(err).Should(BeNil())
   411  		bs, err := ioutil.ReadAll(resp.Body)
   412  		Ω(err).Should(BeNil())
   413  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   414  
   415  		var respBody ListUpsertBatchesResponse
   416  		Ω(json.Unmarshal(bs, &respBody)).Should(BeNil())
   417  		Ω(respBody).Should(ConsistOf(int64(4)))
   418  
   419  		// Fail to get shard.
   420  		resp, err = http.Get(
   421  			fmt.Sprintf("http://%s/debug/%s/%d/redologs/%d/upsertbatches", hostPort, redoLogTableName,
   422  				testTableShardID, redoLogFile))
   423  		Ω(err).Should(BeNil())
   424  		bs, err = ioutil.ReadAll(resp.Body)
   425  		Ω(err).Should(BeNil())
   426  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   427  		Ω(string(bs)).Should(ContainSubstring("Failed to get shard"))
   428  
   429  		// Redo log file does not exist.
   430  		resp, err = http.Get(
   431  			fmt.Sprintf("http://%s/debug/%s/%d/redologs/%d/upsertbatches", hostPort, redoLogTableName,
   432  				redoLogShardID, 1))
   433  		Ω(err).Should(BeNil())
   434  		bs, err = ioutil.ReadAll(resp.Body)
   435  		Ω(err).Should(BeNil())
   436  		Ω(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
   437  		Ω(string(bs)).Should(ContainSubstring("Failed to open redolog file"))
   438  	})
   439  
   440  	ginkgo.It("ReadUpsertBatch should work", func() {
   441  		expectedRows := [][]interface{}{{123, 0}, {234, 1}}
   442  		expectedColumnNames := []string{"c1", "c2"}
   443  		hostPort := testServer.Listener.Addr().String()
   444  		resp, err := http.Get(
   445  			fmt.Sprintf("http://%s/debug/%s/%d/redologs/%d/upsertbatches/%d"+
   446  				"?draw=%d&start=%d&length=%d",
   447  				hostPort, redoLogTableName, redoLogShardID, redoLogFile, 4, 0, 0, 2))
   448  		Ω(err).Should(BeNil())
   449  		bs, err := ioutil.ReadAll(resp.Body)
   450  		Ω(err).Should(BeNil())
   451  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   452  
   453  		respBody := ReadUpsertBatchResponse{
   454  			Draw:            0,
   455  			Data:            expectedRows,
   456  			ColumnNames:     expectedColumnNames,
   457  			RecordsTotal:    2,
   458  			RecordsFiltered: 2,
   459  		}
   460  
   461  		jsonBytes, _ := json.Marshal(respBody)
   462  		Ω(string(bs)).Should(MatchJSON(string(jsonBytes)))
   463  
   464  		// shard does not exist
   465  		resp, err = http.Get(
   466  			fmt.Sprintf("http://%s/debug/%s/%d/redologs/%d/upsertbatches/%d"+
   467  				"?draw=%d&start=%d&length=%d",
   468  				hostPort, redoLogTableName, testTableShardID, redoLogFile, 4, 0, 0, 2))
   469  		Ω(err).Should(BeNil())
   470  		bs, err = ioutil.ReadAll(resp.Body)
   471  		Ω(err).Should(BeNil())
   472  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   473  		Ω(string(bs)).Should(ContainSubstring("Failed to get shard"))
   474  
   475  		// Invalid offset.
   476  		resp, err = http.Get(
   477  			fmt.Sprintf("http://%s/debug/%s/%d/redologs/%d/upsertbatches/%d"+
   478  				"?draw=%d&start=%d&length=%d",
   479  				hostPort, redoLogTableName, redoLogShardID, redoLogFile, 5, 0, 0, 2))
   480  		Ω(err).Should(BeNil())
   481  		bs, err = ioutil.ReadAll(resp.Body)
   482  		Ω(err).Should(BeNil())
   483  		Ω(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
   484  
   485  		// Upsert batch row out of bound.
   486  		resp, err = http.Get(
   487  			fmt.Sprintf("http://%s/debug/%s/%d/redologs/%d/upsertbatches/%d"+
   488  				"?draw=%d&start=%d&length=%d",
   489  				hostPort, redoLogTableName, redoLogShardID, redoLogFile, 4, 0, 2, 2))
   490  		Ω(err).Should(BeNil())
   491  		bs, err = ioutil.ReadAll(resp.Body)
   492  		Ω(err).Should(BeNil())
   493  		Ω(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
   494  		Ω(string(bs)).Should(ContainSubstring("Invalid start or length"))
   495  	})
   496  
   497  	ginkgo.It("ShowShardMeta request should work", func() {
   498  		hostPort := testServer.Listener.Addr().String()
   499  		resp, err := http.Get(
   500  			fmt.Sprintf("http://%s/debug/%s/%d", hostPort, testTableName, testTableShardID))
   501  		Ω(err).Should(BeNil())
   502  		bs, err := ioutil.ReadAll(resp.Body)
   503  		Ω(err).Should(BeNil())
   504  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   505  		Ω(string(bs)).Should(MatchJSON(`
   506  {
   507    "schema": {
   508      "schema": {
   509        "name": "test",
   510        "columns": [
   511          {
   512            "name": "c0",
   513            "type": "",
   514            "config": {},
   515            "hllConfig": {}
   516          },
   517          {
   518            "name": "c1",
   519            "type": "",
   520            "config": {},
   521            "hllConfig": {}
   522          },
   523          {
   524            "name": "c2",
   525            "type": "",
   526            "config": {},
   527            "hllConfig": {}
   528          },
   529          {
   530            "name": "c3",
   531            "type": "",
   532            "config": {},
   533            "hllConfig": {}
   534          },
   535          {
   536            "name": "c4",
   537            "type": "",
   538            "config": {},
   539            "hllConfig": {}
   540          },
   541          {
   542            "name": "c5",
   543            "type": "Bool",
   544            "config": {},
   545            "hllConfig": {}
   546          },
   547          {
   548            "name": "c6",
   549            "type": "SmallEnum",
   550            "config": {},
   551            "hllConfig": {}
   552          }
   553        ],
   554        "primaryKeyColumns": null,
   555        "isFactTable": true,
   556        "config": {
   557          "batchSize": 10,
   558          "backfillMaxBufferSize": 4294967296,
   559          "backfillThresholdInBytes": 2097152
   560        },
   561        "incarnation": 0,
   562        "version": 0
   563      },
   564      "columnIDs": {
   565        "c0": 0,
   566        "c1": 1,
   567        "c2": 2,
   568        "c3": 3,
   569        "c4": 4,
   570        "c5": 5,
   571        "c6": 6
   572      },
   573      "enumDicts": {
   574        "c6": {
   575          "capacity": 0,
   576          "dict": null,
   577          "reverseDict": [
   578            "enum case1"
   579          ]
   580        }
   581      },
   582      "valueTypeByColumn": [
   583        0,
   584        0,
   585        0,
   586        0,
   587        0,
   588        1,
   589        524296
   590      ],
   591      "primaryKeyBytes": 0,
   592      "primaryKeyColumnTypes": []
   593    },
   594    "liveStore": {
   595      "backfillManager": {
   596        "numRecords": 0,
   597        "currentBufferSize": 0,
   598        "backfillingBufferSize": 0,
   599        "maxBufferSize": 4294967296,
   600        "backfillThresholdInBytes": 2097152,
   601        "lastRedoFile": 0,
   602        "lastBatchOffset": 0,
   603        "currentRedoFile": 0,
   604        "currentBatchOffset": 0,
   605        "numUpsertBatches": 0
   606      },
   607      "batchSize": 10,
   608      "batches": {
   609        "-2147483648": {
   610          "capacity": 10,
   611          "numColumns": 7
   612        }
   613      },
   614      "lastModifiedTimePerColumn": null,
   615      "lastReadRecord": {
   616        "batchID": -2147483648,
   617        "index": 1
   618      },
   619      "nextWriteRecord": {
   620        "batchID": -2147483648,
   621        "index": 1
   622      },
   623      "primaryKey": {
   624        "allocatedBytes": 104000,
   625        "capacity": 8000,
   626        "eventTimeCutoff": 0,
   627        "size": 1
   628      },
   629      "redoLogManager": {
   630        "rotationInterval": 0,
   631        "maxRedoLogSize": 0,
   632        "currentRedoLogSize": 0,
   633        "totalRedologSize": 0,
   634        "maxEventTimePerFile": {},
   635        "batchCountPerFile": {},
   636        "sizePerFile": {},
   637        "currentFileCreationTime": 0
   638      },
   639      "snapshotManager": null
   640    },
   641    "archiveStore": {
   642      "currentVersion": {
   643        "batches": {
   644          "1": {
   645            "numColumns": 6,
   646            "size": 5,
   647            "version": 0
   648          }
   649        },
   650        "archivingCutoff": 100
   651      }
   652    }
   653  }
   654  		`))
   655  
   656  		// shard does not exist.
   657  		resp, err = http.Get(
   658  			fmt.Sprintf("http://%s/debug/%s/%d", hostPort, testTableName, 2))
   659  		Ω(err).Should(BeNil())
   660  		bs, err = ioutil.ReadAll(resp.Body)
   661  		Ω(err).Should(BeNil())
   662  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   663  	})
   664  
   665  	ginkgo.It("LoadVectorParty should work", func() {
   666  		hostPort := testServer.Listener.Addr().String()
   667  		columnName := "c0"
   668  
   669  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/%s/%d/batches/%d/vector-parties/%s", hostPort, testTableName, testTableShardID, -1, columnName))
   670  		Ω(err).Should(BeNil())
   671  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   672  
   673  		resp, err = http.Get(fmt.Sprintf("http://%s/debug/%s/%d/batches/%d/vector-parties/%s", hostPort, testTableName, testTableShardID, batchID, columnName))
   674  		Ω(err).Should(BeNil())
   675  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   676  
   677  	})
   678  
   679  	ginkgo.It("EvictVectorParty should work", func() {
   680  		hostPort := testServer.Listener.Addr().String()
   681  		columnName := "c0"
   682  
   683  		request, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/debug/%s/%d/batches/%d/vector-parties/%s", hostPort, testTableName, testTableShardID, -1, columnName), nil)
   684  		resp, err := http.DefaultClient.Do(request)
   685  		Ω(err).Should(BeNil())
   686  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   687  
   688  		request, err = http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/debug/%s/%d/batches/%d/vector-parties/%s", hostPort, testTableName, testTableShardID, batchID, columnName), nil)
   689  		resp, err = http.DefaultClient.Do(request)
   690  		Ω(err).Should(BeNil())
   691  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   692  	})
   693  
   694  	ginkgo.It("ShowJobStatus for archiving job should work", func() {
   695  		expectedStatus := string(`
   696  			{
   697  				"test|1|archiving": {
   698  				  "currentCutoff": 200,
   699  				  "status": "succeeded",
   700  				  "stage": "complete",
   701  				  "runningCutoff": 200,
   702  				  "nextRun": "0001-01-01T00:00:00Z",
   703  				  "lastCutoff": 0,
   704  				  "lastStartTime": "1970-01-01T00:01:40Z",
   705  				  "lastRun": "0001-01-01T00:00:00Z"
   706  				}
   707  			 }
   708        	`)
   709  
   710  		scheduler.On("RLock").Return()
   711  		scheduler.On("RUnlock").Return()
   712  		scheduler.On("GetJobDetails", memCom.ArchivingJobType).Return(map[string]*memstore.ArchiveJobDetail{
   713  			"test|1|archiving": {
   714  				JobDetail: memstore.JobDetail{
   715  					Status:        memstore.JobSucceeded,
   716  					LastStartTime: time.Unix(100, 0).UTC(),
   717  				},
   718  				CurrentCutoff: 200,
   719  				Stage:         memstore.ArchivingComplete,
   720  				RunningCutoff: 200,
   721  				LastCutoff:    0,
   722  			}})
   723  		hostPort := testServer.Listener.Addr().String()
   724  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/jobs/archiving", hostPort))
   725  		Ω(err).Should(BeNil())
   726  		bs, err := ioutil.ReadAll(resp.Body)
   727  		Ω(err).Should(BeNil())
   728  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   729  
   730  		var respBody map[string]*memstore.ArchiveJobDetail
   731  		Ω(json.Unmarshal(bs, &respBody)).Should(BeNil())
   732  		Ω(bs).Should(MatchJSON(expectedStatus))
   733  	})
   734  
   735  	ginkgo.It("ShowJobStatus for backfill job should work", func() {
   736  		expectedStatus := string(`
   737  			{
   738  				"test|1|backfill": {
   739  				  "status": "succeeded",
   740  				  "stage": "complete",
   741  				  "nextRun": "0001-01-01T00:00:00Z",
   742  				  "lastStartTime": "1970-01-01T00:01:40Z",
   743  				  "lastRun": "0001-01-01T00:00:00Z",
   744  				  "redologFile": 0,
   745                    "batchOffset": 0
   746  				}
   747  			 }
   748        	`)
   749  
   750  		scheduler.On("RLock").Return()
   751  		scheduler.On("RUnlock").Return()
   752  		scheduler.On("GetJobDetails", memCom.BackfillJobType).Return(map[string]*memstore.BackfillJobDetail{
   753  			"test|1|backfill": {
   754  				JobDetail: memstore.JobDetail{
   755  					Status:        memstore.JobSucceeded,
   756  					LastStartTime: time.Unix(100, 0).UTC(),
   757  				},
   758  				Stage: memstore.BackfillComplete,
   759  			}})
   760  		hostPort := testServer.Listener.Addr().String()
   761  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/jobs/backfill", hostPort))
   762  		Ω(err).Should(BeNil())
   763  		bs, err := ioutil.ReadAll(resp.Body)
   764  		Ω(err).Should(BeNil())
   765  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   766  
   767  		var respBody map[string]*memstore.BackfillJobDetail
   768  		Ω(json.Unmarshal(bs, &respBody)).Should(BeNil())
   769  		Ω(bs).Should(MatchJSON(expectedStatus))
   770  	})
   771  
   772  	ginkgo.It("ShowJobStatus for snapshot job should work", func() {
   773  		expectedStatus := string(`
   774  			{
   775  				"test|1|snapshot": {
   776  				  "status": "succeeded",
   777  				  "nextRun": "0001-01-01T00:00:00Z",
   778  				  "lastRun": "0001-01-01T00:00:00Z",
   779  				  "lastStartTime": "1970-01-01T00:01:40Z",
   780  				  "numMutations": 0,
   781  				  "numBatches": 0,
   782  				  "redologFile": 0,
   783  				  "batchOffset": 0,
   784  				  "stage": ""
   785  				}
   786  			 }
   787        	`)
   788  
   789  		scheduler.On("RLock").Return()
   790  		scheduler.On("RUnlock").Return()
   791  		scheduler.On("GetJobDetails", memCom.SnapshotJobType).Return(map[string]*memstore.SnapshotJobDetail{
   792  			"test|1|snapshot": {
   793  				JobDetail: memstore.JobDetail{
   794  					Status:        memstore.JobSucceeded,
   795  					LastStartTime: time.Unix(100, 0).UTC(),
   796  				},
   797  			}})
   798  		hostPort := testServer.Listener.Addr().String()
   799  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/jobs/snapshot", hostPort))
   800  		Ω(err).Should(BeNil())
   801  		bs, err := ioutil.ReadAll(resp.Body)
   802  		Ω(err).Should(BeNil())
   803  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   804  
   805  		var respBody map[string]*memstore.SnapshotJobDetail
   806  		Ω(json.Unmarshal(bs, &respBody)).Should(BeNil())
   807  		Ω(bs).Should(MatchJSON(expectedStatus))
   808  	})
   809  
   810  	ginkgo.It("ShowHostMemory should work", func() {
   811  		memoryUsages := map[string]memstore.TableShardMemoryUsage{
   812  			"table1": {
   813  				PrimaryKeyMemory: 10,
   814  			},
   815  		}
   816  
   817  		expectedResponse := string(`
   818  			{
   819  			"table1": {
   820  			  "cols": null,
   821  			  "pk": 10
   822  			}
   823  		  }
   824        	`)
   825  
   826  		memStore.On("GetMemoryUsageDetails").Return(memoryUsages, nil)
   827  
   828  		hostPort := testServer.Listener.Addr().String()
   829  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/host-memory", hostPort))
   830  		Ω(err).Should(BeNil())
   831  		bs, err := ioutil.ReadAll(resp.Body)
   832  		Ω(err).Should(BeNil())
   833  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   834  
   835  		var respBody map[string]memstore.TableShardMemoryUsage
   836  		Ω(json.Unmarshal(bs, &respBody)).Should(BeNil())
   837  		Ω(bs).Should(MatchJSON(expectedResponse))
   838  	})
   839  
   840  	ginkgo.It("ReadBackfillQueueUpsertBatch should work", func() {
   841  		builder := memCom.NewUpsertBatchBuilder()
   842  		builder.AddRow()
   843  		err := builder.AddColumn(1, memCom.Uint8)
   844  		Ω(err).Should(BeNil())
   845  		builder.SetValue(0, 0, uint8(135))
   846  		buffer, err := builder.ToByteArray()
   847  		Ω(err).Should(BeNil())
   848  		upsertBatch, err := memstore.NewUpsertBatch(buffer)
   849  		Ω(upsertBatch).ShouldNot(BeNil())
   850  		Ω(err).Should(BeNil())
   851  
   852  		testShard, _ := memStore.GetTableShard(testTableName, testTableShardID)
   853  		Ω(testShard.LiveStore.BackfillManager.Append(upsertBatch, 0, 0)).Should(BeFalse())
   854  		expectedRows := [][]interface{}{{135}}
   855  		expectedColumnNames := []string{"c1"}
   856  		hostPort := testServer.Listener.Addr().String()
   857  		resp, err := http.Get(
   858  			fmt.Sprintf("http://%s/debug/%s/%d/backfill-manager/upsertbatches/%d"+
   859  				"?draw=%d&start=%d&length=%d",
   860  				hostPort, testTableName, testTableShardID, 0, 0, 0, 2))
   861  		Ω(err).Should(BeNil())
   862  		bs, err := ioutil.ReadAll(resp.Body)
   863  		Ω(err).Should(BeNil())
   864  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   865  
   866  		respBody := ReadUpsertBatchResponse{
   867  			Draw:            0,
   868  			Data:            expectedRows,
   869  			ColumnNames:     expectedColumnNames,
   870  			RecordsTotal:    1,
   871  			RecordsFiltered: 1,
   872  		}
   873  
   874  		jsonBytes, _ := json.Marshal(respBody)
   875  		Ω(string(bs)).Should(MatchJSON(string(jsonBytes)))
   876  
   877  		// shard does not exist
   878  		resp, err = http.Get(
   879  			fmt.Sprintf("http://%s/debug/%s/%d/backfill-manager/upsertbatches/%d"+
   880  				"?draw=%d&start=%d&length=%d",
   881  				hostPort, redoLogTableName, testTableShardID, 0, 0, 0, 2))
   882  		Ω(err).Should(BeNil())
   883  		bs, err = ioutil.ReadAll(resp.Body)
   884  		Ω(err).Should(BeNil())
   885  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   886  		Ω(string(bs)).Should(ContainSubstring("Failed to get shard"))
   887  	})
   888  
   889  	ginkgo.It("ShowDeviceStatus should work", func() {
   890  		expectedStatus := string(`
   891  			{
   892  			"deviceInfos": [
   893  			  {
   894  				"deviceID": 0,
   895  				"queryCount": 0,
   896  				"totalMemory": 25576865792,
   897  				"totalAvailableMemory": 23019177984,
   898  				"totalFreeMemory": 23019177984
   899  			  }
   900  			],
   901  			"timeout": 5,
   902  			"maxAvailableMemory": 23019177984
   903  		  }
   904        	`)
   905  
   906  		hostPort := testServer.Listener.Addr().String()
   907  		resp, err := http.Get(fmt.Sprintf("http://%s/debug/devices", hostPort))
   908  		Ω(err).Should(BeNil())
   909  		bs, err := ioutil.ReadAll(resp.Body)
   910  		Ω(err).Should(BeNil())
   911  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   912  
   913  		var respBody query.DeviceManager
   914  		Ω(json.Unmarshal(bs, &respBody)).Should(BeNil())
   915  		Ω(bs).Should(MatchJSON(expectedStatus))
   916  	})
   917  
   918  	ginkgo.It("Backfill request should work", func() {
   919  		hostPort := testServer.Listener.Addr().String()
   920  		request := &BackfillRequest{}
   921  		bs, err := json.Marshal(request)
   922  		Ω(err).Should(BeNil())
   923  		job := new(memMocks.Job)
   924  		scheduler.On("NewBackfillJob", mock.Anything, mock.Anything, mock.Anything).Return(job)
   925  		scheduler.On("SubmitJob", job).Return(nil, nil)
   926  		job.On("Run", mock.Anything).Return(nil)
   927  		correctURL := fmt.Sprintf("http://%s/debug/%s/%d/backfill", hostPort, testTableName, testTableShardID)
   928  		contentType := "application/json"
   929  		resp, err := http.Post(correctURL, contentType, bytes.NewReader(bs))
   930  		Ω(err).Should(BeNil())
   931  		bs, err = ioutil.ReadAll(resp.Body)
   932  		Ω(err).Should(BeNil())
   933  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   934  		Ω(string(bs)).Should(ContainSubstring("Backfill job submitted"))
   935  
   936  		// shard does not exist.
   937  		resp, err = http.Post(
   938  			fmt.Sprintf("http://%s/debug/%s/%d/backfill", hostPort, testTableName, 2), contentType, nil)
   939  		Ω(err).Should(BeNil())
   940  		bs, err = ioutil.ReadAll(resp.Body)
   941  		Ω(err).Should(BeNil())
   942  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   943  		Ω(string(bs)).Should(ContainSubstring("Failed to get shard"))
   944  	})
   945  
   946  	ginkgo.It("Snapshot request should work", func() {
   947  		hostPort := testServer.Listener.Addr().String()
   948  		request := &SnapshotRequest{}
   949  		bs, err := json.Marshal(request)
   950  		job := new(memMocks.Job)
   951  		scheduler.On("NewSnapshotJob", mock.Anything, mock.Anything, mock.Anything).Return(job)
   952  		scheduler.On("SubmitJob", job).Return(nil, nil)
   953  		job.On("Run", mock.Anything).Return(nil)
   954  		correctURL := fmt.Sprintf("http://%s/debug/%s/%d/snapshot", hostPort, testTableName, testTableShardID)
   955  		contentType := "application/json"
   956  		resp, err := http.Post(correctURL, contentType, bytes.NewReader(bs))
   957  		Ω(err).Should(BeNil())
   958  		bs, err = ioutil.ReadAll(resp.Body)
   959  		Ω(err).Should(BeNil())
   960  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   961  		Ω(string(bs)).Should(ContainSubstring("Snapshot job submitted"))
   962  
   963  		// shard does not exist.
   964  		resp, err = http.Post(
   965  			fmt.Sprintf("http://%s/debug/%s/%d/snapshot", hostPort, testTableName, 2), contentType, nil)
   966  		Ω(err).Should(BeNil())
   967  		bs, err = ioutil.ReadAll(resp.Body)
   968  		Ω(err).Should(BeNil())
   969  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   970  		Ω(string(bs)).Should(ContainSubstring("Failed to get shard"))
   971  	})
   972  
   973  	ginkgo.It("Purge request should work", func() {
   974  		hostPort := testServer.Listener.Addr().String()
   975  		request := &PurgeRequest{}
   976  		bs, err := json.Marshal(request)
   977  		job := new(memMocks.Job)
   978  		scheduler.On("NewPurgeJob", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(job)
   979  		scheduler.On("SubmitJob", job).Return(nil, nil)
   980  		job.On("Run", mock.Anything).Return(nil)
   981  		correctURL := fmt.Sprintf("http://%s/debug/%s/%d/purge", hostPort, testTableName, testTableShardID)
   982  		contentType := "application/json"
   983  		resp, err := http.Post(correctURL, contentType, RequestToBody(&request.Body))
   984  		Ω(err).Should(BeNil())
   985  		bs, err = ioutil.ReadAll(resp.Body)
   986  		Ω(err).Should(BeNil())
   987  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   988  		Ω(string(bs)).Should(ContainSubstring("Purge job submitted"))
   989  
   990  		// shard does not exist.
   991  		resp, err = http.Post(
   992  			fmt.Sprintf("http://%s/debug/%s/%d/purge", hostPort, testTableName, 2), contentType, RequestToBody(&request.Body))
   993  		Ω(err).Should(BeNil())
   994  		bs, err = ioutil.ReadAll(resp.Body)
   995  		Ω(err).Should(BeNil())
   996  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   997  		Ω(string(bs)).Should(ContainSubstring("Failed to get shard"))
   998  
   999  		testSchema.Schema.Config.RecordRetentionInDays = 0
  1000  		request.Body.SafePurge = true
  1001  		resp, err = http.Post(
  1002  			fmt.Sprintf("http://%s/debug/%s/%d/purge", hostPort, testTableName, 1), contentType, RequestToBody(&request.Body))
  1003  		Ω(err).Should(BeNil())
  1004  		bs, err = ioutil.ReadAll(resp.Body)
  1005  		Ω(err).Should(BeNil())
  1006  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
  1007  		Ω(string(bs)).Should(ContainSubstring("safe purge attempted on table with infinite retention"))
  1008  
  1009  		testSchema.Schema.Config.RecordRetentionInDays = 10
  1010  		request.Body.SafePurge = true
  1011  		resp, err = http.Post(
  1012  			fmt.Sprintf("http://%s/debug/%s/%d/purge", hostPort, testTableName, 1), contentType, RequestToBody(&request.Body))
  1013  		Ω(err).Should(BeNil())
  1014  		bs, err = ioutil.ReadAll(resp.Body)
  1015  		Ω(err).Should(BeNil())
  1016  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
  1017  	})
  1018  })