1package client
2
3import (
4 "context"
5 "sync"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9
10 "github.com/bnema/vev/internal/ports"
11 "github.com/bnema/vev/internal/protocol"
12)
13
14// The daemon's SSP action ends the attachment, not an in-attachment modal.
15// Exercise the real worker and supervisor so a notice-only or paint-only fix
16// cannot pass while the attachment still owns terminal input.
17func TestSessionPickerDetachRevokesForegroundBeforePicker(t *testing.T) {
18 picker := newAttachTestPicker()
19 notices := make(chan LifecycleNotice, 4)
20 harness := startAttachHarnessConfig(t, picker, func(cfg *SupervisorConfig) {
21 cfg.NotifyLifecycle = func(notice LifecycleNotice) { notices <- notice }
22 })
23 stream := newSessionTestStream()
24 harness.service.setOpenStream(func(context.Context, ports.BrokerOpenStreamRequest) (ports.BrokerLogicalConnection, error) {
25 return stream, nil
26 })
27 picker.commit(sessionTestRequest(true))
28 awaitStreamHello(t, &sync.Mutex{}, &stream)
29 deliverReadyStream(t, stream)
30 awaitAttachedState(t, harness.sup)
31 foreground := harness.sup.attachments.authority.foreground()
32 require.NotNil(t, foreground)
33 stream.deliver(protocol.Detached{Reason: protocol.ReasonDetachToPicker})
34 awaitPickerState(t, harness.sup)
35 require.Equal(t, LifecycleNoticeDetachToPicker, (<-notices).Kind)
36 require.Nil(t, harness.sup.attachments.authority.foreground())
37 select {
38 case <-foreground.Done():
39 default:
40 t.Fatal("picker published before attachment foreground drained")
41 }
42 require.True(t, picker.owns())
43 require.Len(t, harness.service.openedRequests(), 1, "picker transfer must not reattach")
44 // Reopening is possible only through a fresh client-owned selection.
45 next := newSessionTestStream()
46 harness.service.setOpenStream(func(context.Context, ports.BrokerOpenStreamRequest) (ports.BrokerLogicalConnection, error) {
47 return next, nil
48 })
49 picker.commit(sessionTestRequest(true))
50 awaitStreamHello(t, &sync.Mutex{}, &next)
51 deliverReadyStream(t, next)
52 awaitAttachedState(t, harness.sup)
53 require.NotEqual(t, foreground.Token(), authorityToken(&harness.sup.attachments.authority))
54}