write.test.mjs
164 lines 6.0 KB
Raw
sha256:8d46372e39d2d5a54fd93a8b1c27922fe0d9b22a72197345f1d2c71701cc4ce2 feat(auth): persistent login system + C7 session introspection Human minor ⚠ breaking 17 days ago
1 /**
2 * Write tests: writeNote new file, update, append, path validation.
3 */
4 import { describe, it, before, after } from 'node:test';
5 import assert from 'node:assert';
6 import path from 'path';
7 import fs from 'fs';
8 import { fileURLToPath } from 'url';
9 import {
10 writeNote,
11 deleteNote,
12 isInboxPath,
13 normalizePathPrefix,
14 notePathMatchesPrefix,
15 deleteNotesByPrefix,
16 } from '../lib/write.mjs';
17 import { readNote } from '../lib/vault.mjs';
18
19 const __dirname = path.dirname(fileURLToPath(import.meta.url));
20 const testVault = path.join(__dirname, 'fixtures', 'tmp-write-vault');
21
22 describe('writeNote', () => {
23 before(() => {
24 if (fs.existsSync(testVault)) fs.rmSync(testVault, { recursive: true });
25 fs.mkdirSync(testVault, { recursive: true });
26 });
27
28 after(() => {
29 if (fs.existsSync(testVault)) {
30 try {
31 fs.rmSync(testVault, { recursive: true });
32 } catch (_) {}
33 }
34 });
35
36 it('writes a new note and returns path and written: true', async () => {
37 const result = await writeNote(testVault, 'inbox/new-note.md', {
38 body: '# New\n\nContent.',
39 frontmatter: { title: 'New Note', date: '2025-03-12' },
40 });
41 assert.strictEqual(result.path, 'inbox/new-note.md');
42 assert.strictEqual(result.written, true);
43 const read = readNote(testVault, 'inbox/new-note.md');
44 assert(read.body.includes('New'));
45 assert(read.body.includes('Content.'));
46 assert.strictEqual(read.frontmatter?.title, 'New Note');
47 });
48
49 it('updates existing note when body/frontmatter provided without append', async () => {
50 await writeNote(testVault, 'inbox/update.md', { body: 'First', frontmatter: { date: '2025-01-01' } });
51 const result = await writeNote(testVault, 'inbox/update.md', { body: 'Second', frontmatter: { date: '2025-02-01' } });
52 assert.strictEqual(result.written, true);
53 const read = readNote(testVault, 'inbox/update.md');
54 assert.strictEqual(read.body.trim(), 'Second');
55 assert.strictEqual(read.frontmatter?.date, '2025-02-01');
56 });
57
58 it('append option appends to body', async () => {
59 await writeNote(testVault, 'inbox/append.md', { body: 'Part one.\n\n' });
60 await writeNote(testVault, 'inbox/append.md', { body: 'Part two.', append: true });
61 const read = readNote(testVault, 'inbox/append.md');
62 assert(read.body.includes('Part one.'));
63 assert(read.body.includes('Part two.'));
64 });
65
66 it('throws for path that escapes vault', async () => {
67 await assert.rejects(
68 () => writeNote(testVault, '../../../etc/foo.md', { body: 'x' }),
69 /Invalid path|escapes vault/
70 );
71 });
72 });
73
74 describe('deleteNote', () => {
75 before(() => {
76 if (fs.existsSync(testVault)) fs.rmSync(testVault, { recursive: true });
77 fs.mkdirSync(testVault, { recursive: true });
78 });
79
80 after(() => {
81 if (fs.existsSync(testVault)) {
82 try {
83 fs.rmSync(testVault, { recursive: true });
84 } catch (_) {}
85 }
86 });
87
88 it('removes an existing file and returns path and deleted: true', async () => {
89 await writeNote(testVault, 'inbox/to-delete.md', { body: 'x', frontmatter: { date: '2025-01-01' } });
90 const result = deleteNote(testVault, 'inbox/to-delete.md');
91 assert.strictEqual(result.path, 'inbox/to-delete.md');
92 assert.strictEqual(result.deleted, true);
93 assert.throws(() => readNote(testVault, 'inbox/to-delete.md'), /not found/);
94 });
95
96 it('throws for missing note', () => {
97 assert.throws(() => deleteNote(testVault, 'inbox/missing.md'), /not found/);
98 });
99
100 it('throws for path that escapes vault', () => {
101 assert.throws(
102 () => deleteNote(testVault, '../../../etc/passwd'),
103 /Invalid path|escapes vault/
104 );
105 });
106 });
107
108 describe('deleteNotesByPrefix', () => {
109 before(() => {
110 if (fs.existsSync(testVault)) fs.rmSync(testVault, { recursive: true });
111 fs.mkdirSync(testVault, { recursive: true });
112 });
113
114 after(() => {
115 if (fs.existsSync(testVault)) {
116 try {
117 fs.rmSync(testVault, { recursive: true });
118 } catch (_) {}
119 }
120 });
121
122 it('normalizePathPrefix trims and rejects unsafe segments', () => {
123 assert.strictEqual(normalizePathPrefix(' projects/foo '), 'projects/foo');
124 assert.strictEqual(normalizePathPrefix('projects/foo/'), 'projects/foo');
125 assert.throws(() => normalizePathPrefix(''), /path_prefix/);
126 assert.throws(() => normalizePathPrefix('..'), /Invalid/);
127 assert.throws(() => normalizePathPrefix('a/./b'), /Invalid/);
128 });
129
130 it('notePathMatchesPrefix matches exact path or children only', () => {
131 assert.strictEqual(notePathMatchesPrefix('projects/foo', 'projects/foo'), true);
132 assert.strictEqual(notePathMatchesPrefix('projects/foo/bar.md', 'projects/foo'), true);
133 assert.strictEqual(notePathMatchesPrefix('projects/foobar/x.md', 'projects/foo'), false);
134 assert.strictEqual(notePathMatchesPrefix('other/foo.md', 'projects/foo'), false);
135 });
136
137 it('deletes all .md under prefix and returns paths', async () => {
138 await writeNote(testVault, 'projects/p1/a.md', { body: 'a' });
139 await writeNote(testVault, 'projects/p1/sub/b.md', { body: 'b' });
140 await writeNote(testVault, 'projects/p2/keep.md', { body: 'k' });
141 const { deleted, paths } = deleteNotesByPrefix(testVault, 'projects/p1');
142 assert.strictEqual(deleted, 2);
143 assert.strictEqual(paths.length, 2);
144 assert(paths.includes('projects/p1/a.md'));
145 assert(paths.includes('projects/p1/sub/b.md'));
146 assert.throws(() => readNote(testVault, 'projects/p1/a.md'), /not found/);
147 const kept = readNote(testVault, 'projects/p2/keep.md');
148 assert.strictEqual(kept.body.trim(), 'k');
149 });
150 });
151
152 describe('isInboxPath', () => {
153 it('returns true for inbox and inbox/...', () => {
154 assert.strictEqual(isInboxPath('inbox'), true);
155 assert.strictEqual(isInboxPath('inbox/foo.md'), true);
156 });
157 it('returns true for projects/x/inbox/...', () => {
158 assert.strictEqual(isInboxPath('projects/foo/inbox/bar.md'), true);
159 });
160 it('returns false for other paths', () => {
161 assert.strictEqual(isInboxPath('projects/foo/note.md'), false);
162 assert.strictEqual(isInboxPath('other/foo.md'), false);
163 });
164 });
File History 2 commits
sha256:8d46372e39d2d5a54fd93a8b1c27922fe0d9b22a72197345f1d2c71701cc4ce2 feat(auth): persistent login system + C7 session introspection Human minor 17 days ago