mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-25 03:41:19 +00:00
730 lines
26 KiB
TypeScript
730 lines
26 KiB
TypeScript
import "@testing-library/jest-dom";
|
|
import Explorer from "Explorer/Explorer";
|
|
import * as Logger from "../../../Common/Logger";
|
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
|
import * as dataTransferService from "../../../Utils/arm/generatedClients/dataTransferService/dataTransferJobs";
|
|
import * as CopyJobUtils from "../CopyJobUtils";
|
|
import CreateCopyJobScreensProvider from "../CreateCopyJob/Screens/CreateCopyJobScreensProvider";
|
|
import { CopyJobActions, CopyJobStatusType } from "../Enums/CopyJobEnums";
|
|
import CopyJobDetails from "../MonitorCopyJobs/Components/CopyJobDetails";
|
|
import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefState";
|
|
import { CopyJobContextState, CopyJobType } from "../Types/CopyJobTypes";
|
|
import {
|
|
getCopyJobs,
|
|
openCopyJobDetailsPanel,
|
|
openCreateCopyJobPanel,
|
|
submitCreateCopyJob,
|
|
updateCopyJobStatus,
|
|
} from "./CopyJobActions";
|
|
|
|
jest.mock("UserContext", () => ({
|
|
userContext: {
|
|
databaseAccount: {
|
|
id: "/subscriptions/sub-123/resourceGroups/rg-test/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
|
|
},
|
|
},
|
|
}));
|
|
|
|
jest.mock("../../../hooks/useSidePanel");
|
|
jest.mock("../../../Common/Logger");
|
|
jest.mock("../../../Utils/arm/generatedClients/dataTransferService/dataTransferJobs");
|
|
jest.mock("../MonitorCopyJobs/MonitorCopyJobRefState");
|
|
jest.mock("../CopyJobUtils");
|
|
|
|
describe("CopyJobActions", () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe("openCreateCopyJobPanel", () => {
|
|
it("should open side panel with correct parameters", () => {
|
|
const mockExplorer = {} as Explorer;
|
|
const mockSetPanelHasConsole = jest.fn();
|
|
const mockOpenSidePanel = jest.fn();
|
|
|
|
(useSidePanel.getState as jest.Mock).mockReturnValue({
|
|
setPanelHasConsole: mockSetPanelHasConsole,
|
|
openSidePanel: mockOpenSidePanel,
|
|
});
|
|
|
|
openCreateCopyJobPanel(mockExplorer);
|
|
|
|
expect(mockSetPanelHasConsole).toHaveBeenCalledWith(false);
|
|
expect(mockOpenSidePanel).toHaveBeenCalledWith(expect.any(String), expect.any(Object), "650px");
|
|
});
|
|
|
|
it("should render CreateCopyJobScreensProvider in side panel", () => {
|
|
const mockExplorer = {} as Explorer;
|
|
const mockOpenSidePanel = jest.fn();
|
|
|
|
(useSidePanel.getState as jest.Mock).mockReturnValue({
|
|
setPanelHasConsole: jest.fn(),
|
|
openSidePanel: mockOpenSidePanel,
|
|
});
|
|
|
|
openCreateCopyJobPanel(mockExplorer);
|
|
|
|
const sidePanelContent = mockOpenSidePanel.mock.calls[0][1];
|
|
expect(sidePanelContent.type).toBe(CreateCopyJobScreensProvider);
|
|
expect(sidePanelContent.props.explorer).toBe(mockExplorer);
|
|
});
|
|
});
|
|
|
|
describe("openCopyJobDetailsPanel", () => {
|
|
it("should open side panel with job details", () => {
|
|
const mockJob: CopyJobType = {
|
|
ID: "1",
|
|
Mode: "online",
|
|
Name: "test-job",
|
|
Status: CopyJobStatusType.InProgress,
|
|
CompletionPercentage: 50,
|
|
Duration: "01 hours, 30 minutes, 45 seconds",
|
|
LastUpdatedTime: "1/1/2025, 10:00:00 AM",
|
|
timestamp: 1704106800000,
|
|
Source: {
|
|
component: "CosmosDBSql",
|
|
databaseName: "source-db",
|
|
containerName: "source-container",
|
|
},
|
|
Destination: {
|
|
component: "CosmosDBSql",
|
|
databaseName: "target-db",
|
|
containerName: "target-container",
|
|
},
|
|
};
|
|
|
|
const mockSetPanelHasConsole = jest.fn();
|
|
const mockOpenSidePanel = jest.fn();
|
|
|
|
(useSidePanel.getState as jest.Mock).mockReturnValue({
|
|
setPanelHasConsole: mockSetPanelHasConsole,
|
|
openSidePanel: mockOpenSidePanel,
|
|
});
|
|
|
|
openCopyJobDetailsPanel(mockJob);
|
|
|
|
expect(mockSetPanelHasConsole).toHaveBeenCalledWith(false);
|
|
expect(mockOpenSidePanel).toHaveBeenCalledWith(expect.stringContaining("test-job"), expect.any(Object), "650px");
|
|
});
|
|
|
|
it("should render CopyJobDetails component with correct job", () => {
|
|
const mockJob: CopyJobType = {
|
|
ID: "1",
|
|
Mode: "offline",
|
|
Name: "test-job-2",
|
|
Status: CopyJobStatusType.Completed,
|
|
CompletionPercentage: 100,
|
|
Duration: "02 hours, 15 minutes, 30 seconds",
|
|
LastUpdatedTime: "1/2/2025, 11:00:00 AM",
|
|
timestamp: 1704193200000,
|
|
Source: {
|
|
component: "CosmosDBSql",
|
|
databaseName: "source-db",
|
|
containerName: "source-container",
|
|
},
|
|
Destination: {
|
|
component: "CosmosDBSql",
|
|
databaseName: "target-db",
|
|
containerName: "target-container",
|
|
},
|
|
};
|
|
|
|
const mockOpenSidePanel = jest.fn();
|
|
|
|
(useSidePanel.getState as jest.Mock).mockReturnValue({
|
|
setPanelHasConsole: jest.fn(),
|
|
openSidePanel: mockOpenSidePanel,
|
|
});
|
|
|
|
openCopyJobDetailsPanel(mockJob);
|
|
|
|
const sidePanelContent = mockOpenSidePanel.mock.calls[0][1];
|
|
expect(sidePanelContent.type).toBe(CopyJobDetails);
|
|
expect(sidePanelContent.props.job).toBe(mockJob);
|
|
});
|
|
});
|
|
|
|
describe("getCopyJobs", () => {
|
|
beforeEach(() => {
|
|
(CopyJobUtils.getAccountDetailsFromResourceId as jest.Mock).mockReturnValue({
|
|
subscriptionId: "sub-123",
|
|
resourceGroup: "rg-test",
|
|
accountName: "test-account",
|
|
});
|
|
});
|
|
|
|
it("should fetch and format copy jobs successfully", async () => {
|
|
const mockResponse = {
|
|
value: [
|
|
{
|
|
properties: {
|
|
jobName: "job-1",
|
|
status: "InProgress",
|
|
lastUpdatedUtcTime: "2025-01-01T10:00:00Z",
|
|
processedCount: 50,
|
|
totalCount: 100,
|
|
mode: "online",
|
|
duration: "01:30:45",
|
|
source: {
|
|
component: "CosmosDBSql",
|
|
databaseName: "source-db",
|
|
containerName: "source-container",
|
|
},
|
|
destination: {
|
|
component: "CosmosDBSql",
|
|
databaseName: "target-db",
|
|
containerName: "target-container",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
|
timestamp: 1704106800000,
|
|
});
|
|
(CopyJobUtils.convertTime as jest.Mock).mockReturnValue("01 hours, 30 minutes, 45 seconds");
|
|
(CopyJobUtils.convertToCamelCase as jest.Mock).mockReturnValue("InProgress");
|
|
|
|
const result = await getCopyJobs();
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
ID: "1",
|
|
Name: "job-1",
|
|
Status: "InProgress",
|
|
CompletionPercentage: 50,
|
|
Mode: "online",
|
|
});
|
|
});
|
|
|
|
it("should filter jobs by CosmosDBSql component", async () => {
|
|
const mockResponse = {
|
|
value: [
|
|
{
|
|
properties: {
|
|
jobName: "sql-job",
|
|
status: "Completed",
|
|
lastUpdatedUtcTime: "2025-01-01T10:00:00Z",
|
|
processedCount: 100,
|
|
totalCount: 100,
|
|
mode: "offline",
|
|
duration: "02:00:00",
|
|
source: { component: "CosmosDBSql", databaseName: "db1", containerName: "c1" },
|
|
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
|
},
|
|
},
|
|
{
|
|
properties: {
|
|
jobName: "other-job",
|
|
status: "Completed",
|
|
lastUpdatedUtcTime: "2025-01-01T11:00:00Z",
|
|
processedCount: 100,
|
|
totalCount: 100,
|
|
mode: "offline",
|
|
duration: "01:00:00",
|
|
source: { component: "OtherComponent", databaseName: "db1", containerName: "c1" },
|
|
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
|
timestamp: 1704106800000,
|
|
});
|
|
(CopyJobUtils.convertTime as jest.Mock).mockReturnValue("02 hours");
|
|
(CopyJobUtils.convertToCamelCase as jest.Mock).mockReturnValue("Completed");
|
|
|
|
const result = await getCopyJobs();
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].Name).toBe("sql-job");
|
|
});
|
|
|
|
it("should sort jobs by last updated time (newest first)", async () => {
|
|
const mockResponse = {
|
|
value: [
|
|
{
|
|
properties: {
|
|
jobName: "older-job",
|
|
status: "Completed",
|
|
lastUpdatedUtcTime: "2025-01-01T10:00:00Z",
|
|
processedCount: 100,
|
|
totalCount: 100,
|
|
mode: "offline",
|
|
duration: "01:00:00",
|
|
source: { component: "CosmosDBSql", databaseName: "db1", containerName: "c1" },
|
|
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
|
},
|
|
},
|
|
{
|
|
properties: {
|
|
jobName: "newer-job",
|
|
status: "InProgress",
|
|
lastUpdatedUtcTime: "2025-01-02T10:00:00Z",
|
|
processedCount: 50,
|
|
totalCount: 100,
|
|
mode: "online",
|
|
duration: "00:30:00",
|
|
source: { component: "CosmosDBSql", databaseName: "db3", containerName: "c3" },
|
|
destination: { component: "CosmosDBSql", databaseName: "db4", containerName: "c4" },
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
|
timestamp: 1704106800000,
|
|
});
|
|
(CopyJobUtils.convertTime as jest.Mock).mockReturnValue("01 hours");
|
|
(CopyJobUtils.convertToCamelCase as jest.Mock).mockReturnValue("Completed");
|
|
|
|
const result = await getCopyJobs();
|
|
|
|
expect(result[0].Name).toBe("newer-job");
|
|
expect(result[1].Name).toBe("older-job");
|
|
});
|
|
|
|
it("should calculate completion percentage correctly", async () => {
|
|
const mockResponse = {
|
|
value: [
|
|
{
|
|
properties: {
|
|
jobName: "job-1",
|
|
status: "InProgress",
|
|
lastUpdatedUtcTime: "2025-01-01T10:00:00Z",
|
|
processedCount: 75,
|
|
totalCount: 100,
|
|
mode: "online",
|
|
duration: "01:00:00",
|
|
source: { component: "CosmosDBSql", databaseName: "db1", containerName: "c1" },
|
|
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
|
timestamp: 1704106800000,
|
|
});
|
|
(CopyJobUtils.convertTime as jest.Mock).mockReturnValue("01 hours");
|
|
(CopyJobUtils.convertToCamelCase as jest.Mock).mockReturnValue("InProgress");
|
|
|
|
const result = await getCopyJobs();
|
|
|
|
expect(result[0].CompletionPercentage).toBe(75);
|
|
});
|
|
|
|
it("should handle zero total count gracefully", async () => {
|
|
const mockResponse = {
|
|
value: [
|
|
{
|
|
properties: {
|
|
jobName: "job-1",
|
|
status: "Pending",
|
|
lastUpdatedUtcTime: "2025-01-01T10:00:00Z",
|
|
processedCount: 0,
|
|
totalCount: 0,
|
|
mode: "online",
|
|
duration: "00:00:00",
|
|
source: { component: "CosmosDBSql", databaseName: "db1", containerName: "c1" },
|
|
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
|
timestamp: 1704106800000,
|
|
});
|
|
(CopyJobUtils.convertTime as jest.Mock).mockReturnValue("0 seconds");
|
|
(CopyJobUtils.convertToCamelCase as jest.Mock).mockReturnValue("Pending");
|
|
|
|
const result = await getCopyJobs();
|
|
|
|
expect(result[0].CompletionPercentage).toBe(0);
|
|
});
|
|
|
|
it("should extract error messages if present", async () => {
|
|
const mockError = {
|
|
message: "Error message line 1\r\n\r\nError message line 2",
|
|
code: "ErrorCode123",
|
|
};
|
|
const mockResponse = {
|
|
value: [
|
|
{
|
|
properties: {
|
|
jobName: "failed-job",
|
|
status: "Failed",
|
|
lastUpdatedUtcTime: "2025-01-01T10:00:00Z",
|
|
processedCount: 50,
|
|
totalCount: 100,
|
|
mode: "offline",
|
|
duration: "00:30:00",
|
|
source: { component: "CosmosDBSql", databaseName: "db1", containerName: "c1" },
|
|
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
|
error: mockError,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
|
timestamp: 1704106800000,
|
|
});
|
|
(CopyJobUtils.convertTime as jest.Mock).mockReturnValue("30 minutes");
|
|
(CopyJobUtils.convertToCamelCase as jest.Mock).mockReturnValue("Failed");
|
|
(CopyJobUtils.extractErrorMessage as jest.Mock).mockReturnValue({
|
|
message: "Error message line 1",
|
|
code: "ErrorCode123",
|
|
});
|
|
|
|
const result = await getCopyJobs();
|
|
|
|
expect(result[0].Error).toEqual({
|
|
message: "Error message line 1",
|
|
code: "ErrorCode123",
|
|
});
|
|
expect(CopyJobUtils.extractErrorMessage).toHaveBeenCalledWith(mockError);
|
|
});
|
|
|
|
it("should abort previous request when new request is made", async () => {
|
|
const mockAbortController = {
|
|
abort: jest.fn(),
|
|
signal: {} as AbortSignal,
|
|
};
|
|
(global as any).AbortController = jest.fn(() => mockAbortController);
|
|
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue({ value: [] });
|
|
|
|
getCopyJobs();
|
|
expect(mockAbortController.abort).not.toHaveBeenCalled();
|
|
|
|
getCopyJobs();
|
|
expect(mockAbortController.abort).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should throw error for invalid response format", async () => {
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue({
|
|
value: "not-an-array",
|
|
});
|
|
|
|
await expect(getCopyJobs()).rejects.toThrow("Invalid migration job status response: Expected an array of jobs.");
|
|
});
|
|
|
|
it("should handle abort signal error", async () => {
|
|
const abortError = {
|
|
message: "Aborted",
|
|
content: JSON.stringify({ message: "signal is aborted without reason" }),
|
|
};
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockRejectedValue(abortError);
|
|
|
|
await expect(getCopyJobs()).rejects.toMatchObject({
|
|
message: expect.stringContaining("Previous copy job request was cancelled."),
|
|
});
|
|
});
|
|
|
|
it("should handle generic errors", async () => {
|
|
const genericError = new Error("Network error");
|
|
(dataTransferService.listByDatabaseAccount as jest.Mock).mockRejectedValue(genericError);
|
|
|
|
await expect(getCopyJobs()).rejects.toThrow("Network error");
|
|
});
|
|
});
|
|
|
|
describe("submitCreateCopyJob", () => {
|
|
let mockRefreshJobList: jest.Mock;
|
|
let mockOnSuccess: jest.Mock;
|
|
|
|
beforeEach(() => {
|
|
mockRefreshJobList = jest.fn();
|
|
mockOnSuccess = jest.fn();
|
|
|
|
(CopyJobUtils.getAccountDetailsFromResourceId as jest.Mock).mockReturnValue({
|
|
subscriptionId: "sub-123",
|
|
resourceGroup: "rg-test",
|
|
accountName: "test-account",
|
|
});
|
|
|
|
(MonitorCopyJobsRefState.getState as jest.Mock).mockReturnValue({
|
|
ref: { refreshJobList: mockRefreshJobList },
|
|
});
|
|
});
|
|
|
|
it("should create intra-account copy job successfully", async () => {
|
|
const mockState: CopyJobContextState = {
|
|
jobName: "test-job",
|
|
migrationType: "online" as any,
|
|
source: {
|
|
subscription: {} as any,
|
|
account: { id: "account-1", name: "source-account" } as any,
|
|
databaseId: "source-db",
|
|
containerId: "source-container",
|
|
},
|
|
target: {
|
|
subscriptionId: "sub-123",
|
|
account: { id: "account-1", name: "target-account" } as any,
|
|
databaseId: "target-db",
|
|
containerId: "target-container",
|
|
},
|
|
};
|
|
|
|
(CopyJobUtils.isIntraAccountCopy as jest.Mock).mockReturnValue(true);
|
|
(dataTransferService.create as jest.Mock).mockResolvedValue({ id: "job-id" });
|
|
|
|
await submitCreateCopyJob(mockState, mockOnSuccess);
|
|
|
|
expect(dataTransferService.create).toHaveBeenCalledWith(
|
|
"sub-123",
|
|
"rg-test",
|
|
"test-account",
|
|
"test-job",
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
source: expect.objectContaining({
|
|
component: "CosmosDBSql",
|
|
databaseName: "source-db",
|
|
containerName: "source-container",
|
|
}),
|
|
destination: expect.objectContaining({
|
|
component: "CosmosDBSql",
|
|
databaseName: "target-db",
|
|
containerName: "target-container",
|
|
}),
|
|
mode: "online",
|
|
}),
|
|
}),
|
|
);
|
|
|
|
const callArgs = (dataTransferService.create as jest.Mock).mock.calls[0][4];
|
|
expect(callArgs.properties.source.remoteAccountName).toBeUndefined();
|
|
|
|
expect(mockRefreshJobList).toHaveBeenCalled();
|
|
expect(mockOnSuccess).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should create inter-account copy job with source account name", async () => {
|
|
const mockState: CopyJobContextState = {
|
|
jobName: "cross-account-job",
|
|
migrationType: "offline" as any,
|
|
source: {
|
|
subscription: {} as any,
|
|
account: { id: "account-1", name: "source-account" } as any,
|
|
databaseId: "source-db",
|
|
containerId: "source-container",
|
|
},
|
|
target: {
|
|
subscriptionId: "sub-456",
|
|
account: { id: "account-2", name: "target-account" } as any,
|
|
databaseId: "target-db",
|
|
containerId: "target-container",
|
|
},
|
|
};
|
|
|
|
(CopyJobUtils.isIntraAccountCopy as jest.Mock).mockReturnValue(false);
|
|
(dataTransferService.create as jest.Mock).mockResolvedValue({ id: "job-id" });
|
|
|
|
await submitCreateCopyJob(mockState, mockOnSuccess);
|
|
|
|
const callArgs = (dataTransferService.create as jest.Mock).mock.calls[0][4];
|
|
expect(callArgs.properties.source.remoteAccountName).toBe("source-account");
|
|
expect(mockOnSuccess).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle errors and log them", async () => {
|
|
const mockState: CopyJobContextState = {
|
|
jobName: "failing-job",
|
|
migrationType: "online" as any,
|
|
source: {
|
|
subscription: {} as any,
|
|
account: { id: "account-1", name: "source-account" } as any,
|
|
databaseId: "source-db",
|
|
containerId: "source-container",
|
|
},
|
|
target: {
|
|
subscriptionId: "sub-123",
|
|
account: { id: "account-1", name: "target-account" } as any,
|
|
databaseId: "target-db",
|
|
containerId: "target-container",
|
|
},
|
|
};
|
|
|
|
const mockError = new Error("API Error");
|
|
(CopyJobUtils.isIntraAccountCopy as jest.Mock).mockReturnValue(true);
|
|
(dataTransferService.create as jest.Mock).mockRejectedValue(mockError);
|
|
|
|
await expect(submitCreateCopyJob(mockState, mockOnSuccess)).rejects.toThrow("API Error");
|
|
|
|
expect(Logger.logError).toHaveBeenCalledWith("API Error", "CopyJob/CopyJobActions.submitCreateCopyJob");
|
|
expect(mockOnSuccess).not.toHaveBeenCalled();
|
|
expect(mockRefreshJobList).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle errors without message", async () => {
|
|
const mockState: CopyJobContextState = {
|
|
jobName: "test-job",
|
|
migrationType: "online" as any,
|
|
source: {
|
|
subscription: {} as any,
|
|
account: { id: "account-1", name: "source-account" } as any,
|
|
databaseId: "source-db",
|
|
containerId: "source-container",
|
|
},
|
|
target: {
|
|
subscriptionId: "sub-123",
|
|
account: { id: "account-1", name: "target-account" } as any,
|
|
databaseId: "target-db",
|
|
containerId: "target-container",
|
|
},
|
|
};
|
|
|
|
(CopyJobUtils.isIntraAccountCopy as jest.Mock).mockReturnValue(true);
|
|
(dataTransferService.create as jest.Mock).mockRejectedValue({});
|
|
|
|
await expect(submitCreateCopyJob(mockState, mockOnSuccess)).rejects.toEqual({});
|
|
|
|
expect(Logger.logError).toHaveBeenCalledWith(
|
|
"Error submitting create copy job. Please try again later.",
|
|
"CopyJob/CopyJobActions.submitCreateCopyJob",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("updateCopyJobStatus", () => {
|
|
const mockJob: CopyJobType = {
|
|
ID: "1",
|
|
Mode: "online",
|
|
Name: "test-job",
|
|
Status: CopyJobStatusType.InProgress,
|
|
CompletionPercentage: 50,
|
|
Duration: "01 hours, 30 minutes",
|
|
LastUpdatedTime: "1/1/2025, 10:00:00 AM",
|
|
timestamp: 1704106800000,
|
|
Source: {
|
|
component: "CosmosDBSql",
|
|
databaseName: "source-db",
|
|
containerName: "source-container",
|
|
},
|
|
Destination: {
|
|
component: "CosmosDBSql",
|
|
databaseName: "target-db",
|
|
containerName: "target-container",
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
(CopyJobUtils.getAccountDetailsFromResourceId as jest.Mock).mockReturnValue({
|
|
subscriptionId: "sub-123",
|
|
resourceGroup: "rg-test",
|
|
accountName: "test-account",
|
|
});
|
|
});
|
|
|
|
it("should pause a job successfully", async () => {
|
|
const mockResponse = { id: "job-id", properties: { status: "Paused" } };
|
|
(dataTransferService.pause as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
const result = await updateCopyJobStatus(mockJob, CopyJobActions.pause);
|
|
|
|
expect(dataTransferService.pause).toHaveBeenCalledWith("sub-123", "rg-test", "test-account", "test-job");
|
|
expect(result).toEqual(mockResponse);
|
|
});
|
|
|
|
it("should resume a job successfully", async () => {
|
|
const mockResponse = { id: "job-id", properties: { status: "InProgress" } };
|
|
(dataTransferService.resume as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
const result = await updateCopyJobStatus(mockJob, CopyJobActions.resume);
|
|
|
|
expect(dataTransferService.resume).toHaveBeenCalledWith("sub-123", "rg-test", "test-account", "test-job");
|
|
expect(result).toEqual(mockResponse);
|
|
});
|
|
|
|
it("should cancel a job successfully", async () => {
|
|
const mockResponse = { id: "job-id", properties: { status: "Cancelled" } };
|
|
(dataTransferService.cancel as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
const result = await updateCopyJobStatus(mockJob, CopyJobActions.cancel);
|
|
|
|
expect(dataTransferService.cancel).toHaveBeenCalledWith("sub-123", "rg-test", "test-account", "test-job");
|
|
expect(result).toEqual(mockResponse);
|
|
});
|
|
|
|
it("should complete a job successfully", async () => {
|
|
const mockResponse = { id: "job-id", properties: { status: "Completed" } };
|
|
(dataTransferService.complete as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
const result = await updateCopyJobStatus(mockJob, CopyJobActions.complete);
|
|
|
|
expect(dataTransferService.complete).toHaveBeenCalledWith("sub-123", "rg-test", "test-account", "test-job");
|
|
expect(result).toEqual(mockResponse);
|
|
});
|
|
|
|
it("should handle case-insensitive action names", async () => {
|
|
const mockResponse = { id: "job-id", properties: { status: "Paused" } };
|
|
(dataTransferService.pause as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
await updateCopyJobStatus(mockJob, "PAUSE");
|
|
|
|
expect(dataTransferService.pause).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should throw error for unsupported action", async () => {
|
|
await expect(updateCopyJobStatus(mockJob, "invalid-action")).rejects.toThrow(
|
|
"Unsupported action: invalid-action",
|
|
);
|
|
|
|
expect(Logger.logError).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should normalize error messages with status types", async () => {
|
|
const mockError = {
|
|
message: "Job must be in 'Running' or 'InProgress' state",
|
|
content: { error: "State error" },
|
|
};
|
|
(dataTransferService.pause as jest.Mock).mockRejectedValue(mockError);
|
|
|
|
await expect(updateCopyJobStatus(mockJob, CopyJobActions.pause)).rejects.toEqual(mockError);
|
|
|
|
const loggedMessage = (Logger.logError as jest.Mock).mock.calls[0][0];
|
|
expect(loggedMessage).toContain("Error updating copy job status");
|
|
});
|
|
|
|
it("should log error with correct context", async () => {
|
|
const mockError = new Error("Network failure");
|
|
(dataTransferService.resume as jest.Mock).mockRejectedValue(mockError);
|
|
|
|
await expect(updateCopyJobStatus(mockJob, CopyJobActions.resume)).rejects.toThrow("Network failure");
|
|
|
|
expect(Logger.logError).toHaveBeenCalledWith(
|
|
expect.stringContaining("Error updating copy job status"),
|
|
"CopyJob/CopyJobActions.updateCopyJobStatus",
|
|
);
|
|
});
|
|
|
|
it("should handle errors with content property", async () => {
|
|
const mockError = {
|
|
content: { message: "Content error message" },
|
|
};
|
|
(dataTransferService.cancel as jest.Mock).mockRejectedValue(mockError);
|
|
|
|
await expect(updateCopyJobStatus(mockJob, CopyJobActions.cancel)).rejects.toEqual(mockError);
|
|
|
|
expect(Logger.logError).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|