@itwin/core-backend
Version:
iTwin.js backend components
349 lines • 17.6 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 { expect } from "chai";
import { assert, OpenMode } from "@itwin/core-bentley";
import { StandaloneDb } from "../../IModelDb";
import { IModelHost } from "../../IModelHost";
import { SettingsPriority } from "../../workspace/Settings";
import { IModelTestUtils } from "../IModelTestUtils";
import { GeoCoordConfig } from "../../GeoCoordConfig";
describe("Settings", () => {
let iModel;
before(() => {
IModelHost.settingsSchemas.addFile(IModelTestUtils.resolveAssetFile("TestSettings.schema.json"));
const seedFileName = IModelTestUtils.resolveAssetFile("CompatibilityTestSeed.bim");
const testFileName = IModelTestUtils.prepareOutputFile("SettingsTest", "SettingsTest.bim");
iModel = IModelTestUtils.createSnapshotFromSeed(testFileName, seedFileName);
});
after(() => {
iModel.close();
});
const app1 = {
description: "",
schemaPrefix: "app1",
settingDefs: {
sub1: {
type: "string",
enum: ["va1", "alt1"],
enumDescriptions: [
"descr1",
"descr2",
],
default: "val1",
description: "the first value",
},
sub2: {
type: "array",
description: "an array",
items: {
type: "string",
},
},
boolVal: {
type: "boolean",
default: true,
description: "boolean defaults to true",
},
strVal: {
type: "string",
default: "default string val",
},
intVal: {
type: "integer",
default: 22,
},
obj: {
type: "object",
properties: {
out: {
type: "object",
properties: {
o1: {
type: "object",
required: ["m1", "m2"],
properties: {
m1: {
type: "string",
},
m2: {
type: "number",
},
},
},
},
},
},
},
databases: {
type: "array",
items: {
type: "object",
required: ["name", "dbName", "containerName"],
properties: {
name: {
type: "string",
},
dbName: {
type: "string",
},
containerName: {
type: "string",
},
b2: {
type: "object",
properties: {
o1: {
type: "object",
required: ["m1"],
properties: {
m1: {
type: "string",
},
m2: {
type: "number",
},
},
},
},
},
},
},
},
},
};
const app1Settings = {
"app1/sub1": "app1 value",
"app1/sub2": {
arr: ["app1", "app2"],
},
"app1/setting2": 1002,
"app1/setting3": "app setting 3 val",
"app1/setting5": "app setting 5 val",
};
const imodel1Settings = {
"app1/sub1": "imodel1 value",
"app1/sub2": {
arr: ["a1", "a2"],
},
"app1/setting2": 2,
"app1/setting3": "setting 3 val",
};
const imodel2Settings = {
"app1/sub1": "imodel2 value",
"app1/sub2": {
arr: ["a21", "a22"],
},
};
const iTwinSettings = {
"app1/sub1": "val3",
"app1/sub2": {
arr: ["a31", "a32", "a33"],
},
"app2/setting6": "val 6",
"app3/obj": {
member1: "test2",
member2: "test3",
member3: {
part1: "p1",
part2: [
{ m1: 0 },
{ m1: 2 },
],
},
},
};
it("settings priorities", () => {
const settings = iModel.workspace.settings;
IModelHost.settingsSchemas.addGroup(app1);
IModelHost.appWorkspace.settings.addDictionary({ name: "app1", priority: SettingsPriority.application }, app1Settings);
let settingsChanged = 0;
settings.onSettingsChanged.addListener(() => settingsChanged++);
settings.addDictionary({ name: "iModel1.setting.json", priority: SettingsPriority.iModel }, imodel1Settings);
expect(settingsChanged).eq(1);
settings.addDictionary({ name: "iModel2.setting.json", priority: SettingsPriority.iModel }, imodel2Settings);
expect(settingsChanged).eq(2);
settings.addDictionary({ name: "iTwin.setting.json", priority: SettingsPriority.iTwin }, iTwinSettings);
expect(settingsChanged).eq(3);
expect(() => IModelHost.appWorkspace.settings.addDictionary({ name: "iModel", priority: SettingsPriority.iModel }, imodel1Settings)).to.throw("Use IModelSettings");
expect(settings.getString("app1/sub1")).equals(imodel2Settings["app1/sub1"]);
expect(settings.getString("app2/setting6")).equals(iTwinSettings["app2/setting6"]);
expect(settings.getString("app1/setting5")).equals(app1Settings["app1/setting5"]); // comes from app settings
expect(settings.getSetting("app1/sub2").arr).deep.equals(imodel2Settings["app1/sub2"].arr);
const app3obj = settings.getSetting("app3/obj");
expect(app3obj).deep.equals(iTwinSettings["app3/obj"]);
app3obj.member3.part2[0].m1 = "bad"; // should modify a copy
expect(iTwinSettings["app3/obj"].member3.part2[0].m1).equal(0);
expect(settings.getObject("app3/obj")).deep.equals(iTwinSettings["app3/obj"]); // should be original value
expect(settings.getBoolean("app1/boolVal")).equals(true);
expect(settings.getBoolean("app1/not there", true)).equals(true);
expect(settings.getBoolean("app1/not there", false)).equals(false);
expect(settings.getString("app1/strVal")).equals(app1.settingDefs.strVal.default);
expect(settings.getNumber("app1/intVal")).equals(22);
expect(() => settings.getObject("app1/intVal")).throws("app1/intVal");
expect(() => settings.getArray("app1/intVal")).throws("app1/intVal");
expect(() => settings.getString("app1/intVal", "oops")).throws("app1/intVal");
expect(() => settings.getBoolean("app1/intVal", true)).throws("app1/intVal");
expect(settings.getNumber("app1/not there", 33)).equals(33);
expect(settings.getSetting("app1/not there")).is.undefined;
expect(settings.getString("app2/not there", "fallback")).equals("fallback");
// list of default Gcs databases is in the backend.setting.json5 file loaded on startup
const defaultGcs = settings.getArray(GeoCoordConfig.settingName.defaultDatabases);
assert(undefined !== defaultGcs);
expect(defaultGcs.length).equals(2);
expect(defaultGcs[0].baseUri).equal("https://geocoord-workspace.itwinjs.org");
expect(defaultGcs[1].baseUri).equal(defaultGcs[1].baseUri);
expect(defaultGcs[0].dbName).equal("base");
expect(defaultGcs[1].dbName).equal("allEarth");
expect(defaultGcs[0].priority).equals(10000);
expect(defaultGcs[1].priority).equals(100);
expect(defaultGcs[0].prefetch).true;
expect(defaultGcs[1].prefetch).undefined;
expect(defaultGcs[1].storageType).equals("azure");
iTwinSettings["app2/setting6"] = "new value for 6";
settings.addDictionary({ name: "iTwin.setting.json", priority: SettingsPriority.iTwin }, iTwinSettings);
expect(settings.getString("app2/setting6")).equals(iTwinSettings["app2/setting6"]);
expect(settingsChanged).eq(4);
app1.settingDefs.strVal.default = "new default";
IModelHost.settingsSchemas.addGroup(app1);
// after re-registering, the new default should be updated
expect(settings.getString("app1/strVal")).equals(app1.settingDefs.strVal.default);
expect(settings.dictionaries.length).eq(3);
const inspect = Array.from(settings.getSettingEntries("app1/sub1"));
expect(inspect.length).equals(5);
expect(inspect[0].dictionary.props).to.deep.equal({ name: "iModel2.setting.json", priority: SettingsPriority.iModel });
expect(inspect[0].value).equal("imodel2 value");
expect(inspect[1].dictionary.props).to.deep.equal({ name: "iModel1.setting.json", priority: SettingsPriority.iModel });
expect(inspect[1].value).equal("imodel1 value");
expect(inspect[2].dictionary.props).to.deep.equal({ name: "iTwin.setting.json", priority: SettingsPriority.iTwin });
expect(inspect[2].value).equal("val3");
expect(inspect[3].dictionary.props).to.deep.equal({ name: "app1", priority: SettingsPriority.application });
expect(inspect[3].value).equal("app1 value");
expect(inspect[4].dictionary.props).to.deep.equal({ name: "_default_", priority: 0 });
expect(inspect[4].value).equal("val1");
settings.dropDictionary({ name: "iTwin.setting.json" });
expect(settingsChanged).eq(5);
expect(settings.getString("app2/setting6")).is.undefined;
// test validation of values vs. setting schemas
const workspace = { dbName: "abc", containerId: "123", baseUri: "aab.com" };
const fontListVal = [{ workspace, fontName: "arial" }, { workspace, fontName: "helvetica", fontType: 3 }];
expect(() => IModelHost.settingsSchemas.validateSetting(fontListVal, "testApp/fontList")).throws("required value for \"workspaceLimit\" is missing");
workspace.workspaceLimit = 4; // add missing value
expect(() => IModelHost.settingsSchemas.validateSetting(fontListVal, "testApp/fontList")).throws("value for testApp/fontList[1].fontType");
fontListVal[1].fontType = "ttf"; // correct font type to string
expect(IModelHost.settingsSchemas.validateSetting(fontListVal, "testApp/fontList")).equal(fontListVal); // should now pass
});
it("read settings file", () => {
const appSettings = IModelHost.appWorkspace.settings;
const iModelSettings = iModel.workspace.settings;
const settingFileName = IModelTestUtils.resolveAssetFile("test.setting.json5");
expect(() => appSettings.addFile(settingFileName, SettingsPriority.iTwin)).throws("Use IModelSettings");
appSettings.addFile(settingFileName, SettingsPriority.application);
expect(() => iModelSettings.addFile(settingFileName, SettingsPriority.application)).to.throw("Use IModelHost.appSettings");
expect(appSettings.getString("app1/colorTheme")).equals("Light Theme");
expect(iModelSettings.getString("app1/colorTheme")).equals("Light Theme");
const token = appSettings.getSetting("editor/tokenColorCustomizations");
expect(token["Visual Studio Light"].textMateRules[0].settings.foreground).equals("#d16c6c");
expect(token["Default High Contrast"].comments).equals("#FF0000");
expect(appSettings.getArray("editor/enableFiletypes").length).equals(17);
appSettings.dropDictionary({ name: settingFileName });
});
it("IModel persistent settings ", () => {
const iModelName = IModelTestUtils.prepareOutputFile("IModelSetting", "test.bim");
const iModel2 = IModelTestUtils.createSnapshotFromSeed(iModelName, IModelTestUtils.resolveAssetFile("test.bim"));
const setting1 = {
"imodel/setting1": "this is from setting1",
};
const setting1changed = {
"imodel/setting1": "this is changed setting1",
};
const gcsDbDict = {
"gcs/databases": ["gcs/Usa", "gcs/Canada"],
};
iModel2.saveSettingDictionary("gcs-dbs", gcsDbDict);
iModel2.saveSettingDictionary("test1", setting1);
iModel2.close();
let iModel3 = StandaloneDb.openFile(iModelName, OpenMode.ReadWrite);
expect(iModel3.workspace.settings.getObject("gcs/databases")).to.deep.equal(gcsDbDict["gcs/databases"]);
expect(iModel3.workspace.settings.getString("imodel/setting1")).equal(setting1["imodel/setting1"]);
iModel3.saveSettingDictionary("test1", setting1changed);
iModel3.close();
iModel3 = StandaloneDb.openFile(iModelName);
expect(iModel3.workspace.settings.getObject("gcs/databases")).to.deep.equal(gcsDbDict["gcs/databases"]);
expect(iModel3.workspace.settings.getString("imodel/setting1")).equal(setting1changed["imodel/setting1"]);
iModel3.deleteSettingDictionary("test1");
iModel3.close();
iModel3 = StandaloneDb.openFile(iModelName);
expect(iModel3.workspace.settings.getObject("gcs/databases")).to.deep.equal(gcsDbDict["gcs/databases"]);
expect(iModel3.workspace.settings.getString("imodel/setting1")).to.be.undefined;
iModel3.close();
});
describe("combineArray", () => {
function addGroup(schemaPrefix, type, combineArray) {
const group = {
description: "",
schemaPrefix,
settingDefs: {
array: {
type: "array",
items: { type },
combineArray,
},
},
};
IModelHost.settingsSchemas.addGroup(group);
}
function addArray(schemaPrefix, name, value, priority) {
const settings = {};
settings[`${schemaPrefix}/array`] = value;
IModelHost.appWorkspace.settings.addDictionary({
name,
priority,
}, settings);
}
it("combines arrays only if the combineArray flag is explicitly set to true", () => {
addGroup("false", "number", false);
addGroup("true", "number", true);
addGroup("default", "number", undefined);
let dictNum = 0;
const prefixes = ["false", "true", "default"];
for (const schemaPrefix of prefixes) {
addArray(schemaPrefix, `app${dictNum++}`, [1, 2], SettingsPriority.application);
addArray(schemaPrefix, `def${dictNum++}`, [3, 4], SettingsPriority.defaults);
}
for (const prefix of prefixes) {
const settingName = `${prefix}/array`;
expect(IModelHost.appWorkspace.settings.getSetting(settingName)).to.deep.equal([1, 2]);
const expected = ("true" === prefix ? [1, 2, 3, 4] : [1, 2]);
expect(IModelHost.appWorkspace.settings.getArray(settingName)).to.deep.equal(expected);
}
});
it("orders elements by priority", () => {
addGroup("combine", "number", true);
function addDictionary(priority) {
addArray("combine", priority.toString(), [priority], priority);
}
addDictionary(100);
addDictionary(120);
addDictionary(80);
addDictionary(150);
addDictionary(30);
expect(IModelHost.appWorkspace.settings.getArray("combine/array")).to.deep.equal([150, 120, 100, 80, 30]);
});
it("ignores duplicates with lower priority", () => {
addGroup("numbers", "number", true);
addArray("numbers", "a", [4, 8], 101);
addArray("numbers", "c", [3, 6, 9, 12], 99);
addArray("numbers", "b", [2, 4, 6, 8, 10, 12], 100);
expect(IModelHost.appWorkspace.settings.getArray("numbers/array")).to.deep.equal([4, 8, 2, 6, 10, 12, 3, 9]);
;
addGroup("points", "object", true);
addArray("points", "a", [{ x: 1, y: 1 }, { x: 1, y: 2 }], 101);
addArray("points", "b", [{ x: 2, y: 1 }, { x: 1, y: 2 }], 100);
addArray("points", "c", [{ y: 1, x: 1 }, { y: 2, x: 1 }, { x: 3, y: 3 }], 99);
expect(IModelHost.appWorkspace.settings.getArray("points/array")).to.deep.equal([{ x: 1, y: 1 }, { x: 1, y: 2 }, { x: 2, y: 1 }, { x: 3, y: 3 }]);
});
});
});
//# sourceMappingURL=Settings.test.js.map