| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package service |
| |
| import ( |
| "errors" |
| "fmt" |
| "net/http" |
| "testing" |
| |
| "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/core/entity" |
| "github.com/apisix/manager-api/internal/core/store" |
| "github.com/apisix/manager-api/internal/handler" |
| ) |
| |
| 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, |
| }, |
| }, |
| } |
| |
| 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) |
| |
| 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) |
| }) |
| } |
| } |
| |
| 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, |
| }, |
| }, |
| { |
| 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), |
| "priority": float64(10), |
| }, |
| }, |
| }, |
| }, |
| {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, |
| Priority: 10, |
| }, |
| }, |
| }}, |
| &entity.Service{Name: "s2"}, |
| &entity.Service{Name: "test_service"}, |
| &entity.Service{Name: "service_test"}, |
| }, |
| TotalSize: 4, |
| }, |
| }, |
| } |
| |
| 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 |
| } |
| |
| returnData = append(returnData, input.Format(c)) |
| } |
| } |
| return &store.ListOutput{ |
| Rows: returnData, |
| TotalSize: len(returnData), |
| } |
| }, tc.giveErr) |
| |
| 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) |
| }) |
| } |
| } |
| |
| 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{} |
| nameExistRet []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")), |
| }, |
| } |
| |
| 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) |
| |
| 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) |
| |
| serviceStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { |
| }).Return(func(input store.ListInput) *store.ListOutput { |
| return &store.ListOutput{ |
| Rows: tc.nameExistRet, |
| TotalSize: len(tc.nameExistRet), |
| } |
| }, nil) |
| |
| 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) |
| }) |
| } |
| } |
| |
| 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{} |
| nameExistRet []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"), |
| }, |
| { |
| caseDesc: "update failed, update return error", |
| getCalled: true, |
| giveInput: &UpdateInput{ |
| ID: "s1", |
| Service: entity.Service{ |
| Name: "s1", |
| UpstreamID: "u1", |
| Desc: "test service", |
| }, |
| }, |
| giveErr: fmt.Errorf("update failed"), |
| upstreamInput: "u1", |
| upstreamRet: entity.Upstream{ |
| BaseInfo: entity.BaseInfo{ |
| ID: "u1", |
| }, |
| }, |
| wantInput: &entity.Service{ |
| BaseInfo: entity.BaseInfo{ID: "s1"}, |
| Name: "s1", |
| UpstreamID: "u1", |
| Desc: "test service", |
| }, |
| wantErr: fmt.Errorf("update failed"), |
| wantRet: handler.SpecCodeResponse(fmt.Errorf("update failed")), |
| }, |
| } |
| |
| for _, tc := range tests { |
| t.Run(tc.caseDesc, func(t *testing.T) { |
| getCalled := false |
| serviceStore := &store.MockInterface{} |
| serviceStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { |
| getCalled = true |
| input := args.Get(1).(*entity.Service) |
| createIfNotExist := args.Get(2).(bool) |
| assert.Equal(t, tc.wantInput, input) |
| assert.True(t, createIfNotExist) |
| }).Return(tc.giveRet, tc.giveErr) |
| |
| 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) |
| |
| serviceStore.On("List", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { |
| }).Return(func(input store.ListInput) *store.ListOutput { |
| return &store.ListOutput{ |
| Rows: tc.nameExistRet, |
| TotalSize: len(tc.nameExistRet), |
| } |
| }, nil) |
| |
| h := Handler{serviceStore: serviceStore, upstreamStore: upstreamStore} |
| ctx := droplet.NewContext() |
| ctx.SetInput(tc.giveInput) |
| ret, err := h.Update(ctx) |
| assert.Equal(t, tc.getCalled, getCalled) |
| assert.Equal(t, tc.wantRet, ret) |
| assert.Equal(t, tc.wantErr, err) |
| }) |
| } |
| } |
| |
| func TestService_Patch(t *testing.T) { |
| existService := &entity.Service{ |
| BaseInfo: entity.BaseInfo{ |
| ID: "s1", |
| CreateTime: 1609340491, |
| UpdateTime: 1609340491, |
| }, |
| Name: "exist_service", |
| UpstreamID: "u1", |
| EnableWebsocket: false, |
| Labels: map[string]string{ |
| "version": "v1", |
| }, |
| Plugins: map[string]interface{}{ |
| "limit-count": map[string]interface{}{ |
| "count": 2, |
| "time_window": 60, |
| "rejected_code": 503, |
| "key": "remote_addr", |
| }, |
| }, |
| } |
| |
| tests := []struct { |
| caseDesc string |
| giveInput *PatchInput |
| giveErr error |
| giveRet interface{} |
| wantInput *entity.Service |
| wantErr error |
| wantRet interface{} |
| serviceInput string |
| serviceRet *entity.Service |
| serviceErr error |
| called bool |
| }{ |
| { |
| caseDesc: "patch all success", |
| giveInput: &PatchInput{ |
| ID: "s1", |
| SubPath: "", |
| Body: []byte(`{ |
| "name":"patched", |
| "upstream_id":"u2", |
| "enable_websocket":true, |
| "labels":{ |
| "version":"v1", |
| "build":"16" |
| }, |
| "plugins":{ |
| "limit-count":{ |
| "count":2, |
| "time_window":60, |
| "rejected_code": 504, |
| "key":"remote_addr" |
| }, |
| "key-auth":{ |
| "key":"auth-one" |
| } |
| } |
| }`), |
| }, |
| wantInput: &entity.Service{ |
| BaseInfo: entity.BaseInfo{ |
| ID: "s1", |
| CreateTime: 1609340491, |
| UpdateTime: 1609340491, |
| }, |
| Name: "patched", |
| UpstreamID: "u2", |
| EnableWebsocket: true, |
| Labels: map[string]string{ |
| "version": "v1", |
| "build": "16", |
| }, |
| Plugins: map[string]interface{}{ |
| "limit-count": map[string]interface{}{ |
| "count": float64(2), |
| "time_window": float64(60), |
| "rejected_code": float64(504), |
| "key": "remote_addr", |
| }, |
| "key-auth": map[string]interface{}{ |
| "key": "auth-one", |
| }, |
| }, |
| }, |
| serviceInput: "s1", |
| serviceRet: existService, |
| called: true, |
| }, |
| { |
| caseDesc: "patch part of service success", |
| giveInput: &PatchInput{ |
| ID: "s1", |
| SubPath: "", |
| Body: []byte(`{ |
| "name":"patched", |
| "upstream_id":"u2", |
| "enable_websocket":true, |
| "labels":{ |
| "version":"v1", |
| "build":"16" |
| } |
| }`), |
| }, |
| wantInput: &entity.Service{ |
| BaseInfo: entity.BaseInfo{ |
| ID: "s1", |
| CreateTime: 1609340491, |
| UpdateTime: 1609340491, |
| }, |
| Name: "patched", |
| UpstreamID: "u2", |
| EnableWebsocket: true, |
| Labels: map[string]string{ |
| "version": "v1", |
| "build": "16", |
| }, |
| Plugins: map[string]interface{}{ |
| "limit-count": map[string]interface{}{ |
| "count": float64(2), |
| "time_window": float64(60), |
| "rejected_code": float64(503), |
| "key": "remote_addr", |
| }, |
| }, |
| }, |
| serviceInput: "s1", |
| serviceRet: existService, |
| called: true, |
| }, |
| { |
| caseDesc: "patch name success with sub path", |
| giveInput: &PatchInput{ |
| ID: "s1", |
| SubPath: "/upstream_id", |
| Body: []byte(`{"upstream_id":"u3"}`), |
| }, |
| wantInput: &entity.Service{ |
| BaseInfo: entity.BaseInfo{ |
| ID: "s1", |
| CreateTime: 1609340491, |
| UpdateTime: 1609340491, |
| }, |
| Name: "exist_service", |
| UpstreamID: map[string]interface{}{ |
| "upstream_id": "u3", |
| }, |
| EnableWebsocket: false, |
| Labels: map[string]string{ |
| "version": "v1", |
| }, |
| Plugins: map[string]interface{}{ |
| "limit-count": map[string]interface{}{ |
| "count": float64(2), |
| "time_window": float64(60), |
| "rejected_code": float64(503), |
| "key": "remote_addr", |
| }, |
| }, |
| }, |
| serviceInput: "s1", |
| serviceRet: existService, |
| called: true, |
| }, |
| { |
| caseDesc: "patch labels success", |
| giveInput: &PatchInput{ |
| ID: "s1", |
| SubPath: "/labels", |
| Body: []byte(`{"version": "v3"}`), |
| }, |
| wantInput: &entity.Service{ |
| BaseInfo: entity.BaseInfo{ |
| ID: "s1", |
| CreateTime: 1609340491, |
| UpdateTime: 1609340491, |
| }, |
| Name: "exist_service", |
| EnableWebsocket: false, |
| Labels: map[string]string{ |
| "version": "v3", |
| }, |
| UpstreamID: "u1", |
| Plugins: map[string]interface{}{ |
| "limit-count": map[string]interface{}{ |
| "count": float64(2), |
| "time_window": float64(60), |
| "rejected_code": float64(503), |
| "key": "remote_addr", |
| }, |
| }, |
| }, |
| serviceInput: "s1", |
| serviceRet: existService, |
| called: true, |
| }, |
| { |
| caseDesc: "patch failed, service store get error", |
| giveInput: &PatchInput{ |
| ID: "s1", |
| Body: []byte{}, |
| }, |
| serviceInput: "s1", |
| serviceErr: fmt.Errorf("get error"), |
| wantRet: handler.SpecCodeResponse(fmt.Errorf("get error")), |
| wantErr: fmt.Errorf("get error"), |
| called: false, |
| }, |
| } |
| |
| for _, tc := range tests { |
| t.Run(tc.caseDesc, func(t *testing.T) { |
| getCalled := false |
| serviceStore := &store.MockInterface{} |
| serviceStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { |
| getCalled = true |
| input := args.Get(1).(*entity.Service) |
| createIfNotExist := args.Get(2).(bool) |
| assert.Equal(t, tc.wantInput, input) |
| assert.False(t, createIfNotExist) |
| }).Return(tc.giveRet, tc.giveErr) |
| |
| serviceStore.On("Get", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { |
| input := args.Get(0).(string) |
| assert.Equal(t, tc.serviceInput, input) |
| }).Return(tc.serviceRet, tc.serviceErr) |
| |
| h := Handler{serviceStore: serviceStore} |
| ctx := droplet.NewContext() |
| ctx.SetInput(tc.giveInput) |
| ret, err := h.Patch(ctx) |
| assert.Equal(t, tc.called, getCalled) |
| assert.Equal(t, tc.wantRet, ret) |
| assert.Equal(t, tc.wantErr, err) |
| }) |
| } |
| } |
| |
| func TestServices_Delete(t *testing.T) { |
| tests := []struct { |
| caseDesc string |
| giveInput *BatchDelete |
| giveErr error |
| wantInput []string |
| wantErr error |
| wantRet interface{} |
| routeMockData []*entity.Route |
| routeMockErr error |
| getCalled bool |
| }{ |
| { |
| caseDesc: "delete success", |
| giveInput: &BatchDelete{ |
| IDs: "s1", |
| }, |
| wantInput: []string{"s1"}, |
| getCalled: true, |
| }, |
| { |
| caseDesc: "batch delete success", |
| giveInput: &BatchDelete{ |
| IDs: "s1,s2", |
| }, |
| wantInput: []string{"s1", "s2"}, |
| getCalled: true, |
| }, |
| { |
| caseDesc: "delete failed", |
| giveInput: &BatchDelete{ |
| IDs: "s1", |
| }, |
| giveErr: fmt.Errorf("delete error"), |
| wantInput: []string{"s1"}, |
| wantRet: handler.SpecCodeResponse(fmt.Errorf("delete error")), |
| wantErr: fmt.Errorf("delete error"), |
| getCalled: true, |
| }, |
| { |
| caseDesc: "delete failed, route is using", |
| giveInput: &BatchDelete{ |
| IDs: "s1", |
| }, |
| wantInput: []string{"s1"}, |
| routeMockData: []*entity.Route{ |
| &entity.Route{ |
| BaseInfo: entity.BaseInfo{ |
| ID: "r1", |
| CreateTime: 1609746531, |
| }, |
| Name: "route1", |
| Desc: "test_route", |
| UpstreamID: "u1", |
| ServiceID: "s1", |
| Labels: map[string]string{ |
| "version": "v1", |
| }, |
| }, |
| }, |
| routeMockErr: nil, |
| getCalled: false, |
| wantRet: &data.SpecCodeResponse{StatusCode: 400}, |
| wantErr: errors.New("route: route1 is using this service"), |
| }, |
| { |
| caseDesc: "delete failed, route list error", |
| giveInput: &BatchDelete{ |
| IDs: "s1", |
| }, |
| wantInput: []string{"s1"}, |
| routeMockData: nil, |
| routeMockErr: errors.New("route list error"), |
| wantRet: handler.SpecCodeResponse(errors.New("route list error")), |
| wantErr: errors.New("route list error"), |
| getCalled: false, |
| }, |
| } |
| |
| for _, tc := range tests { |
| t.Run(tc.caseDesc, func(t *testing.T) { |
| getCalled := false |
| serviceStore := &store.MockInterface{} |
| serviceStore.On("BatchDelete", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { |
| getCalled = true |
| input := args.Get(1).([]string) |
| assert.Equal(t, tc.wantInput, input) |
| }).Return(tc.giveErr) |
| |
| routeStore := &store.MockInterface{} |
| routeStore.On("List", mock.Anything).Return(func(input store.ListInput) *store.ListOutput { |
| var returnData []interface{} |
| for _, c := range tc.routeMockData { |
| if input.Predicate(c) { |
| if input.Format == nil { |
| returnData = append(returnData, c) |
| continue |
| } |
| |
| returnData = append(returnData, input.Format(c)) |
| } |
| } |
| |
| return &store.ListOutput{ |
| Rows: returnData, |
| TotalSize: len(returnData), |
| } |
| }, tc.routeMockErr) |
| |
| h := Handler{serviceStore: serviceStore, routeStore: routeStore} |
| ctx := droplet.NewContext() |
| ctx.SetInput(tc.giveInput) |
| ret, err := h.BatchDelete(ctx) |
| assert.Equal(t, tc.getCalled, getCalled) |
| assert.Equal(t, tc.wantRet, ret) |
| assert.Equal(t, tc.wantErr, err) |
| }) |
| } |
| } |