// TODO Should be able to remove this in next version of nodejs // https://github.com/nodejs/node/commit/932be0164fb3ed869ae3ddfff391721d2fd8e1af // (global).TextEncoder = require('util').TextEncoder // (global).TextDecoder = require('util').TextDecoder import * as sinon from "sinon"; import { GremlinSimpleClient, GremlinSimpleClientParameters, Result, GremlinRequestMessage, GremlinResponseMessage, } from "./GremlinSimpleClient"; describe("Gremlin Simple Client", () => { let sandbox: sinon.SinonSandbox; let fakeSocket: any; const createParams = (): GremlinSimpleClientParameters => { return { endpoint: "endpoint", user: "user", password: "password", successCallback: (result: Result) => {}, progressCallback: (result: Result) => {}, failureCallback: (result: Result, error: string) => {}, infoCallback: (msg: string) => {}, }; }; const fakeStatus = ( code: number, requestCharge: number, ): { attributes: { "x-ms-request-charge": number; "x-ms-total-request-charge": number; }; code: number; message: string; } => ({ attributes: { "x-ms-request-charge": requestCharge, "x-ms-total-request-charge": -123, }, code: code, message: null, }); beforeEach(() => { sandbox = sinon.sandbox.create(); fakeSocket = { fakeResponse: null, onopen: null, onerror: null, onmessage: null, onclose: null, send: (msg: any) => { if (fakeSocket.onmessage) { fakeSocket.onmessage(fakeSocket.fakeResponse); } }, close: () => {}, }; sandbox.stub(GremlinSimpleClient, "createWebSocket").returns(fakeSocket); }); afterEach(() => { sandbox.restore(); }); it("should encode utf-8 strings when building Gremlin message", () => { const msg = "é"; const expected = [16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 34, 195, 169, 34]; const actual = GremlinSimpleClient.buildGremlinMessage(msg); // TODO Array.from(actual) isn't supported in phantomJS. Must iterate through for now const actualArray = []; for (let i = 0; i < actual.length; i++) { actualArray.push(actual[i]); } expect(actualArray).toEqual(expected); }); it("should decode response from Gremlin server", () => { const expectedDecodedData = { requestId: "d772f897-0d4d-4cd1-b360-ddf6c86b93a3", status: { code: 200, attributes: { graphExecutionStatus: 200, StorageRU: 2.29, ComputeRU: 1.07, PerPartitionComputeCharges: {} }, message: "", }, result: { data: ["é"], meta: {} }, }; const expectedDecodedUint8ArrayValues = [ 123, 34, 114, 101, 113, 117, 101, 115, 116, 73, 100, 34, 58, 34, 100, 55, 55, 50, 102, 56, 57, 55, 45, 48, 100, 52, 100, 45, 52, 99, 100, 49, 45, 98, 51, 54, 48, 45, 100, 100, 102, 54, 99, 56, 54, 98, 57, 51, 97, 51, 34, 44, 34, 115, 116, 97, 116, 117, 115, 34, 58, 123, 34, 99, 111, 100, 101, 34, 58, 50, 48, 48, 44, 34, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 34, 58, 123, 34, 103, 114, 97, 112, 104, 69, 120, 101, 99, 117, 116, 105, 111, 110, 83, 116, 97, 116, 117, 115, 34, 58, 50, 48, 48, 44, 34, 83, 116, 111, 114, 97, 103, 101, 82, 85, 34, 58, 50, 46, 50, 57, 44, 34, 67, 111, 109, 112, 117, 116, 101, 82, 85, 34, 58, 49, 46, 48, 55, 44, 34, 80, 101, 114, 80, 97, 114, 116, 105, 116, 105, 111, 110, 67, 111, 109, 112, 117, 116, 101, 67, 104, 97, 114, 103, 101, 115, 34, 58, 123, 125, 125, 44, 34, 109, 101, 115, 115, 97, 103, 101, 34, 58, 34, 34, 125, 44, 34, 114, 101, 115, 117, 108, 116, 34, 58, 123, 34, 100, 97, 116, 97, 34, 58, 91, 34, 195, 169, 34, 93, 44, 34, 109, 101, 116, 97, 34, 58, 123, 125, 125, 125, ]; // We do our best here to emulate what the server should return const gremlinResponseData = new Uint8Array(expectedDecodedUint8ArrayValues).buffer; const client = new GremlinSimpleClient(createParams()); const actualDecoded = client.decodeMessage({ data: gremlinResponseData }); expect(actualDecoded).toEqual(expectedDecodedData); }); it("should connect before sending", () => { const params = createParams(); params.infoCallback = sandbox.spy(); const client = new GremlinSimpleClient(params); client.executeGremlinQuery("test"); try { fakeSocket.onopen(); } catch (e) { // Eat expected json parse exception and not pollute test output } expect((params.infoCallback).calledOnce).toBe(true); }); it("should handle incoming message", () => { fakeSocket.fakeResponse = "test"; const params = createParams(); const client = new GremlinSimpleClient(params); const fakeResponse: GremlinResponseMessage = { status: fakeStatus(200, null), requestId: "id", result: { data: "mydata" }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); const onMessageSpy = sandbox.spy(client, "onMessage"); client.executeGremlinQuery("test"); fakeSocket.onopen(); expect(onMessageSpy.calledOnce).toBe(true); }); it("should call fail callback on incoming invalid message", () => { const params = createParams(); const client = new GremlinSimpleClient(params); const onFailureSpy = sandbox.spy(client.params, "failureCallback"); fakeSocket.fakeResponse = { status: fakeStatus(200, null), requestId: Object.keys(client.pendingRequests)[0], data: new Uint8Array([1, 1, 1, 1]).buffer, }; client.executeGremlinQuery("test"); fakeSocket.onopen(); expect(onFailureSpy.calledOnce).toBe(true); }); it("should handle 200 status code", () => { fakeSocket.send = (msg: any) => {}; const params = createParams(); const client = new GremlinSimpleClient(params); const onSuccessSpy = sandbox.spy(client.params, "successCallback"); client.executeGremlinQuery("test"); fakeSocket.onopen(); const RU = 99; const fakeResponse: GremlinResponseMessage = { status: fakeStatus(200, RU), requestId: Object.keys(client.pendingRequests)[0], result: { data: "mydata" }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(new MessageEvent("test2")); expect( onSuccessSpy.calledWith({ requestId: fakeResponse.requestId, data: fakeResponse.result.data, requestCharge: RU, }), ).toBe(true); }); it("should handle 204 status code (no content)", () => { fakeSocket.send = (msg: any) => {}; const params = createParams(); const client = new GremlinSimpleClient(params); const onSuccessSpy = sandbox.spy(client.params, "successCallback"); client.executeGremlinQuery("test"); fakeSocket.onopen(); const RU = 99; const fakeResponse: GremlinResponseMessage = { status: fakeStatus(204, RU), requestId: Object.keys(client.pendingRequests)[0], result: { data: "THIS SHOULD BE IGNORED" }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(new MessageEvent("test2")); expect( onSuccessSpy.calledWith({ requestId: fakeResponse.requestId, data: null, requestCharge: RU, }), ).toBe(true); }); it("should handle 206 status code (partial)", () => { fakeSocket.send = (msg: any) => {}; const params = createParams(); const client = new GremlinSimpleClient(params); const onSuccessSpy = sandbox.spy(client.params, "successCallback"); const onProgressSpy = sandbox.spy(client.params, "progressCallback"); client.executeGremlinQuery("test"); fakeSocket.onopen(); const RU = 99; const fakeResponse: GremlinResponseMessage = { status: fakeStatus(206, RU), requestId: Object.keys(client.pendingRequests)[0], result: { data: [1, 2, 3] }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(new MessageEvent("test2")); expect( onProgressSpy.calledWith({ requestId: fakeResponse.requestId, data: fakeResponse.result.data, requestCharge: RU, }), ).toBe(true); expect(onSuccessSpy.notCalled).toBe(true); }); it("should handle 407 status code (auth challenge)", () => { const socketSendStub = sandbox.stub(fakeSocket, "send").callsFake(() => {}); const params = createParams(); const client = new GremlinSimpleClient(params); const onSuccessSpy = sandbox.spy(client.params, "successCallback"); const buildChallengeSpy = sandbox.spy(client, "buildChallengeResponse"); client.executeGremlinQuery("test"); fakeSocket.onopen(); const fakeResponse: GremlinResponseMessage = { status: fakeStatus(407, null), requestId: Object.keys(client.pendingRequests)[0], result: { data: null }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(new MessageEvent("test2")); expect(onSuccessSpy.notCalled).toBe(true); expect(buildChallengeSpy.calledOnce).toBe(true); expect(socketSendStub.calledTwice).toBe(true); // Once to send the query, once to send auth response }); describe("error status codes", () => { let params: GremlinSimpleClientParameters; let client: GremlinSimpleClient; let onSuccessSpy: sinon.SinonSpy; let onFailureSpy: sinon.SinonSpy; beforeEach(() => { fakeSocket.send = (msg: any) => {}; params = createParams(); client = new GremlinSimpleClient(params); onSuccessSpy = sandbox.spy(client.params, "successCallback"); onFailureSpy = sandbox.spy(client.params, "failureCallback"); client.executeGremlinQuery("test"); fakeSocket.onopen(); }); it("should handle 401 status code (error)", () => { const fakeResponse: GremlinResponseMessage = { status: fakeStatus(401, null), requestId: "id", result: { data: null }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(null); expect(onFailureSpy.calledOnce).toBe(true); expect(onSuccessSpy.notCalled).toBe(true); }); it("should handle 401 status code (error)", () => { const fakeResponse: GremlinResponseMessage = { status: fakeStatus(401, null), requestId: "id", result: { data: null }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(null); expect(onFailureSpy.calledOnce).toBe(true); expect(onSuccessSpy.notCalled).toBe(true); }); it("should handle 498 status code (error)", () => { const fakeResponse: GremlinResponseMessage = { status: fakeStatus(498, null), requestId: "id", result: { data: null }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(null); expect(onFailureSpy.calledOnce).toBe(true); expect(onSuccessSpy.notCalled).toBe(true); }); it("should handle 500 status code (error)", () => { const fakeResponse: GremlinResponseMessage = { status: fakeStatus(500, null), requestId: "id", result: { data: null }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(null); expect(onFailureSpy.calledOnce).toBe(true); expect(onSuccessSpy.notCalled).toBe(true); }); it("should handle 597 status code (error)", () => { const fakeResponse: GremlinResponseMessage = { status: fakeStatus(597, null), requestId: "id", result: { data: null }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(null); expect(onFailureSpy.calledOnce).toBe(true); expect(onSuccessSpy.notCalled).toBe(true); }); it("should handle 598 status code (error)", () => { const fakeResponse: GremlinResponseMessage = { status: fakeStatus(598, null), requestId: "id", result: { data: null }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(null); expect(onFailureSpy.calledOnce).toBe(true); expect(onSuccessSpy.notCalled).toBe(true); }); it("should handle 599 status code (error)", () => { const fakeResponse: GremlinResponseMessage = { status: fakeStatus(599, null), requestId: "id", result: { data: null }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(null); expect(onFailureSpy.calledOnce).toBe(true); expect(onSuccessSpy.notCalled).toBe(true); }); it("should handle unknown status code", () => { const fakeResponse: GremlinResponseMessage = { status: fakeStatus(123123123, null), requestId: "id", result: { data: null }, }; sandbox.stub(client, "decodeMessage").returns(fakeResponse); client.onMessage(null); expect(onFailureSpy.calledOnce).toBe(true); expect(onSuccessSpy.notCalled).toBe(true); }); }); it("should build auth message", () => { const params = createParams(); params.user = "éà"; params.password = "="; const client = new GremlinSimpleClient(params); const expectedSASLResult = "AMOpw6AAPQ=="; const request: GremlinRequestMessage = { requestId: "id", op: "eval", processor: "processor", args: { gremlin: "gremlin", bindings: {}, language: "language", }, }; const expectedResult: GremlinRequestMessage = { requestId: request.requestId, processor: request.processor, op: "authentication", args: { SASL: expectedSASLResult, }, }; const actual = client.buildChallengeResponse(request); expect(actual).toEqual(expectedResult); }); it("should convert utf8 to b64", () => { expect(GremlinSimpleClient.utf8ToB64("小飼弾")).toEqual("5bCP6aO85by+"); expect(GremlinSimpleClient.utf8ToB64("à mon hôpital préféré")).toEqual("w6AgbW9uIGjDtHBpdGFsIHByw6lmw6lyw6k="); }); });