1package ports
2
3import (
4 "encoding/hex"
5 "testing"
6
7 "github.com/bnema/vev/internal/domain"
8 "github.com/stretchr/testify/require"
9)
10
11func TestNavigationActionWireTable(t *testing.T) {
12 tests := []struct {
13 name string
14 action NavigationAction
15 payload []byte
16 valid bool
17 }{
18 {name: "open home", action: NavigationOpenHomePicker, payload: []byte{1}, valid: true},
19 {name: "back", action: NavigationBack, payload: []byte{2}, valid: true},
20 {name: "zero", action: 0, payload: []byte{0}, valid: false},
21 {name: "unknown", action: 3, payload: []byte{3}, valid: false},
22 }
23
24 for _, tt := range tests {
25 t.Run(tt.name, func(t *testing.T) {
26 got := MarshalNavigationAction(tt.action)
27 if !tt.valid {
28 require.Nil(t, got)
29 _, err := UnmarshalNavigationAction(tt.payload)
30 require.ErrorIs(t, err, ErrInvalidNavigation)
31 return
32 }
33 require.Equal(t, tt.payload, got)
34 decoded, err := UnmarshalNavigationAction(got)
35 require.NoError(t, err)
36 require.Equal(t, tt.action, decoded)
37 assertAllPrefixesFail(t, got, UnmarshalNavigationAction)
38 _, err = UnmarshalNavigationAction(append(append([]byte(nil), got...), 0))
39 require.ErrorIs(t, err, ErrInvalidNavigation)
40 })
41 }
42}
43
44func TestHelloNavigationValidationTable(t *testing.T) {
45 base := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}
46 remoteTarget := &domain.RemoteSessionTarget{
47 Endpoint: "arch", DisplayOrigin: "arch", LifecycleID: domain.SessionLifecycleID{1},
48 SessionName: "work", LiveTabID: "tab-1",
49 }
50 tests := []struct {
51 name string
52 hello Hello
53 valid bool
54 capability NavigationCapabilities
55 overlay StartupOverlay
56 wantPayload string
57 }{
58 {name: "ordinary", hello: base, valid: true},
59 {name: "resume route", hello: Hello{Version: ProtocolVersion, Intent: IntentResume, Size: domain.Size{Cols: 80, Rows: 24}}, valid: true},
60 {name: "daemon-owned home picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: true, capability: NavigationCapabilityHomePicker},
61 {name: "remote-target home picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityHomePicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: true, capability: NavigationCapabilityHomePicker},
62 {name: "client-owned home picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker}, valid: false},
63 {name: "daemon-owned back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false},
64 {name: "remote-target back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false},
65 {name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "001d02" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0000" + "0201"},
66 {name: "new rejects navigation", hello: Hello{Version: ProtocolVersion, Intent: IntentNew, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: false},
67 {name: "unknown capability", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: 4}, valid: false},
68 {name: "back without picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack}, valid: false},
69 {name: "picker without back", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, StartupOverlay: StartupOverlaySessionPicker}, valid: false},
70 }
71
72 for _, tt := range tests {
73 t.Run(tt.name, func(t *testing.T) {
74 err := ValidateHello(tt.hello)
75 if tt.valid {
76 require.NoError(t, err)
77 payload := MarshalHello(tt.hello)
78 require.NotNil(t, payload)
79 if tt.wantPayload != "" {
80 expected, decodeErr := hex.DecodeString(tt.wantPayload)
81 require.NoError(t, decodeErr)
82 require.Equal(t, expected, payload)
83 assertAllPrefixesFail(t, expected, UnmarshalHello)
84 assertTrailingGarbageFails(t, expected, UnmarshalHello)
85 }
86 decoded, decodeErr := UnmarshalHello(payload)
87 require.NoError(t, decodeErr)
88 require.Equal(t, tt.capability, decoded.NavigationCapabilities)
89 require.Equal(t, tt.overlay, decoded.StartupOverlay)
90 return
91 }
92 require.Error(t, err)
93 require.Nil(t, MarshalHello(tt.hello))
94 })
95 }
96}