realm-object-server
Version:
278 lines • 12.6 kB
JavaScript
;
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