Address dynamic corner-cases - Task 15982357: [Beta] [AI] Dynamic config for Properties extension (#1977)

- when the referenced property is not an object/array (ie. undefined or null)
- we are assigning a referenced object/array to null / undefined
This commit is contained in:
Nev 2023-01-28 16:46:59 -08:00 коммит произвёл GitHub
Родитель af918812b6
Коммит bc620b524c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 227 добавлений и 14 удалений

Просмотреть файл

@ -700,7 +700,6 @@ export class DynamicConfigTests extends AITestClass {
}
});
this.testCase({
name: "Validate read-only",
useFakeTimers: true,
@ -751,6 +750,203 @@ export class DynamicConfigTests extends AITestClass {
}
});
this.testCase({
name: "Validate updating referenced objects / arrays with non object or null / undefined",
useFakeTimers: true,
test: () => {
let expectedUserCfg = {
userTest: {} as any
};
let theConfig: any = {
instrumentationKey: "testiKey",
endpointUrl: "https://localhost:9001",
enableDebugExceptions: false,
loggingLevelConsole: 1,
extensionConfig: {
"test": {} as any
},
userCfg: {
userTest: {} as any
}
};
let handler = createDynamicConfig(theConfig, {});
let config = handler.cfg;
let userCfg = handler.ref(theConfig, "userCfg");
Assert.deepEqual(expectedUserCfg, userCfg, "Validate that the expected user Cfg")
Assert.ok(userCfg === config.userCfg, "Validate userCfg reference is as expected")
// Assign the referenced object with null / undefined
config.userCfg = null;
Assert.deepEqual({ userTest: undefined }, config.userCfg);
Assert.ok(userCfg === config.userCfg, "The previous referenced value should still match");
config.userCfg = undefined;
Assert.deepEqual({ userTest: undefined }, config.userCfg);
Assert.ok(userCfg === config.userCfg, "The previous referenced value should still match");
// Assign back to an object (should become automatically referenced)
config.userCfg = {};
Assert.deepEqual({ userTest: undefined }, config.userCfg);
Assert.ok(userCfg === config.userCfg, "The previous referenced value should still match");
// Grab a reference and update
userCfg = config.userCfg;
Assert.ok(userCfg === config.userCfg, "References should now match");
config.userCfg = { hello: "World" };
Assert.ok(userCfg === config.userCfg, "References should still match");
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property");
Assert.equal("World", config.userCfg.hello);
Assert.ok(objHasOwn(userCfg, "hello"), "previous reference should have the new property");
Assert.equal("World", userCfg.hello);
config.userCfg = expectedUserCfg;
Assert.ok(userCfg === config.userCfg, "References should still match");
Assert.ok(objHasOwn(config.userCfg, "userTest"), "Direct config reference should have the new property");
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property");
Assert.equal(undefined, config.userCfg.hello);
Assert.deepEqual({}, config.userCfg.userTest);
Assert.ok(objHasOwn(userCfg, "userTest"), "Direct config reference should have the new property");
Assert.ok(objHasOwn(userCfg, "hello"), "previous reference should have the new property");
Assert.equal(undefined, userCfg.hello);
Assert.deepEqual({}, userCfg.userTest);
// ---------------------------------------------------------------------
// Now try deleting the key, which will also drop the previous reference
// ---------------------------------------------------------------------
delete config.userCfg;
Assert.ok(!objHasOwn(config, "userCfg"), "userCfg is no longer present");
Assert.equal(undefined, config.userCfg, "Validate that the config.userCfg was removed");
Assert.ok(userCfg !== config.userCfg, "Validate userCfg reference should no longer match");
// Assign the referenced object with null / undefined
config.userCfg = null;
Assert.ok(objHasOwn(config, "userCfg"), "userCfg is present");
Assert.equal(null, config.userCfg, "Validate that the config.userCfg is null");
Assert.ok(userCfg !== config.userCfg, "Validate userCfg reference should no longer match");
config.userCfg = undefined;
Assert.ok(objHasOwn(config, "userCfg"), "userCfg is present");
Assert.equal(undefined, config.userCfg, "Validate that the config.userCfg is undefined");
Assert.ok(userCfg !== config.userCfg, "The previous referenced value should still match");
// Assign back to an object (should become automatically referenced)
config.userCfg = {};
Assert.deepEqual({}, config.userCfg);
// Grab a reference and update
userCfg = config.userCfg;
Assert.ok(userCfg === config.userCfg, "References should now match");
config.userCfg = { hello: "World" };
Assert.ok(userCfg !== config.userCfg, "References should not match");
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property");
Assert.equal("World", config.userCfg.hello);
Assert.ok(!objHasOwn(userCfg, "hello"), "previous reference should have the new property");
Assert.equal(undefined, userCfg.hello);
}
});
this.testCase({
name: "Validate updating initial referenced undefined value with objects / arrays with non object or null / undefined",
useFakeTimers: true,
test: () => {
let expectedUserCfg = {
userTest: {} as any
};
let theConfig: any = {
instrumentationKey: "testiKey",
endpointUrl: "https://localhost:9001",
enableDebugExceptions: false,
loggingLevelConsole: 1,
userCfg: undefined
};
let handler = createDynamicConfig(theConfig, {});
let config = handler.cfg;
let userCfg = handler.ref(theConfig, "userCfg");
Assert.equal(undefined, config.userCfg, "Check that the userCfg value is as expected");
Assert.deepEqual(undefined, userCfg, "Validate that the expected user Cfg")
Assert.ok(userCfg === config.userCfg, "Validate userCfg reference is as expected")
// Assign the referenced object with null / undefined
config.userCfg = null;
Assert.equal(null, config.userCfg);
Assert.ok(userCfg !== config.userCfg, "The previous referenced value should no longer match");
config.userCfg = undefined;
Assert.equal(undefined, config.userCfg);
Assert.ok(userCfg === config.userCfg, "The previous referenced value should no longer match");
// Assign back to an object (should become automatically referenced)
config.userCfg = {};
Assert.equal(0, objKeys(config.userCfg), "Check the number of keys");
Assert.ok(userCfg !== config.userCfg, "The previous referenced value should no longer match");
// Grab a reference and update
userCfg = config.userCfg;
Assert.ok(userCfg === config.userCfg, "References should now match");
config.userCfg = { hello: "World" };
Assert.ok(userCfg === config.userCfg, "References should still match");
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property")
Assert.equal("World", config.userCfg.hello);
Assert.ok(objHasOwn(userCfg, "hello"), "previous reference should have the new property")
Assert.equal("World", userCfg.hello);
config.userCfg = expectedUserCfg;
Assert.ok(userCfg === config.userCfg, "References should still match");
Assert.ok(objHasOwn(config.userCfg, "userTest"), "Direct config reference should have the new property")
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property")
Assert.equal(undefined, config.userCfg.hello);
Assert.deepEqual({}, config.userCfg.userTest);
Assert.ok(objHasOwn(userCfg, "userTest"), "Direct config reference should have the new property")
Assert.ok(objHasOwn(userCfg, "hello"), "previous reference should have the new property")
Assert.equal(undefined, userCfg.hello);
Assert.deepEqual({}, userCfg.userTest);
// ---------------------------------------------------------------------
// Now try deleting the key, which will also drop the previous reference
// ---------------------------------------------------------------------
delete config.userCfg;
Assert.ok(!objHasOwn(config, "userCfg"), "userCfg is no longer present");
Assert.equal(undefined, config.userCfg, "Validate that the config.userCfg was removed");
Assert.ok(userCfg !== config.userCfg, "Validate userCfg reference should no longer match");
// Assign the referenced object with null / undefined
config.userCfg = null;
Assert.ok(objHasOwn(config, "userCfg"), "userCfg is present");
Assert.equal(null, config.userCfg, "Validate that the config.userCfg is null");
Assert.ok(userCfg !== config.userCfg, "Validate userCfg reference should no longer match");
config.userCfg = undefined;
Assert.ok(objHasOwn(config, "userCfg"), "userCfg is present");
Assert.equal(undefined, config.userCfg, "Validate that the config.userCfg is undefined");
Assert.ok(userCfg !== config.userCfg, "The previous referenced value should still match");
// Assign back to an object (should become automatically referenced)
config.userCfg = {};
Assert.deepEqual({}, config.userCfg);
// Grab a reference and update
userCfg = config.userCfg;
Assert.ok(userCfg === config.userCfg, "References should now match");
config.userCfg = { hello: "World" };
Assert.ok(userCfg !== config.userCfg, "References should not match");
Assert.ok(objHasOwn(config.userCfg, "hello"), "Direct config reference should have the new property");
Assert.equal("World", config.userCfg.hello);
Assert.ok(!objHasOwn(userCfg, "hello"), "previous reference should have the new property");
Assert.equal(undefined, userCfg.hello);
}
});
function createPerfMgr(core: IAppInsightsCore, manager: INotificationManager): IPerfManager {
return createPerfMgr(core, manager);
}

Просмотреть файл

@ -4,6 +4,7 @@
import {
arrForEach, arrIndexOf, dumpObj, isArray, isPlainObject, objDefineAccessors, objDefineProp, objForEachKey, objGetOwnPropertyDescriptor
} from "@nevware21/ts-utils";
import { UNDEFINED_VALUE } from "../JavaScriptSDK/InternalConstants";
import { CFG_HANDLER_LINK, throwInvalidAccess } from "./DynamicSupport";
import { IWatcherHandler, _IDynamicDetail } from "./IDynamicWatcher";
import { _IDynamicConfigHandlerState, _IDynamicGetter } from "./_IDynamicConfigHandlerState";
@ -63,10 +64,15 @@ function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<
// Flag to optimize lookup response time by avoiding additional function calls
let checkDynamic = true;
let isObjectOrArray = false;
function _getProperty() {
if (checkDynamic) {
if (value && !value[CFG_HANDLER_LINK] && (isPlainObject(value) || isArray(value))) {
isObjectOrArray = isObjectOrArray || (value && (isPlainObject(value) || isArray(value)));
// Make sure that if it's an object that we make it dynamic
if (value && !value[CFG_HANDLER_LINK] && isObjectOrArray) {
// It doesn't look like it's already dynamic so lets make sure it's converted the object into a dynamic Config as well
value = _makeDynamicObject(state, value);
}
@ -98,13 +104,21 @@ function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<
throwInvalidAccess("[" + name + "] is read-only:" + dumpObj(theConfig));
}
let isReferenced = _getProperty[state.rf];
if(isPlainObject(value) || isArray(value)) {
if (checkDynamic) {
isObjectOrArray = isObjectOrArray || (value && (isPlainObject(value) || isArray(value)));
checkDynamic = false;
}
// The value must be a plain object or an array to enforce the reference (in-place updates)
let isReferenced = isObjectOrArray && _getProperty[state.rf];
if(isObjectOrArray) {
// We are about to replace a plain object or an array
if (isReferenced) {
// Reassign the properties from the current value to the same properties from the newValue
// This will set properties not in the newValue to undefined
objForEachKey(value, (key) => {
value[key] = newValue[key];
value[key] = newValue ? newValue[key] : UNDEFINED_VALUE;
});
// Now assign / re-assign value with all of the keys from newValue
@ -129,14 +143,17 @@ function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<
}
}
checkDynamic = false;
if (!isReferenced && (isPlainObject(newValue) || isArray(newValue))) {
// As the newValue is an object/array lets preemptively make it dynamic
_makeDynamicObject(state, newValue);
}
if (newValue !== value) {
let newIsObjectOrArray = newValue && (isPlainObject(newValue) || isArray(newValue));
if (!isReferenced && newIsObjectOrArray) {
// As the newValue is an object/array lets preemptively make it dynamic
_makeDynamicObject(state, newValue);
}
// Now assign the internal "value" to the newValue
value = newValue;
// Now assign the internal "value" to the newValue
value = newValue;
isObjectOrArray = newIsObjectOrArray;
}
// Cause any listeners to be scheduled for notification
state.add(detail);
@ -178,9 +195,9 @@ export function _setDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandler
return value;
}
export function _makeDynamicObject<T>(state: _IDynamicConfigHandlerState<T>, target: any/*, newValues?: any*/) {
export function _makeDynamicObject<T>(state: _IDynamicConfigHandlerState<T>, target: any) {
// Assign target with new value properties (converting into dynamic properties in the process)
objForEachKey(/*newValues || */target, (key, value) => {
objForEachKey(target, (key, value) => {
// Assign and/or make the property dynamic
_setDynamicProperty(state, target, key, value);
});