171 lines
4.6 KiB
Go
171 lines
4.6 KiB
Go
package tui
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRequestPermission_BlocksUntilAnswered(t *testing.T) {
|
|
m := newTestModel(t)
|
|
m.permCh = make(chan permissionRequest)
|
|
|
|
resultCh := make(chan bool, 1)
|
|
go func() {
|
|
resultCh <- m.requestPermission(context.Background(), "edit", json.RawMessage(`{"path":"main.go"}`))
|
|
}()
|
|
|
|
var req permissionRequest
|
|
select {
|
|
case req = <-m.permCh:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timed out waiting for the request on permCh")
|
|
}
|
|
if req.tool != "edit" {
|
|
t.Errorf("req.tool = %q, want edit", req.tool)
|
|
}
|
|
|
|
select {
|
|
case <-resultCh:
|
|
t.Fatal("requestPermission returned before the response was sent")
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
|
|
req.resp <- true
|
|
select {
|
|
case got := <-resultCh:
|
|
if !got {
|
|
t.Error("expected requestPermission to return true after resp<-true")
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timed out waiting for requestPermission to return")
|
|
}
|
|
}
|
|
|
|
func TestRequestPermission_ContextCancelDeniesWithoutHanging(t *testing.T) {
|
|
m := newTestModel(t)
|
|
m.permCh = make(chan permissionRequest) // niemand liest daraus
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
resultCh := make(chan bool, 1)
|
|
go func() {
|
|
resultCh <- m.requestPermission(ctx, "bash", json.RawMessage(`{"command":"ls"}`))
|
|
}()
|
|
|
|
cancel()
|
|
select {
|
|
case got := <-resultCh:
|
|
if got {
|
|
t.Error("expected a cancelled context to deny, got true")
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("requestPermission did not return after context cancellation")
|
|
}
|
|
}
|
|
|
|
func TestHandlePermissionKey_YesAllows(t *testing.T) {
|
|
m := newTestModel(t)
|
|
resp := make(chan bool, 1)
|
|
m.permReq = &permissionRequest{tool: "edit", input: json.RawMessage(`{}`), resp: resp}
|
|
|
|
m.handlePermissionKey(yKey())
|
|
|
|
if m.permReq != nil {
|
|
t.Error("expected permReq to be cleared")
|
|
}
|
|
select {
|
|
case got := <-resp:
|
|
if !got {
|
|
t.Error("expected 'y' to allow")
|
|
}
|
|
default:
|
|
t.Fatal("expected a response to be sent on resp")
|
|
}
|
|
if len(m.entries) != 1 || m.entries[0].kind != entryCommand {
|
|
t.Errorf("expected a recorded command entry, got %+v", m.entries)
|
|
}
|
|
}
|
|
|
|
func TestHandlePermissionKey_AnyOtherKeyDenies(t *testing.T) {
|
|
m := newTestModel(t)
|
|
resp := make(chan bool, 1)
|
|
m.permReq = &permissionRequest{tool: "bash", input: json.RawMessage(`{}`), resp: resp}
|
|
|
|
m.handlePermissionKey(escKey())
|
|
|
|
if m.permReq != nil {
|
|
t.Error("expected permReq to be cleared")
|
|
}
|
|
select {
|
|
case got := <-resp:
|
|
if got {
|
|
t.Error("expected esc to deny")
|
|
}
|
|
default:
|
|
t.Fatal("expected a response to be sent on resp")
|
|
}
|
|
}
|
|
|
|
func TestUpdate_PermissionRequestMsgOpensPromptAndReListens(t *testing.T) {
|
|
m := newTestModel(t)
|
|
m.permCh = make(chan permissionRequest, 1)
|
|
|
|
req := permissionRequest{tool: "write", input: json.RawMessage(`{"path":"x"}`), resp: make(chan bool, 1)}
|
|
_, cmd := m.Update(permissionRequestMsg{req: req})
|
|
|
|
if m.permReq == nil || m.permReq.tool != "write" {
|
|
t.Fatalf("expected permReq to be set to the incoming request, got %+v", m.permReq)
|
|
}
|
|
if cmd == nil {
|
|
t.Fatal("expected Update to re-arm listenPermissionRequests")
|
|
}
|
|
if len(m.entries) != 1 || m.entries[0].kind != entryPermission {
|
|
t.Fatalf("expected the request to appear inline in the transcript, got %+v", m.entries)
|
|
}
|
|
if !strings.Contains(m.entries[0].text, "write") {
|
|
t.Errorf("permission entry should name the tool, got: %q", m.entries[0].text)
|
|
}
|
|
}
|
|
|
|
// TestView_PermissionRequestDoesNotTakeOverTheScreen ist der eigentliche
|
|
// Regressionstest für das Overlay-Problem: eine offene Rückfrage darf die
|
|
// Nachrichtenliste/Eingabe/Statuszeile nicht verdecken, sonst sieht man
|
|
// nicht mehr, was vorher im Transkript passiert ist.
|
|
func TestView_PermissionRequestDoesNotTakeOverTheScreen(t *testing.T) {
|
|
m := newTestModelWithStore(t) // renderStatusLine liest m.store.ID
|
|
m.ready = true
|
|
m.width, m.height = 80, 24
|
|
m.viewport.Width, m.viewport.Height = 80, 20
|
|
m.entries = []entry{{kind: entryUser, text: "hello from before"}}
|
|
m.renderViewport()
|
|
m.permReq = &permissionRequest{tool: "edit", input: json.RawMessage(`{}`), resp: make(chan bool, 1)}
|
|
|
|
view := m.View()
|
|
if !strings.Contains(view, m.textarea.View()) {
|
|
t.Error("expected the input box to remain visible while a permission request is pending")
|
|
}
|
|
if !strings.Contains(stripANSI(view), "hello from before") {
|
|
t.Error("expected prior transcript content to remain visible, not be replaced by a full-screen overlay")
|
|
}
|
|
}
|
|
|
|
func stripANSI(s string) string {
|
|
var b strings.Builder
|
|
inEscape := false
|
|
for _, r := range s {
|
|
if r == '\x1b' {
|
|
inEscape = true
|
|
continue
|
|
}
|
|
if inEscape {
|
|
if r == 'm' {
|
|
inEscape = false
|
|
}
|
|
continue
|
|
}
|
|
b.WriteRune(r)
|
|
}
|
|
return b.String()
|
|
}
|