func TestHandlerInternalFailure(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() headers := make(http.Header) headers.Set(CallerHeader, "somecaller") headers.Set(EncodingHeader, "raw") headers.Set(TTLMSHeader, "1000") headers.Set(ProcedureHeader, "hello") headers.Set(ServiceHeader, "fake") request := http.Request{ Method: "POST", Header: headers, Body: ioutil.NopCloser(bytes.NewReader([]byte{})), } rpcHandler := transporttest.NewMockUnaryHandler(mockCtrl) rpcHandler.EXPECT().Handle( transporttest.NewContextMatcher(t, transporttest.ContextTTL(time.Second)), transporttest.NewRequestMatcher( t, &transport.Request{ Caller: "somecaller", Service: "fake", Encoding: raw.Encoding, Procedure: "hello", Body: bytes.NewReader([]byte{}), }, ), gomock.Any(), ).Return(fmt.Errorf("great sadness")) registry := transporttest.NewMockRegistry(mockCtrl) spec := transport.NewUnaryHandlerSpec(rpcHandler) registry.EXPECT().GetHandlerSpec("fake", "hello").Return(spec, nil) httpHandler := handler{Registry: registry} httpResponse := httptest.NewRecorder() httpHandler.ServeHTTP(httpResponse, &request) code := httpResponse.Code assert.True(t, code >= 500 && code < 600, "expected 500 level response") assert.Equal(t, `UnexpectedError: error for procedure "hello" of service "fake": great sadness`+"\n", httpResponse.Body.String()) }
func TestHandlerSucces(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() headers := make(http.Header) headers.Set(CallerHeader, "moe") headers.Set(EncodingHeader, "raw") headers.Set(TTLMSHeader, "1000") headers.Set(ProcedureHeader, "nyuck") headers.Set(ServiceHeader, "curly") registry := transporttest.NewMockRegistry(mockCtrl) rpcHandler := transporttest.NewMockUnaryHandler(mockCtrl) spec := transport.NewUnaryHandlerSpec(rpcHandler) registry.EXPECT().GetHandlerSpec("curly", "nyuck").Return(spec, nil) rpcHandler.EXPECT().Handle( transporttest.NewContextMatcher(t, transporttest.ContextTTL(time.Second), ), transporttest.NewRequestMatcher( t, &transport.Request{ Caller: "moe", Service: "curly", Encoding: raw.Encoding, Procedure: "nyuck", Body: bytes.NewReader([]byte("Nyuck Nyuck")), }, ), gomock.Any(), ).Return(nil) httpHandler := handler{Registry: registry} req := &http.Request{ Method: "POST", Header: headers, Body: ioutil.NopCloser(bytes.NewReader([]byte("Nyuck Nyuck"))), } rw := httptest.NewRecorder() httpHandler.ServeHTTP(rw, req) code := rw.Code assert.Equal(t, code, 200, "expected 200 code") assert.Equal(t, rw.Body.String(), "") }
func TestHandlerHeaders(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() tests := []struct { giveHeaders http.Header wantTTL time.Duration wantHeaders map[string]string }{ { giveHeaders: http.Header{ TTLMSHeader: {"1000"}, "Rpc-Header-Foo": {"bar"}, }, wantTTL: time.Second, wantHeaders: map[string]string{ "foo": "bar", }, }, { giveHeaders: http.Header{ TTLMSHeader: {"100"}, "Rpc-Foo": {"ignored"}, }, wantTTL: 100 * time.Millisecond, wantHeaders: map[string]string{}, }, } for _, tt := range tests { registry := transporttest.NewMockRegistry(mockCtrl) rpcHandler := transporttest.NewMockUnaryHandler(mockCtrl) spec := transport.NewUnaryHandlerSpec(rpcHandler) registry.EXPECT().GetHandlerSpec("service", "hello").Return(spec, nil) httpHandler := handler{Registry: registry} rpcHandler.EXPECT().Handle( transporttest.NewContextMatcher(t, transporttest.ContextTTL(tt.wantTTL), ), transporttest.NewRequestMatcher(t, &transport.Request{ Caller: "caller", Service: "service", Encoding: raw.Encoding, Procedure: "hello", Headers: transport.HeadersFromMap(tt.wantHeaders), Body: bytes.NewReader([]byte("world")), }), gomock.Any(), ).Return(nil) headers := http.Header{} for k, vs := range tt.giveHeaders { for _, v := range vs { headers.Add(k, v) } } headers.Set(CallerHeader, "caller") headers.Set(ServiceHeader, "service") headers.Set(EncodingHeader, "raw") headers.Set(ProcedureHeader, "hello") req := &http.Request{ Method: "POST", Header: headers, Body: ioutil.NopCloser(bytes.NewReader([]byte("world"))), } rw := httptest.NewRecorder() httpHandler.ServeHTTP(rw, req) assert.Equal(t, 200, rw.Code, "expected 200 status code") } }
func TestHandlerErrors(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() tests := []struct { format tchannel.Format headers []byte wantHeaders map[string]string }{ { format: tchannel.JSON, headers: []byte(`{"Rpc-Header-Foo": "bar"}`), wantHeaders: map[string]string{"rpc-header-foo": "bar"}, }, { format: tchannel.Thrift, headers: []byte{ 0x00, 0x01, // 1 header 0x00, 0x03, 'F', 'o', 'o', // Foo 0x00, 0x03, 'B', 'a', 'r', // Bar }, wantHeaders: map[string]string{"foo": "Bar"}, }, } for _, tt := range tests { rpcHandler := transporttest.NewMockUnaryHandler(mockCtrl) registry := transporttest.NewMockRegistry(mockCtrl) spec := transport.NewUnaryHandlerSpec(rpcHandler) tchHandler := handler{Registry: registry} registry.EXPECT().GetHandlerSpec("service", "hello").Return(spec, nil) rpcHandler.EXPECT().Handle( transporttest.NewContextMatcher(t), transporttest.NewRequestMatcher(t, &transport.Request{ Caller: "caller", Service: "service", Headers: transport.HeadersFromMap(tt.wantHeaders), Encoding: transport.Encoding(tt.format), Procedure: "hello", Body: bytes.NewReader([]byte("world")), }), gomock.Any(), ).Return(nil) respRecorder := newResponseRecorder() ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() tchHandler.handle(ctx, &fakeInboundCall{ service: "service", caller: "caller", format: tt.format, method: "hello", arg2: tt.headers, arg3: []byte("world"), resp: respRecorder, }) assert.NoError(t, respRecorder.systemErr, "did not expect an error") } }
func TestHandlerFailures(t *testing.T) { tests := []struct { desc string // context to use in the callm a default one is used otherwise. ctx context.Context ctxFunc func() (context.Context, context.CancelFunc) sendCall *fakeInboundCall expectCall func(*transporttest.MockUnaryHandler) wantErrors []string // error message contents wantStatus tchannel.SystemErrCode // expected status }{ { desc: "no timeout on context", ctx: context.Background(), sendCall: &fakeInboundCall{ service: "foo", caller: "bar", method: "hello", format: tchannel.Raw, arg2: []byte{0x00, 0x00}, arg3: []byte{0x00}, }, wantErrors: []string{"timeout required"}, wantStatus: tchannel.ErrCodeBadRequest, }, { desc: "arg2 reader error", sendCall: &fakeInboundCall{ service: "foo", caller: "bar", method: "hello", format: tchannel.Raw, arg2: nil, arg3: []byte{0x00}, }, wantErrors: []string{ `BadRequest: failed to decode "raw" request headers for`, `procedure "hello" of service "foo" from caller "bar"`, }, wantStatus: tchannel.ErrCodeBadRequest, }, { desc: "arg2 parse error", sendCall: &fakeInboundCall{ service: "foo", caller: "bar", method: "hello", format: tchannel.JSON, arg2: []byte("{not valid JSON}"), arg3: []byte{0x00}, }, wantErrors: []string{ `BadRequest: failed to decode "json" request headers for`, `procedure "hello" of service "foo" from caller "bar"`, }, wantStatus: tchannel.ErrCodeBadRequest, }, { desc: "arg3 reader error", sendCall: &fakeInboundCall{ service: "foo", caller: "bar", method: "hello", format: tchannel.Raw, arg2: []byte{0x00, 0x00}, arg3: nil, }, wantErrors: []string{ `UnexpectedError: error for procedure "hello" of service "foo"`, }, wantStatus: tchannel.ErrCodeUnexpected, }, { desc: "internal error", sendCall: &fakeInboundCall{ service: "foo", caller: "bar", method: "hello", format: tchannel.Raw, arg2: []byte{0x00, 0x00}, arg3: []byte{0x00}, }, expectCall: func(h *transporttest.MockUnaryHandler) { h.EXPECT().Handle( transporttest.NewContextMatcher(t, transporttest.ContextTTL(time.Second)), transporttest.NewRequestMatcher( t, &transport.Request{ Caller: "bar", Service: "foo", Encoding: raw.Encoding, Procedure: "hello", Body: bytes.NewReader([]byte{0x00}), }, ), gomock.Any(), ).Return(fmt.Errorf("great sadness")) }, wantErrors: []string{ `UnexpectedError: error for procedure "hello" of service "foo":`, "great sadness", }, wantStatus: tchannel.ErrCodeUnexpected, }, { desc: "arg3 encode error", sendCall: &fakeInboundCall{ service: "foo", caller: "bar", method: "hello", format: tchannel.JSON, arg2: []byte("{}"), arg3: []byte("{}"), }, expectCall: func(h *transporttest.MockUnaryHandler) { req := &transport.Request{ Caller: "bar", Service: "foo", Encoding: json.Encoding, Procedure: "hello", Body: bytes.NewReader([]byte("{}")), } h.EXPECT().Handle( transporttest.NewContextMatcher(t, transporttest.ContextTTL(time.Second)), transporttest.NewRequestMatcher(t, req), gomock.Any(), ).Return( encoding.ResponseBodyEncodeError(req, errors.New( "serialization derp", ))) }, wantErrors: []string{ `UnexpectedError: failed to encode "json" response body for`, `procedure "hello" of service "foo" from caller "bar":`, `serialization derp`, }, wantStatus: tchannel.ErrCodeUnexpected, }, { desc: "handler timeout", ctxFunc: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Millisecond) }, sendCall: &fakeInboundCall{ service: "foo", caller: "bar", method: "waituntiltimeout", format: tchannel.Raw, arg2: []byte{0x00, 0x00}, arg3: []byte{0x00}, }, expectCall: func(h *transporttest.MockUnaryHandler) { req := &transport.Request{ Service: "foo", Caller: "bar", Procedure: "waituntiltimeout", Encoding: raw.Encoding, Body: bytes.NewReader([]byte{0x00}), } h.EXPECT().Handle( transporttest.NewContextMatcher( t, transporttest.ContextTTL(time.Millisecond)), transporttest.NewRequestMatcher(t, req), gomock.Any(), ).Do(func(ctx context.Context, _ *transport.Request, _ transport.ResponseWriter) { <-ctx.Done() }).Return(context.DeadlineExceeded) }, wantErrors: []string{ `tchannel error ErrCodeTimeout: Timeout: call to procedure "waituntiltimeout" of service "foo" from caller "bar" timed out after `}, wantStatus: tchannel.ErrCodeTimeout, }, { desc: "handler panic", sendCall: &fakeInboundCall{ service: "foo", caller: "bar", method: "panic", format: tchannel.Raw, arg2: []byte{0x00, 0x00}, arg3: []byte{0x00}, }, expectCall: func(h *transporttest.MockUnaryHandler) { req := &transport.Request{ Service: "foo", Caller: "bar", Procedure: "panic", Encoding: raw.Encoding, Body: bytes.NewReader([]byte{0x00}), } h.EXPECT().Handle( transporttest.NewContextMatcher( t, transporttest.ContextTTL(time.Second)), transporttest.NewRequestMatcher(t, req), gomock.Any(), ).Do(func(context.Context, *transport.Request, transport.ResponseWriter) { panic("oops I panicked!") }) }, wantErrors: []string{ `UnexpectedError: error for procedure "panic" of service "foo": panic: oops I panicked!`, }, wantStatus: tchannel.ErrCodeUnexpected, }, } for _, tt := range tests { ctx, cancel := context.WithTimeout(context.Background(), time.Second) if tt.ctx != nil { ctx = tt.ctx } else if tt.ctxFunc != nil { ctx, cancel = tt.ctxFunc() } defer cancel() mockCtrl := gomock.NewController(t) thandler := transporttest.NewMockUnaryHandler(mockCtrl) spec := transport.NewUnaryHandlerSpec(thandler) if tt.expectCall != nil { tt.expectCall(thandler) } resp := newResponseRecorder() tt.sendCall.resp = resp registry := transporttest.NewMockRegistry(mockCtrl) registry.EXPECT().GetHandlerSpec(tt.sendCall.service, tt.sendCall.method). Return(spec, nil).AnyTimes() handler{Registry: registry}.handle(ctx, tt.sendCall) err := resp.systemErr require.Error(t, err, "expected error for %q", tt.desc) systemErr, isSystemErr := err.(tchannel.SystemError) require.True(t, isSystemErr, "expected %v for %q to be a system error", err, tt.desc) assert.Equal(t, tt.wantStatus, systemErr.Code(), tt.desc) for _, msg := range tt.wantErrors { assert.Contains( t, err.Error(), msg, "error should contain message for %q", tt.desc) } mockCtrl.Finish() } }