package fabricproto import ( "bytes" "context" "errors" "io" "testing" ) func TestTransportLoopRunsFramesAndWritesResponses(t *testing.T) { var input bytes.Buffer writeTestFrame(t, &input, Frame{Type: FrameOpenStream, TrafficClass: TrafficClassInteractive, StreamID: 7}) writeTestFrame(t, &input, Frame{Type: FrameData, TrafficClass: TrafficClassInteractive, StreamID: 7, Sequence: 3, Payload: []byte("input")}) writeTestFrame(t, &input, Frame{Type: FramePing, Sequence: 4, Payload: []byte("probe")}) var output bytes.Buffer var events []SessionEventType loop := TransportLoop{ Session: NewSession(SessionConfig{}), OnEvent: func(event SessionEvent) ([]Frame, error) { events = append(events, event.Type) return nil, nil }, } if err := loop.Run(context.Background(), &input, &output); err != nil { t.Fatalf("run loop: %v", err) } if len(events) != 3 || events[0] != SessionEventStreamOpened || events[1] != SessionEventData || events[2] != SessionEventPing { t.Fatalf("events = %+v", events) } ack := readTestFrame(t, &output) if ack.Type != FrameAck || ack.StreamID != 7 || ack.Sequence != 3 { t.Fatalf("ack = %+v", ack) } pong := readTestFrame(t, &output) if pong.Type != FramePong || pong.Sequence != 4 || string(pong.Payload) != "probe" { t.Fatalf("pong = %+v", pong) } if _, err := ReadFrame(&output, DefaultMaxPayload); !errors.Is(err, io.EOF) { t.Fatalf("extra response err = %v, want EOF", err) } } func TestTransportLoopEventHandlerCanEmitFrames(t *testing.T) { var output bytes.Buffer loop := TransportLoop{ Session: NewSession(SessionConfig{}), OnEvent: func(event SessionEvent) ([]Frame, error) { if event.Type != SessionEventHello { return nil, nil } return []Frame{{Type: FrameSessionReady, Payload: []byte("ready")}}, nil }, } if err := loop.Handle(context.Background(), Frame{Type: FrameHello, Payload: []byte("hello")}, &output); err != nil { t.Fatalf("handle hello: %v", err) } ready := readTestFrame(t, &output) if ready.Type != FrameSessionReady || string(ready.Payload) != "ready" { t.Fatalf("ready = %+v", ready) } } func TestTransportLoopPropagatesHandlerError(t *testing.T) { wantErr := errors.New("handler failed") loop := TransportLoop{ Session: NewSession(SessionConfig{}), OnEvent: func(SessionEvent) ([]Frame, error) { return nil, wantErr }, } err := loop.Handle(context.Background(), Frame{Type: FrameHello}, io.Discard) if !errors.Is(err, wantErr) { t.Fatalf("err = %v, want %v", err, wantErr) } } func TestTransportLoopRequiresSession(t *testing.T) { err := (TransportLoop{}).Handle(context.Background(), Frame{Type: FrameHello}, io.Discard) if !errors.Is(err, ErrSessionNotConfigured) { t.Fatalf("err = %v, want %v", err, ErrSessionNotConfigured) } } func TestWriteFrameDetectsShortWrite(t *testing.T) { err := WriteFrame(shortWriter{}, Frame{Type: FrameHello}) if !errors.Is(err, io.ErrShortWrite) { t.Fatalf("err = %v, want %v", err, io.ErrShortWrite) } } type shortWriter struct{} func (shortWriter) Write([]byte) (int, error) { return 0, nil } func writeTestFrame(t *testing.T, w io.Writer, frame Frame) { t.Helper() if err := WriteFrame(w, frame); err != nil { t.Fatalf("write frame %+v: %v", frame, err) } } func readTestFrame(t *testing.T, r io.Reader) Frame { t.Helper() frame, err := ReadFrame(r, DefaultMaxPayload) if err != nil { t.Fatalf("read frame: %v", err) } return frame }