| 1 | package protocol |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | func TestRegistryIsSortedAndPinned(t *testing.T) { |
| 9 | if err := ValidateRegistry(); err != nil { |
| 10 | t.Fatalf("ValidateRegistry: %v", err) |
| 11 | } |
| 12 | registry := Registry() |
| 13 | if len(registry) != 16 { |
| 14 | t.Fatalf("registry has %d methods, want 16", len(registry)) |
| 15 | } |
| 16 | for i := 1; i < len(registry); i++ { |
| 17 | if registry[i-1].Name >= registry[i].Name { |
| 18 | t.Fatalf("registry is not strictly sorted at %q >= %q", registry[i-1].Name, registry[i].Name) |
| 19 | } |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | func TestRegistryMethodDirections(t *testing.T) { |
| 24 | want := map[Method]Direction{ |
| 25 | MethodExtensionInitialize: DirectionHostToExtensionRequest, |
| 26 | MethodExtensionInitialized: DirectionHostToExtensionNotification, |
| 27 | MethodExtensionShutdown: DirectionHostToExtensionRequest, |
| 28 | MethodExtensionIntercept: DirectionHostToExtensionRequest, |
| 29 | MethodExtensionEvent: DirectionHostToExtensionNotification, |
| 30 | MethodExtensionResourcesChanged: DirectionHostToExtensionNotification, |
| 31 | MethodExtensionProviderCatalog: DirectionHostToExtensionRequest, |
| 32 | MethodExtensionProviderStreamOpen: DirectionHostToExtensionRequest, |
| 33 | MethodExtensionProviderStreamCancel: DirectionHostToExtensionRequest, |
| 34 | MethodExtensionProviderStreamChunk: DirectionExtensionToHostNotification, |
| 35 | MethodExtensionProviderStreamEnd: DirectionExtensionToHostNotification, |
| 36 | MethodExtensionUIAction: DirectionHostToExtensionRequest, |
| 37 | MethodExtensionUISubmit: DirectionHostToExtensionRequest, |
| 38 | MethodHostUIPublish: DirectionExtensionToHostRequest, |
| 39 | MethodHostUIRequest: DirectionExtensionToHostRequest, |
| 40 | MethodHostContentRead: DirectionExtensionToHostRequest, |
| 41 | } |
| 42 | if len(want) != 16 { |
| 43 | t.Fatalf("test pins %d methods, want 16", len(want)) |
| 44 | } |
| 45 | for method, direction := range want { |
| 46 | spec, ok := LookupMethod(method) |
| 47 | if !ok { |
| 48 | t.Fatalf("LookupMethod(%q) not found", method) |
| 49 | } |
| 50 | if spec.Direction != direction { |
| 51 | t.Fatalf("%s direction = %q, want %q", method, spec.Direction, direction) |
| 52 | } |
| 53 | if spec.Notification() != direction.IsNotification() { |
| 54 | t.Fatalf("%s notification flag disagrees with direction %q", method, direction) |
| 55 | } |
| 56 | } |
| 57 | if _, ok := LookupMethod("extension/bogus"); ok { |
| 58 | t.Fatal("LookupMethod accepted an unregistered method") |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | func TestRegistryClasses(t *testing.T) { |
| 63 | want := map[Method]OperationClass{ |
| 64 | MethodExtensionInitialize: ClassLifecycle, |
| 65 | MethodExtensionInitialized: ClassLifecycle, |
| 66 | MethodExtensionShutdown: ClassLifecycle, |
| 67 | MethodExtensionIntercept: ClassIntercept, |
| 68 | MethodExtensionEvent: ClassObservation, |
| 69 | MethodExtensionResourcesChanged: ClassObservation, |
| 70 | MethodExtensionProviderCatalog: ClassProvider, |
| 71 | MethodExtensionProviderStreamOpen: ClassProvider, |
| 72 | MethodExtensionProviderStreamCancel: ClassProvider, |
| 73 | MethodExtensionProviderStreamChunk: ClassProvider, |
| 74 | MethodExtensionProviderStreamEnd: ClassProvider, |
| 75 | MethodExtensionUIAction: ClassUI, |
| 76 | MethodExtensionUISubmit: ClassUI, |
| 77 | MethodHostUIPublish: ClassUI, |
| 78 | MethodHostUIRequest: ClassUI, |
| 79 | MethodHostContentRead: ClassContent, |
| 80 | } |
| 81 | for method, class := range want { |
| 82 | spec, _ := LookupMethod(method) |
| 83 | if spec.Class != class { |
| 84 | t.Fatalf("%s class = %q, want %q", method, spec.Class, class) |
| 85 | } |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | func TestDecodeHelpersRejectWrongDirection(t *testing.T) { |
| 90 | raw := []byte(`{}`) |
| 91 | if _, err := DecodeHostRequestParams(MethodHostContentRead, raw); err == nil { |
| 92 | t.Fatal("DecodeHostRequestParams accepted an extension request method") |
| 93 | } |
| 94 | if _, err := DecodeExtensionRequestParams(MethodExtensionInitialize, raw); err == nil { |
| 95 | t.Fatal("DecodeExtensionRequestParams accepted a host request method") |
| 96 | } |
| 97 | if _, err := DecodeHostNotificationParams(MethodExtensionEvent, []byte(`{"event":"session.start","payload":{}}`)); err == nil { |
| 98 | // extension/event IS a host notification; must decode. |
| 99 | } else { |
| 100 | t.Fatalf("DecodeHostNotificationParams(extension/event) = %v", err) |
| 101 | } |
| 102 | if _, err := DecodeExtensionNotificationParams(MethodExtensionEvent, raw); err == nil { |
| 103 | t.Fatal("DecodeExtensionNotificationParams accepted a host notification method") |
| 104 | } |
| 105 | if _, err := DecodeHostRequestParams("extension/bogus", raw); err == nil { |
| 106 | t.Fatal("DecodeHostRequestParams accepted an unregistered method") |
| 107 | } |
| 108 | if _, err := DecodeHostRequestResult(MethodExtensionEvent, raw); err == nil { |
| 109 | t.Fatal("DecodeHostRequestResult accepted a notification (no result)") |
| 110 | } |
| 111 | if _, err := DecodeExtensionRequestResult(MethodExtensionProviderStreamChunk, raw); err == nil { |
| 112 | t.Fatal("DecodeExtensionRequestResult accepted a notification method") |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | func TestInterceptEventsFrozen(t *testing.T) { |
| 117 | events := InterceptEvents() |
| 118 | if len(events) != 17 { |
| 119 | t.Fatalf("InterceptEvents has %d entries, want 17", len(events)) |
| 120 | } |
| 121 | seen := map[string]bool{} |
| 122 | for i, event := range events { |
| 123 | if seen[event] { |
| 124 | t.Fatalf("duplicate intercept event %q", event) |
| 125 | } |
| 126 | seen[event] = true |
| 127 | if i > 0 && events[i-1] >= event { |
| 128 | t.Fatalf("intercept events not sorted at %q", event) |
| 129 | } |
| 130 | if !strings.Contains(event, ".") { |
| 131 | t.Fatalf("intercept event %q does not follow the <area>.<point> shape", event) |
| 132 | } |
| 133 | } |
| 134 | // Every frozen event must round-trip through the strict enum check. |
| 135 | for _, event := range events { |
| 136 | raw := []byte(`{"event":"` + event + `","payload":{}}`) |
| 137 | if _, err := DecodeHostNotificationParams(MethodExtensionEvent, raw); err != nil { |
| 138 | t.Fatalf("frozen event %q rejected: %v", event, err) |
| 139 | } |
| 140 | } |
| 141 | raw := []byte(`{"event":"session.bogus","payload":{}}`) |
| 142 | if _, err := DecodeHostNotificationParams(MethodExtensionEvent, raw); err == nil { |
| 143 | t.Fatal("unknown intercept event accepted") |
| 144 | } |
| 145 | } |
| 146 |