Beispiel #1
0
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())
}
Beispiel #2
0
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(), "")
}
Beispiel #3
0
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")
	}
}
Beispiel #4
0
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()
	}
}