feat(projects): auto-create NAS folder for order-spawned projects
Manual project creation already creates the 02_PROJEKTY/<number>_<name> folder (projects.service.createProject). Projects created from an order — both from an accepted offer (createOrderFromQuotation) and a manual offer-less order (createOrder) — did not, so they had no NAS folder until a user opened the project and clicked "create folder" or uploaded a file. - orders.service: best-effort, non-fatal createProjectFolder after each project-creating transaction commits (mirrors createProject; gated on isConfigured(), logs on failure, never rolls back the order). - projects.service: log when the existing auto-create returns false (was silently discarded — CLAUDE.md known-issue #4). - nas-file-manager: optional constructor basePath seam for tests. - tests: NasFileManager.createProjectFolder coverage — naming, idempotency, Czech diacritics, empty name, not-mounted (152 -> 158 tests). Bump version to 2.0.3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
70
src/__tests__/nas-project-folder.test.ts
Normal file
70
src/__tests__/nas-project-folder.test.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { NasFileManager } from "../services/nas-file-manager";
|
||||
|
||||
/**
|
||||
* Folder auto-creation primitive used by both the manual project-create path
|
||||
* (projects.service createProject) and the order→project paths (orders.service
|
||||
* createOrder / createOrderFromQuotation). Uses an isolated temp dir via the
|
||||
* constructor seam so it is deterministic and passes even where the real NAS
|
||||
* (Z:) is not mounted.
|
||||
*/
|
||||
describe("NasFileManager.createProjectFolder", () => {
|
||||
let tmpBase: string;
|
||||
let nas: NasFileManager;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "nas-proj-"));
|
||||
nas = new NasFileManager(tmpBase);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tmpBase, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("creates a <number>_<name> folder for a new project", () => {
|
||||
const ok = nas.createProjectFolder("26710001", "Rekonstrukce haly");
|
||||
expect(ok).toBe(true);
|
||||
expect(
|
||||
fs.existsSync(path.join(tmpBase, "26710001_Rekonstrukce_haly")),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("is idempotent — a second call does not create a duplicate", () => {
|
||||
expect(nas.createProjectFolder("26710002", "Hala")).toBe(true);
|
||||
expect(nas.createProjectFolder("26710002", "Hala")).toBe(true);
|
||||
const matches = fs
|
||||
.readdirSync(tmpBase)
|
||||
.filter((e) => e.startsWith("26710002_"));
|
||||
expect(matches).toEqual(["26710002_Hala"]);
|
||||
});
|
||||
|
||||
it("preserves Czech diacritics (no transliteration)", () => {
|
||||
expect(nas.createProjectFolder("26710003", "Měření haly")).toBe(true);
|
||||
expect(fs.existsSync(path.join(tmpBase, "26710003_Měření_haly"))).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("strips illegal characters and collapses spaces", () => {
|
||||
expect(nas.createProjectFolder("26710004", "A / B: C")).toBe(true);
|
||||
expect(fs.existsSync(path.join(tmpBase, "26710004_A_B_C"))).toBe(true);
|
||||
});
|
||||
|
||||
it("handles an empty name without crashing (trailing underscore)", () => {
|
||||
expect(nas.createProjectFolder("26710005", "")).toBe(true);
|
||||
expect(fs.existsSync(path.join(tmpBase, "26710005_"))).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false and creates nothing when the NAS base is not mounted", () => {
|
||||
const missing = new NasFileManager(
|
||||
path.join(tmpBase, "does-not-exist-subdir"),
|
||||
);
|
||||
expect(missing.createProjectFolder("26710006", "X")).toBe(false);
|
||||
expect(fs.existsSync(path.join(tmpBase, "does-not-exist-subdir"))).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user