mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-07 11:36:47 +00:00
DDM in DE for NOSQL (#2224)
* ddm for DE for noSQL * ddm for DE for noSQL * ddm for DE for noSQL * ddm fix for the default case and test fix * formatting issue * updated the text change * added validation errors --------- Co-authored-by: Sakshi Gupta <sakshig@microsoft.com>
This commit is contained in:
@@ -5,7 +5,12 @@ import {
|
||||
getMongoIndexType,
|
||||
getMongoIndexTypeText,
|
||||
getMongoNotification,
|
||||
getPartitionKeyName,
|
||||
getPartitionKeyPlaceHolder,
|
||||
getPartitionKeySubtext,
|
||||
getPartitionKeyTooltipText,
|
||||
getSanitizedInputValue,
|
||||
getTabTitle,
|
||||
hasDatabaseSharedThroughput,
|
||||
isDirty,
|
||||
isIndexTransforming,
|
||||
@@ -14,6 +19,7 @@ import {
|
||||
MongoWildcardPlaceHolder,
|
||||
parseConflictResolutionMode,
|
||||
parseConflictResolutionProcedure,
|
||||
SettingsV2TabTypes,
|
||||
SingleFieldText,
|
||||
WildcardText,
|
||||
} from "./SettingsUtils";
|
||||
@@ -50,14 +56,46 @@ describe("SettingsUtils", () => {
|
||||
expect(hasDatabaseSharedThroughput(newCollection)).toEqual(true);
|
||||
});
|
||||
|
||||
it("parseConflictResolutionMode", () => {
|
||||
expect(parseConflictResolutionMode("custom")).toEqual(DataModels.ConflictResolutionMode.Custom);
|
||||
expect(parseConflictResolutionMode("lastwriterwins")).toEqual(DataModels.ConflictResolutionMode.LastWriterWins);
|
||||
describe("parseConflictResolutionMode", () => {
|
||||
it("parses valid modes correctly", () => {
|
||||
expect(parseConflictResolutionMode("custom")).toEqual(DataModels.ConflictResolutionMode.Custom);
|
||||
expect(parseConflictResolutionMode("lastwriterwins")).toEqual(DataModels.ConflictResolutionMode.LastWriterWins);
|
||||
expect(parseConflictResolutionMode("Custom")).toEqual(DataModels.ConflictResolutionMode.Custom);
|
||||
expect(parseConflictResolutionMode("CUSTOM")).toEqual(DataModels.ConflictResolutionMode.Custom);
|
||||
expect(parseConflictResolutionMode("LastWriterWins")).toEqual(DataModels.ConflictResolutionMode.LastWriterWins);
|
||||
});
|
||||
|
||||
it("handles empty/undefined input", () => {
|
||||
expect(parseConflictResolutionMode(undefined)).toBeUndefined();
|
||||
expect(parseConflictResolutionMode(null)).toBeUndefined();
|
||||
expect(parseConflictResolutionMode("")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("defaults to LastWriterWins for invalid inputs", () => {
|
||||
expect(parseConflictResolutionMode("invalid")).toEqual(DataModels.ConflictResolutionMode.LastWriterWins);
|
||||
expect(parseConflictResolutionMode("123")).toEqual(DataModels.ConflictResolutionMode.LastWriterWins);
|
||||
});
|
||||
});
|
||||
|
||||
it("parseConflictResolutionProcedure", () => {
|
||||
expect(parseConflictResolutionProcedure("/dbs/db/colls/coll/sprocs/conflictResSproc")).toEqual("conflictResSproc");
|
||||
expect(parseConflictResolutionProcedure("conflictResSproc")).toEqual("conflictResSproc");
|
||||
describe("parseConflictResolutionProcedure", () => {
|
||||
it("extracts procedure name from valid paths", () => {
|
||||
expect(parseConflictResolutionProcedure("/dbs/db/colls/coll/sprocs/conflictResSproc")).toEqual(
|
||||
"conflictResSproc",
|
||||
);
|
||||
expect(parseConflictResolutionProcedure("conflictResSproc")).toEqual("conflictResSproc");
|
||||
expect(parseConflictResolutionProcedure("/dbs/mydb/colls/mycoll/sprocs/myProc")).toEqual("myProc");
|
||||
});
|
||||
|
||||
it("handles empty/undefined input", () => {
|
||||
expect(parseConflictResolutionProcedure(undefined)).toBeUndefined();
|
||||
expect(parseConflictResolutionProcedure(null)).toBeUndefined();
|
||||
expect(parseConflictResolutionProcedure("")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("handles invalid path formats", () => {
|
||||
expect(parseConflictResolutionProcedure("/invalid/path")).toBeUndefined();
|
||||
expect(parseConflictResolutionProcedure("/dbs/db/colls/coll/wrongtype/name")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("isDirty", () => {
|
||||
@@ -68,68 +106,235 @@ describe("SettingsUtils", () => {
|
||||
excludedPaths: [],
|
||||
} as DataModels.IndexingPolicy;
|
||||
|
||||
it("works on all types", () => {
|
||||
expect(isDirty("baseline", "baseline")).toEqual(false);
|
||||
expect(isDirty(0, 0)).toEqual(false);
|
||||
expect(isDirty(true, true)).toEqual(false);
|
||||
expect(isDirty(undefined, undefined)).toEqual(false);
|
||||
expect(isDirty(indexingPolicy, indexingPolicy)).toEqual(false);
|
||||
describe("primitive types", () => {
|
||||
it("handles strings", () => {
|
||||
expect(isDirty("baseline", "baseline")).toBeFalsy();
|
||||
expect(isDirty("baseline", "current")).toBeTruthy();
|
||||
expect(isDirty("", "")).toBeFalsy();
|
||||
expect(isDirty("test", "")).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(isDirty("baseline", "current")).toEqual(true);
|
||||
expect(isDirty(0, 1)).toEqual(true);
|
||||
expect(isDirty(true, false)).toEqual(true);
|
||||
expect(isDirty(undefined, indexingPolicy)).toEqual(true);
|
||||
expect(isDirty(indexingPolicy, { ...indexingPolicy, automatic: false })).toEqual(true);
|
||||
it("handles numbers", () => {
|
||||
expect(isDirty(0, 0)).toBeFalsy();
|
||||
expect(isDirty(1, 1)).toBeFalsy();
|
||||
expect(isDirty(0, 1)).toBeTruthy();
|
||||
expect(isDirty(-1, 1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("handles booleans", () => {
|
||||
expect(isDirty(true, true)).toBeFalsy();
|
||||
expect(isDirty(false, false)).toBeFalsy();
|
||||
expect(isDirty(true, false)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("handles undefined and null", () => {
|
||||
expect(isDirty(undefined, undefined)).toBeFalsy();
|
||||
expect(isDirty(null, null)).toBeFalsy();
|
||||
expect(isDirty(undefined, null)).toBeTruthy();
|
||||
expect(isDirty(undefined, "value")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("complex types", () => {
|
||||
it("handles indexing policy", () => {
|
||||
expect(isDirty(indexingPolicy, indexingPolicy)).toBeFalsy();
|
||||
expect(isDirty(indexingPolicy, { ...indexingPolicy, automatic: false })).toBeTruthy();
|
||||
expect(isDirty(indexingPolicy, { ...indexingPolicy, includedPaths: ["/path"] })).toBeTruthy();
|
||||
});
|
||||
|
||||
it("handles array type policies", () => {
|
||||
const computedProperties: DataModels.ComputedProperties = [
|
||||
{ name: "prop1", query: "SELECT * FROM c" },
|
||||
{ name: "prop2", query: "SELECT * FROM c" },
|
||||
];
|
||||
const otherProperties: DataModels.ComputedProperties = [{ name: "prop1", query: "SELECT * FROM c" }];
|
||||
expect(isDirty(computedProperties, computedProperties)).toBeFalsy();
|
||||
expect(isDirty(computedProperties, otherProperties)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("type mismatch handling", () => {
|
||||
it("throws error for mismatched types", () => {
|
||||
expect(() => isDirty("string", 123)).toThrow("current and baseline values are not of the same type");
|
||||
expect(() => isDirty(true, "true")).toThrow("current and baseline values are not of the same type");
|
||||
expect(() => isDirty(0, false)).toThrow("current and baseline values are not of the same type");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("getSanitizedInputValue", () => {
|
||||
describe("getSanitizedInputValue", () => {
|
||||
const max = 100;
|
||||
expect(getSanitizedInputValue("", max)).toEqual(0);
|
||||
expect(getSanitizedInputValue("999", max)).toEqual(100);
|
||||
expect(getSanitizedInputValue("10", max)).toEqual(10);
|
||||
|
||||
it("handles empty or invalid inputs", () => {
|
||||
expect(getSanitizedInputValue("", max)).toEqual(0);
|
||||
expect(getSanitizedInputValue("abc", max)).toEqual(0);
|
||||
expect(getSanitizedInputValue("!@#", max)).toEqual(0);
|
||||
expect(getSanitizedInputValue(null, max)).toEqual(0);
|
||||
expect(getSanitizedInputValue(undefined, max)).toEqual(0);
|
||||
});
|
||||
|
||||
it("handles valid inputs within max", () => {
|
||||
expect(getSanitizedInputValue("10", max)).toEqual(10);
|
||||
expect(getSanitizedInputValue("50", max)).toEqual(50);
|
||||
expect(getSanitizedInputValue("100", max)).toEqual(100);
|
||||
});
|
||||
|
||||
it("handles inputs exceeding max", () => {
|
||||
expect(getSanitizedInputValue("101", max)).toEqual(100);
|
||||
expect(getSanitizedInputValue("999", max)).toEqual(100);
|
||||
expect(getSanitizedInputValue("1000000", max)).toEqual(100);
|
||||
});
|
||||
|
||||
it("handles inputs without max constraint", () => {
|
||||
expect(getSanitizedInputValue("10")).toEqual(10);
|
||||
expect(getSanitizedInputValue("1000")).toEqual(1000);
|
||||
expect(getSanitizedInputValue("999999")).toEqual(999999);
|
||||
});
|
||||
|
||||
it("handles negative numbers", () => {
|
||||
expect(getSanitizedInputValue("-10", max)).toEqual(-10);
|
||||
expect(getSanitizedInputValue("-999", max)).toEqual(-999);
|
||||
});
|
||||
});
|
||||
|
||||
it("getMongoIndexType", () => {
|
||||
expect(getMongoIndexType(["Single"])).toEqual(MongoIndexTypes.Single);
|
||||
expect(getMongoIndexType(["Wildcard.$**"])).toEqual(MongoIndexTypes.Wildcard);
|
||||
expect(getMongoIndexType(["Key1", "Key2"])).toEqual(undefined);
|
||||
describe("getMongoIndexType", () => {
|
||||
it("correctly identifies single field indexes", () => {
|
||||
expect(getMongoIndexType(["Single"])).toEqual(MongoIndexTypes.Single);
|
||||
expect(getMongoIndexType(["field1"])).toEqual(MongoIndexTypes.Single);
|
||||
expect(getMongoIndexType(["name"])).toEqual(MongoIndexTypes.Single);
|
||||
});
|
||||
|
||||
it("correctly identifies wildcard indexes", () => {
|
||||
expect(getMongoIndexType(["Wildcard.$**"])).toEqual(MongoIndexTypes.Wildcard);
|
||||
expect(getMongoIndexType(["field.$**"])).toEqual(MongoIndexTypes.Wildcard);
|
||||
expect(getMongoIndexType(["nested.path.$**"])).toEqual(MongoIndexTypes.Wildcard);
|
||||
});
|
||||
|
||||
it("returns undefined for invalid or compound indexes", () => {
|
||||
expect(getMongoIndexType(["Key1", "Key2"])).toBeUndefined();
|
||||
expect(getMongoIndexType([])).toBeUndefined();
|
||||
expect(getMongoIndexType(undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("getMongoIndexTypeText", () => {
|
||||
expect(getMongoIndexTypeText(MongoIndexTypes.Single)).toEqual(SingleFieldText);
|
||||
expect(getMongoIndexTypeText(MongoIndexTypes.Wildcard)).toEqual(WildcardText);
|
||||
describe("getMongoIndexTypeText", () => {
|
||||
it("returns correct text for single field indexes", () => {
|
||||
expect(getMongoIndexTypeText(MongoIndexTypes.Single)).toEqual(SingleFieldText);
|
||||
});
|
||||
|
||||
it("returns correct text for wildcard indexes", () => {
|
||||
expect(getMongoIndexTypeText(MongoIndexTypes.Wildcard)).toEqual(WildcardText);
|
||||
});
|
||||
});
|
||||
|
||||
it("getMongoNotification", () => {
|
||||
describe("getMongoNotification", () => {
|
||||
const singleIndexDescription = "sampleKey";
|
||||
const wildcardIndexDescription = "sampleKey.$**";
|
||||
|
||||
let notification = getMongoNotification(singleIndexDescription, undefined);
|
||||
expect(notification.message).toEqual("Please select a type for each index.");
|
||||
expect(notification.type).toEqual(MongoNotificationType.Warning);
|
||||
describe("type validation", () => {
|
||||
it("returns warning when type is missing", () => {
|
||||
const notification = getMongoNotification(singleIndexDescription, undefined);
|
||||
expect(notification.message).toEqual("Please select a type for each index.");
|
||||
expect(notification.type).toEqual(MongoNotificationType.Warning);
|
||||
});
|
||||
|
||||
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Single);
|
||||
expect(notification).toEqual(undefined);
|
||||
it("returns undefined for valid type and description combinations", () => {
|
||||
expect(getMongoNotification(singleIndexDescription, MongoIndexTypes.Single)).toBeUndefined();
|
||||
expect(getMongoNotification(wildcardIndexDescription, MongoIndexTypes.Wildcard)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
notification = getMongoNotification(wildcardIndexDescription, MongoIndexTypes.Wildcard);
|
||||
expect(notification).toEqual(undefined);
|
||||
describe("field name validation", () => {
|
||||
it("returns error when field name is empty", () => {
|
||||
const notification = getMongoNotification("", MongoIndexTypes.Single);
|
||||
expect(notification.message).toEqual("Please enter a field name.");
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
|
||||
notification = getMongoNotification("", MongoIndexTypes.Single);
|
||||
expect(notification.message).toEqual("Please enter a field name.");
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
const whitespaceNotification = getMongoNotification(" ", MongoIndexTypes.Single);
|
||||
expect(whitespaceNotification.message).toEqual("Please enter a field name.");
|
||||
expect(whitespaceNotification.type).toEqual(MongoNotificationType.Error);
|
||||
});
|
||||
|
||||
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Wildcard);
|
||||
expect(notification.message).toEqual(
|
||||
"Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder,
|
||||
);
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
it("returns error when wildcard index is missing $** pattern", () => {
|
||||
const notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Wildcard);
|
||||
expect(notification.message).toEqual(
|
||||
"Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder,
|
||||
);
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
});
|
||||
});
|
||||
|
||||
it("handles undefined field name", () => {
|
||||
const notification = getMongoNotification(undefined, MongoIndexTypes.Single);
|
||||
expect(notification.message).toEqual("Please enter a field name.");
|
||||
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||
});
|
||||
});
|
||||
it("isIndexingTransforming", () => {
|
||||
expect(isIndexTransforming(undefined)).toBeFalsy();
|
||||
expect(isIndexTransforming(0)).toBeTruthy();
|
||||
expect(isIndexTransforming(90)).toBeTruthy();
|
||||
expect(isIndexTransforming(100)).toBeFalsy();
|
||||
});
|
||||
|
||||
describe("getTabTitle", () => {
|
||||
it("returns correct titles for each tab type", () => {
|
||||
expect(getTabTitle(SettingsV2TabTypes.ScaleTab)).toBe("Scale");
|
||||
expect(getTabTitle(SettingsV2TabTypes.ConflictResolutionTab)).toBe("Conflict Resolution");
|
||||
expect(getTabTitle(SettingsV2TabTypes.SubSettingsTab)).toBe("Settings");
|
||||
expect(getTabTitle(SettingsV2TabTypes.IndexingPolicyTab)).toBe("Indexing Policy");
|
||||
expect(getTabTitle(SettingsV2TabTypes.ComputedPropertiesTab)).toBe("Computed Properties");
|
||||
expect(getTabTitle(SettingsV2TabTypes.ContainerVectorPolicyTab)).toBe("Container Policies");
|
||||
expect(getTabTitle(SettingsV2TabTypes.ThroughputBucketsTab)).toBe("Throughput Buckets");
|
||||
expect(getTabTitle(SettingsV2TabTypes.DataMaskingTab)).toBe("Masking Policy (preview)");
|
||||
});
|
||||
|
||||
it("handles partition key tab title based on fabric native", () => {
|
||||
// Assuming initially not fabric native
|
||||
expect(getTabTitle(SettingsV2TabTypes.PartitionKeyTab)).toBe("Partition Keys (preview)");
|
||||
});
|
||||
|
||||
it("throws error for unknown tab type", () => {
|
||||
expect(() => getTabTitle(999 as SettingsV2TabTypes)).toThrow("Unknown tab 999");
|
||||
});
|
||||
});
|
||||
|
||||
describe("partition key utils", () => {
|
||||
it("getPartitionKeyName returns correct name based on API type", () => {
|
||||
expect(getPartitionKeyName("Mongo")).toBe("Shard key");
|
||||
expect(getPartitionKeyName("SQL")).toBe("Partition key");
|
||||
expect(getPartitionKeyName("Mongo", true)).toBe("shard key");
|
||||
expect(getPartitionKeyName("SQL", true)).toBe("partition key");
|
||||
});
|
||||
|
||||
it("getPartitionKeyTooltipText returns correct tooltip based on API type", () => {
|
||||
const mongoTooltip = getPartitionKeyTooltipText("Mongo");
|
||||
expect(mongoTooltip).toContain("shard key");
|
||||
expect(mongoTooltip).toContain("replica sets");
|
||||
|
||||
const sqlTooltip = getPartitionKeyTooltipText("SQL");
|
||||
expect(sqlTooltip).toContain("partition key");
|
||||
expect(sqlTooltip).toContain("id is often a good choice");
|
||||
});
|
||||
|
||||
it("getPartitionKeySubtext returns correct subtext", () => {
|
||||
expect(getPartitionKeySubtext(true, "SQL")).toBe(
|
||||
"For small workloads, the item ID is a suitable choice for the partition key.",
|
||||
);
|
||||
expect(getPartitionKeySubtext(true, "Mongo")).toBe(
|
||||
"For small workloads, the item ID is a suitable choice for the partition key.",
|
||||
);
|
||||
expect(getPartitionKeySubtext(false, "SQL")).toBe("");
|
||||
expect(getPartitionKeySubtext(true, "Other")).toBe("");
|
||||
});
|
||||
|
||||
it("getPartitionKeyPlaceHolder returns correct placeholder based on API type", () => {
|
||||
expect(getPartitionKeyPlaceHolder("Mongo")).toBe("e.g., categoryId");
|
||||
expect(getPartitionKeyPlaceHolder("Gremlin")).toBe("e.g., /address");
|
||||
expect(getPartitionKeyPlaceHolder("SQL")).toBe("Required - first partition key e.g., /TenantId");
|
||||
expect(getPartitionKeyPlaceHolder("SQL", 0)).toBe("second partition key e.g., /UserId");
|
||||
expect(getPartitionKeyPlaceHolder("SQL", 1)).toBe("third partition key e.g., /SessionId");
|
||||
expect(getPartitionKeyPlaceHolder("Other")).toBe("e.g., /address/zipCode");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("isIndexingTransforming", () => {
|
||||
expect(isIndexTransforming(undefined)).toBeFalsy();
|
||||
expect(isIndexTransforming(0)).toBeTruthy();
|
||||
expect(isIndexTransforming(90)).toBeTruthy();
|
||||
expect(isIndexTransforming(100)).toBeFalsy();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user