Files
supabase/apps/studio/lib/api/snippets.utils.test.ts
T
Alaister Young 1b1d05ff96 chore: upgrade vite to v8 and vitest to v4 (#44833)
Upgrade vite and vitest to their latest major versions across the
monorepo, along with related packages.

**Changed:**
- `vite` catalog: `^7.3.2` → `^8.0.8` (Rolldown replaces esbuild/Rollup)
- `vitest` catalog: `^3.2.0` → `^4.1.4`
- `@vitejs/plugin-react`: `^4.3.4` → `^6.0.1`
- `@vitest/coverage-v8`: `^3.2.0` → `^4.1.4`
- `@vitest/ui`: `^3.2.0` → `^4.1.4`
- `vite-tsconfig-paths`: `^4.3.2` / `^5.1.4` → `^6.1.1`

**Pinned to vite 7:**
- `apps/lite-studio` — `@react-router/dev` hasn't declared vite 8
support yet
- `blocks/vue` — Nuxt plugins (`vite-plugin-inspect`, `vite-dev-rpc`,
`vite-hot-client`, `vite-plugin-vue-tracer`) haven't declared vite 8
support yet

**Test fixes for vitest 4 breaking changes:**
- **`apps/studio/lib/api/snippets.utils.test.ts`** — Replaced
`vi.mock('fs/promises')` automock with an explicit factory. Vitest 4's
automocking doesn't create mock functions for getter-based exports on
Node built-ins, so `mockedFS.access.mockResolvedValue` etc. were
`undefined`.
- **`apps/studio/lib/api/self-hosted/functions/index.test.ts`** —
Changed `mockReturnValue` to `mockImplementation(function() { ... })`
for a constructor mock. Vitest 4 no longer allows `mockReturnValue` when
the mock is called with `new`.
- **`apps/studio/tests/pages/api/mcp/index.test.ts`** — Changed arrow
function to regular `function` in `mockImplementation` for
`StreamableHTTPServerTransport`. Arrow functions can't be constructors,
and vitest 4 now enforces this.
- **`packages/ui-patterns/vitest.setup.ts`** — Changed `ResizeObserver`
mock from arrow function to regular `function` for the same constructor
enforcement reason. This was crashing Radix popover rendering in jsdom.

## To test

- `pnpm test:studio` — all 226 test files should pass
- `pnpm --filter ui-patterns vitest run` — all 183 tests should pass
- `pnpm --filter www test -- --run` — all 19 tests should pass
- `pnpm --filter ui vitest run` — all tests should pass
- `pnpm --filter dev-tools vitest run` — all tests should pass
- `pnpm --filter ai-commands vitest run` — all tests should pass

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Standardized and updated development tooling versions and version
sources for consistent installs across the repo (Vite, Vitest,
vite-tsconfig-paths and related plugins/catalog entries).
* **Tests**
* Improved test mocks and typings (updated mock
factories/implementations and tightened spy/type assertions) to increase
test reliability and compatibility with updated tooling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-04-16 00:13:48 +09:00

1648 lines
61 KiB
TypeScript

import fs from 'fs/promises'
import path from 'path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { generateDeterministicUuid } from './snippets.browser'
import {
createFolder,
deleteFolder,
deleteSnippet,
getFilesystemEntries,
getFolders,
getSnippet,
getSnippets,
saveSnippet,
updateSnippet,
type Snippet,
} from './snippets.utils'
// Mock fs/promises — explicit factory required since Vitest 4 automocking
// doesn't create mock functions for getter-based exports on Node built-ins.
vi.mock('fs/promises', () => ({
default: {
access: vi.fn(),
mkdir: vi.fn(),
readdir: vi.fn(),
readFile: vi.fn(),
writeFile: vi.fn(),
unlink: vi.fn(),
rm: vi.fn(),
stat: vi.fn(),
},
}))
const mockedFS = vi.mocked(fs)
// Mock SNIPPETS_DIR from constants
vi.mock('./snippets.constants', () => ({
SNIPPETS_DIR: '/mock/snippets/dir',
}))
describe('snippets.utils', () => {
const MOCK_SNIPPETS_DIR = '/mock/snippets/dir'
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
vi.resetAllMocks()
})
describe('getFilesystemEntries', () => {
it('should create the snippets directory if it does not exist', async () => {
const accessError = new Error('Directory not found')
mockedFS.access.mockRejectedValue(accessError)
mockedFS.mkdir.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await getFilesystemEntries()
expect(mockedFS.access).toHaveBeenCalledWith(MOCK_SNIPPETS_DIR)
expect(mockedFS.mkdir).toHaveBeenCalledWith(MOCK_SNIPPETS_DIR, { recursive: true })
})
it('should read and parse SQL files from the snippets directory', async () => {
const files = [
{
name: 'snippet1.sql',
isDirectory: () => false,
isFile: () => true,
},
{
name: 'snippet2.sql',
isDirectory: () => false,
isFile: () => true,
},
{
name: 'not-sql.txt',
isDirectory: () => false,
isFile: () => true,
},
]
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue(files as any)
mockedFS.readFile
.mockResolvedValueOnce('SELECT * FROM users;')
.mockResolvedValueOnce('SELECT * FROM posts;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const entries = await getFilesystemEntries()
expect(entries).toHaveLength(2)
expect(entries[0].name).toBe('snippet1')
expect(entries[0].content).toBe('SELECT * FROM users;')
expect(entries[0].type).toBe('file')
expect(entries[1].name).toBe('snippet2')
expect(entries[1].content).toBe('SELECT * FROM posts;')
expect(entries[1].type).toBe('file')
expect(mockedFS.readFile).toHaveBeenCalledTimes(2)
})
it('should return empty array when no SQL files exist', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'not-sql.txt', isDirectory: () => false, isFile: () => true },
] as any)
const entries = await getFilesystemEntries()
expect(entries).toEqual([])
})
it('should read SQL files from subdirectories (folders)', async () => {
// Mock fs.Dirent objects for different file types
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
// Mock the main directory structure
const mainDirItems = [
createMockDirent('root-snippet.sql', false),
createMockDirent('folder1', true),
createMockDirent('not-sql.txt', false),
]
// Mock folder1 contents
const folder1Items = [createMockDirent('folder1-snippet.sql', false)]
mockedFS.access.mockResolvedValue(undefined)
// Mock readdir to return different results based on the path
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve(mainDirItems as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, 'folder1')) {
return Promise.resolve(folder1Items as any)
}
return Promise.resolve([])
})
// Mock readFile to return different content based on the file
mockedFS.readFile.mockImplementation((filePath: any) => {
const fileName = path.basename(filePath as string)
switch (fileName) {
case 'root-snippet.sql':
return Promise.resolve('SELECT * FROM root;')
case 'folder1-snippet.sql':
return Promise.resolve('SELECT * FROM folder1_table;')
default:
return Promise.reject(new Error('File not found'))
}
})
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const entries = await getFilesystemEntries()
// Should find 1 folder and 2 SQL files
expect(entries).toHaveLength(3)
const folders = entries.filter((e) => e.type === 'folder')
const files = entries.filter((e) => e.type === 'file')
expect(folders).toHaveLength(1)
expect(folders[0].name).toBe('folder1')
expect(folders[0].folderId).toBe(null) // Folders are always at root level
expect(files).toHaveLength(2)
const rootFile = files.find((f) => f.name === 'root-snippet')
const folderFile = files.find((f) => f.name === 'folder1-snippet')
expect(rootFile?.folderId).toBe(null)
expect(folderFile?.folderId).toBe(generateDeterministicUuid(['folder1']))
})
it('should skip subdirectories inside folders (not supported)', async () => {
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
const mainDirItems = [createMockDirent('folder1', true)]
const folder1Items = [
createMockDirent('snippet.sql', false),
createMockDirent('subfolder', true), // This should be skipped
]
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve(mainDirItems as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, 'folder1')) {
return Promise.resolve(folder1Items as any)
}
return Promise.resolve([])
})
mockedFS.readFile.mockResolvedValue('SELECT * FROM table;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const entries = await getFilesystemEntries()
// Should only find 1 folder and 1 file (subfolder should be skipped)
expect(entries).toHaveLength(2)
expect(entries.filter((e) => e.type === 'folder')).toHaveLength(1)
expect(entries.filter((e) => e.type === 'file')).toHaveLength(1)
})
it('should handle empty SQL files', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'empty.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const entries = await getFilesystemEntries()
expect(entries).toHaveLength(1)
expect(entries[0].content).toBe('')
})
it('should handle file read errors gracefully', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'error.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockRejectedValue(new Error('Permission denied'))
await expect(getFilesystemEntries()).rejects.toThrow('Permission denied')
})
it('should handle mkdir failure', async () => {
mockedFS.access.mockRejectedValue(new Error('Directory not found'))
mockedFS.mkdir.mockRejectedValue(new Error('Permission denied'))
await expect(getFilesystemEntries()).rejects.toThrow('Permission denied')
})
})
describe('getSnippet', () => {
it('should get a specific snippet by id', async () => {
const snippetName = 'test-snippet'
const snippetId = generateDeterministicUuid([`${snippetName}.sql`])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'test-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM test;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const snippet = await getSnippet(snippetId)
// The snippet ID is generated from the full filename in buildSnippet
expect(snippet.id).toBe(generateDeterministicUuid([`${snippetName}.sql`]))
expect(snippet.name).toBe(snippetName)
expect(snippet.content.sql).toBe('SELECT * FROM test;')
expect(snippet.folder_id).toBe(null)
})
it('should get a snippet from a folder', async () => {
const folderName = 'my-folder'
const folderId = generateDeterministicUuid([folderName])
const snippetName = 'folder-snippet'
const snippetId = generateDeterministicUuid([folderId, `${snippetName}.sql`])
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve([createMockDirent(folderName, true)] as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, folderName)) {
return Promise.resolve([createMockDirent(`${snippetName}.sql`, false)] as any)
}
return Promise.resolve([])
})
mockedFS.readFile.mockResolvedValue('SELECT * FROM folder_table;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const snippet = await getSnippet(snippetId)
expect(snippet.id).toBe(generateDeterministicUuid([folderId, `${snippetName}.sql`]))
expect(snippet.name).toBe(snippetName)
expect(snippet.content.sql).toBe('SELECT * FROM folder_table;')
expect(snippet.folder_id).toBe(generateDeterministicUuid(['my-folder']))
})
it('should throw error when snippet not found', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(getSnippet('non-existent-id')).rejects.toThrow(
'Snippet with id non-existent-id not found'
)
})
})
describe('getSnippets', () => {
it('should get all snippets from root folder', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'snippet1.sql', isDirectory: () => false, isFile: () => true },
{ name: 'snippet2.sql', isDirectory: () => false, isFile: () => true },
{ name: 'folder1', isDirectory: () => true, isFile: () => false },
] as any)
mockedFS.readFile
.mockResolvedValueOnce('SELECT * FROM table1;')
.mockResolvedValueOnce('SELECT * FROM table2;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ folderId: null, sort: 'name', sortOrder: 'asc' })
expect(result.snippets).toHaveLength(2)
expect(result.snippets[0].name).toBe('snippet1')
expect(result.snippets[1].name).toBe('snippet2')
expect(result.snippets[0].folder_id).toBe(null)
expect(result.snippets[1].folder_id).toBe(null)
expect(result.cursor).toBeUndefined()
})
it('should get snippets from a specific folder', async () => {
const folderId = generateDeterministicUuid(['my-folder'])
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve([createMockDirent('my-folder', true)] as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, 'my-folder')) {
return Promise.resolve([createMockDirent('folder-snippet.sql', false)] as any)
}
return Promise.resolve([])
})
mockedFS.readFile.mockResolvedValue('SELECT * FROM folder_table;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ folderId })
expect(result.snippets).toHaveLength(1)
expect(result.snippets[0].name).toBe('folder-snippet')
expect(result.snippets[0].folder_id).toBe(folderId)
})
it('should return empty result when no snippets in folder', async () => {
const folderId = generateDeterministicUuid(['empty-folder'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
const result = await getSnippets({ folderId })
expect(result.snippets).toEqual([])
expect(result.cursor).toBeUndefined()
})
it('should filter snippets by search term across all folders', async () => {
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve([
createMockDirent('user-query.sql', false),
createMockDirent('admin-report.sql', false),
createMockDirent('test-user-data.sql', false),
createMockDirent('my-folder', true),
] as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, 'my-folder')) {
return Promise.resolve([createMockDirent('user-permissions.sql', false)] as any)
}
return Promise.resolve([])
})
mockedFS.readFile.mockImplementation((filePath: any) => {
const fileName = path.basename(filePath as string)
return Promise.resolve(`SELECT * FROM ${fileName.replace('.sql', '')};`)
})
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ searchTerm: 'user', sort: 'name', sortOrder: 'asc' })
expect(result.snippets).toHaveLength(3)
expect(result.snippets.map((s) => s.name)).toEqual([
'test-user-data',
'user-permissions',
'user-query',
])
})
it('should sort snippets by name in ascending order', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'zebra.sql', isDirectory: () => false, isFile: () => true },
{ name: 'apple.sql', isDirectory: () => false, isFile: () => true },
{ name: 'banana.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ sort: 'name', sortOrder: 'asc' })
expect(result.snippets.map((s) => s.name)).toEqual(['apple', 'banana', 'zebra'])
})
it('should sort snippets by name in descending order', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'zebra.sql', isDirectory: () => false, isFile: () => true },
{ name: 'apple.sql', isDirectory: () => false, isFile: () => true },
{ name: 'banana.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ sort: 'name', sortOrder: 'desc' })
expect(result.snippets.map((s) => s.name)).toEqual(['zebra', 'banana', 'apple'])
})
it('should sort snippets by creation date', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'newest.sql', isDirectory: () => false, isFile: () => true },
{ name: 'oldest.sql', isDirectory: () => false, isFile: () => true },
{ name: 'middle.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockImplementation((filePath: any) => {
const fileName = path.basename(filePath as string)
switch (fileName) {
case 'newest.sql':
return Promise.resolve({ birthtime: new Date('2023-03-01') } as any)
case 'oldest.sql':
return Promise.resolve({ birthtime: new Date('2023-01-01') } as any)
case 'middle.sql':
return Promise.resolve({ birthtime: new Date('2023-02-01') } as any)
default:
return Promise.resolve({ birthtime: new Date('2023-01-01') } as any)
}
})
// Test descending order (newest first - default behavior)
const resultDesc = await getSnippets({ sort: 'inserted_at', sortOrder: 'desc' })
expect(resultDesc.snippets.map((s) => s.name)).toEqual(['newest', 'middle', 'oldest'])
// Test ascending order (oldest first)
const resultAsc = await getSnippets({ sort: 'inserted_at', sortOrder: 'asc' })
expect(resultAsc.snippets.map((s) => s.name)).toEqual(['oldest', 'middle', 'newest'])
})
it('should paginate results with limit', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'snippet1.sql', isDirectory: () => false, isFile: () => true },
{ name: 'snippet2.sql', isDirectory: () => false, isFile: () => true },
{ name: 'snippet3.sql', isDirectory: () => false, isFile: () => true },
{ name: 'snippet4.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ limit: 2, sort: 'name', sortOrder: 'asc' })
expect(result.snippets).toHaveLength(2)
expect(result.snippets.map((s) => s.name)).toEqual(['snippet1', 'snippet2'])
expect(result.cursor).toBeDefined() // Should have a cursor for next page
})
it('should use cursor for pagination', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'snippet1.sql', isDirectory: () => false, isFile: () => true },
{ name: 'snippet2.sql', isDirectory: () => false, isFile: () => true },
{ name: 'snippet3.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
// Get first page
const firstPage = await getSnippets({ limit: 1, sort: 'name', sortOrder: 'asc' })
expect(firstPage.snippets).toHaveLength(1)
expect(firstPage.snippets[0].name).toBe('snippet1')
expect(firstPage.cursor).toBeDefined()
// Get second page using cursor
const secondPage = await getSnippets({
cursor: firstPage.cursor,
limit: 1,
sort: 'name',
sortOrder: 'asc',
})
expect(secondPage.snippets).toHaveLength(1)
expect(secondPage.snippets[0].name).toBe('snippet2')
})
it('should handle invalid cursor gracefully', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'snippet1.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ cursor: 'invalid-cursor-id' })
expect(result.snippets).toHaveLength(1) // Should return all snippets
expect(result.snippets[0].name).toBe('snippet1')
})
it('should use default parameters when none provided', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'snippet1.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({})
expect(result.snippets).toHaveLength(1)
expect(result.cursor).toBeUndefined()
// Default sort should be by inserted_at desc
})
it('should handle empty search term', async () => {
const folderId = generateDeterministicUuid(['my-folder'])
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve([
createMockDirent('root-snippet.sql', false),
createMockDirent('my-folder', true),
] as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, 'my-folder')) {
return Promise.resolve([createMockDirent('folder-snippet.sql', false)] as any)
}
return Promise.resolve([])
})
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
// Empty search term should filter by folderId
const result = await getSnippets({ searchTerm: ' ', folderId })
expect(result.snippets).toHaveLength(1)
expect(result.snippets[0].name).toBe('folder-snippet')
})
it('should handle filesystem errors', async () => {
mockedFS.access.mockRejectedValue(new Error('Directory not accessible'))
mockedFS.mkdir.mockRejectedValue(new Error('Directory not accessible'))
await expect(getSnippets({})).rejects.toThrow('Directory not accessible')
})
it('should return correct snippet structure', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'test-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM users;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01T10:00:00Z') } as any)
const result = await getSnippets({})
expect(result.snippets).toHaveLength(1)
const snippet = result.snippets[0]
expect(snippet).toMatchObject({
name: 'test-snippet',
type: 'sql',
content: {
sql: 'SELECT * FROM users;',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: null,
owner_id: 1,
owner: { id: 1, username: 'johndoe' },
updated_by: { id: 1, username: 'johndoe' },
})
expect(snippet.id).toBeDefined()
expect(snippet.inserted_at).toBe('2023-01-01T10:00:00.000Z')
expect(snippet.updated_at).toBe('2023-01-01T10:00:00.000Z')
})
it('should perform case-insensitive search', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'USER-query.sql', isDirectory: () => false, isFile: () => true },
{ name: 'admin-report.sql', isDirectory: () => false, isFile: () => true },
{ name: 'Test-User-Data.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ searchTerm: 'user', sort: 'name', sortOrder: 'asc' })
expect(result.snippets).toHaveLength(2)
expect(result.snippets.map((s) => s.name)).toEqual(['Test-User-Data', 'USER-query'])
})
it('should perform case-insensitive name sorting', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'zebra.sql', isDirectory: () => false, isFile: () => true },
{ name: 'Apple.sql', isDirectory: () => false, isFile: () => true },
{ name: 'banana.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ sort: 'name', sortOrder: 'asc' })
expect(result.snippets.map((s) => s.name)).toEqual(['Apple', 'banana', 'zebra'])
})
it('should validate limit parameter', async () => {
await expect(getSnippets({ limit: 0 })).rejects.toThrow('Limit must be a positive number')
await expect(getSnippets({ limit: -5 })).rejects.toThrow('Limit must be a positive number')
await expect(getSnippets({ limit: 1001 })).rejects.toThrow('Limit cannot exceed 1000')
})
it('should handle large valid limit', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'snippet1.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT 1;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await getSnippets({ limit: 1000 })
expect(result.snippets).toHaveLength(1)
})
})
describe('saveSnippet', () => {
it('should save a snippet to the filesystem', async () => {
const mockSnippet: Snippet = {
id: 'test-id',
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: 'test-snippet',
description: 'Test snippet',
favorite: false,
content: {
sql: 'SELECT * FROM test;',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: null,
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
// Mock that no existing snippet exists
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
mockedFS.writeFile.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await saveSnippet(mockSnippet)
expect(mockedFS.writeFile).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'test-snippet.sql'),
'SELECT * FROM test;',
'utf-8'
)
expect(result.name).toBe('test-snippet')
expect(result.content.sql).toBe('SELECT * FROM test;')
})
it('should handle empty content', async () => {
const mockSnippet: Snippet = {
id: 'test-id',
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: 'empty-snippet',
description: '',
favorite: false,
content: {
sql: '',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: null,
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
mockedFS.writeFile.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await saveSnippet(mockSnippet)
expect(mockedFS.writeFile).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'empty-snippet.sql'),
'',
'utf-8'
)
expect(result.content.sql).toBe('')
})
it('should throw error when snippet already exists', async () => {
const mockSnippet: Snippet = {
id: generateDeterministicUuid(['existing-snippet.sql']),
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: 'existing-snippet',
description: '',
favorite: false,
content: {
sql: 'SELECT * FROM test;',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: null,
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
// Mock that snippet already exists
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'existing-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM existing;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
await expect(saveSnippet(mockSnippet)).rejects.toThrow(
`Snippet with id ${mockSnippet.id} already exists`
)
})
it('should save snippet to a folder when folder_id is provided', async () => {
const mockSnippet: Snippet = {
id: 'test-id',
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: 'folder-snippet',
description: '',
favorite: false,
content: {
sql: 'SELECT * FROM folder_table;',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: generateDeterministicUuid(['test-folder']),
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'test-folder', isDirectory: () => true, isFile: () => false },
] as any)
mockedFS.writeFile.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await saveSnippet(mockSnippet)
expect(mockedFS.writeFile).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'test-folder', 'folder-snippet.sql'),
'SELECT * FROM folder_table;',
'utf-8'
)
expect(result.folder_id).toBe(generateDeterministicUuid(['test-folder']))
})
it('should throw error when folder_id is provided but folder does not exist', async () => {
const mockSnippet: Snippet = {
id: 'test-id',
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: 'orphan-snippet',
description: '',
favorite: false,
content: {
sql: 'SELECT * FROM table;',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: 'non-existent-folder-id',
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(saveSnippet(mockSnippet)).rejects.toThrow(
'Folder with id non-existent-folder-id not found'
)
})
it('should reject path traversal attempts with ../', async () => {
const mockSnippet: Snippet = {
id: 'test-id',
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: '../malicious-snippet',
description: '',
favorite: false,
content: {
sql: 'SELECT * FROM test;',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: null,
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(saveSnippet(mockSnippet)).rejects.toThrow(
'Invalid name: path traversal or null bytes detected'
)
})
it('should reject path traversal attempts with nested directories', async () => {
const mockSnippet: Snippet = {
id: 'test-id',
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: 'foo/bar/snippet',
description: '',
favorite: false,
content: {
sql: 'SELECT * FROM test;',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: null,
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(saveSnippet(mockSnippet)).rejects.toThrow(
'Invalid name: path traversal or null bytes detected'
)
})
it('should reject names with null bytes', async () => {
const mockSnippet: Snippet = {
id: 'test-id',
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: 'malicious\0snippet',
description: '',
favorite: false,
content: {
sql: 'SELECT * FROM test;',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: null,
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(saveSnippet(mockSnippet)).rejects.toThrow(
'Invalid name: path traversal or null bytes detected'
)
})
it('should reject absolute path attempts', async () => {
const mockSnippet: Snippet = {
id: 'test-id',
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: '/etc/passwd',
description: '',
favorite: false,
content: {
sql: 'SELECT * FROM test;',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: null,
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(saveSnippet(mockSnippet)).rejects.toThrow(
'Invalid name: path traversal or null bytes detected'
)
})
it('should allow valid names with special characters', async () => {
const mockSnippet: Snippet = {
id: 'test-id',
inserted_at: '2023-01-01T00:00:00.000Z',
updated_at: '2023-01-01T00:00:00.000Z',
type: 'sql',
name: 'my-snippet_v2 (copy)',
description: '',
favorite: false,
content: {
sql: 'SELECT * FROM test;',
content_id: 'content-id',
schema_version: '1.0',
},
visibility: 'user',
project_id: 1,
folder_id: null,
owner_id: 1,
owner: { id: 1, username: 'testuser' },
updated_by: { id: 1, username: 'testuser' },
}
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
mockedFS.writeFile.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const result = await saveSnippet(mockSnippet)
expect(result.name).toBe('my-snippet_v2 (copy)')
expect(mockedFS.writeFile).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'my-snippet_v2 (copy).sql'),
'SELECT * FROM test;',
'utf-8'
)
})
})
describe('deleteSnippet', () => {
it('should delete a snippet file', async () => {
const snippetId = generateDeterministicUuid(['test-snippet.sql'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'test-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM test;')
mockedFS.unlink.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
await deleteSnippet(snippetId)
expect(mockedFS.unlink).toHaveBeenCalledWith(path.join(MOCK_SNIPPETS_DIR, `test-snippet.sql`))
})
it('should not throw error when file does not exist', async () => {
const snippetId = generateDeterministicUuid(['test-snippet.sql'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'test-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM test;')
const error = new Error('File not found') as NodeJS.ErrnoException
error.code = 'ENOENT'
mockedFS.unlink.mockRejectedValue(error)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
await expect(deleteSnippet(snippetId)).resolves.not.toThrow()
})
it('should throw error for other filesystem errors', async () => {
const snippetId = generateDeterministicUuid(['test-snippet.sql'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'test-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM test;')
const error = new Error('Permission denied') as NodeJS.ErrnoException
error.code = 'EACCES'
mockedFS.unlink.mockRejectedValue(error)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
await expect(deleteSnippet(snippetId)).rejects.toThrow('Permission denied')
})
it('should throw error when snippet not found', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(deleteSnippet('non-existent-id')).rejects.toThrow(
'Snippet with id non-existent-id not found'
)
})
it('should delete a snippet file from a folder', async () => {
const folderName = 'my-folder'
const folderId = generateDeterministicUuid([folderName])
const snippetName = 'folder-snippet'
const snippetId = generateDeterministicUuid([folderId, `${snippetName}.sql`])
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve([createMockDirent(folderName, true)] as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, folderName)) {
return Promise.resolve([createMockDirent('folder-snippet.sql', false)] as any)
}
return Promise.resolve([])
})
mockedFS.readFile.mockResolvedValue('SELECT * FROM folder_table;')
mockedFS.unlink.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
await deleteSnippet(snippetId)
expect(mockedFS.unlink).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'my-folder', 'folder-snippet.sql')
)
})
})
describe('updateSnippet', () => {
it('should update an existing snippet', async () => {
const id = generateDeterministicUuid(['existing-snippet.sql'])
mockedFS.access.mockResolvedValue(undefined)
// First call to getFilesystemEntries - find existing snippet
mockedFS.readdir.mockResolvedValue([
{ name: 'existing-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
// Read the existing snippet content
mockedFS.readFile.mockResolvedValue('SELECT * FROM old;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const updates = { name: 'updated-snippet', content: { sql: 'SELECT * FROM new;' } }
const result = await updateSnippet(id, updates)
expect(mockedFS.unlink).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'existing-snippet.sql')
)
expect(mockedFS.writeFile).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'updated-snippet.sql'),
'SELECT * FROM new;',
'utf-8'
)
expect(result.name).toBe('updated-snippet')
expect(result.content.sql).toBe('SELECT * FROM new;')
})
it('should throw error when snippet not found', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(updateSnippet('non-existent-id', {})).rejects.toThrow(
'Snippet with id non-existent-id not found'
)
})
it('should update only provided fields', async () => {
const id = generateDeterministicUuid(['existing-snippet.sql'])
mockedFS.access.mockResolvedValue(undefined)
// Mock readdir for both getFilesystemEntries calls (initial and final)
mockedFS.readdir.mockResolvedValue([
{ name: 'existing-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
// Mock readFile for both calls (initial and final)
mockedFS.readFile.mockResolvedValue('SELECT * FROM old;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
mockedFS.writeFile.mockResolvedValue(undefined)
const updates = {
name: 'new-name',
}
const result = await updateSnippet(id, updates)
expect(result.name).toBe('new-name')
expect(result.content.sql).toBe('SELECT * FROM old;')
})
it('should move snippet to a folder when folder_id is updated', async () => {
const id = generateDeterministicUuid(['existing-snippet.sql'])
const targetFolderId = generateDeterministicUuid(['target-folder'])
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
// Mock existing snippet in root directory
mockedFS.access.mockResolvedValue(undefined)
// Mock multiple readdir calls - the function calls getFilesystemEntries twice
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve([
createMockDirent('existing-snippet.sql', false),
createMockDirent('target-folder', true),
] as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, 'target-folder')) {
return Promise.resolve([])
}
return Promise.resolve([])
})
mockedFS.readFile.mockResolvedValue('SELECT * FROM old;')
mockedFS.writeFile.mockResolvedValue(undefined)
mockedFS.unlink.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const updates = { folder_id: targetFolderId }
const result = await updateSnippet(id, updates)
// Should write to the folder path
expect(mockedFS.writeFile).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'target-folder', 'existing-snippet.sql'),
'SELECT * FROM old;',
'utf-8'
)
// Should delete the old file from root
expect(mockedFS.unlink).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'existing-snippet.sql')
)
expect(result.folder_id).toBe(targetFolderId)
})
it('should move snippet from folder to root when folder_id is set to null', async () => {
const folderName = 'source-folder'
const folderId = generateDeterministicUuid([folderName])
const id = generateDeterministicUuid([folderId, 'existing-snippet.sql'])
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
mockedFS.access.mockResolvedValue(undefined)
// Mock existing snippet in a folder - the function expects the snippet to have folder_id set
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve([createMockDirent(folderName, true)] as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, folderName)) {
return Promise.resolve([createMockDirent('existing-snippet.sql', false)] as any)
}
return Promise.resolve([])
})
mockedFS.readFile.mockResolvedValue('SELECT * FROM folder;')
mockedFS.writeFile.mockResolvedValue(undefined)
mockedFS.unlink.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const updates = { folder_id: null }
const result = await updateSnippet(id, updates)
// Should write to the root path
expect(mockedFS.writeFile).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'existing-snippet.sql'),
'SELECT * FROM folder;',
'utf-8'
)
// Should delete the old file from folder
expect(mockedFS.unlink).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'source-folder', 'existing-snippet.sql')
)
expect(result.folder_id).toBe(null)
})
it('should throw error when trying to move to non-existent folder', async () => {
const id = generateDeterministicUuid(['existing-snippet.sql'])
const nonExistentFolderId = 'non-existent-folder-id'
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'existing-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM table;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const updates = { folder_id: nonExistentFolderId }
await expect(updateSnippet(id, updates)).rejects.toThrow(
`Folder with id ${nonExistentFolderId} not found`
)
})
it('should handle renaming snippet while moving to folder', async () => {
const id = generateDeterministicUuid(['existing-snippet.sql'])
const targetFolderId = generateDeterministicUuid(['target-folder'])
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve([
createMockDirent('existing-snippet.sql', false),
createMockDirent('target-folder', true),
] as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, 'target-folder')) {
return Promise.resolve([])
}
return Promise.resolve([])
})
mockedFS.readFile.mockResolvedValue('SELECT * FROM old;')
mockedFS.writeFile.mockResolvedValue(undefined)
mockedFS.unlink.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const updates = {
name: 'renamed-snippet',
folder_id: targetFolderId,
content: { sql: 'SELECT * FROM new;' },
}
const result = await updateSnippet(id, updates as any)
// Should write to the folder path with new name
expect(mockedFS.writeFile).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'target-folder', 'renamed-snippet.sql'),
'SELECT * FROM new;',
'utf-8'
)
// Should delete the old file from root with old name
expect(mockedFS.unlink).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'existing-snippet.sql')
)
expect(result.name).toBe('renamed-snippet')
expect(result.folder_id).toBe(targetFolderId)
expect(result.content.sql).toBe('SELECT * FROM new;')
})
it('should update content to empty string', async () => {
const id = generateDeterministicUuid(['existing-snippet.sql'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'existing-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM old;')
mockedFS.writeFile.mockResolvedValue(undefined)
mockedFS.unlink.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const updates = { content: { sql: '' } }
const result = await updateSnippet(id, updates as any)
expect(result.content.sql).toBe('')
expect(mockedFS.writeFile).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'existing-snippet.sql'),
'',
'utf-8'
)
})
it('should handle old file deletion errors other than ENOENT', async () => {
const id = generateDeterministicUuid(['existing-snippet.sql'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'existing-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM old;')
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const error = new Error('Permission denied') as NodeJS.ErrnoException
error.code = 'EACCES'
mockedFS.unlink.mockRejectedValue(error)
const updates = { name: 'new-name' }
await expect(updateSnippet(id, updates)).rejects.toThrow('Permission denied')
})
it('should continue when old file deletion fails with ENOENT', async () => {
const id = generateDeterministicUuid(['existing-snippet.sql'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'existing-snippet.sql', isDirectory: () => false, isFile: () => true },
] as any)
mockedFS.readFile.mockResolvedValue('SELECT * FROM old;')
mockedFS.writeFile.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const error = new Error('File not found') as NodeJS.ErrnoException
error.code = 'ENOENT'
mockedFS.unlink.mockRejectedValue(error)
const updates = { name: 'new-name' }
const result = await updateSnippet(id, updates)
expect(result.name).toBe('new-name')
expect(result.content.sql).toBe('SELECT * FROM old;')
})
it('should throw error when moving snippet to folder that already contains snippet with same name', async () => {
const snippetName = 'existing-snippet'
const existingSnippetId = generateDeterministicUuid([`${snippetName}.sql`])
const targetFolderId = generateDeterministicUuid(['target-folder'])
const createMockDirent = (name: string, isDirectory: boolean) => ({
name,
isDirectory: () => isDirectory,
isFile: () => !isDirectory,
})
mockedFS.access.mockResolvedValue(undefined)
// Mock filesystem structure with:
// - existing-snippet.sql in root
// - target-folder/ with existing-snippet.sql inside
mockedFS.readdir.mockImplementation((dirPath: any) => {
if (dirPath === MOCK_SNIPPETS_DIR) {
return Promise.resolve([
createMockDirent('existing-snippet.sql', false),
createMockDirent('target-folder', true),
] as any)
} else if (dirPath === path.join(MOCK_SNIPPETS_DIR, 'target-folder')) {
return Promise.resolve([createMockDirent(`${snippetName}.sql`, false)] as any)
}
return Promise.resolve([])
})
mockedFS.readFile.mockResolvedValue('SELECT * FROM table;')
mockedFS.unlink.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const updates = { folder_id: targetFolderId }
await expect(updateSnippet(existingSnippetId, updates)).rejects.toThrow(
`Snippet named "${snippetName}" already exists in the specified folder`
)
})
})
describe('getFolders', () => {
it('should read directories from the snippets folder', async () => {
const mockDirents = [
{ name: 'folder1', isDirectory: () => true, isFile: () => false },
{ name: 'folder2', isDirectory: () => true, isFile: () => false },
{ name: 'snippet.sql', isDirectory: () => false, isFile: () => true },
]
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir
.mockResolvedValueOnce(mockDirents as any) // First call for getFilesystemEntries
.mockResolvedValueOnce([]) // folder1 contents (empty)
.mockResolvedValueOnce([]) // folder2 contents (empty)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const folders = await getFolders()
expect(folders).toHaveLength(2)
expect(folders[0].name).toBe('folder1')
expect(folders[1].name).toBe('folder2')
expect(folders[0].id).toBe(generateDeterministicUuid(['folder1']))
expect(folders[1].id).toBe(generateDeterministicUuid(['folder2']))
})
it('should return empty array when no directories exist', async () => {
const mockDirents = [{ name: 'snippet.sql', isDirectory: () => false, isFile: () => true }]
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue(mockDirents as any)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
const folders = await getFolders()
expect(folders).toEqual([])
})
it('should handle directory creation when it does not exist', async () => {
mockedFS.access.mockRejectedValue(new Error('Directory does not exist'))
mockedFS.readdir.mockResolvedValue([])
const folders = await getFolders()
expect(folders).toEqual([])
})
})
describe('createFolder', () => {
it('should create a new folder as an actual directory', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([]) // No existing folders
mockedFS.mkdir.mockResolvedValue(undefined)
const result = await createFolder('New Folder')
expect(result.id).toBe(generateDeterministicUuid(['New Folder']))
expect(result.name).toBe('New Folder')
expect(result.owner_id).toBe(1)
expect(mockedFS.mkdir).toHaveBeenCalledWith(path.join(MOCK_SNIPPETS_DIR, 'New Folder'), {
recursive: true,
})
})
it('should handle folder creation with special characters in name', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
mockedFS.mkdir.mockResolvedValue(undefined)
const result = await createFolder('Folder with spaces & symbols!')
expect(result.id).toBe(generateDeterministicUuid(['Folder with spaces & symbols!']))
expect(result.name).toBe('Folder with spaces & symbols!')
expect(mockedFS.mkdir).toHaveBeenCalledWith(
path.join(MOCK_SNIPPETS_DIR, 'Folder with spaces & symbols!'),
{ recursive: true }
)
})
it('should throw error when folder already exists', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'Existing Folder', isDirectory: () => true, isFile: () => false },
] as any)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
await expect(createFolder('Existing Folder')).rejects.toThrow(
'Folder with name Existing Folder already exists'
)
})
it('should handle mkdir errors', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
mockedFS.mkdir.mockRejectedValue(new Error('Permission denied'))
await expect(createFolder('New Folder')).rejects.toThrow('Permission denied')
})
it('should create directory when snippets directory does not exist', async () => {
mockedFS.access.mockRejectedValue(new Error('Directory does not exist'))
mockedFS.readdir.mockResolvedValue([])
mockedFS.mkdir.mockResolvedValue(undefined)
const result = await createFolder('New Folder')
expect(result.name).toBe('New Folder')
expect(mockedFS.mkdir).toHaveBeenCalledWith(path.join(MOCK_SNIPPETS_DIR, 'New Folder'), {
recursive: true,
})
})
it('should reject path traversal attempts with ../', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(createFolder('../malicious-folder')).rejects.toThrow(
'Invalid name: path traversal or null bytes detected'
)
})
it('should reject path traversal attempts with nested directories', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(createFolder('foo/bar/folder')).rejects.toThrow(
'Invalid name: path traversal or null bytes detected'
)
})
it('should reject names with null bytes', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(createFolder('malicious\0folder')).rejects.toThrow(
'Invalid name: path traversal or null bytes detected'
)
})
it('should reject absolute path attempts', async () => {
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(createFolder('/etc/malicious')).rejects.toThrow(
'Invalid name: path traversal or null bytes detected'
)
})
})
describe('deleteFolder', () => {
it('should delete an existing folder directory', async () => {
const folderId = generateDeterministicUuid(['Delete Me'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'Delete Me', isDirectory: () => true, isFile: () => false, folderId: null },
{ name: 'Keep Me', isDirectory: () => true, isFile: () => false, folderId: null },
] as any)
mockedFS.rm.mockResolvedValue(undefined)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
await deleteFolder(folderId)
expect(mockedFS.rm).toHaveBeenCalledWith(path.join(MOCK_SNIPPETS_DIR, 'Delete Me'), {
recursive: true,
force: true,
})
})
it('should throw error when folder not found', async () => {
const nonExistentId = 'non-existent-folder-id'
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([])
await expect(deleteFolder(nonExistentId)).rejects.toThrow(
`Folder with id ${nonExistentId} not found`
)
})
it('should handle directory deletion errors gracefully', async () => {
const folderId = generateDeterministicUuid(['Test Folder'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'Test Folder', isDirectory: () => true, isFile: () => false, folderId: null },
] as any)
const error = new Error('Permission denied') as NodeJS.ErrnoException
error.code = 'EACCES'
mockedFS.rm.mockRejectedValue(error)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
await expect(deleteFolder(folderId)).rejects.toThrow('Permission denied')
})
it('should throw original error when directory does not exist during deletion', async () => {
const folderId = generateDeterministicUuid(['Test Folder'])
mockedFS.access.mockResolvedValue(undefined)
mockedFS.readdir.mockResolvedValue([
{ name: 'Test Folder', isDirectory: () => true, isFile: () => false, folderId: null },
] as any)
const error = new Error('Directory not found') as NodeJS.ErrnoException
error.code = 'ENOENT'
mockedFS.rm.mockRejectedValue(error)
mockedFS.stat.mockResolvedValue({ birthtime: new Date('2023-01-01') } as any)
await expect(deleteFolder(folderId)).rejects.toThrow(`Folder with id ${folderId} not found`)
})
it('should handle case when snippets directory needs to be created first', async () => {
const nonExistentId = 'non-existent-folder-id'
mockedFS.access.mockRejectedValue(new Error('Directory does not exist'))
mockedFS.readdir.mockResolvedValue([])
await expect(deleteFolder(nonExistentId)).rejects.toThrow(
`Folder with id ${nonExistentId} not found`
)
})
})
})