Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions apps/server/src/__tests__/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { expect, test } from "bun:test";
import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { BASE_PATH, resolveFromBase, toStoredPath } from "../config.ts";

Expand All @@ -21,3 +23,25 @@ test("toStoredPath - files outside BASE_PATH are stored absolute", () => {
expect(toStoredPath("/etc/snapraid.conf")).toEqual("/etc/snapraid.conf");
expect(toStoredPath("../outside.conf")).toEqual(resolveFromBase("../outside.conf"));
});

// BASE_PATH is read once, when config.ts is imported: the first start runs in a process of its own
test("loadAppConfig - the first start creates the data directory and saves the default config", async () => {
const dir = await mkdtemp(join(tmpdir(), "snapraid-ui-first-start-"));
const base = join(dir, "snapraid");
try {
const proc = Bun.spawn(
[process.execPath, "-e", `import { loadAppConfig } from ${JSON.stringify(join(import.meta.dir, "..", "config-parser.ts"))}; await loadAppConfig();`],
{ env: { ...process.env, SNAPRAID_BASE_PATH: base }, stdout: "pipe", stderr: "pipe" },
);
const [stderr, exitCode] = await Promise.all([new Response(proc.stderr).text(), proc.exited]);

expect(stderr).not.toContain("Failed to save default config");
expect(exitCode).toBe(0);
// As the log directory and every other directory the backend makes: mkdir's default, less the umask
expect((await stat(base)).mode & 0o777).toBe(0o777 & ~process.umask());
const saved = JSON.parse(await readFile(join(base, "config.json"), "utf8"));
expect(saved.snapraidConfigs[0].path).toBe("snapraid.conf");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
6 changes: 4 additions & 2 deletions apps/server/src/config-parser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { basename, join } from "node:path";
import { basename, dirname, join } from "node:path";
import type { AppConfig, ParityLevel, ParsedSnapRaidConfig } from "@shared/types.ts";
import { resolveFromBase } from "./config.ts";
import { effectiveSmartIgnore, parseSmartIgnoreLine } from "./smart-ignore.ts";
import { writeTextFileAtomic } from "./atomic-write.ts";
import { readFile } from "node:fs/promises";
import { mkdir, readFile } from "node:fs/promises";
import { isNotFound } from "./fs.ts";

export const MAX_PARITY_LEVEL = 6;
Expand Down Expand Up @@ -174,5 +174,7 @@ export const loadAppConfig = async (): Promise<AppConfig> => {
*/
export const saveAppConfig = async (config: AppConfig): Promise<void> => {
const configPath = resolveFromBase("config.json");
// On the first start the data directory is not there yet; made as the log directory is
await mkdir(dirname(configPath), { recursive: true });
await writeTextFileAtomic(configPath, JSON.stringify(config, null, 2));
};
8 changes: 7 additions & 1 deletion e2e/harness/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,13 @@ export const test = base.extend<{ app: App }>({
)
},
expectFinished: async (label) => {
await expect(page.getByText(`"${label}" finished successfully`)).toBeVisible({ timeout: 90_000 })
const toast = page.locator('[data-sonner-toast]').filter({ hasText: `"${label}" finished successfully` })
await expect(toast).toBeVisible({ timeout: 90_000 })
// Each job gets its own toast, they stack for 5 s (Feedback.tsx): closed, so the toast
// of the next job of the same command is the only one the next call finds. Dispatched,
// as an open dialog's overlay (a wizard's) lies over the toasts
await toast.getByRole('button', { name: 'Close toast' }).dispatchEvent('click')
await expect(toast).toHaveCount(0)
await waitForIdle()
},
waitForIdle,
Expand Down
3 changes: 3 additions & 0 deletions e2e/tests/schedules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ test('a disk that is not mounted pauses the scheduled sync', async ({ page, app
// Back in place, nothing changed since the last sync: parity still has the files of d1
await array.remount('d1')
expect(await array.snapraid('diff')).toMatch(/No differences/)
// The dashboard shows the status it kept (persisted, fresh for 10 s) without reading it again:
// the disk came back behind the app's back, so read it again as a user would
await page.goto('/')
await page.getByRole('button', { name: 'Refresh', exact: true }).click()
await expectHealth(page, 'All good')
})

Expand Down
Loading