You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2021/02/04 07:16:06 UTC

[GitHub] [apisix-dashboard] starsz commented on a change in pull request #1169: feat: remove the etcd dependency in the service unit test

starsz commented on a change in pull request #1169:
URL: https://github.com/apache/apisix-dashboard/pull/1169#discussion_r569993804



##########
File path: api/internal/handler/service/service_test.go
##########
@@ -18,274 +18,832 @@
 package service
 
 import (
-	"encoding/json"
-	"strings"
+	"fmt"
+	"net/http"
 	"testing"
-	"time"
 
 	"github.com/shiningrush/droplet"
+	"github.com/shiningrush/droplet/data"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 
-	"github.com/apisix/manager-api/internal/conf"
 	"github.com/apisix/manager-api/internal/core/entity"
-	"github.com/apisix/manager-api/internal/core/storage"
 	"github.com/apisix/manager-api/internal/core/store"
+	"github.com/apisix/manager-api/internal/handler"
 )
 
-func TestService(t *testing.T) {
-	// init
-	err := storage.InitETCDClient(conf.ETCDConfig)
-	assert.Nil(t, err)
-	err = store.InitStores()
-	assert.Nil(t, err)
-
-	handler := &Handler{
-		serviceStore: store.GetStore(store.HubKeyService),
+func TestService_Get(t *testing.T) {
+	tests := []struct {
+		caseDesc   string
+		giveInput  *GetInput
+		giveRet    *entity.Service
+		giveErr    error
+		wantErr    error
+		wantGetKey string
+		wantRet    interface{}
+	}{
+		{
+			caseDesc:   "normal",
+			giveInput:  &GetInput{ID: "s1"},
+			wantGetKey: "s1",
+			giveRet: &entity.Service{
+				BaseInfo: entity.BaseInfo{
+					ID: "s1",
+				},
+				Plugins: map[string]interface{}{
+					"limit-count": map[string]interface{}{
+						"count":         2,
+						"time_window":   60,
+						"rejected_code": 503,
+						"key":           "remote_addr",
+					},
+				},
+			},
+			wantRet: &entity.Service{
+				BaseInfo: entity.BaseInfo{
+					ID: "s1",
+				},
+				Plugins: map[string]interface{}{
+					"limit-count": map[string]interface{}{
+						"count":         2,
+						"time_window":   60,
+						"rejected_code": 503,
+						"key":           "remote_addr",
+					},
+				},
+			},
+		},
+		{
+			caseDesc:   "store get failed",
+			giveInput:  &GetInput{ID: "failed_key"},
+			wantGetKey: "failed_key",
+			giveErr:    fmt.Errorf("get failed"),
+			wantErr:    fmt.Errorf("get failed"),
+			wantRet: &data.SpecCodeResponse{
+				StatusCode: http.StatusInternalServerError,
+			},
+		},
 	}
-	assert.NotNil(t, handler)
 
-	//create
-	ctx := droplet.NewContext()
-	service := &entity.Service{}
-	reqBody := `{
-      "id": "1",
-      "plugins": {
-          "limit-count": {
-              "count": 2,
-              "time_window": 60,
-              "rejected_code": 503,
-              "key": "remote_addr"
-          }
-      },
-      "upstream": {
-          "type": "roundrobin",
-          "nodes": [{
-              "host": "39.97.63.215",
-              "port": 80,
-              "weight": 1
-          }]
-      }
-  }`
-	err = json.Unmarshal([]byte(reqBody), service)
-	assert.Nil(t, err)
-	ctx.SetInput(service)
-	ret, err := handler.Create(ctx)
-	assert.Nil(t, err)
-	objRet, ok := ret.(*entity.Service)
-	assert.True(t, ok)
-	assert.Equal(t, "1", objRet.ID)
+	for _, tc := range tests {
+		t.Run(tc.caseDesc, func(t *testing.T) {
+			getCalled := true
+			mStore := &store.MockInterface{}
+			mStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
+				getCalled = true
+				assert.Equal(t, tc.wantGetKey, args.Get(0))
+			}).Return(tc.giveRet, tc.giveErr)
 
-	//sleep
-	time.Sleep(time.Duration(100) * time.Millisecond)
-
-	//get
-	input := &GetInput{}
-	input.ID = "1"
-	ctx.SetInput(input)
-	ret, err = handler.Get(ctx)
-	stored := ret.(*entity.Service)
-	assert.Nil(t, err)
-	assert.Equal(t, stored.ID, service.ID)
+			h := Handler{serviceStore: mStore}
+			ctx := droplet.NewContext()
+			ctx.SetInput(tc.giveInput)
+			ret, err := h.Get(ctx)
+			assert.True(t, getCalled)
+			assert.Equal(t, tc.wantRet, ret)
+			assert.Equal(t, tc.wantErr, err)
+		})
+	}
+}
 
-	//update
-	service2 := &UpdateInput{}
-	service2.ID = "1"
-	reqBody = `{
-		"name": "test-service",
-		"plugins": {
-		  "limit-count": {
-		      "count": 2,
-		      "time_window": 60,
-		      "rejected_code": 503,
-		      "key": "remote_addr"
-		  }
+func TestService_List(t *testing.T) {
+	tests := []struct {
+		caseDesc  string
+		giveInput *ListInput
+		giveData  []*entity.Service
+		giveErr   error
+		wantErr   error
+		wantInput store.ListInput
+		wantRet   interface{}
+	}{
+		{
+			caseDesc: "list all service",
+			giveInput: &ListInput{
+				Pagination: store.Pagination{
+					PageSize:   10,
+					PageNumber: 10,
+				},
+			},
+			wantInput: store.ListInput{
+				PageSize:   10,
+				PageNumber: 10,
+			},
+			giveData: []*entity.Service{
+				{Name: "s1"},
+				{Name: "s2"},
+				{Name: "test_service"},
+				{Name: "service_test"},
+			},
+			wantRet: &store.ListOutput{
+				Rows: []interface{}{
+					&entity.Service{Name: "s1"},
+					&entity.Service{Name: "s2"},
+					&entity.Service{Name: "test_service"},
+					&entity.Service{Name: "service_test"},
+				},
+				TotalSize: 4,
+			},
 		},
-		"enable_websocket": true,
-		"upstream": {
-		  "type": "roundrobin",
-		  "nodes": [{
-		      "host": "39.97.63.215",
-		      "port": 80,
-		      "weight": 1
-		  }]
-		}
-	}`
-	err = json.Unmarshal([]byte(reqBody), service2)
-	assert.Nil(t, err)
-	ctx.SetInput(service2)
-	ret, err = handler.Update(ctx)
-	assert.Nil(t, err)
-	// Check the returned value
-	objRet, ok = ret.(*entity.Service)
-	assert.True(t, ok)
-	assert.Equal(t, service2.ID, objRet.ID)
-	assert.Equal(t, service2.Name, objRet.Name)
+		{
+			caseDesc: "list service with 'service'",
+			giveInput: &ListInput{
+				Name: "service",
+				Pagination: store.Pagination{
+					PageSize:   10,
+					PageNumber: 10,
+				},
+			},
+			wantInput: store.ListInput{
+				PageSize:   10,
+				PageNumber: 10,
+			},
+			giveData: []*entity.Service{
+				{BaseInfo: entity.BaseInfo{CreateTime: 1609376661}, Name: "s1"},
+				{BaseInfo: entity.BaseInfo{CreateTime: 1609376662}, Name: "s2"},
+				{BaseInfo: entity.BaseInfo{CreateTime: 1609376663}, Name: "test_service"},
+				{BaseInfo: entity.BaseInfo{CreateTime: 1609376664}, Name: "service_test"},
+			},
+			wantRet: &store.ListOutput{
+				Rows: []interface{}{
+					&entity.Service{BaseInfo: entity.BaseInfo{CreateTime: 1609376663}, Name: "test_service"},
+					&entity.Service{BaseInfo: entity.BaseInfo{CreateTime: 1609376664}, Name: "service_test"},
+				},
+				TotalSize: 2,
+			},
+		},
+		{
+			caseDesc: "list service with key s1",
+			giveInput: &ListInput{
+				Name: "s1",
+				Pagination: store.Pagination{
+					PageSize:   10,
+					PageNumber: 10,
+				},
+			},
+			wantInput: store.ListInput{
+				PageSize:   10,
+				PageNumber: 10,
+			},
+			giveData: []*entity.Service{
+				{Name: "s1"},
+				{Name: "s2"},
+				{Name: "test_service"},
+				{Name: "service_test"},
+			},
+			wantRet: &store.ListOutput{
+				Rows: []interface{}{
+					&entity.Service{Name: "s1"},
+				},
+				TotalSize: 1,
+			},
+		},
+		{
+			caseDesc: "list service and format",
+			giveInput: &ListInput{
+				Pagination: store.Pagination{
+					PageSize:   10,
+					PageNumber: 10,
+				},
+			},
+			wantInput: store.ListInput{
+				PageSize:   10,
+				PageNumber: 10,
+			},
+			giveData: []*entity.Service{
+				{
+					Name: "s1",
+					Upstream: &entity.UpstreamDef{
+						Nodes: []interface{}{
+							map[string]interface{}{
+								"host":   "39.97.63.215",
+								"port":   float64(80),
+								"weight": float64(1),
+							},
+						},
+					},
+				},
+				{Name: "s2"},
+				{Name: "test_service"},
+				{Name: "service_test"},
+			},
+			wantRet: &store.ListOutput{
+				Rows: []interface{}{
+					&entity.Service{Name: "s1", Upstream: &entity.UpstreamDef{
+						Nodes: []*entity.Node{
+							{
+								Host:   "39.97.63.215",
+								Port:   80,
+								Weight: 1,
+							},
+						},
+					}},
+					&entity.Service{Name: "s2"},
+					&entity.Service{Name: "test_service"},
+					&entity.Service{Name: "service_test"},
+				},
+				TotalSize: 4,
+			},
+		},
+	}
 
-	//sleep
-	time.Sleep(time.Duration(100) * time.Millisecond)
+	for _, tc := range tests {
+		t.Run(tc.caseDesc, func(t *testing.T) {
+			getCalled := true
+			mStore := &store.MockInterface{}
+			mStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
+				getCalled = true
+				input := args.Get(0).(store.ListInput)
+				assert.Equal(t, tc.wantInput.PageSize, input.PageSize)
+				assert.Equal(t, tc.wantInput.PageNumber, input.PageNumber)
+			}).Return(func(input store.ListInput) *store.ListOutput {
+				var returnData []interface{}
+				for _, c := range tc.giveData {
+					if input.Predicate(c) {
+						if input.Format == nil {
+							returnData = append(returnData, c)
+							continue
+						}
 
-	//list
-	listInput := &ListInput{}
-	reqBody = `{"page_size": 1, "page": 1}`
-	err = json.Unmarshal([]byte(reqBody), listInput)
-	assert.Nil(t, err)
-	ctx.SetInput(listInput)
-	retPage, err := handler.List(ctx)
-	assert.Nil(t, err)
-	dataPage := retPage.(*store.ListOutput)
-	assert.Equal(t, len(dataPage.Rows), 1)
+						returnData = append(returnData, input.Format(c))
+					}
+				}
+				return &store.ListOutput{
+					Rows:      returnData,
+					TotalSize: len(returnData),
+				}
+			}, tc.giveErr)
 
-	//list search match
-	listInput2 := &ListInput{}
-	reqBody = `{"page_size": 1, "page": 1, "name": "test"}`
-	err = json.Unmarshal([]byte(reqBody), listInput2)
-	assert.Nil(t, err)
-	ctx.SetInput(listInput2)
-	retPage, err = handler.List(ctx)
-	assert.Nil(t, err)
-	dataPage = retPage.(*store.ListOutput)
-	assert.Equal(t, len(dataPage.Rows), 1)
+			h := Handler{serviceStore: mStore}
+			ctx := droplet.NewContext()
+			ctx.SetInput(tc.giveInput)
+			ret, err := h.List(ctx)
+			assert.True(t, getCalled)
+			assert.Equal(t, tc.wantRet, ret)
+			assert.Equal(t, tc.wantErr, err)
+		})
+	}
+}
 
-	//list search not match
-	listInput3 := &ListInput{}
-	reqBody = `{"page_size": 1, "page": 1, "name": "not-exists"}`
-	err = json.Unmarshal([]byte(reqBody), listInput3)
-	assert.Nil(t, err)
-	ctx.SetInput(listInput3)
-	retPage, err = handler.List(ctx)
-	assert.Nil(t, err)
-	dataPage = retPage.(*store.ListOutput)
-	assert.Equal(t, len(dataPage.Rows), 0)
+func TestService_Create(t *testing.T) {
+	tests := []struct {
+		caseDesc      string
+		getCalled     bool
+		giveInput     *entity.Service
+		giveRet       interface{}
+		giveErr       error
+		wantInput     *entity.Service
+		wantErr       error
+		wantRet       interface{}
+		upstreamInput string
+		upstreamRet   interface{}
+		upstreamErr   interface{}
+	}{
+		{
+			caseDesc:  "create success",
+			getCalled: true,
+			giveInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			wantInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			upstreamInput: "u1",
+			upstreamRet: entity.Upstream{
+				BaseInfo: entity.BaseInfo{
+					ID: "u1",
+				},
+			},
+		},
+		{
+			caseDesc:  "create failed, upstream not found",
+			getCalled: false,
+			giveInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			wantInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			wantErr:       fmt.Errorf("upstream id: u1 not found"),
+			wantRet:       &data.SpecCodeResponse{StatusCode: http.StatusBadRequest},
+			upstreamInput: "u1",
+			upstreamErr:   data.ErrNotFound,
+		},
+		{
+			caseDesc:  "create failed, upstream return error",
+			getCalled: false,
+			giveInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			wantInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			wantErr:       fmt.Errorf("unknown error"),
+			wantRet:       &data.SpecCodeResponse{StatusCode: http.StatusBadRequest},
+			upstreamInput: "u1",
+			upstreamErr:   fmt.Errorf("unknown error"),
+		},
+		{
+			caseDesc:  "create failed, create return error",
+			getCalled: true,
+			giveInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			giveErr: fmt.Errorf("create failed"),
+			wantInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			upstreamInput: "u1",
+			upstreamRet: entity.Upstream{
+				BaseInfo: entity.BaseInfo{
+					ID: "u1",
+				},
+			},
+			wantErr: fmt.Errorf("create failed"),
+			wantRet: handler.SpecCodeResponse(fmt.Errorf("create failed")),
+		},
+	}
 
-	//delete test data
-	inputDel := &BatchDelete{}
-	reqBody = `{"ids": "1"}`
-	err = json.Unmarshal([]byte(reqBody), inputDel)
-	assert.Nil(t, err)
-	ctx.SetInput(inputDel)
-	_, err = handler.BatchDelete(ctx)
-	assert.Nil(t, err)
+	for _, tc := range tests {
+		t.Run(tc.caseDesc, func(t *testing.T) {
+			getCalled := false
+			serviceStore := &store.MockInterface{}
+			serviceStore.On("Create", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
+				getCalled = true
+				input := args.Get(1).(*entity.Service)
+				assert.Equal(t, tc.wantInput, input)
+			}).Return(tc.giveRet, tc.giveErr)
 
-	//create without upstream
-	service11 := &entity.Service{}
-	reqBody = `{
-      "id": "11",
-      "plugins": {
-          "limit-count": {
-              "count": 2,
-              "time_window": 60,
-              "rejected_code": 503,
-              "key": "remote_addr"
-          }
-      }
-  }`
-	err = json.Unmarshal([]byte(reqBody), service11)
-	assert.Nil(t, err)
-	ctx.SetInput(service11)
-	ret, err = handler.Create(ctx)
-	assert.Nil(t, err)
-	objRet, ok = ret.(*entity.Service)
-	assert.True(t, ok)
-	assert.Equal(t, "11", objRet.ID)
+			upstreamStore := &store.MockInterface{}
+			upstreamStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
+				id := args.Get(0).(string)
+				assert.Equal(t, tc.upstreamInput, id)
+			}).Return(tc.upstreamRet, tc.upstreamErr)
 
-	//sleep
-	time.Sleep(time.Duration(100) * time.Millisecond)
+			h := Handler{serviceStore: serviceStore, upstreamStore: upstreamStore}
+			ctx := droplet.NewContext()
+			ctx.SetInput(tc.giveInput)
+			ret, err := h.Create(ctx)
+			assert.Equal(t, tc.getCalled, getCalled)
+			assert.Equal(t, tc.wantRet, ret)
+			assert.Equal(t, tc.wantErr, err)
+		})
+	}
+}
 
-	//get
-	input11 := &GetInput{}
-	input11.ID = "11"
-	ctx.SetInput(input11)
-	ret, err = handler.Get(ctx)
-	stored = ret.(*entity.Service)
-	assert.Nil(t, err)
-	assert.Equal(t, "11", stored.ID)
+func TestService_Update(t *testing.T) {
+	tests := []struct {
+		caseDesc      string
+		getCalled     bool
+		giveInput     *UpdateInput
+		giveErr       error
+		giveRet       interface{}
+		wantInput     *entity.Service
+		wantErr       error
+		wantRet       interface{}
+		upstreamInput string
+		upstreamRet   interface{}
+		upstreamErr   interface{}
+	}{
+		{
+			caseDesc:  "create success",
+			getCalled: true,
+			giveInput: &UpdateInput{
+				ID: "s1",
+				Service: entity.Service{
+					Name:       "s1",
+					UpstreamID: "u1",
+					Desc:       "test service",
+				},
+			},
+			wantInput: &entity.Service{
+				BaseInfo: entity.BaseInfo{
+					ID: "s1",
+				},
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			upstreamInput: "u1",
+			upstreamRet: entity.Upstream{
+				BaseInfo: entity.BaseInfo{
+					ID: "u1",
+				},
+			},
+		},
+		{
+			caseDesc: "create failed, different id",
+			giveInput: &UpdateInput{
+				ID: "s1",
+				Service: entity.Service{
+					BaseInfo: entity.BaseInfo{
+						ID: "s2",
+					},
+					Name:       "s1",
+					UpstreamID: "u1",
+					Desc:       "test service",
+				},
+			},
+			wantRet: &data.SpecCodeResponse{StatusCode: http.StatusBadRequest},
+			wantErr: fmt.Errorf("ID on path (s1) doesn't match ID on body (s2)"),
+		},
+		{
+			caseDesc: "update failed, upstream not found",
+			giveInput: &UpdateInput{
+				ID: "s1",
+				Service: entity.Service{
+					Name:       "s1",
+					UpstreamID: "u1",
+					Desc:       "test service",
+				},
+			},
+			wantInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			wantErr:       fmt.Errorf("upstream id: u1 not found"),
+			wantRet:       &data.SpecCodeResponse{StatusCode: http.StatusBadRequest},
+			upstreamInput: "u1",
+			upstreamErr:   data.ErrNotFound,
+		},
+		{
+			caseDesc: "update failed, upstream return error",
+			giveInput: &UpdateInput{
+				ID: "s1",
+				Service: entity.Service{
+					Name:       "s1",
+					UpstreamID: "u1",
+					Desc:       "test service",
+				},
+			},
+			wantInput: &entity.Service{
+				Name:       "s1",
+				UpstreamID: "u1",
+				Desc:       "test service",
+			},
+			wantErr:       fmt.Errorf("unknown error"),
+			wantRet:       &data.SpecCodeResponse{StatusCode: http.StatusBadRequest},
+			upstreamInput: "u1",
+			upstreamErr:   fmt.Errorf("unknown error"),
+		},
+		{

Review comment:
       It's hard to test this.
   Because we don't call `upstream.Get` in the `Update` function.
   What we can test is that the third argument in the `Update` function is true.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org