@itwin/core-backend
Version:
iTwin.js backend components
193 lines • 9.99 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { assert, expect } from "chai";
import * as path from "path";
import * as sinon from "sinon";
import { RpcRegistry } from "@itwin/core-common";
import { BriefcaseManager } from "../BriefcaseManager";
import { SnapshotDb } from "../IModelDb";
import { IModelHost, KnownLocations } from "../IModelHost";
import { Schemas } from "../Schema";
import { KnownTestLocations } from "./KnownTestLocations";
import { AzureServerStorage } from "@itwin/object-storage-azure";
import { TestUtils } from "./TestUtils";
import { IModelTestUtils } from "./IModelTestUtils";
import { Logger, LogLevel } from "@itwin/core-bentley";
import { overrideSyncNativeLogLevels } from "../internal/NativePlatform";
import { _getHubAccess, _hubAccess } from "../internal/Symbols";
describe("IModelHost", () => {
const opts = { cacheDir: TestUtils.getCacheDir() };
beforeEach(async () => {
await TestUtils.shutdownBackend();
});
afterEach(async () => {
sinon.restore();
});
after(async () => {
await TestUtils.startBackend();
});
it("valid default configuration", async () => {
await IModelHost.startup(opts);
// Valid registered implemented RPCs
expect(RpcRegistry.instance.implementationClasses.size).to.equal(4);
expect(RpcRegistry.instance.implementationClasses.get("IModelReadRpcInterface")).to.exist;
expect(RpcRegistry.instance.implementationClasses.get("IModelTileRpcInterface")).to.exist;
expect(RpcRegistry.instance.implementationClasses.get("SnapshotIModelRpcInterface")).to.exist;
expect(RpcRegistry.instance.implementationClasses.get("DevToolsRpcInterface")).to.exist;
expect(Schemas.getRegisteredSchema("BisCore")).to.exist;
expect(Schemas.getRegisteredSchema("Generic")).to.exist;
expect(Schemas.getRegisteredSchema("Functional")).to.exist;
});
it("should properly cleanup beforeExit event listeners on shutdown", async () => {
const beforeCount = process.listenerCount("beforeExit");
for (let i = 0; i <= 15; i++) {
await IModelHost.startup();
await IModelHost.shutdown();
}
const afterCount = process.listenerCount("beforeExit");
expect(beforeCount).to.be.equal(afterCount);
});
it("should call logger sync function", async () => {
let nSyncCalls = 0;
overrideSyncNativeLogLevels(() => ++nSyncCalls);
await IModelHost.startup(opts);
expect(nSyncCalls).to.equal(0);
Logger.setLevel("test-cat", LogLevel.Warning);
expect(nSyncCalls).to.equal(1);
overrideSyncNativeLogLevels(undefined);
});
it("should raise onAfterStartup events", async () => {
const eventHandler = sinon.spy();
IModelHost.onAfterStartup.addOnce(eventHandler);
await IModelHost.startup(opts);
expect(eventHandler.calledOnce).to.be.true;
});
it("should raise onBeforeShutdown events", async () => {
await TestUtils.startBackend();
const eventHandler = sinon.spy();
IModelHost.onBeforeShutdown.addOnce(eventHandler);
const filename = IModelTestUtils.resolveAssetFile("GetSetAutoHandledStructProperties.bim");
const workspaceClose = sinon.spy(IModelHost.appWorkspace, "close");
const saveSettings = IModelHost.appWorkspace.settings;
const settingClose = sinon.spy(saveSettings, "close");
expect(workspaceClose.callCount).eq(0);
expect(settingClose.callCount).eq(0);
expect(saveSettings._remove).to.not.be.undefined;
// shutdown should close any opened iModels. Make sure that happens
const imodel1 = SnapshotDb.openFile(filename, { key: "imodel1" });
const imodel2 = SnapshotDb.openFile(filename, { key: "imodel2" });
const imodel3 = SnapshotDb.openFile(filename, { key: "imodel3" });
const imodel4 = SnapshotDb.openFile(filename, { key: "imodel4" });
assert.notEqual(imodel1, imodel2);
assert.notEqual(imodel2, imodel3);
expect(imodel1.isOpen).to.be.true;
expect(imodel2.isOpen).to.be.true;
expect(imodel3.isOpen).to.be.true;
imodel4.close(); // make sure it gets removed so we don't try to close it again on shutdown
await TestUtils.shutdownBackend();
expect(eventHandler.calledOnce).to.be.true;
assert.isFalse(imodel1.isOpen, "shutdown should close iModel1");
assert.isFalse(imodel2.isOpen, "shutdown should close iModel2");
assert.isFalse(imodel3.isOpen, "shutdown should close iModel3");
expect(workspaceClose.callCount).eq(1);
expect(settingClose.callCount).eq(1);
expect(saveSettings._remove).to.be.undefined;
});
it("should auto-shutdown on process beforeExit event", async () => {
await TestUtils.startBackend();
expect(IModelHost.isValid).to.be.true;
const eventHandler = sinon.spy();
IModelHost.onBeforeShutdown.addOnce(eventHandler);
process.emit("beforeExit", 0);
await new Promise((resolve) => setImmediate(resolve));
expect(eventHandler.calledOnce).to.be.true;
expect(IModelHost.isValid).to.be.false;
});
it("should set the briefcase cache directory to expected locations", async () => {
const config = {};
const cacheSubDir = "imodels";
// Test cache default location
await IModelHost.shutdown();
await IModelHost.startup(config);
let expectedDir = path.join(IModelHost.cacheDir, cacheSubDir);
assert.strictEqual(expectedDir, BriefcaseManager.cacheDir);
// Test custom cache location
await IModelHost.shutdown();
config.cacheDir = KnownLocations.tmpdir;
await IModelHost.startup(config);
expectedDir = path.join(KnownLocations.tmpdir, cacheSubDir);
assert.strictEqual(expectedDir, BriefcaseManager.cacheDir);
});
it("should set Azure cloud storage provider for tile cache given credentials", async () => {
const config = {};
config.tileCacheAzureCredentials = {
account: "testAccount",
accessKey: "testAccessKey",
};
await IModelHost.startup(config);
assert.isDefined(IModelHost.tileStorage);
assert.isDefined(IModelHost.tileStorage.storage);
assert.instanceOf(IModelHost.tileStorage.storage, AzureServerStorage);
assert.equal((IModelHost.tileStorage?.storage)._config.baseUrl, `https://${config.tileCacheAzureCredentials.account}.blob.core.windows.net`);
});
it("should set Azure cloud storage provider for tile cache with custom baseUrl", async () => {
const config = {};
config.tileCacheAzureCredentials = {
account: "testAccount",
accessKey: "testAccessKey",
baseUrl: "https://custom.blob.core.windows.net",
};
await IModelHost.startup(config);
assert.isDefined(IModelHost.tileStorage);
assert.isDefined(IModelHost.tileStorage.storage);
assert.instanceOf(IModelHost.tileStorage.storage, AzureServerStorage);
assert.equal((IModelHost.tileStorage?.storage)._config.baseUrl, config.tileCacheAzureCredentials.baseUrl);
});
it("should set custom cloud storage provider for tile cache", async () => {
const config = {};
config.tileCacheStorage = {};
await IModelHost.startup(config);
assert.isDefined(IModelHost.tileStorage);
assert.equal(IModelHost.tileStorage.storage, config.tileCacheStorage);
});
it("should throw if both tileCacheStorage and tileCacheAzureCredentials are set", async () => {
const config = {};
config.tileCacheAzureCredentials = {
account: "testAccount",
accessKey: "testAccessKey",
};
config.tileCacheStorage = {};
await expect(IModelHost.startup(config)).to.be.rejectedWith("Cannot use both Azure and custom cloud storage providers for tile cache.");
});
it("should use local cache if cloud storage provider for tile cache is not set", async () => {
await IModelHost.startup(opts);
assert.isUndefined(IModelHost.tileStorage);
});
it("should cleanup tileStorage on shutdown", async () => {
const config = {};
config.tileCacheStorage = {};
await IModelHost.startup(config);
assert.equal(IModelHost.tileStorage?.storage, config.tileCacheStorage);
await IModelHost.shutdown();
assert.isUndefined(IModelHost.tileStorage);
});
it("should throw if hubAccess is undefined and getter is called", async () => {
await IModelHost.startup(opts);
expect(IModelHost[_getHubAccess]()).undefined;
expect(() => IModelHost[_hubAccess]).throws();
});
it("computeSchemaChecksum", () => {
const assetsDir = path.join(KnownTestLocations.assetsDir, "ECSchemaOps");
const schemaXmlPath = path.join(assetsDir, "SchemaA.ecschema.xml");
let referencePaths = [assetsDir];
let sha1 = IModelHost.computeSchemaChecksum({ schemaXmlPath, referencePaths });
expect(sha1).equal("3ac6578060902aa0b8426b61d62045fdf7fa0b2b");
expect(() => IModelHost.computeSchemaChecksum({ schemaXmlPath, referencePaths, exactMatch: true })).throws("Failed to read schema SchemaA.ecschema");
referencePaths = [path.join(assetsDir, "exact-match")];
sha1 = IModelHost.computeSchemaChecksum({ schemaXmlPath, referencePaths, exactMatch: true });
expect(sha1).equal("2a618664fbba1df7c05f27d7c0e8f58de250003b");
});
});
//# sourceMappingURL=IModelHost.test.js.map