Added comprehensive unit test coverage for Container Copy jobs (#2275)

* copy job uts

* unit test coverage

* lint fix

* normalize account dropdown id
This commit is contained in:
BChoudhury-ms
2025-12-09 09:35:58 +05:30
committed by GitHub
parent ca858c08fb
commit a714ef02c0
80 changed files with 23899 additions and 35 deletions

View File

@@ -0,0 +1,490 @@
import { DatabaseAccount } from "Contracts/DataModels";
import * as CopyJobUtils from "./CopyJobUtils";
import { CopyJobContextState, CopyJobErrorType, CopyJobType } from "./Types/CopyJobTypes";
describe("CopyJobUtils", () => {
describe("buildResourceLink", () => {
const mockResource: DatabaseAccount = {
id: "/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1",
name: "account1",
location: "eastus",
type: "Microsoft.DocumentDB/databaseAccounts",
kind: "GlobalDocumentDB",
properties: {},
};
let originalLocation: Location;
beforeEach(() => {
originalLocation = window.location;
});
afterEach(() => {
(window as any).location = originalLocation;
});
it("should build resource link with Azure portal endpoint", () => {
delete (window as any).location;
(window as any).location = {
...originalLocation,
origin: "https://portal.azure.com",
ancestorOrigins: ["https://portal.azure.com"] as any,
} as Location;
const link = CopyJobUtils.buildResourceLink(mockResource);
expect(link).toBe(
"https://portal.azure.com/#resource/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1",
);
});
it("should replace cosmos.azure with portal.azure", () => {
delete (window as any).location;
(window as any).location = {
...originalLocation,
origin: "https://cosmos.azure.com",
ancestorOrigins: ["https://cosmos.azure.com"] as any,
} as Location;
const link = CopyJobUtils.buildResourceLink(mockResource);
expect(link).toContain("https://portal.azure.com");
});
it("should use Azure portal endpoint for localhost", () => {
delete (window as any).location;
(window as any).location = {
...originalLocation,
origin: "http://localhost:1234",
ancestorOrigins: ["http://localhost:1234"] as any,
} as Location;
const link = CopyJobUtils.buildResourceLink(mockResource);
expect(link).toContain("https://ms.portal.azure.com");
});
it("should remove trailing slash from origin", () => {
delete (window as any).location;
(window as any).location = {
...originalLocation,
origin: "https://portal.azure.com/",
ancestorOrigins: ["https://portal.azure.com/"] as any,
} as Location;
const link = CopyJobUtils.buildResourceLink(mockResource);
expect(link).toBe(
"https://portal.azure.com/#resource/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1",
);
});
});
describe("buildDataTransferJobPath", () => {
it("should build basic path without jobName or action", () => {
const path = CopyJobUtils.buildDataTransferJobPath({
subscriptionId: "sub123",
resourceGroup: "rg1",
accountName: "account1",
});
expect(path).toBe(
"/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1/dataTransferJobs",
);
});
it("should build path with jobName", () => {
const path = CopyJobUtils.buildDataTransferJobPath({
subscriptionId: "sub123",
resourceGroup: "rg1",
accountName: "account1",
jobName: "job1",
});
expect(path).toBe(
"/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1/dataTransferJobs/job1",
);
});
it("should build path with jobName and action", () => {
const path = CopyJobUtils.buildDataTransferJobPath({
subscriptionId: "sub123",
resourceGroup: "rg1",
accountName: "account1",
jobName: "job1",
action: "cancel",
});
expect(path).toBe(
"/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1/dataTransferJobs/job1/cancel",
);
});
});
describe("convertTime", () => {
it("should convert time string with hours, minutes, and seconds", () => {
const result = CopyJobUtils.convertTime("02:30:45");
expect(result).toBe("02 hours, 30 minutes, 45 seconds");
});
it("should convert time string with only seconds", () => {
const result = CopyJobUtils.convertTime("00:00:30");
expect(result).toBe("30 seconds");
});
it("should convert time string with only minutes and seconds", () => {
const result = CopyJobUtils.convertTime("00:05:15");
expect(result).toBe("05 minutes, 15 seconds");
});
it("should round seconds", () => {
const result = CopyJobUtils.convertTime("00:00:45.678");
expect(result).toBe("46 seconds");
});
it("should return '0 seconds' for zero time", () => {
const result = CopyJobUtils.convertTime("00:00:00");
expect(result).toBe("0 seconds");
});
it("should return null for invalid time format", () => {
const result = CopyJobUtils.convertTime("invalid");
expect(result).toBeNull();
});
it("should return null for incomplete time string", () => {
const result = CopyJobUtils.convertTime("10:30");
expect(result).toBeNull();
});
it("should pad single digit values", () => {
const result = CopyJobUtils.convertTime("1:5:9");
expect(result).toBe("01 hours, 05 minutes, 09 seconds");
});
});
describe("formatUTCDateTime", () => {
it("should format valid UTC date string", () => {
const result = CopyJobUtils.formatUTCDateTime("2025-11-26T10:30:00Z");
expect(result).not.toBeNull();
expect(result?.formattedDateTime).toContain("11/26/25, 10:30:00 AM");
expect(result?.timestamp).toBeGreaterThan(0);
});
it("should return null for invalid date string", () => {
const result = CopyJobUtils.formatUTCDateTime("invalid-date");
expect(result).toBeNull();
});
it("should return timestamp for valid date", () => {
const result = CopyJobUtils.formatUTCDateTime("2025-01-01T00:00:00Z");
expect(result).not.toBeNull();
expect(typeof result?.timestamp).toBe("number");
expect(result?.timestamp).toBe(new Date("2025-01-01T00:00:00Z").getTime());
});
});
describe("convertToCamelCase", () => {
it("should convert string to camel case", () => {
const result = CopyJobUtils.convertToCamelCase("hello world");
expect(result).toBe("HelloWorld");
});
it("should handle single word", () => {
const result = CopyJobUtils.convertToCamelCase("hello");
expect(result).toBe("Hello");
});
it("should handle multiple spaces", () => {
const result = CopyJobUtils.convertToCamelCase("hello world test");
expect(result).toBe("HelloWorldTest");
});
it("should handle mixed case input", () => {
const result = CopyJobUtils.convertToCamelCase("HELLO WORLD");
expect(result).toBe("HelloWorld");
});
it("should handle empty string", () => {
const result = CopyJobUtils.convertToCamelCase("");
expect(result).toBe("");
});
});
describe("extractErrorMessage", () => {
it("should extract first part of error message before line breaks", () => {
const error: CopyJobErrorType = {
message: "Error occurred\r\n\r\nAdditional details\r\n\r\nMore info",
code: "500",
};
const result = CopyJobUtils.extractErrorMessage(error);
expect(result.message).toBe("Error occurred");
expect(result.code).toBe("500");
});
it("should return same message if no line breaks", () => {
const error: CopyJobErrorType = {
message: "Simple error message",
code: "404",
};
const result = CopyJobUtils.extractErrorMessage(error);
expect(result.message).toBe("Simple error message");
expect(result.code).toBe("404");
});
});
describe("getAccountDetailsFromResourceId", () => {
it("should extract account details from valid resource ID", () => {
const resourceId =
"/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1";
const details = CopyJobUtils.getAccountDetailsFromResourceId(resourceId);
expect(details).toEqual({
subscriptionId: "sub123",
resourceGroup: "rg1",
accountName: "account1",
});
});
it("should be case insensitive", () => {
const resourceId =
"/subscriptions/sub123/resourceGroups/rg1/providers/microsoft.documentdb/databaseAccounts/account1";
const details = CopyJobUtils.getAccountDetailsFromResourceId(resourceId);
expect(details).toEqual({
subscriptionId: "sub123",
resourceGroup: "rg1",
accountName: "account1",
});
});
it("should return null for undefined resource ID", () => {
const details = CopyJobUtils.getAccountDetailsFromResourceId(undefined);
expect(details).toBeNull();
});
it("should return null for invalid resource ID", () => {
const details = CopyJobUtils.getAccountDetailsFromResourceId("invalid-resource-id");
expect(details).toEqual({ accountName: undefined, resourceGroup: undefined, subscriptionId: undefined });
});
});
describe("getContainerIdentifiers", () => {
it("should extract container identifiers", () => {
const container = {
account: {
id: "/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1",
name: "account1",
location: "eastus",
type: "Microsoft.DocumentDB/databaseAccounts",
kind: "GlobalDocumentDB",
properties: {},
},
databaseId: "db1",
containerId: "container1",
} as CopyJobContextState["source"];
const identifiers = CopyJobUtils.getContainerIdentifiers(container);
expect(identifiers).toEqual({
accountId: container.account.id,
databaseId: "db1",
containerId: "container1",
});
});
it("should return empty strings for undefined values", () => {
const container = {
account: undefined,
databaseId: undefined,
containerId: undefined,
} as CopyJobContextState["source"];
const identifiers = CopyJobUtils.getContainerIdentifiers(container);
expect(identifiers).toEqual({
accountId: "",
databaseId: "",
containerId: "",
});
});
});
describe("isIntraAccountCopy", () => {
const sourceAccountId =
"/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1";
const targetAccountId =
"/subscriptions/sub123/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1";
const differentAccountId =
"/subscriptions/sub456/resourceGroups/rg2/providers/Microsoft.DocumentDB/databaseAccounts/account2";
it("should return true for same account", () => {
const result = CopyJobUtils.isIntraAccountCopy(sourceAccountId, targetAccountId);
expect(result).toBe(true);
});
it("should return false for different accounts", () => {
const result = CopyJobUtils.isIntraAccountCopy(sourceAccountId, differentAccountId);
expect(result).toBe(false);
});
it("should return false for different subscriptions", () => {
const differentSubId =
"/subscriptions/sub999/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1";
const result = CopyJobUtils.isIntraAccountCopy(sourceAccountId, differentSubId);
expect(result).toBe(false);
});
it("should return false for different resource groups", () => {
const differentRgId =
"/subscriptions/sub123/resourceGroups/rg999/providers/Microsoft.DocumentDB/databaseAccounts/account1";
const result = CopyJobUtils.isIntraAccountCopy(sourceAccountId, differentRgId);
expect(result).toBe(false);
});
it("should return false for undefined source", () => {
const result = CopyJobUtils.isIntraAccountCopy(undefined, targetAccountId);
expect(result).toBe(false);
});
it("should return false for undefined target", () => {
const result = CopyJobUtils.isIntraAccountCopy(sourceAccountId, undefined);
expect(result).toBe(false);
});
});
describe("isEqual", () => {
const createMockJob = (name: string, status: string): CopyJobType => ({
ID: name,
Mode: "Online",
Name: name,
Status: status as any,
CompletionPercentage: 50,
Duration: "00:05:00",
LastUpdatedTime: "2025-11-26T10:00:00Z",
timestamp: Date.now(),
Source: {} as any,
Destination: {} as any,
});
it("should return true for equal job arrays", () => {
const jobs1 = [createMockJob("job1", "Running"), createMockJob("job2", "Completed")];
const jobs2 = [createMockJob("job1", "Running"), createMockJob("job2", "Completed")];
const result = CopyJobUtils.isEqual(jobs1, jobs2);
expect(result).toBe(true);
});
it("should return false for different lengths", () => {
const jobs1 = [createMockJob("job1", "Running")];
const jobs2 = [createMockJob("job1", "Running"), createMockJob("job2", "Completed")];
const result = CopyJobUtils.isEqual(jobs1, jobs2);
expect(result).toBe(false);
});
it("should return false for different status", () => {
const jobs1 = [createMockJob("job1", "Running")];
const jobs2 = [createMockJob("job1", "Completed")];
const result = CopyJobUtils.isEqual(jobs1, jobs2);
expect(result).toBe(false);
});
it("should return false for missing job in second array", () => {
const jobs1 = [createMockJob("job1", "Running")];
const jobs2 = [createMockJob("job2", "Running")];
const result = CopyJobUtils.isEqual(jobs1, jobs2);
expect(result).toBe(false);
});
it("should return true for empty arrays", () => {
const result = CopyJobUtils.isEqual([], []);
expect(result).toBe(true);
});
});
describe("getDefaultJobName", () => {
beforeEach(() => {
jest.spyOn(Date.prototype, "getTime").mockReturnValue(1234567890);
});
afterEach(() => {
jest.restoreAllMocks();
});
it("should generate default job name for single container", () => {
const containers = [
{
sourceDatabaseName: "sourceDb",
sourceContainerName: "sourceCont",
targetDatabaseName: "targetDb",
targetContainerName: "targetCont",
},
];
const jobName = CopyJobUtils.getDefaultJobName(containers);
expect(jobName).toBe("sourc.sourc_targe.targe_1234567890");
});
it("should truncate long names", () => {
const containers = [
{
sourceDatabaseName: "veryLongSourceDatabaseName",
sourceContainerName: "veryLongSourceContainerName",
targetDatabaseName: "veryLongTargetDatabaseName",
targetContainerName: "veryLongTargetContainerName",
},
];
const jobName = CopyJobUtils.getDefaultJobName(containers);
expect(jobName).toBe("veryL.veryL_veryL.veryL_1234567890");
});
it("should return empty string for multiple containers", () => {
const containers = [
{
sourceDatabaseName: "db1",
sourceContainerName: "cont1",
targetDatabaseName: "db2",
targetContainerName: "cont2",
},
{
sourceDatabaseName: "db3",
sourceContainerName: "cont3",
targetDatabaseName: "db4",
targetContainerName: "cont4",
},
];
const jobName = CopyJobUtils.getDefaultJobName(containers);
expect(jobName).toBe("");
});
it("should return empty string for empty array", () => {
const jobName = CopyJobUtils.getDefaultJobName([]);
expect(jobName).toBe("");
});
it("should handle short names without truncation", () => {
const containers = [
{
sourceDatabaseName: "src",
sourceContainerName: "cont",
targetDatabaseName: "tgt",
targetContainerName: "dest",
},
];
const jobName = CopyJobUtils.getDefaultJobName(containers);
expect(jobName).toBe("src.cont_tgt.dest_1234567890");
});
});
describe("constants", () => {
it("should have correct COSMOS_SQL_COMPONENT value", () => {
expect(CopyJobUtils.COSMOS_SQL_COMPONENT).toBe("CosmosDBSql");
});
it("should have correct COPY_JOB_API_VERSION value", () => {
expect(CopyJobUtils.COPY_JOB_API_VERSION).toBe("2025-05-01-preview");
});
});
});