UNPKG

realm-object-server

Version:

Realm Object Server

278 lines 12.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const chai_1 = require("chai"); const sinon = require("sinon"); const chai = require("chai"); chai.use(require("chai-as-promised")); const proxyquire = require("proxyquire"); const TestServer_1 = require("../TestServer"); const Token_1 = require("../shared/Token"); const SyncService_1 = require("../services/SyncService"); const Constants_1 = require("../shared/Constants"); const shared_1 = require("../shared"); const events_1 = require("events"); describe("SyncProxyService", () => { let service; let server; let httpProxy; let proxy; let syncService; let startArgs; beforeEach(() => __awaiter(this, void 0, void 0, function* () { server = new TestServer_1.TestServer(); proxy = { on: sinon.stub(), ws: sinon.stub(), close: sinon.stub(), }; httpProxy = { createProxy: sinon.stub().returns(proxy), }; const proxied = proxyquire("./SyncProxyService", { "http-proxy": httpProxy }); service = new proxied.SyncProxyService(); syncService = new SyncService_1.SyncService(); yield server.start(Object.assign({ services: [service, syncService] }, (startArgs || {}))); })); afterEach(() => __awaiter(this, void 0, void 0, function* () { yield server.shutdown().catch((err) => { }); })); function runHandler(req) { return __awaiter(this, void 0, void 0, function* () { const socket = new events_1.EventEmitter(); socket.destroy = sinon.stub(); if (!req.headers["sec-websocket-protocol"]) { req.headers["sec-websocket-protocol"] = "io.realm.sync.24"; } yield service["websocketHandler"](req, socket, sinon.stub()); return socket; }); } function createAuthorizationHeader(token, version = 1) { return `Realm-Access-Token version=${version} token="${token.sign(server.privateKey)}"`; } const validToken = new Token_1.AccessToken({ access: ["download"], identity: "some-user", appId: "io.realm.Test", path: "/some-uid/myrealm", syncLabel: "default", }); const adminToken = new Token_1.AccessToken({ access: ["download"], identity: Constants_1.Constants.AdminUserId, appId: "io.realm.Test", syncLabel: "default", }); const noSyncLabelToken = new Token_1.AccessToken({ access: ["download"], identity: "some-user", appId: "io.realm.Test", path: "/some-uid/myrealm" }); function assertConnectionProxied() { sinon.assert.called(httpProxy.createProxy); sinon.assert.called(proxy.on); sinon.assert.called(proxy.ws); } function describeAuthorizationBehavior(header = "authorization") { describe("without an authorization header", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(runHandler({ headers: {}, }), "Authorization header was not provided"); })); }); describe("with an invalid access token header", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(runHandler({ headers: { [header]: "gibberish", }, }), "Authorization header is not in a valid format"); })); }); describe("with a valid access token header", () => { it("should open a proxy connection", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(runHandler({ headers: { [header]: createAuthorizationHeader(validToken), }, params: { path: "/some-uid/myrealm", } })); assertConnectionProxied(); })); describe("with mismatching path parameters", () => { describe("as a regular user", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(runHandler({ headers: { [header]: createAuthorizationHeader(validToken), }, params: { path: "/some-uid/otherrealm", } }), "Your request parameters did not validate."); })); }); describe("as an admin user", () => { it("should fulfill", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(runHandler({ headers: { [header]: createAuthorizationHeader(adminToken), }, params: { path: "/some-uid/otherrealm", } })); assertConnectionProxied(); })); }); }); describe("without a syncLabel", () => { it("should lookup the location", () => __awaiter(this, void 0, void 0, function* () { const stub = sinon.stub(server.realmDirectoryClient, "findByPath").returns(undefined); yield chai_1.assert.isRejected(runHandler({ headers: { [header]: createAuthorizationHeader(noSyncLabelToken), }, params: { path: "/some-uid/myrealm", } }), "AccessToken is not valid: sync label was not found in token"); sinon.assert.calledWithMatch(stub, { realmPath: "/some-uid/myrealm", shouldCreate: false, }); })); }); }); } describe("websocketHandler", () => { describe("with default authorization header name", () => { describeAuthorizationBehavior(); }); describe("with custom authorization header name", () => { before(() => { startArgs = { authorizationHeaderName: "MyCustomHeader" }; }); describeAuthorizationBehavior("mycustomheader"); after(() => { startArgs = undefined; }); }); describe.skip("sync service failover", () => { it("should close existing connection", () => __awaiter(this, void 0, void 0, function* () { const socket = yield chai_1.assert.isFulfilled(runHandler({ headers: { "authorization": createAuthorizationHeader(validToken), }, params: { path: "/some-uid/myrealm", } })); sinon.assert.calledOnce(proxy.ws); sinon.assert.notCalled(proxy.close); yield server.discovery.deregisterService(syncService); sinon.assert.calledOnce(proxy.close); sinon.assert.calledOnce(socket.destroy); yield chai_1.assert.isRejected(runHandler({ headers: { "authorization": createAuthorizationHeader(validToken), }, params: { path: "/some-uid/myrealm", } }), "The requested service is temporarily unavailable."); })); }); describe("with minimum sync protocol version", () => { before(() => { startArgs = { minimumSupportedSyncProtocolVersion: 20 }; }); function request(protocolVersion) { return runHandler({ headers: { "authorization": createAuthorizationHeader(validToken), "sec-websocket-protocol": `io.realm.sync.${protocolVersion}` }, params: { path: "/some-uid/myrealm", } }); } describe("when request protocol is lower version", () => { it("should return error", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(request(10), "The server was not configured to support the requested operation."); })); }); describe("when request protocol is same version", () => { it("should open a proxy connection", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(request(20)); assertConnectionProxied(); })); }); describe("when request protocol is higher version", () => { it("should open a proxy connection", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(request(30)); assertConnectionProxied(); })); }); after(() => { startArgs = undefined; }); }); }); describe("stop", () => { describe("with existing connections", () => { it("should close existing connections", () => __awaiter(this, void 0, void 0, function* () { const socket = yield chai_1.assert.isFulfilled(runHandler({ headers: { "authorization": createAuthorizationHeader(validToken), }, params: { path: "/some-uid/myrealm", } })); yield server.shutdown(); sinon.assert.called(proxy.close); sinon.assert.called(socket.destroy); })); }); }); describe("as event emitter", () => { it("should emit socket connected/disconnected", () => __awaiter(this, void 0, void 0, function* () { const connectedPromise = shared_1.waitForEvent(service, "socketConnected"); const disconnectedPromise = shared_1.waitForEvent(service, "socketDisconnected"); const socket = yield runHandler({ headers: { ["authorization"]: createAuthorizationHeader(validToken), ["user-agent"]: "some-user-agent", }, params: { path: "/some-uid/myrealm", } }); const { path: connectedPath, userAgent: connectedUserAgent, socketId: connectedSocketId } = yield connectedPromise; chai_1.assert.equal(connectedUserAgent, "some-user-agent"); chai_1.assert.equal(connectedPath, "/some-uid/myrealm"); socket.emit("close"); const { path: disconnectedPath, userAgent: disconnectedUserAgent, socketId: disconnectedSocketId } = yield disconnectedPromise; chai_1.assert.equal(disconnectedUserAgent, "some-user-agent"); chai_1.assert.equal(disconnectedPath, "/some-uid/myrealm"); chai_1.assert.equal(connectedSocketId, disconnectedSocketId); })); }); }); //# sourceMappingURL=SyncProxyService.spec.js.map