From 21d904cfd688d2c718a0bab7c66e34dd9e270e11 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 9 Aug 2026 08:24:37 +0000 Subject: [PATCH] fix(storage): adapt timestamp guard to current Commonlib Use the packaged Commonlib type import and add regression coverage for every Obsidian vault and storage write method. Verify that fractional timestamps are floored without mutating caller-owned options. --- .../ObsidianStorageAdapter.unit.spec.ts | 32 +++++++++++++++++++ .../ObsidianVaultAdapter.unit.spec.ts | 32 ++++++++++++++++++- .../sanitizeWriteOptions.ts | 2 +- 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.unit.spec.ts diff --git a/src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.unit.spec.ts b/src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.unit.spec.ts new file mode 100644 index 00000000..7172879f --- /dev/null +++ b/src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.unit.spec.ts @@ -0,0 +1,32 @@ +import { describe, expect, it, vi } from "vitest"; +import type { App } from "obsidian"; +import { ObsidianStorageAdapter } from "./ObsidianStorageAdapter"; + +describe("ObsidianStorageAdapter", () => { + it("floors write-option timestamps before calling Obsidian storage methods", async () => { + const write = vi.fn().mockResolvedValue(undefined); + const writeBinary = vi.fn().mockResolvedValue(undefined); + const append = vi.fn().mockResolvedValue(undefined); + const app = { + vault: { + adapter: { + write, + writeBinary, + append, + }, + }, + } as unknown as App; + const adapter = new ObsidianStorageAdapter(app); + const options = { ctime: 1778511180024.462, mtime: 1778511180999.913 }; + const expectedOptions = { ctime: 1778511180024, mtime: 1778511180999 }; + + await adapter.write("note.md", "text", options); + await adapter.writeBinary("image.bin", new ArrayBuffer(0), options); + await adapter.append("log.md", "text", options); + + expect(write).toHaveBeenCalledWith("note.md", "text", expectedOptions); + expect(writeBinary).toHaveBeenCalledWith("image.bin", expect.any(ArrayBuffer), expectedOptions); + expect(append).toHaveBeenCalledWith("log.md", "text", expectedOptions); + expect(options).toEqual({ ctime: 1778511180024.462, mtime: 1778511180999.913 }); + }); +}); diff --git a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.unit.spec.ts b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.unit.spec.ts index 5a307d52..0c22f212 100644 --- a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.unit.spec.ts +++ b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.unit.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import type { App, TFile } from "obsidian"; import { ObsidianVaultAdapter } from "./ObsidianVaultAdapter"; -describe("ObsidianVaultAdapter.read", () => { +describe("ObsidianVaultAdapter", () => { it("preserves a UTF-8 BOM so the content size matches the file stat", async () => { const path = "Transcripts/字幕.md"; const contentWithoutBom = "字幕の検証行です。\n"; @@ -34,4 +34,34 @@ describe("ObsidianVaultAdapter.read", () => { expect(adapterRead).toHaveBeenCalledWith(path); expect(read).not.toHaveBeenCalled(); }); + + it("floors write-option timestamps before calling Obsidian vault methods", async () => { + const modify = vi.fn().mockResolvedValue(undefined); + const modifyBinary = vi.fn().mockResolvedValue(undefined); + const create = vi.fn().mockResolvedValue({}); + const createBinary = vi.fn().mockResolvedValue({}); + const app = { + vault: { + modify, + modifyBinary, + create, + createBinary, + }, + } as unknown as App; + const file = { path: "note.md" } as TFile; + const adapter = new ObsidianVaultAdapter(app); + const options = { ctime: 1778511180024.462, mtime: 1778511180999.913 }; + const expectedOptions = { ctime: 1778511180024, mtime: 1778511180999 }; + + await adapter.modify(file, "text", options); + await adapter.modifyBinary(file, new ArrayBuffer(0), options); + await adapter.create("created.md", "text", options); + await adapter.createBinary("created.bin", new ArrayBuffer(0), options); + + expect(modify).toHaveBeenCalledWith(file, "text", expectedOptions); + expect(modifyBinary).toHaveBeenCalledWith(file, expect.any(ArrayBuffer), expectedOptions); + expect(create).toHaveBeenCalledWith("created.md", "text", expectedOptions); + expect(createBinary).toHaveBeenCalledWith("created.bin", expect.any(ArrayBuffer), expectedOptions); + expect(options).toEqual({ ctime: 1778511180024.462, mtime: 1778511180999.913 }); + }); }); diff --git a/src/serviceModules/FileSystemAdapters/sanitizeWriteOptions.ts b/src/serviceModules/FileSystemAdapters/sanitizeWriteOptions.ts index 4cb92355..3c055c16 100644 --- a/src/serviceModules/FileSystemAdapters/sanitizeWriteOptions.ts +++ b/src/serviceModules/FileSystemAdapters/sanitizeWriteOptions.ts @@ -1,4 +1,4 @@ -import type { UXDataWriteOptions } from "@lib/common/types"; +import type { UXDataWriteOptions } from "@vrtmrz/livesync-commonlib/compat/common/types"; /** * Coerce the timestamp fields of a write-options object to integer milliseconds.