158 lines
3.6 KiB
Go
158 lines
3.6 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hjbdev/patterm/internal/protocol"
|
|
)
|
|
|
|
func TestNetClientFrameLoopSendsFocusedInput(t *testing.T) {
|
|
clientT, daemonT := protocol.NewLoopbackPair()
|
|
inR, inW := ioPipe(t)
|
|
out := &lockedBuffer{}
|
|
|
|
gotInput := make(chan protocol.Input, 1)
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
f, err := daemonT.Recv()
|
|
if err != nil {
|
|
errCh <- err
|
|
return
|
|
}
|
|
if f.Type != protocol.FrameAttach {
|
|
t.Errorf("first frame = %s, want attach", f.Type)
|
|
errCh <- nil
|
|
return
|
|
}
|
|
sendTestFrame(t, daemonT, protocol.FrameHello, protocol.Hello{Version: 1, ClientID: "test", ProjectKey: "project"})
|
|
sendTestFrame(t, daemonT, protocol.FrameProjectList, protocol.ProjectList{})
|
|
model := chromeModel{
|
|
ProjectKey: "project",
|
|
FocusedID: "p1",
|
|
Processes: []childModel{{ID: "p1", Name: "shell", Kind: string(KindCommand), Status: string(StatusRunning)}},
|
|
Sidebar: []navEntryModel{{ChildID: "p1"}},
|
|
}
|
|
sendTestFrame(t, daemonT, protocol.FrameChrome, protocol.Chrome{ProjectKey: "project", Model: mustMarshalTest(t, model)})
|
|
sendTestFrame(t, daemonT, protocol.FramePaneSnapshot, protocol.PaneSnapshot{PaneID: "p1", Bytes: []byte("READY")})
|
|
for {
|
|
f, err := daemonT.Recv()
|
|
if err != nil {
|
|
errCh <- err
|
|
return
|
|
}
|
|
if f.Type != protocol.FrameInput {
|
|
continue
|
|
}
|
|
input, err := protocol.Decode[protocol.Input](f)
|
|
if err != nil {
|
|
errCh <- err
|
|
return
|
|
}
|
|
gotInput <- input
|
|
_ = daemonT.Close()
|
|
errCh <- nil
|
|
return
|
|
}
|
|
}()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
runCh := make(chan error, 1)
|
|
go func() {
|
|
runCh <- RunAttachedClient(ctx, ClientOptions{
|
|
Transport: clientT,
|
|
Stdin: inR,
|
|
Stdout: out,
|
|
Cols: 80,
|
|
Rows: 24,
|
|
})
|
|
}()
|
|
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
for time.Now().Before(deadline) && !bytes.Contains(out.Bytes(), []byte("READY")) {
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if !bytes.Contains(out.Bytes(), []byte("READY")) {
|
|
t.Fatalf("snapshot was not rendered before input; output=%q", out.String())
|
|
}
|
|
if _, err := inW.Write([]byte("echo hi\r")); err != nil {
|
|
t.Fatalf("write stdin: %v", err)
|
|
}
|
|
select {
|
|
case input := <-gotInput:
|
|
if input.PaneID != "p1" || string(input.Bytes) != "echo hi\r" {
|
|
t.Fatalf("input = %#v", input)
|
|
}
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatalf("client did not forward input")
|
|
}
|
|
cancel()
|
|
_ = inW.Close()
|
|
select {
|
|
case err := <-runCh:
|
|
if err != nil {
|
|
t.Fatalf("client run: %v", err)
|
|
}
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatalf("client did not stop")
|
|
}
|
|
if err := <-errCh; err != nil && err != protocol.ErrTransportClosed {
|
|
t.Fatalf("daemon side: %v", err)
|
|
}
|
|
}
|
|
|
|
type lockedBuffer struct {
|
|
mu sync.Mutex
|
|
b bytes.Buffer
|
|
}
|
|
|
|
func (b *lockedBuffer) Write(p []byte) (int, error) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.b.Write(p)
|
|
}
|
|
|
|
func (b *lockedBuffer) Bytes() []byte {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return append([]byte(nil), b.b.Bytes()...)
|
|
}
|
|
|
|
func (b *lockedBuffer) String() string {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.b.String()
|
|
}
|
|
|
|
func ioPipe(t *testing.T) (*io.PipeReader, *io.PipeWriter) {
|
|
t.Helper()
|
|
r, w := io.Pipe()
|
|
return r, w
|
|
}
|
|
|
|
func sendTestFrame[T any](t *testing.T, tr protocol.Transport, typ protocol.FrameType, payload T) {
|
|
t.Helper()
|
|
f, err := protocol.NewFrame(typ, payload)
|
|
if err != nil {
|
|
t.Fatalf("frame %s: %v", typ, err)
|
|
}
|
|
if err := tr.Send(f); err != nil {
|
|
t.Fatalf("send %s: %v", typ, err)
|
|
}
|
|
}
|
|
|
|
func mustMarshalTest(t *testing.T, v any) []byte {
|
|
t.Helper()
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
return b
|
|
}
|