Avoid a byte copy when delivering a HTTP request

Added tests for the HTTP sig transport.
This commit is contained in:
Cory Slep 2020-07-05 17:06:52 +02:00
parent c3b24964d4
commit 107afb6f17
3 changed files with 326 additions and 4 deletions

166
pub/mock_httpsig_test.go Normal file
View File

@ -0,0 +1,166 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ../../httpsig/httpsig.go
// Package pub is a generated GoMock package.
package pub
import (
crypto "crypto"
httpsig "github.com/go-fed/httpsig"
gomock "github.com/golang/mock/gomock"
http "net/http"
reflect "reflect"
)
// MockSigner is a mock of Signer interface
type MockSigner struct {
ctrl *gomock.Controller
recorder *MockSignerMockRecorder
}
// MockSignerMockRecorder is the mock recorder for MockSigner
type MockSignerMockRecorder struct {
mock *MockSigner
}
// NewMockSigner creates a new mock instance
func NewMockSigner(ctrl *gomock.Controller) *MockSigner {
mock := &MockSigner{ctrl: ctrl}
mock.recorder = &MockSignerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockSigner) EXPECT() *MockSignerMockRecorder {
return m.recorder
}
// SignRequest mocks base method
func (m *MockSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignRequest", pKey, pubKeyId, r, body)
ret0, _ := ret[0].(error)
return ret0
}
// SignRequest indicates an expected call of SignRequest
func (mr *MockSignerMockRecorder) SignRequest(pKey, pubKeyId, r, body interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignRequest", reflect.TypeOf((*MockSigner)(nil).SignRequest), pKey, pubKeyId, r, body)
}
// SignResponse mocks base method
func (m *MockSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignResponse", pKey, pubKeyId, r, body)
ret0, _ := ret[0].(error)
return ret0
}
// SignResponse indicates an expected call of SignResponse
func (mr *MockSignerMockRecorder) SignResponse(pKey, pubKeyId, r, body interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignResponse", reflect.TypeOf((*MockSigner)(nil).SignResponse), pKey, pubKeyId, r, body)
}
// MockSSHSigner is a mock of SSHSigner interface
type MockSSHSigner struct {
ctrl *gomock.Controller
recorder *MockSSHSignerMockRecorder
}
// MockSSHSignerMockRecorder is the mock recorder for MockSSHSigner
type MockSSHSignerMockRecorder struct {
mock *MockSSHSigner
}
// NewMockSSHSigner creates a new mock instance
func NewMockSSHSigner(ctrl *gomock.Controller) *MockSSHSigner {
mock := &MockSSHSigner{ctrl: ctrl}
mock.recorder = &MockSSHSignerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockSSHSigner) EXPECT() *MockSSHSignerMockRecorder {
return m.recorder
}
// SignRequest mocks base method
func (m *MockSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignRequest", pubKeyId, r, body)
ret0, _ := ret[0].(error)
return ret0
}
// SignRequest indicates an expected call of SignRequest
func (mr *MockSSHSignerMockRecorder) SignRequest(pubKeyId, r, body interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignRequest", reflect.TypeOf((*MockSSHSigner)(nil).SignRequest), pubKeyId, r, body)
}
// SignResponse mocks base method
func (m *MockSSHSigner) SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignResponse", pubKeyId, r, body)
ret0, _ := ret[0].(error)
return ret0
}
// SignResponse indicates an expected call of SignResponse
func (mr *MockSSHSignerMockRecorder) SignResponse(pubKeyId, r, body interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignResponse", reflect.TypeOf((*MockSSHSigner)(nil).SignResponse), pubKeyId, r, body)
}
// MockVerifier is a mock of Verifier interface
type MockVerifier struct {
ctrl *gomock.Controller
recorder *MockVerifierMockRecorder
}
// MockVerifierMockRecorder is the mock recorder for MockVerifier
type MockVerifierMockRecorder struct {
mock *MockVerifier
}
// NewMockVerifier creates a new mock instance
func NewMockVerifier(ctrl *gomock.Controller) *MockVerifier {
mock := &MockVerifier{ctrl: ctrl}
mock.recorder = &MockVerifierMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockVerifier) EXPECT() *MockVerifierMockRecorder {
return m.recorder
}
// KeyId mocks base method
func (m *MockVerifier) KeyId() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "KeyId")
ret0, _ := ret[0].(string)
return ret0
}
// KeyId indicates an expected call of KeyId
func (mr *MockVerifierMockRecorder) KeyId() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyId", reflect.TypeOf((*MockVerifier)(nil).KeyId))
}
// Verify mocks base method
func (m *MockVerifier) Verify(pKey crypto.PublicKey, algo httpsig.Algorithm) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Verify", pKey, algo)
ret0, _ := ret[0].(error)
return ret0
}
// Verify indicates an expected call of Verify
func (mr *MockVerifierMockRecorder) Verify(pKey, algo interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockVerifier)(nil).Verify), pKey, algo)
}

View File

@ -137,10 +137,7 @@ func (h HttpSigTransport) Dereference(c context.Context, iri *url.URL) ([]byte,
// Deliver sends a POST request with an HTTP Signature.
func (h HttpSigTransport) Deliver(c context.Context, b []byte, to *url.URL) error {
byteCopy := make([]byte, len(b))
copy(byteCopy, b)
buf := bytes.NewBuffer(byteCopy)
req, err := http.NewRequest("POST", to.String(), buf)
req, err := http.NewRequest("POST", to.String(), bytes.NewReader(b))
if err != nil {
return err
}

159
pub/transport_test.go Normal file
View File

@ -0,0 +1,159 @@
package pub
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/golang/mock/gomock"
)
const (
testAppAgent = "testApp"
testPubKeyId = "myPubKeyId"
)
var (
testPrivKey = []byte("some private key")
testRespBody = []byte("test resp body")
httpSigSetupFn = func(ctl *gomock.Controller) (t *HttpSigTransport, c *MockClock, hc *MockHttpClient, gs, ps *MockSigner) {
c = NewMockClock(ctl)
hc = NewMockHttpClient(ctl)
gs = NewMockSigner(ctl)
ps = NewMockSigner(ctl)
t = NewHttpSigTransport(
hc,
testAppAgent,
c,
gs,
ps,
testPubKeyId,
testPrivKey)
return
}
)
func TestHttpSigTransportDereference(t *testing.T) {
ctx := context.Background()
t.Run("ReturnsErrorWhenHTTPStatusError", func(t *testing.T) {
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
tp, c, hc, gs, _ := httpSigSetupFn(ctl)
resp := &http.Response{}
testErr := fmt.Errorf("test error")
// Mock
c.EXPECT().Now().Return(now())
gs.EXPECT().SignRequest(testPrivKey, testPubKeyId, gomock.Any(), nil)
hc.EXPECT().Do(gomock.Any()).Return(resp, testErr)
// Run & Verify
b, err := tp.Dereference(ctx, mustParse(testNoteId1))
assertEqual(t, len(b), 0)
assertEqual(t, err, testErr)
})
t.Run("Dereferences", func(t *testing.T) {
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
tp, c, hc, gs, _ := httpSigSetupFn(ctl)
expectReq, err := http.NewRequest("GET", testNoteId1, nil)
assertEqual(t, err, nil)
expectReq = expectReq.WithContext(ctx)
expectReq.Header.Add(acceptHeader, acceptHeaderValue)
expectReq.Header.Add("Accept-Charset", "utf-8")
expectReq.Header.Add("Date", nowDateHeader())
expectReq.Header.Add("User-Agent", fmt.Sprintf("%s %s", testAppAgent, goFedUserAgent()))
respR := httptest.NewRecorder()
respR.Write(testRespBody)
resp := respR.Result()
// Mock
c.EXPECT().Now().Return(now())
gs.EXPECT().SignRequest(testPrivKey, testPubKeyId, expectReq, nil)
hc.EXPECT().Do(expectReq).Return(resp, nil)
// Run & Verify
b, err := tp.Dereference(ctx, mustParse(testNoteId1))
assertByteEqual(t, b, testRespBody)
assertEqual(t, err, nil)
})
}
func TestHttpSigTransportDeliver(t *testing.T) {
ctx := context.Background()
t.Run("ReturnsErrorWhenHTTPStatusError", func(t *testing.T) {
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
tp, c, hc, _, ps := httpSigSetupFn(ctl)
resp := &http.Response{}
testErr := fmt.Errorf("test error")
// Mock
c.EXPECT().Now().Return(now())
ps.EXPECT().SignRequest(testPrivKey, testPubKeyId, gomock.Any(), gomock.Any())
hc.EXPECT().Do(gomock.Any()).Return(resp, testErr)
// Run & Verify
err := tp.Deliver(ctx, testRespBody, mustParse(testNoteId1))
assertEqual(t, err, testErr)
})
t.Run("Delivers", func(t *testing.T) {
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
tp, c, hc, _, ps := httpSigSetupFn(ctl)
// gomock cannot handle http.NewRequest w/ Body differences.
respR := httptest.NewRecorder()
respR.WriteHeader(http.StatusOK)
resp := respR.Result()
// Mock
c.EXPECT().Now().Return(now())
ps.EXPECT().SignRequest(testPrivKey, testPubKeyId, gomock.Any(), testRespBody)
hc.EXPECT().Do(gomock.Any()).Return(resp, nil)
// Run & Verify
err := tp.Deliver(ctx, testRespBody, mustParse(testFederatedActorIRI))
assertEqual(t, err, nil)
})
}
func TestHttpSigTransportBatchDeliver(t *testing.T) {
ctx := context.Background()
t.Run("BatchDelivers", func(t *testing.T) {
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
tp, c, hc, _, ps := httpSigSetupFn(ctl)
// gomock cannot handle http.NewRequest w/ Body differences.
respR := httptest.NewRecorder()
respR.WriteHeader(http.StatusOK)
resp := respR.Result()
// Mock
c.EXPECT().Now().Return(now()).Times(2)
ps.EXPECT().SignRequest(testPrivKey, testPubKeyId, gomock.Any(), testRespBody).Times(2)
hc.EXPECT().Do(gomock.Any()).Return(resp, nil).Times(2)
// Run & Verify
err := tp.BatchDeliver(ctx, testRespBody, []*url.URL{mustParse(testFederatedActorIRI), mustParse(testFederatedActorIRI2)})
assertEqual(t, err, nil)
})
t.Run("ReturnsErrorWhenOneErrors", func(t *testing.T) {
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
tp, c, hc, _, ps := httpSigSetupFn(ctl)
// gomock cannot handle http.NewRequest w/ Body differences.
respR := httptest.NewRecorder()
respR.WriteHeader(http.StatusOK)
resp := respR.Result()
errResp := &http.Response{}
testErr := fmt.Errorf("test error")
// Mock
c.EXPECT().Now().Return(now()).Times(2)
ps.EXPECT().SignRequest(testPrivKey, testPubKeyId, gomock.Any(), testRespBody).Times(2)
first := hc.EXPECT().Do(gomock.Any()).Return(resp, nil)
hc.EXPECT().Do(gomock.Any()).Return(errResp, testErr).After(first)
// Run & Verify
err := tp.BatchDeliver(ctx, testRespBody, []*url.URL{mustParse(testFederatedActorIRI), mustParse(testFederatedActorIRI2)})
assertNotEqual(t, err, nil)
})
}