sotashimozono_obsidian-remo.../plugin/tests/DirCache.test.ts
Souta 21bb70a5d0 chore: monorepo restructure (plugin/ + server/ + proto/)
Move the entire Obsidian plugin tree under plugin/ and add an empty
server/ tree (Go) and proto/ directory so the upcoming
obsidian-remote-server daemon has a home in the same repo. The plugin
remains the only thing that builds today; the server is a hello-world
skeleton that prints "not implemented yet" and exits non-zero, with a
Makefile that knows how to cross-compile for linux/darwin × amd64/arm64
once real code lands.

Background:
  Approach A (vault-adapter monkey-patch) hit structural ceilings —
  vault index frozen at startup, third-party plugins bypass DataAdapter,
  empty-remote .obsidian read failures cascade through Templater/Kanban
  etc. We are pivoting to the VS Code Remote-SSH model: a small Go
  daemon on the remote host, JSON-RPC over a SSH-tunnelled WebSocket,
  fsnotify-driven push events, attachment serving over HTTP on the same
  socket. This restructure is the first mechanical step before the
  protocol and transport land.

Mechanical changes:
- git mv all plugin sources, manifest, configs, scripts, and tests
  into plugin/.
- plugin/scripts/dev-install.mjs walks one extra parent (`pluginRoot`
  → `repoRoot` → `..` for the dev vault) so REMOTE_SSH_DEV_VAULT
  defaulting still resolves to ../SelfArchive-dev relative to the
  repo, not to plugin/.
- CI: ci.yml runs lint/test/build with `working-directory: plugin`
  and a server job that builds + tests the Go module. release.yml
  builds out of plugin/ and uploads plugin/{main.js,manifest.json,
  styles.css}.
- Dependabot: npm directory becomes /plugin; new gomod directory /server.
- Labeler: paths re-anchored to plugin/, with new plugin/server/proto
  area labels.

Verification:
  cd plugin && npm install && npx tsc --noEmit && npm test (76/76)
  cd plugin && npm run build:install (367 KB; dev vault refreshed).
  Go side waits on CI (no Go toolchain on the dev host).
2026-04-25 10:52:52 +09:00

84 lines
2.6 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { DirCache } from '../src/cache/DirCache';
import type { RemoteEntry } from '../src/types';
const e = (name: string, isDirectory = false): RemoteEntry => ({
name,
isDirectory,
isFile: !isDirectory,
isSymbolicLink: false,
mtime: 0,
size: 0,
});
describe('DirCache', () => {
it('returns null when the path was never put', () => {
const cache = new DirCache();
expect(cache.get('docs')).toBeNull();
});
it('returns the cached entries within TTL', () => {
let now = 1000;
const cache = new DirCache({ ttlMs: 100, now: () => now });
cache.put('docs', [e('a.md'), e('b.md')]);
now = 1099; // still within TTL
expect(cache.get('docs')?.length).toBe(2);
});
it('returns null and drops the entry once TTL expires', () => {
let now = 1000;
const cache = new DirCache({ ttlMs: 100, now: () => now });
cache.put('docs', [e('a.md')]);
now = 1100; // exactly at TTL boundary; treat as expired
expect(cache.get('docs')).toBeNull();
expect(cache.size()).toBe(0);
});
it('put refreshes the TTL when re-cached', () => {
let now = 1000;
const cache = new DirCache({ ttlMs: 100, now: () => now });
cache.put('docs', [e('a.md')]);
now = 1090;
cache.put('docs', [e('a.md'), e('b.md')]); // refresh
now = 1180; // would have expired against the original
expect(cache.get('docs')?.length).toBe(2);
});
it('invalidate removes a single path', () => {
const cache = new DirCache();
cache.put('docs', [e('a.md')]);
cache.invalidate('docs');
expect(cache.get('docs')).toBeNull();
});
it('invalidatePrefix drops the dir and its descendants', () => {
const cache = new DirCache();
cache.put('docs', [e('a.md')]);
cache.put('docs/sub', [e('b.md')]);
cache.put('docs/sub/deep', [e('c.md')]);
cache.put('other', [e('x.md')]);
const removed = cache.invalidatePrefix('docs');
expect(removed).toBe(3);
expect(cache.get('docs')).toBeNull();
expect(cache.get('docs/sub')).toBeNull();
expect(cache.get('docs/sub/deep')).toBeNull();
expect(cache.get('other')).not.toBeNull();
});
it('invalidatePrefix does not match unrelated keys with shared prefix', () => {
const cache = new DirCache();
cache.put('docs', [e('a.md')]);
cache.put('docs2', [e('b.md')]);
cache.invalidatePrefix('docs');
expect(cache.get('docs')).toBeNull();
expect(cache.get('docs2')).not.toBeNull();
});
it('clear empties the cache', () => {
const cache = new DirCache();
cache.put('a', [e('x')]);
cache.put('b', [e('y')]);
cache.clear();
expect(cache.size()).toBe(0);
});
});