1// gRPC-Web library
2import {
3  grpc,
4} from "@improbable-eng/grpc-web";
5
6import { debug } from "../../../client/grpc-web/src/debug";
7import { assert } from "chai";
8
9// Generated Test Classes
10import {
11  PingRequest,
12  PingResponse, TextMessage,
13} from "../_proto/improbable/grpcweb/test/test_pb";
14import { FailService, TestService } from "../_proto/improbable/grpcweb/test/test_pb_service";
15import { DEBUG, UncaughtExceptionListener } from "./util";
16import {
17  headerTrailerCombos, runWithHttp1AndHttp2, runWithSupportedTransports
18} from "./testRpcCombinations";
19import { conditionallyRunTestSuite, SuiteEnum } from "../suiteUtils";
20
21conditionallyRunTestSuite(SuiteEnum.unary, () => {
22  runWithHttp1AndHttp2(({ testHostUrl, corsHostUrl, unavailableHost, emptyHost }) => {
23    runWithSupportedTransports(transport => {
24      it(`should reject a server-streaming method`, () => {
25        const ping = new PingRequest();
26        ping.setValue("hello world");
27
28        assert.throw(() => {
29          grpc.unary(TestService.PingList as any as grpc.UnaryMethodDefinition<PingRequest, PingResponse>, {
30            debug: DEBUG,
31            transport: transport,
32            request: ping,
33            host: testHostUrl,
34            onEnd: ({ status, statusMessage, headers, message, trailers }) => {
35              DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
36            }
37          })
38        }, ".unary cannot be used with server-streaming methods. Use .invoke or .client instead.");
39      });
40
41      it(`should reject a client-streaming method`, () => {
42        const ping = new PingRequest();
43        ping.setValue("hello world");
44
45        assert.throw(() => {
46          grpc.unary(TestService.PingStream as any as grpc.UnaryMethodDefinition<PingRequest, PingResponse>, {
47            debug: DEBUG,
48            transport: transport,
49            request: ping,
50            host: testHostUrl,
51            onEnd: ({ status, statusMessage, headers, message, trailers }) => {
52              DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
53            }
54          })
55        }, ".unary cannot be used with client-streaming methods. Use .client instead.");
56      });
57
58      headerTrailerCombos((withHeaders, withTrailers) => {
59        it(`should make a unary request`, (done) => {
60          const ping = new PingRequest();
61          ping.setValue("hello world");
62          ping.setSendHeaders(withHeaders);
63          ping.setSendTrailers(withTrailers);
64
65          grpc.unary(TestService.Ping, {
66            debug: DEBUG,
67            transport: transport,
68            request: ping,
69            host: testHostUrl,
70            onEnd: ({ status, statusMessage, headers, message, trailers }) => {
71              DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
72              assert.strictEqual(status, grpc.Code.OK, "expected OK (0)");
73              assert.isNotOk(statusMessage, "expected no message");
74              if (withHeaders) {
75                assert.deepEqual(headers.get("HeaderTestKey1"), ["ServerValue1"]);
76                assert.deepEqual(headers.get("HeaderTestKey2"), ["ServerValue2"]);
77              }
78              assert.ok(message instanceof PingResponse);
79              const asPingResponse: PingResponse = message as PingResponse;
80              assert.deepEqual(asPingResponse.getValue(), "hello world");
81              assert.deepEqual(asPingResponse.getCounter(), 252);
82              if (withTrailers) {
83                assert.deepEqual(trailers.get("TrailerTestKey1"), ["ServerValue1"]);
84                assert.deepEqual(trailers.get("TrailerTestKey2"), ["ServerValue2"]);
85              }
86              done();
87            }
88          });
89        });
90      });
91
92      headerTrailerCombos((withHeaders, withTrailers) => {
93        it(`should make a large unary request`, (done) => {
94          let text = "";
95          const iterations = 1024;
96          for (let i = 0; i < iterations; i++) {
97            text += "0123456789ABCDEF";
98          }
99          assert.equal(text.length, 16384, "generated message length");
100
101          const textMessage = new TextMessage();
102          textMessage.setText(text);
103          textMessage.setSendHeaders(withHeaders);
104          textMessage.setSendTrailers(withTrailers);
105
106          grpc.unary(TestService.Echo, {
107            debug: DEBUG,
108            transport: transport,
109            request: textMessage,
110            host: testHostUrl,
111            onEnd: ({ status, statusMessage, headers, message, trailers }) => {
112              DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
113              assert.strictEqual(status, grpc.Code.OK, "expected OK (0)");
114              assert.isNotOk(statusMessage, "expected no message");
115              if (withHeaders) {
116                assert.deepEqual(headers.get("HeaderTestKey1"), ["ServerValue1"]);
117                assert.deepEqual(headers.get("HeaderTestKey2"), ["ServerValue2"]);
118              }
119              assert.ok(message instanceof TextMessage);
120              const asTextMessage: TextMessage = message as TextMessage;
121              assert.equal(asTextMessage.getText().length, 16384, "response message length");
122              if (withTrailers) {
123                assert.deepEqual(trailers.get("TrailerTestKey1"), ["ServerValue1"]);
124                assert.deepEqual(trailers.get("TrailerTestKey2"), ["ServerValue2"]);
125              }
126              done();
127            }
128          });
129        });
130      });
131
132      headerTrailerCombos((withHeaders, withTrailers) => {
133        it(`should make a unary request with metadata`, (done) => {
134          const ping = new PingRequest();
135          ping.setValue("hello world");
136          ping.setCheckMetadata(true);
137          ping.setSendHeaders(withHeaders);
138          ping.setSendTrailers(withTrailers);
139
140          grpc.unary(TestService.Ping, {
141            debug: DEBUG,
142            transport: transport,
143            request: ping,
144            metadata: new grpc.Metadata({
145              "HeaderTestKey1": "ClientValue1",
146              "HeaderTestKey2": "ClientValue2",
147            }),
148            host: testHostUrl,
149            onEnd: ({ status, statusMessage, headers, message, trailers }) => {
150              DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
151              assert.strictEqual(status, grpc.Code.OK, "expected OK (0)");
152              assert.isNotOk(statusMessage, "expected no message");
153              if (withHeaders) {
154                assert.deepEqual(headers.get("HeaderTestKey1"), ["ServerValue1"]);
155                assert.deepEqual(headers.get("HeaderTestKey2"), ["ServerValue2"]);
156              }
157              assert.ok(message instanceof PingResponse);
158              const asPingResponse: PingResponse = message as PingResponse;
159              assert.deepEqual(asPingResponse.getValue(), "hello world");
160              assert.deepEqual(asPingResponse.getCounter(), 252);
161              if (withTrailers) {
162                assert.deepEqual(trailers.get("TrailerTestKey1"), ["ServerValue1"]);
163                assert.deepEqual(trailers.get("TrailerTestKey2"), ["ServerValue2"]);
164              }
165              done();
166            }
167          });
168        });
169      });
170
171      headerTrailerCombos((withHeaders, withTrailers) => {
172        it(`should report status code for error with headers + trailers`, (done) => {
173          const ping = new PingRequest();
174          ping.setFailureType(PingRequest.FailureType.CODE);
175          ping.setErrorCodeReturned(12);
176          ping.setSendHeaders(withHeaders);
177          ping.setSendTrailers(withTrailers);
178
179          grpc.unary(TestService.PingError, {
180            debug: DEBUG,
181            transport: transport,
182            request: ping,
183            host: testHostUrl,
184            onEnd: ({ status, statusMessage, headers, message, trailers }) => {
185              DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
186              assert.strictEqual(status, grpc.Code.Unimplemented);
187              assert.strictEqual(statusMessage, "Intentionally returning error for PingError");
188              if (withHeaders) {
189                assert.deepEqual(headers.get("HeaderTestKey1"), ["ServerValue1"]);
190                assert.deepEqual(headers.get("HeaderTestKey2"), ["ServerValue2"]);
191              }
192              assert.isNull(message);
193              assert.deepEqual(trailers.get("grpc-status"), ["12"]);
194              assert.deepEqual(trailers.get("grpc-message"), ["Intentionally returning error for PingError"]);
195              if (withTrailers) {
196                assert.deepEqual(trailers.get("TrailerTestKey1"), ["ServerValue1"]);
197                assert.deepEqual(trailers.get("TrailerTestKey2"), ["ServerValue2"]);
198              }
199              done();
200            }
201          });
202        });
203      });
204
205      if (!process.env.DISABLE_CORS_TESTS) {
206        it(`should report failure for a CORS failure`, (done) => {
207          const ping = new PingRequest();
208
209          grpc.unary(FailService.NonExistant, { // The test server hasn't registered this service, so it should fail CORS
210            debug: DEBUG,
211            transport: transport,
212            request: ping,
213            // This test is actually calling the same server as the other tests, but the server should reject the OPTIONS call
214            // because the service isn't registered. This could be the same host as all other tests (that should be CORS
215            // requests because they differ by port from the page the tests are run from), but IE treats different ports on
216            // the same host as the same origin, so this request has to be made to a different host to trigger CORS behaviour.
217            host: corsHostUrl,
218            onEnd: ({ status, statusMessage, headers, message, trailers }) => {
219              DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
220              // Some browsers return empty Headers for failed requests
221              assert.strictEqual(statusMessage, "Response closed without headers");
222              assert.strictEqual(status, grpc.Code.Unknown);
223              done();
224            }
225          });
226        });
227      }
228
229      it(`should report failure for a request to an invalid host`, (done) => {
230        const ping = new PingRequest();
231
232        grpc.unary(TestService.Ping, {
233          debug: DEBUG,
234          transport: transport,
235          request: ping,
236          host: unavailableHost, // Should not be available
237          onEnd: ({ status, statusMessage, headers, message, trailers }) => {
238            DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
239            assert.strictEqual(statusMessage, "Response closed without headers");
240            assert.strictEqual(status, grpc.Code.Unknown);
241            assert.isNull(message);
242            done();
243          }
244        });
245      });
246
247      it(`should report failure for a trailers-only response`, (done) => {
248        const ping = new PingRequest();
249
250        grpc.unary(FailService.NonExistant, { // The test server hasn't registered this service, so it should return an error
251          debug: DEBUG,
252          transport: transport,
253          request: ping,
254          host: emptyHost,
255          onEnd: ({ status, statusMessage, headers, message, trailers }) => {
256            DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
257            assert.strictEqual(statusMessage, "unknown service improbable.grpcweb.test.FailService");
258            assert.strictEqual(status, 12);
259            assert.isNull(message);
260            assert.deepEqual(headers.get("grpc-status"), ["12"]);
261            assert.deepEqual(headers.get("grpc-message"), ["unknown service improbable.grpcweb.test.FailService"]);
262            assert.deepEqual(trailers.get("grpc-status"), ["12"]);
263            assert.deepEqual(trailers.get("grpc-message"), ["unknown service improbable.grpcweb.test.FailService"]);
264            done();
265          }
266        });
267      });
268
269      describe(`exception handling`, () => {
270        let uncaughtHandler: UncaughtExceptionListener;
271        beforeEach(() => {
272          uncaughtHandler = new UncaughtExceptionListener();
273          uncaughtHandler.attach();
274        });
275
276        afterEach(() => {
277          uncaughtHandler.detach();
278        });
279
280        it(`should not suppress exceptions`, (done) => {
281          const ping = new PingRequest();
282          ping.setValue("hello world");
283
284          grpc.unary(TestService.Ping, {
285            debug: DEBUG,
286            transport: transport,
287            request: ping,
288            host: testHostUrl,
289            onEnd: ({ status, statusMessage, headers, message, trailers }) => {
290              DEBUG && debug("status", status, "statusMessage", statusMessage, "headers", headers, "res", message, "trailers", trailers);
291              setTimeout(() => {
292                uncaughtHandler.detach();
293                const exceptionsCaught = uncaughtHandler.getMessages();
294                assert.lengthOf(exceptionsCaught, 1);
295                assert.include(exceptionsCaught[0], "onEnd exception");
296                done();
297              }, 100);
298              throw new Error("onEnd exception");
299            }
300          });
301        });
302      });
303    });
304  });
305});
306