test: fix flaky test depending on mutex timing
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
This commit is contained in:
parent
add0f2788f
commit
e265f7d478
4 changed files with 64 additions and 30 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import type { ComponentProps } from "react";
|
||||
import { afterEach, describe, expect, test } from "bun:test";
|
||||
import { HttpResponse, http, server } from "~/test/msw/server";
|
||||
import { cleanup, render, screen, userEvent, waitFor, within } from "~/test/test-utils";
|
||||
import { cleanup, fireEvent, render, screen, userEvent, waitFor, within } from "~/test/test-utils";
|
||||
|
||||
type SnapshotFilesRequest = {
|
||||
shortId: string;
|
||||
|
|
@ -82,7 +82,7 @@ describe("SnapshotTreeBrowser", () => {
|
|||
expect(await screen.findByRole("button", { name: "project" })).toBeTruthy();
|
||||
});
|
||||
|
||||
test("renders a single file when no display base path is available", async () => {
|
||||
test("renders synthesized ancestor folders for a single file when no display base path is available", async () => {
|
||||
const requests = mockListSnapshotFiles({
|
||||
files: [{ name: "report.txt", path: "/mnt/project/report.txt", type: "file" }],
|
||||
});
|
||||
|
|
@ -92,7 +92,14 @@ describe("SnapshotTreeBrowser", () => {
|
|||
displayBasePath: undefined,
|
||||
});
|
||||
|
||||
expect(await screen.findByRole("button", { name: "report.txt" })).toBeTruthy();
|
||||
const mntRow = await screen.findByRole("button", { name: "mnt" });
|
||||
const mntExpandIcon = mntRow.querySelector("svg");
|
||||
if (!mntExpandIcon) {
|
||||
throw new Error("Expected expand icon for mnt row");
|
||||
}
|
||||
fireEvent.click(mntExpandIcon);
|
||||
|
||||
expect(await screen.findByRole("button", { name: "project" })).toBeTruthy();
|
||||
expect(requests[0]).toEqual({
|
||||
shortId: "repo-1",
|
||||
snapshotId: "snap-1",
|
||||
|
|
@ -252,7 +259,7 @@ describe("SnapshotTreeBrowser", () => {
|
|||
}
|
||||
|
||||
const initialRequestCount = requests.length;
|
||||
await userEvent.click(expandIcon);
|
||||
fireEvent.click(expandIcon);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(requests.length).toBeGreaterThan(initialRequestCount);
|
||||
|
|
|
|||
|
|
@ -114,20 +114,25 @@ export const FileTree = memo((props: Props) => {
|
|||
|
||||
const toggleCollapseState = useCallback(
|
||||
(fullPath: string) => {
|
||||
const shouldExpand = collapsedFolders.has(fullPath);
|
||||
|
||||
setCollapsedFolders((prevSet) => {
|
||||
const newSet = new Set(prevSet);
|
||||
|
||||
if (newSet.has(fullPath)) {
|
||||
newSet.delete(fullPath);
|
||||
onFolderExpand?.(fullPath);
|
||||
} else {
|
||||
newSet.add(fullPath);
|
||||
}
|
||||
|
||||
return newSet;
|
||||
});
|
||||
|
||||
if (shouldExpand) {
|
||||
onFolderExpand?.(fullPath);
|
||||
}
|
||||
},
|
||||
[onFolderExpand],
|
||||
[collapsedFolders, onFolderExpand],
|
||||
);
|
||||
|
||||
// Add new folders to collapsed set when file list changes
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { logger } from "~/client/lib/logger";
|
||||
import type { FileEntry } from "../components/file-tree";
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ export const useFileBrowser = (props: UseFileBrowserOptions) => {
|
|||
const stripPath = pathTransform?.strip;
|
||||
const addPath = pathTransform?.add;
|
||||
|
||||
useMemo(() => {
|
||||
useEffect(() => {
|
||||
if (initialData?.files) {
|
||||
const files = initialData.files;
|
||||
setAllFiles((prev) => {
|
||||
|
|
|
|||
|
|
@ -218,12 +218,24 @@ describe("stop backup", () => {
|
|||
repositoryId: repository.id,
|
||||
});
|
||||
|
||||
resticBackupMock.mockImplementation(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
return { exitCode: 0, summary: generateBackupOutput(), error: "" };
|
||||
resticBackupMock.mockImplementation(({ signal }: SafeSpawnParams) => {
|
||||
return new Promise((resolve) => {
|
||||
if (signal?.aborted) {
|
||||
resolve({ exitCode: 1, summary: "", error: "" });
|
||||
return;
|
||||
}
|
||||
|
||||
signal?.addEventListener(
|
||||
"abort",
|
||||
() => {
|
||||
resolve({ exitCode: 1, summary: "", error: "" });
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
void backupsExecutionService.executeBackup(schedule.id);
|
||||
const executePromise = backupsExecutionService.executeBackup(schedule.id);
|
||||
|
||||
await waitForExpect(async () => {
|
||||
const runningSchedule = await backupsService.getScheduleById(schedule.id);
|
||||
|
|
@ -232,6 +244,7 @@ describe("stop backup", () => {
|
|||
|
||||
// act
|
||||
await backupsExecutionService.stopBackup(schedule.id);
|
||||
await executePromise;
|
||||
|
||||
// assert
|
||||
const updatedSchedule = await backupsService.getScheduleById(schedule.id);
|
||||
|
|
@ -248,30 +261,39 @@ describe("stop backup", () => {
|
|||
repositoryId: repository.id,
|
||||
});
|
||||
|
||||
const releaseLock = await repoMutex.acquireExclusive(repository.id, "test");
|
||||
spyOn(repoMutex, "acquireShared").mockImplementation((_repositoryId, _operation, signal) => {
|
||||
return new Promise((_, reject) => {
|
||||
if (signal?.aborted) {
|
||||
reject(signal.reason instanceof Error ? signal.reason : new Error("Operation aborted"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const executePromise = backupsExecutionService.executeBackup(schedule.id);
|
||||
|
||||
await waitForExpect(async () => {
|
||||
const queuedSchedule = await backupsService.getScheduleById(schedule.id);
|
||||
expect(queuedSchedule.lastBackupStatus).toBe("in_progress");
|
||||
signal?.addEventListener(
|
||||
"abort",
|
||||
() => {
|
||||
reject(signal.reason instanceof Error ? signal.reason : new Error("Operation aborted"));
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(resticBackupMock).not.toHaveBeenCalled();
|
||||
const executePromise = backupsExecutionService.executeBackup(schedule.id);
|
||||
|
||||
await backupsExecutionService.stopBackup(schedule.id);
|
||||
releaseLock();
|
||||
await waitForExpect(async () => {
|
||||
const queuedSchedule = await backupsService.getScheduleById(schedule.id);
|
||||
expect(queuedSchedule.lastBackupStatus).toBe("in_progress");
|
||||
});
|
||||
|
||||
await executePromise;
|
||||
expect(resticBackupMock).not.toHaveBeenCalled();
|
||||
|
||||
const updatedSchedule = await backupsService.getScheduleById(schedule.id);
|
||||
expect(updatedSchedule.lastBackupStatus).toBe("warning");
|
||||
expect(updatedSchedule.lastBackupError).toBe("Backup was stopped by the user");
|
||||
expect(resticBackupMock).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
releaseLock();
|
||||
}
|
||||
await backupsExecutionService.stopBackup(schedule.id);
|
||||
await executePromise;
|
||||
|
||||
const updatedSchedule = await backupsService.getScheduleById(schedule.id);
|
||||
expect(updatedSchedule.lastBackupStatus).toBe("warning");
|
||||
expect(updatedSchedule.lastBackupError).toBe("Backup was stopped by the user");
|
||||
expect(resticBackupMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should throw ConflictError when trying to stop non-running backup", async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue