write.test.mjs
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
sha256:6a102aafafdfe7e70a24f4e59740200f0ee713ce7915f1b53e9d4ba5ee8b4410
Initial Muse snapshot
Human
48 days ago