UNPKG

@itwin/core-backend

Version:
247 lines • 12.9 kB
/*--------------------------------------------------------------------------------------------- * 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 { EditTxnError, RpcRegistry } from "@itwin/core-common"; import { BriefcaseManager } from "../BriefcaseManager"; import { EditTxn } from "../EditTxn"; import { IModelJsFs } from "../IModelJsFs"; import { SnapshotDb, StandaloneDb } 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; expect(EditTxn.implicitWriteEnforcement).to.equal("allow"); }); it("should allow configuring explicit transaction behavior", async () => { await IModelHost.startup({ ...opts, implicitWriteEnforcement: "log" }); expect(EditTxn.implicitWriteEnforcement).to.equal("log"); expect(IModelHost.configuration?.implicitWriteEnforcement).to.equal("log"); }); 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"); }); it("should log implicit transaction writes when configured to log", async () => { await IModelHost.startup({ ...opts, implicitWriteEnforcement: "log" }); const logError = sinon.spy(Logger, "logError"); const fileName = IModelTestUtils.prepareOutputFile("IModelHost", "implicitWriteLog.bim"); const db = StandaloneDb.createEmpty(fileName, { rootSubject: { name: "implicitWriteLog" }, enableTransactions: true, }); try { // eslint-disable-next-line @typescript-eslint/no-deprecated db.saveFileProperty({ name: "log-mode", namespace: "IModelHostTest" }, "value"); expect(db.queryFilePropertyString({ name: "log-mode", namespace: "IModelHostTest" })).to.equal("value"); expect(logError.calledOnce).to.be.true; expect(logError.firstCall.args[0]).to.equal("core-backend.IModelDb"); expect(EditTxnError.isError(logError.firstCall.args[1], "implicit-txn-write-disallowed")).to.be.true; } finally { if (db.isOpen) db.close(); IModelJsFs.removeSync(fileName); } }); it("should reject implicit transaction writes when configured to enforce", async () => { await IModelHost.startup({ ...opts, implicitWriteEnforcement: "throw" }); const fileName = IModelTestUtils.prepareOutputFile("IModelHost", "implicitWriteEnforce.bim"); const db = StandaloneDb.createEmpty(fileName, { rootSubject: { name: "implicitWriteEnforce" }, enableTransactions: true, }); try { expect(() => { // eslint-disable-next-line @typescript-eslint/no-deprecated db.saveFileProperty({ name: "enforce-mode", namespace: "IModelHostTest" }, "value"); }).to.throw().that.satisfies((error) => EditTxnError.isError(error, "implicit-txn-write-disallowed")); const txn = new EditTxn(db, "explicit test"); txn.start(); txn.saveFileProperty({ name: "explicit", namespace: "IModelHostTest" }, "value"); txn.end(); expect(db.queryFilePropertyString({ name: "explicit", namespace: "IModelHostTest" })).to.equal("value"); } finally { if (db.isOpen) db.close(); IModelJsFs.removeSync(fileName); } }); }); //# sourceMappingURL=IModelHost.test.js.map