Repository: ansible/receptor
Pull request #1279: Add unit tests for `Conn` methods in conn.go in the netceptor package
Author: login: dleehr
Created: 2025-03-10T13:59:27Z | Merged: 2025-03-11T13:26:00Z

Adds unit tests for the methods of `Conn` defined in `conn.go` in the netceptor package.

Most of the `Conn` methods wrap functionality in `quic.Stream` and `quic.Connection`, so the approach here was to 

1. Establish interfaces in `conn.go` that shadow the `quic.Stream` and `quic.Connection` interfaces and update `conn.go` to use these interfaces.
2. Run `mockgen` against `conn.go`, generating mock implementations for these interfaces
3. Write tests for `Conn` instances that use these mock implementations to test that the `Conn` functionality is using the encapsulated `quic` instances as expected.


Diff:
diff --git a/pkg/netceptor/conn.go b/pkg/netceptor/conn.go
index 07438a740..e0fa40748 100644
--- a/pkg/netceptor/conn.go
+++ b/pkg/netceptor/conn.go
@@ -30,6 +30,14 @@ var MaxIdleTimeoutForQuicConnections = 30 * time.Second
 // Having this variablized allows the tests to set KeepAliveForQuicConnections = False so that things will properly fail.
 var KeepAliveForQuicConnections = true
 
+type QuicStreamForConn interface {
+	quic.Stream
+}
+
+type QuicConnectionForConn interface {
+	quic.Connection
+}
+
 type acceptResult struct {
 	conn net.Conn
 	err  error
@@ -304,13 +312,28 @@ func (li *Listener) Addr() net.Addr {
 type Conn struct {
 	s        *Netceptor
 	pc       PacketConner
-	qc       quic.Connection
-	qs       quic.Stream
+	qc       QuicConnectionForConn
+	qs       QuicStreamForConn
 	doneChan chan struct{}
 	doneOnce *sync.Once
 	ctx      context.Context
 }
 
+// NewConn constructs a new Conn instance, so that the test package can create one.
+func NewConn(s *Netceptor, pc PacketConner, qc QuicConnectionForConn, qs QuicStreamForConn, doneChan chan struct{}, doneOnce *sync.Once, ctx context.Context) *Conn {
+	conn := &Conn{
+		s:        s,
+		pc:       pc,
+		qc:       qc,
+		qs:       qs,
+		doneChan: doneChan,
+		doneOnce: doneOnce,
+		ctx:      ctx,
+	}
+
+	return conn
+}
+
 // Dial returns a stream connection compatible with Go's net.Conn.
 func (s *Netceptor) Dial(node string, service string, tlscfg *tls.Config) (*Conn, error) {
 	return s.DialContext(context.Background(), node, service, tlscfg)
@@ -416,15 +439,7 @@ func (s *Netceptor) DialContext(ctx context.Context, node string, service string
 			return
 		}
 	}()
-	conn := &Conn{
-		s:        s,
-		pc:       pc,
-		qc:       qc,
-		qs:       qs,
-		doneChan: doneChan,
-		doneOnce: &sync.Once{},
-		ctx:      cctx,
-	}
+	conn := NewConn(s, pc, qc, qs, doneChan, &sync.Once{}, cctx)
 
 	return conn, nil
 }
diff --git a/pkg/netceptor/conn_test.go b/pkg/netceptor/conn_test.go
new file mode 100644
index 000000000..f5a351626
--- /dev/null
+++ b/pkg/netceptor/conn_test.go
@@ -0,0 +1,234 @@
+package netceptor_test
+
+import (
+	"context"
+	"errors"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/ansible/receptor/pkg/netceptor"
+	"github.com/ansible/receptor/pkg/netceptor/mock_netceptor"
+	"github.com/quic-go/quic-go"
+	"go.uber.org/mock/gomock"
+)
+
+type TestConn struct {
+	pc netceptor.PacketConner
+	qc netceptor.QuicConnectionForConn
+	qs netceptor.QuicStreamForConn
+}
+
+func makeConn(t testing.TB, tc TestConn) *netceptor.Conn {
+	t.Helper()
+	conn := netceptor.NewConn(
+		netceptor.New(context.TODO(), "test-node"), // netceptor
+		tc.pc,                  // PacketConner
+		tc.qc,                  // Connection
+		tc.qs,                  // Stream
+		make(chan struct{}, 1), // doneChan
+		&sync.Once{},           // doneOnce
+		context.TODO(),         // context
+	)
+
+	return conn
+}
+
+// These tests operate on the quic Stream.
+func TestRead(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	buf := make([]byte, 1)
+	// Create a mock QuicStream
+	mockQs := mock_netceptor.NewMockQuicStreamForConn(ctrl)
+	// both success and error
+	t.Run("Returns number of bytes from successful Read", func(t *testing.T) {
+		want := 1
+		mockQs.EXPECT().Read(gomock.Eq(buf)).Return(want, nil).Times(1)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		got, err := conn.Read(buf)
+		if err != nil {
+			t.Fatalf("Read returned unexpected error %v", err)
+		}
+		if got != want {
+			t.Errorf("Wanted %v, got %v", want, got)
+		}
+	})
+
+	t.Run("Returns error from unsuccessful Read", func(t *testing.T) {
+		wantErr := errors.New("Read error")
+		mockQs.EXPECT().Read(gomock.Eq(buf)).Return(0, wantErr).Times(1)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		_, gotErr := conn.Read(buf)
+		if gotErr == nil {
+			t.Errorf("Read did not return expected error")
+		}
+		if gotErr != wantErr {
+			t.Errorf("Wanted %v, got %v", wantErr, gotErr)
+		}
+	})
+}
+
+func TestCancelRead(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	mockQs := mock_netceptor.NewMockQuicStreamForConn(ctrl)
+	mockQs.EXPECT().CancelRead(gomock.Eq(quic.StreamErrorCode(499))).Times(1)
+	conn := makeConn(t, TestConn{qs: mockQs})
+	conn.CancelRead()
+}
+
+func TestWrite(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	mockQs := mock_netceptor.NewMockQuicStreamForConn(ctrl)
+	bytes := []byte{4, 8, 15, 16, 23, 42}
+	t.Run("Returns number of bytes written in successful Write", func(t *testing.T) {
+		want := 6
+		mockQs.EXPECT().Write(gomock.Eq(bytes)).Return(want, nil).Times(1)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		got, err := conn.Write(bytes)
+		if err != nil {
+			t.Fatalf("Write returned unexpected error %v", err)
+		}
+		if got != want {
+			t.Errorf("Wanted %v, got %v", want, got)
+		}
+	})
+	t.Run("Returns error from unsuccessful Write", func(t *testing.T) {
+		wantErr := errors.New("Write error")
+		mockQs.EXPECT().Write(gomock.Eq(bytes)).Return(0, wantErr).Times(1)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		_, gotErr := conn.Write(bytes)
+		if gotErr == nil {
+			t.Errorf("Write did not return expected error")
+		}
+		if gotErr != wantErr {
+			t.Errorf("Wanted %v, got %v", wantErr, gotErr)
+		}
+	})
+}
+
+func TestClose(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	mockQs := mock_netceptor.NewMockQuicStreamForConn(ctrl)
+	mockQs.EXPECT().Close().Return(nil)
+	conn := makeConn(t, TestConn{qs: mockQs})
+	err := conn.Close() // This calls the doneOnce and closes the doneChan
+	// would be nice to test that the doneChan is closed
+	if err != nil {
+		t.Fatalf("conn.Close returned error %v", err)
+	}
+}
+
+// These tests operate on the quic Connection.
+func TestCloseConnection(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	// quic Connection should be closed
+	mockQc := mock_netceptor.NewMockQuicConnectionForConn(ctrl)
+	mockQc.EXPECT().CloseWithError(quic.ApplicationErrorCode(0), gomock.Eq("normal close")).Return(nil).Times(1)
+
+	// PacketConner should be cancelled
+	mockPc := mock_netceptor.NewMockPacketConner(ctrl)
+	mockPc.EXPECT().Cancel().Times(1)
+
+	// The CloseConnection method logs some information to the netceptor's Logger, so mock them
+	mockPc.EXPECT().LocalService().Return("test-local-service").Times(1)
+	mockQc.EXPECT().RemoteAddr().Return(netceptor.Addr{})
+
+	conn := makeConn(t, TestConn{pc: mockPc, qc: mockQc})
+	err := conn.CloseConnection()
+	if err != nil {
+		t.Fatalf("conn.CloseConnection returned error %v", err)
+	}
+}
+
+func TestLocalAddr(t *testing.T) {
+	want := netceptor.Addr{} // Could mock the net interacted here rather than an empty Addr{}
+	ctrl := gomock.NewController(t)
+	mockQc := mock_netceptor.NewMockQuicConnectionForConn(ctrl)
+	mockQc.EXPECT().LocalAddr().Return(want).Times(1)
+	conn := makeConn(t, TestConn{qc: mockQc})
+	got := conn.LocalAddr()
+	if got != want {
+		t.Errorf("Wanted %v, got %v", want, got)
+	}
+}
+
+func TestRemoteAddr(t *testing.T) {
+	want := netceptor.Addr{} // Could mock the net interacted here rather than an empty Addr{}
+	ctrl := gomock.NewController(t)
+	mockQc := mock_netceptor.NewMockQuicConnectionForConn(ctrl)
+	mockQc.EXPECT().RemoteAddr().Return(want).Times(1)
+	conn := makeConn(t, TestConn{qc: mockQc})
+	got := conn.RemoteAddr()
+	if got != want {
+		t.Errorf("Wanted %v, got %v", want, got)
+	}
+}
+
+func TestSetDeadline(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	mockQs := mock_netceptor.NewMockQuicStreamForConn(ctrl)
+	want := time.Now().Add(10 * time.Second)
+	t.Run("Returns no error after successful SetDeadline", func(t *testing.T) {
+		mockQs.EXPECT().SetDeadline(gomock.Eq(want)).Return(nil)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		err := conn.SetDeadline(want)
+		if err != nil {
+			t.Fatalf("conn.TestSetDeadline returned error %v", err)
+		}
+	})
+	t.Run("Returns error from unsuccessful SetDeadline", func(t *testing.T) {
+		wantErr := errors.New("SetDeadline error")
+		mockQs.EXPECT().SetDeadline(gomock.Eq(want)).Return(wantErr)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		gotErr := conn.SetDeadline(want)
+		if gotErr != wantErr {
+			t.Errorf("Wanted %v, got %v", wantErr, gotErr)
+		}
+	})
+}
+
+func TestSetReadDeadline(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	mockQs := mock_netceptor.NewMockQuicStreamForConn(ctrl)
+	want := time.Now().Add(10 * time.Second)
+	t.Run("Returns no error after successful SetReadDeadline", func(t *testing.T) {
+		mockQs.EXPECT().SetReadDeadline(gomock.Eq(want)).Return(nil)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		err := conn.SetReadDeadline(want)
+		if err != nil {
+			t.Fatalf("conn.SetReadDeadline returned error %v", err)
+		}
+	})
+	t.Run("Returns error from unsuccessful SetReadDeadline", func(t *testing.T) {
+		wantErr := errors.New("SetReadDeadline error")
+		mockQs.EXPECT().SetReadDeadline(gomock.Eq(want)).Return(wantErr)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		gotErr := conn.SetReadDeadline(want)
+		if gotErr != wantErr {
+			t.Errorf("Wanted %v, got %v", wantErr, gotErr)
+		}
+	})
+}
+
+func TestSetWriteDeadline(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	mockQs := mock_netceptor.NewMockQuicStreamForConn(ctrl)
+	want := time.Now().Add(10 * time.Second)
+	t.Run("Returns no error after successful SetWriteDeadline", func(t *testing.T) {
+		mockQs.EXPECT().SetWriteDeadline(gomock.Eq(want)).Return(nil)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		err := conn.SetWriteDeadline(want)
+		if err != nil {
+			t.Fatalf("conn.SetWriteDeadline returned error %v", err)
+		}
+	})
+	t.Run("Returns error from unsuccessful SetWriteDeadline", func(t *testing.T) {
+		wantErr := errors.New("SetWriteDeadline error")
+		mockQs.EXPECT().SetWriteDeadline(gomock.Eq(want)).Return(wantErr)
+		conn := makeConn(t, TestConn{qs: mockQs})
+		gotErr := conn.SetWriteDeadline(want)
+		if gotErr != wantErr {
+			t.Errorf("Wanted %v, got %v", wantErr, gotErr)
+		}
+	})
+}
diff --git a/pkg/netceptor/mock_netceptor/conn.go b/pkg/netceptor/mock_netceptor/conn.go
new file mode 100644
index 000000000..093708648
--- /dev/null
+++ b/pkg/netceptor/mock_netceptor/conn.go
@@ -0,0 +1,395 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: pkg/netceptor/conn.go
+//
+// Generated by this command:
+//
+//	mockgen -source=pkg/netceptor/conn.go -destination=pkg/netceptor/mock_netceptor/conn.go
+//
+
+// Package mock_netceptor is a generated GoMock package.
+package mock_netceptor
+
+import (
+	context "context"
+	net "net"
+	reflect "reflect"
+	time "time"
+
+	quic "github.com/quic-go/quic-go"
+	gomock "go.uber.org/mock/gomock"
+)
+
+// MockQuicStreamForConn is a mock of QuicStreamForConn interface.
+type MockQuicStreamForConn struct {
+	ctrl     *gomock.Controller
+	recorder *MockQuicStreamForConnMockRecorder
+	isgomock struct{}
+}
+
+// MockQuicStreamForConnMockRecorder is the mock recorder for MockQuicStreamForConn.
+type MockQuicStreamForConnMockRecorder struct {
+	mock *MockQuicStreamForConn
+}
+
+// NewMockQuicStreamForConn creates a new mock instance.
+func NewMockQuicStreamForConn(ctrl *gomock.Controller) *MockQuicStreamForConn {
+	mock := &MockQuicStreamForConn{ctrl: ctrl}
+	mock.recorder = &MockQuicStreamForConnMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockQuicStreamForConn) EXPECT() *MockQuicStreamForConnMockRecorder {
+	return m.recorder
+}
+
+// CancelRead mocks base method.
+func (m *MockQuicStreamForConn) CancelRead(arg0 quic.StreamErrorCode) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "CancelRead", arg0)
+}
+
+// CancelRead indicates an expected call of CancelRead.
+func (mr *MockQuicStreamForConnMockRecorder) CancelRead(arg0 any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelRead", reflect.TypeOf((*MockQuicStreamForConn)(nil).CancelRead), arg0)
+}
+
+// CancelWrite mocks base method.
+func (m *MockQuicStreamForConn) CancelWrite(arg0 quic.StreamErrorCode) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "CancelWrite", arg0)
+}
+
+// CancelWrite indicates an expected call of CancelWrite.
+func (mr *MockQuicStreamForConnMockRecorder) CancelWrite(arg0 any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelWrite", reflect.TypeOf((*MockQuicStreamForConn)(nil).CancelWrite), arg0)
+}
+
+// Close mocks base method.
+func (m *MockQuicStreamForConn) Close() error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Close")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Close indicates an expected call of Close.
+func (mr *MockQuicStreamForConnMockRecorder) Close() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockQuicStreamForConn)(nil).Close))
+}
+
+// Context mocks base method.
+func (m *MockQuicStreamForConn) Context() context.Context {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Context")
+	ret0, _ := ret[0].(context.Context)
+	return ret0
+}
+
+// Context indicates an expected call of Context.
+func (mr *MockQuicStreamForConnMockRecorder) Context() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockQuicStreamForConn)(nil).Context))
+}
+
+// Read mocks base method.
+func (m *MockQuicStreamForConn) Read(p []byte) (int, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Read", p)
+	ret0, _ := ret[0].(int)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Read indicates an expected call of Read.
+func (mr *MockQuicStreamForConnMockRecorder) Read(p any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockQuicStreamForConn)(nil).Read), p)
+}
+
+// SetDeadline mocks base method.
+func (m *MockQuicStreamForConn) SetDeadline(t time.Time) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SetDeadline", t)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// SetDeadline indicates an expected call of SetDeadline.
+func (mr *MockQuicStreamForConnMockRecorder) SetDeadline(t any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockQuicStreamForConn)(nil).SetDeadline), t)
+}
+
+// SetReadDeadline mocks base method.
+func (m *MockQuicStreamForConn) SetReadDeadline(t time.Time) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SetReadDeadline", t)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// SetReadDeadline indicates an expected call of SetReadDeadline.
+func (mr *MockQuicStreamForConnMockRecorder) SetReadDeadline(t any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockQuicStreamForConn)(nil).SetReadDeadline), t)
+}
+
+// SetWriteDeadline mocks base method.
+func (m *MockQuicStreamForConn) SetWriteDeadline(t time.Time) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SetWriteDeadline", t)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// SetWriteDeadline indicates an expected call of SetWriteDeadline.
+func (mr *MockQuicStreamForConnMockRecorder) SetWriteDeadline(t any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockQuicStreamForConn)(nil).SetWriteDeadline), t)
+}
+
+// StreamID mocks base method.
+func (m *MockQuicStreamForConn) StreamID() quic.StreamID {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "StreamID")
+	ret0, _ := ret[0].(quic.StreamID)
+	return ret0
+}
+
+// StreamID indicates an expected call of StreamID.
+func (mr *MockQuicStreamForConnMockRecorder) StreamID() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StreamID", reflect.TypeOf((*MockQuicStreamForConn)(nil).StreamID))
+}
+
+// Write mocks base method.
+func (m *MockQuicStreamForConn) Write(p []byte) (int, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Write", p)
+	ret0, _ := ret[0].(int)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Write indicates an expected call of Write.
+func (mr *MockQuicStreamForConnMockRecorder) Write(p any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockQuicStreamForConn)(nil).Write), p)
+}
+
+// MockQuicConnectionForConn is a mock of QuicConnectionForConn interface.
+type MockQuicConnectionForConn struct {
+	ctrl     *gomock.Controller
+	recorder *MockQuicConnectionForConnMockRecorder
+	isgomock struct{}
+}
+
+// MockQuicConnectionForConnMockRecorder is the mock recorder for MockQuicConnectionForConn.
+type MockQuicConnectionForConnMockRecorder struct {
+	mock *MockQuicConnectionForConn
+}
+
+// NewMockQuicConnectionForConn creates a new mock instance.
+func NewMockQuicConnectionForConn(ctrl *gomock.Controller) *MockQuicConnectionForConn {
+	mock := &MockQuicConnectionForConn{ctrl: ctrl}
+	mock.recorder = &MockQuicConnectionForConnMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockQuicConnectionForConn) EXPECT() *MockQuicConnectionForConnMockRecorder {
+	return m.recorder
+}
+
+// AcceptStream mocks base method.
+func (m *MockQuicConnectionForConn) AcceptStream(arg0 context.Context) (quic.Stream, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AcceptStream", arg0)
+	ret0, _ := ret[0].(quic.Stream)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// AcceptStream indicates an expected call of AcceptStream.
+func (mr *MockQuicConnectionForConnMockRecorder) AcceptStream(arg0 any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptStream", reflect.TypeOf((*MockQuicConnectionForConn)(nil).AcceptStream), arg0)
+}
+
+// AcceptUniStream mocks base method.
+func (m *MockQuicConnectionForConn) AcceptUniStream(arg0 context.Context) (quic.ReceiveStream, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AcceptUniStream", arg0)
+	ret0, _ := ret[0].(quic.ReceiveStream)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// AcceptUniStream indicates an expected call of AcceptUniStream.
+func (mr *MockQuicConnectionForConnMockRecorder) AcceptUniStream(arg0 any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptUniStream", reflect.TypeOf((*MockQuicConnectionForConn)(nil).AcceptUniStream), arg0)
+}
+
+// CloseWithError mocks base method.
+func (m *MockQuicConnectionForConn) CloseWithError(arg0 quic.ApplicationErrorCode, arg1 string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CloseWithError", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CloseWithError indicates an expected call of CloseWithError.
+func (mr *MockQuicConnectionForConnMockRecorder) CloseWithError(arg0, arg1 any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseWithError", reflect.TypeOf((*MockQuicConnectionForConn)(nil).CloseWithError), arg0, arg1)
+}
+
+// ConnectionState mocks base method.
+func (m *MockQuicConnectionForConn) ConnectionState() quic.ConnectionState {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ConnectionState")
+	ret0, _ := ret[0].(quic.ConnectionState)
+	return ret0
+}
+
+// ConnectionState indicates an expected call of ConnectionState.
+func (mr *MockQuicConnectionForConnMockRecorder) ConnectionState() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConnectionState", reflect.TypeOf((*MockQuicConnectionForConn)(nil).ConnectionState))
+}
+
+// Context mocks base method.
+func (m *MockQuicConnectionForConn) Context() context.Context {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Context")
+	ret0, _ := ret[0].(context.Context)
+	return ret0
+}
+
+// Context indicates an expected call of Context.
+func (mr *MockQuicConnectionForConnMockRecorder) Context() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockQuicConnectionForConn)(nil).Context))
+}
+
+// LocalAddr mocks base method.
+func (m *MockQuicConnectionForConn) LocalAddr() net.Addr {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "LocalAddr")
+	ret0, _ := ret[0].(net.Addr)
+	return ret0
+}
+
+// LocalAddr indicates an expected call of LocalAddr.
+func (mr *MockQuicConnectionForConnMockRecorder) LocalAddr() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockQuicConnectionForConn)(nil).LocalAddr))
+}
+
+// OpenStream mocks base method.
+func (m *MockQuicConnectionForConn) OpenStream() (quic.Stream, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "OpenStream")
+	ret0, _ := ret[0].(quic.Stream)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// OpenStream indicates an expected call of OpenStream.
+func (mr *MockQuicConnectionForConnMockRecorder) OpenStream() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenStream", reflect.TypeOf((*MockQuicConnectionForConn)(nil).OpenStream))
+}
+
+// OpenStreamSync mocks base method.
+func (m *MockQuicConnectionForConn) OpenStreamSync(arg0 context.Context) (quic.Stream, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "OpenStreamSync", arg0)
+	ret0, _ := ret[0].(quic.Stream)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// OpenStreamSync indicates an expected call of OpenStreamSync.
+func (mr *MockQuicConnectionForConnMockRecorder) OpenStreamSync(arg0 any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenStreamSync", reflect.TypeOf((*MockQuicConnectionForConn)(nil).OpenStreamSync), arg0)
+}
+
+// OpenUniStream mocks base method.
+func (m *MockQuicConnectionForConn) OpenUniStream() (quic.SendStream, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "OpenUniStream")
+	ret0, _ := ret[0].(quic.SendStream)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// OpenUniStream indicates an expected call of OpenUniStream.
+func (mr *MockQuicConnectionForConnMockRecorder) OpenUniStream() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenUniStream", reflect.TypeOf((*MockQuicConnectionForConn)(nil).OpenUniStream))
+}
+
+// OpenUniStreamSync mocks base method.
+func (m *MockQuicConnectionForConn) OpenUniStreamSync(arg0 context.Context) (quic.SendStream, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "OpenUniStreamSync", arg0)
+	ret0, _ := ret[0].(quic.SendStream)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// OpenUniStreamSync indicates an expected call of OpenUniStreamSync.
+func (mr *MockQuicConnectionForConnMockRecorder) OpenUniStreamSync(arg0 any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenUniStreamSync", reflect.TypeOf((*MockQuicConnectionForConn)(nil).OpenUniStreamSync), arg0)
+}
+
+// ReceiveDatagram mocks base method.
+func (m *MockQuicConnectionForConn) ReceiveDatagram(arg0 context.Context) ([]byte, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ReceiveDatagram", arg0)
+	ret0, _ := ret[0].([]byte)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ReceiveDatagram indicates an expected call of ReceiveDatagram.
+func (mr *MockQuicConnectionForConnMockRecorder) ReceiveDatagram(arg0 any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveDatagram", reflect.TypeOf((*MockQuicConnectionForConn)(nil).ReceiveDatagram), arg0)
+}
+
+// RemoteAddr mocks base method.
+func (m *MockQuicConnectionForConn) RemoteAddr() net.Addr {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "RemoteAddr")
+	ret0, _ := ret[0].(net.Addr)
+	return ret0
+}
+
+// RemoteAddr indicates an expected call of RemoteAddr.
+func (mr *MockQuicConnectionForConnMockRecorder) RemoteAddr() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockQuicConnectionForConn)(nil).RemoteAddr))
+}
+
+// SendDatagram mocks base method.
+func (m *MockQuicConnectionForConn) SendDatagram(payload []byte) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SendDatagram", payload)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// SendDatagram indicates an expected call of SendDatagram.
+func (mr *MockQuicConnectionForConnMockRecorder) SendDatagram(payload any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendDatagram", reflect.TypeOf((*MockQuicConnectionForConn)(nil).SendDatagram), payload)
+}


Reviews:
user: login: AaronH88
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T11:04:31Z
user: login: dleehr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T13:06:36Z
user: login: AaronH88
body: lgtm 
OK to merge. 

SonarCloud is giving a warning for code you havent touched, can you make a bug ticket for this please, to address the error?
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T13:25:49Z

Review comments:
diff_hunk: @@ -300,17 +300,40 @@ func (li *Listener) Addr() net.Addr {
 	return li.pc.LocalAddr()
 }
 
+type QuicStreamForConn interface {
path: pkg/netceptor/conn.go
user: login: AaronH88
body: How do you feel about putting interface definitions at the top of the file? 

Would you think it would be more or less readable? 
Personally I find it more readable but I would like more opinions.
created_at: 2025-03-11T11:04:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -300,17 +300,40 @@ func (li *Listener) Addr() net.Addr {
 	return li.pc.LocalAddr()
 }
 
+type QuicStreamForConn interface {
path: pkg/netceptor/conn.go
user: login: dleehr
body: I agree it's more readable to have types at the top, moved in 65e94c06396dc1c20af35bb6029e75642fe24d52 .
created_at: 2025-03-11T13:06:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-03-10T14:08:49Z
body: ## [Codecov](https://app.codecov.io/gh/ansible/receptor/pull/1279?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible) Report
Attention: Patch coverage is `92.30769%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 40.76%. Comparing base [(`87251b8`)](https://app.codecov.io/gh/ansible/receptor/commit/87251b8eff83e336e9f28388a601be1af5f455c7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible) to head [(`65e94c0`)](https://app.codecov.io/gh/ansible/receptor/commit/65e94c06396dc1c20af35bb6029e75642fe24d52?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible).
> Report is 27 commits behind head on devel.

| [Files with missing lines](https://app.codecov.io/gh/ansible/receptor/pull/1279?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible) | Patch % | Lines |
|---|---|---|
| [pkg/netceptor/conn.go](https://app.codecov.io/gh/ansible/receptor/pull/1279?src=pr&el=tree&filepath=pkg%2Fnetceptor%2Fconn.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible#diff-cGtnL25ldGNlcHRvci9jb25uLmdv) | 92.30% | [1 Missing :warning: ](https://app.codecov.io/gh/ansible/receptor/pull/1279?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible) |

```diff
@@            Coverage Diff             @@
##            devel    #1279      +/-   ##
==========================================
+ Coverage   39.62%   40.76%   +1.13%     
==========================================
  Files          45       48       +3     
  Lines        9445    10029     +584     
==========================================
+ Hits         3743     4088     +345     
- Misses       5407     5651     +244     
+ Partials      295      290       -5     
```

| [Files with missing lines](https://app.codecov.io/gh/ansible/receptor/pull/1279?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible) | Coverage Δ | |
|---|---|---|
| [pkg/netceptor/conn.go](https://app.codecov.io/gh/ansible/receptor/pull/1279?src=pr&el=tree&filepath=pkg%2Fnetceptor%2Fconn.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible#diff-cGtnL25ldGNlcHRvci9jb25uLmdv) | `14.07% <92.30%> (+10.26%)` | :arrow_up: |

... and [5 files with indirect coverage changes](https://app.codecov.io/gh/ansible/receptor/pull/1279/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible)

| [Components](https://app.codecov.io/gh/ansible/receptor/pull/1279/components?src=pr&el=components&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible) | Coverage Δ | |
|---|---|---|
| [Go](https://app.codecov.io/gh/ansible/receptor/pull/1279/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible) | `40.23% <92.30%> (+0.60%)` | :arrow_up: |
| [Receptorctl](https://app.codecov.io/gh/ansible/receptor/pull/1279/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ansible) | `49.31% <ø> (∅)` | |

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: dleehr
created_at: 2025-03-10T15:25:21Z
body: The quality gate is complaining about creating a `tls.Config` with `InsecureSkipVerify: true`:

https://github.com/ansible/receptor/blob/20f0d733a4de91795194daa5d109cb676a7d981e/pkg/netceptor/conn.go#L574-L575

but this line isn't changing in my PR
author_association: CONTRIBUTOR
user: login: matoval
created_at: 2025-03-10T19:32:45Z
body: > The quality gate is complaining about creating a `tls.Config` with `InsecureSkipVerify: true`:
> 
> https://github.com/ansible/receptor/blob/20f0d733a4de91795194daa5d109cb676a7d981e/pkg/netceptor/conn.go#L574-L575
> 
> but this line isn't changing in my PR

You can ignore this quality gate with `// #nosec G402`
https://github.com/securego/gosec?tab=readme-ov-file#annotating-code
author_association: COLLABORATOR
user: login: AaronH88
created_at: 2025-03-11T11:07:12Z
body: Looks really good, you flew through it! 

I have one style question I would like to get your opinion, but this looks good to merge
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-11T13:07:10Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=ansible_receptor&pullRequest=1279) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [D Security Rating on New Code](https://sonarcloud.io/dashboard?id=ansible_receptor&pullRequest=1279) (required ≥ A)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ansible_receptor&pullRequest=1279)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png '') [SonarQube for IDE](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)


author_association: NONE
user: login: dleehr
created_at: 2025-03-11T14:01:51Z
body: > lgtm OK to merge.
> 
> SonarCloud is giving a warning for code you havent touched, can you make a bug ticket for this please, to address the error?

https://issues.redhat.com/browse/AAP-41819
author_association: CONTRIBUTOR