func TestChain(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() req := &transport.Request{ Caller: "somecaller", Service: "someservice", Encoding: transport.Encoding("raw"), Procedure: "hello", Body: bytes.NewReader([]byte{1, 2, 3}), } resw := new(transporttest.FakeResponseWriter) h := transporttest.NewMockUnaryHandler(mockCtrl) h.EXPECT().Handle(ctx, req, resw).After( h.EXPECT().Handle(ctx, req, resw).Return(errors.New("great sadness")), ).Return(nil) before := &countInterceptor{} after := &countInterceptor{} err := transport.ApplyInterceptor( h, Chain(before, retryInterceptor, after), ).Handle(ctx, req, resw) assert.NoError(t, err, "expected success") assert.Equal(t, 1, before.Count, "expected outer interceptor to be called once") assert.Equal(t, 2, after.Count, "expected inner interceptor to be called twice") }
func TestMapRegistry_ServiceProcedures(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() m := transport.NewMapRegistry("myservice") bar := transporttest.NewMockUnaryHandler(mockCtrl) foo := transporttest.NewMockUnaryHandler(mockCtrl) aww := transporttest.NewMockUnaryHandler(mockCtrl) m.Register([]transport.Registrant{ { Service: "anotherservice", Procedure: "bar", HandlerSpec: transport.NewUnaryHandlerSpec(bar), }, { Procedure: "foo", HandlerSpec: transport.NewUnaryHandlerSpec(foo), }, { Service: "anotherservice", Procedure: "aww", HandlerSpec: transport.NewUnaryHandlerSpec(aww), }, }) expectedOrderedServiceProcedures := []transport.ServiceProcedure{ { Service: "anotherservice", Procedure: "aww", }, { Service: "anotherservice", Procedure: "bar", }, { Service: "myservice", Procedure: "foo", }, } serviceProcedures := m.ServiceProcedures() assert.Equal(t, expectedOrderedServiceProcedures, serviceProcedures) }
func TestMapRegistry(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() m := transport.NewMapRegistry("myservice") foo := transporttest.NewMockUnaryHandler(mockCtrl) bar := transporttest.NewMockUnaryHandler(mockCtrl) m.Register([]transport.Registrant{ { Procedure: "foo", HandlerSpec: transport.NewUnaryHandlerSpec(foo), }, { Service: "anotherservice", Procedure: "bar", HandlerSpec: transport.NewUnaryHandlerSpec(bar), }, }) tests := []struct { service, procedure string want transport.UnaryHandler }{ {"myservice", "foo", foo}, {"", "foo", foo}, {"anotherservice", "foo", nil}, {"", "bar", nil}, {"myservice", "bar", nil}, {"anotherservice", "bar", bar}, } for _, tt := range tests { got, err := m.GetHandlerSpec(tt.service, tt.procedure) if tt.want != nil { assert.NoError(t, err, "GetHandlerSpec(%q, %q) failed", tt.service, tt.procedure) assert.True(t, tt.want == got.Unary(), // want == match, not deep equals "GetHandlerSpec(%q, %q) did not match", tt.service, tt.procedure) } else { assert.Error(t, err) } } }
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 TestNopInterceptor(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() h := transporttest.NewMockUnaryHandler(mockCtrl) wrappedH := transport.ApplyInterceptor(h, transport.NopInterceptor) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() req := &transport.Request{ Caller: "somecaller", Service: "someservice", Encoding: raw.Encoding, Procedure: "hello", Body: bytes.NewReader([]byte{1, 2, 3}), } resw := new(transporttest.FakeResponseWriter) err := errors.New("great sadness") h.EXPECT().Handle(ctx, req, resw).Return(err) assert.Equal(t, err, wrappedH.Handle(ctx, req, resw)) }
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 TestInboundMux(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mux := http.NewServeMux() mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("healthy")) }) i := NewInbound(":0", Mux("/rpc/v1", mux)) h := transporttest.NewMockUnaryHandler(mockCtrl) reg := transporttest.NewMockRegistry(mockCtrl) require.NoError(t, i.Start(transport.ServiceDetail{Name: "foo", Registry: reg}, transport.NoDeps)) defer i.Stop() addr := fmt.Sprintf("http://%v/", i.Addr().String()) resp, err := http.Get(addr + "health") if assert.NoError(t, err, "/health failed") { defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if assert.NoError(t, err, "/health body read error") { assert.Equal(t, "healthy", string(body), "/health body mismatch") } } // this should fail o := NewOutbound(addr) require.NoError(t, o.Start(transport.NoDeps), "failed to start outbound") defer o.Stop() ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() _, err = o.Call(ctx, &transport.Request{ Caller: "foo", Service: "bar", Procedure: "hello", Encoding: raw.Encoding, Body: bytes.NewReader([]byte("derp")), }) if assert.Error(t, err, "RPC call to / should have failed") { assert.Equal(t, err.Error(), "404 page not found") } o = NewOutbound(addr + "rpc/v1") require.NoError(t, o.Start(transport.NoDeps), "failed to start outbound") defer o.Stop() spec := transport.NewUnaryHandlerSpec(h) reg.EXPECT().GetHandlerSpec("bar", "hello").Return(spec, nil) h.EXPECT().Handle(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) res, err := o.Call(ctx, &transport.Request{ Caller: "foo", Service: "bar", Procedure: "hello", Encoding: raw.Encoding, Body: bytes.NewReader([]byte("derp")), }) if assert.NoError(t, err, "expected rpc request to succeed") { defer res.Body.Close() s, err := ioutil.ReadAll(res.Body) if assert.NoError(t, err) { assert.Empty(t, s) } } }
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() } }