1package uidriver
2
3import (
4 "strings"
5 "testing"
6 "time"
7
8 "github.com/bnema/vev/internal/ports"
9 "github.com/stretchr/testify/require"
10)
11
12func TestDecodeRequestStrictOperationSchemas(t *testing.T) {
13 for _, tt := range []struct {
14 name, input string
15 code ports.UIErrorCode
16 }{
17 {"capture", `{"version":1,"id":1,"op":"capture","attachment":"a"}`, ""},
18 {"keys", `{"version":1,"id":1,"op":"keys","attachment":"a","generation":2,"keys":["Alt+Space"],"timeout_ms":30}`, ""},
19 {"text", `{"version":1,"id":1,"op":"text","attachment":"a","generation":2,"text":"hello"}`, ""},
20 {"wait", `{"version":1,"id":1,"op":"wait","attachment":"a","after_action":3,"expect":{"text_contains":"hello","status":"attached","focus":{"tab_id":"t","pane_id":"p"},"session":{"lifecycle_id":"01000000000000000000000000000000","session_name":"work"}}}`, ""},
21 {"unknown", `{"version":1,"id":1,"op":"capture","attachment":"a","extra":0}`, ports.UIErrInvalidRequest},
22 {"wrong case", `{"Version":1,"id":1,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
23 {"duplicate", `{"version":1,"id":1,"id":2,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
24 {"escaped duplicate", `{"version":1,"id":1,"\u0069d":2,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
25 {"nested duplicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"attached","status":"detached"}}`, ports.UIErrInvalidRequest},
26 {"picker predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"picker"}}`, ""},
27 {"connecting predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"connecting"}}`, ""},
28 {"removed detached predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"detached"}}`, ports.UIErrInvalidRequest},
29 {"removed reconnecting predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"reconnecting"}}`, ports.UIErrInvalidRequest},
30 {"removed transitioning predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"transitioning"}}`, ports.UIErrInvalidRequest},
31 {"unknown status predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"terminating"}}`, ports.UIErrInvalidRequest},
32 {"nested unknown", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"focus":{"tab_id":"t","pane_id":"p","extra":"x"}}}`, ports.UIErrInvalidRequest},
33 {"unknown predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"regex":".*"}}`, ports.UIErrInvalidRequest},
34 {"empty predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{}}`, ports.UIErrInvalidRequest},
35 {"null option", `{"version":1,"id":1,"op":"capture","attachment":"a","format":null}`, ports.UIErrInvalidRequest},
36 {"zero id", `{"version":1,"id":0,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
37 {"negative id", `{"version":1,"id":-1,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
38 {"wrong version", `{"version":2,"id":1,"op":"capture","attachment":"a"}`, ports.UIErrUnsupportedVersion},
39 {"mixed operation", `{"version":1,"id":1,"op":"keys","attachment":"a","generation":2,"keys":["x"],"text":"x"}`, ports.UIErrInvalidRequest},
40 {"capture generation", `{"version":1,"id":1,"op":"capture","attachment":"a","generation":2}`, ports.UIErrInvalidRequest},
41 {"zero generation", `{"version":1,"id":1,"op":"text","attachment":"a","generation":0,"text":"x"}`, ports.UIErrInvalidRequest},
42 {"timeout bound", `{"version":1,"id":1,"op":"text","attachment":"a","generation":2,"text":"x","timeout_ms":30001}`, ports.UIErrInvalidRequest},
43 {"truncated", `{"version":1`, ports.UIErrInvalidRequest},
44 {"trailing", `{"version":1,"id":1,"op":"capture","attachment":"a"}{}`, ports.UIErrInvalidRequest},
45 {"oversized", strings.Repeat(" ", maxRequestBytes+1), ports.UIErrInvalidRequest},
46 } {
47 t.Run(tt.name, func(t *testing.T) {
48 decoded, err := decodeRequest([]byte(tt.input))
49 if tt.code == "" {
50 require.NoError(t, err)
51 require.Equal(t, uint64(1), decoded.ID)
52 } else {
53 var uiErr *ports.UIError
54 require.ErrorAs(t, err, &uiErr)
55 require.Equal(t, tt.code, uiErr.Code)
56 }
57 })
58 }
59}
60
61func TestDecodeRequestDefaultsAndTypedValues(t *testing.T) {
62 capture, err := decodeRequest([]byte(`{"version":1,"id":1,"op":"capture","attachment":"a"}`))
63 require.NoError(t, err)
64 require.Equal(t, formatText, capture.Format)
65 keys, err := decodeRequest([]byte(`{"version":1,"id":2,"op":"keys","attachment":"a","generation":4,"keys":["Escape"],"timeout_ms":25}`))
66 require.NoError(t, err)
67 require.Equal(t, 25*time.Millisecond, keys.Action.Timeout)
68 require.Equal(t, uint64(4), keys.Action.Generation)
69}