import { describe, it, expect, vi, beforeEach } from 'vitest' import { setActivePinia, createPinia } from 'pinia' vi.mock('@/api/filebrowser-client', () => ({ fileBrowserClient: { login: vi.fn(), listDirectory: vi.fn(), upload: vi.fn(), deleteItem: vi.fn(), downloadUrl: vi.fn(), createFolder: vi.fn(), }, })) import { useCloudStore } from '../cloud' import { fileBrowserClient } from '@/api/filebrowser-client' const mockedClient = vi.mocked(fileBrowserClient) const mockItems = [ { name: 'photos', path: '/photos', size: 0, modified: '2026-01-01', isDir: true, type: '', extension: '' }, { name: 'readme.md', path: '/readme.md', size: 256, modified: '2026-01-02', isDir: false, type: '', extension: 'md' }, { name: 'archive.zip', path: '/archive.zip', size: 4096, modified: '2026-01-03', isDir: false, type: '', extension: 'zip' }, ] describe('useCloudStore', () => { beforeEach(() => { setActivePinia(createPinia()) vi.clearAllMocks() }) it('starts with default state', () => { const store = useCloudStore() expect(store.currentPath).toBe('/') expect(store.items).toEqual([]) expect(store.loading).toBe(false) expect(store.error).toBeNull() expect(store.authenticated).toBe(false) }) it('init authenticates with filebrowser', async () => { mockedClient.login.mockResolvedValue(true) const store = useCloudStore() const result = await store.init() expect(result).toBe(true) expect(store.authenticated).toBe(true) expect(mockedClient.login).toHaveBeenCalledOnce() }) it('init returns false on auth failure', async () => { mockedClient.login.mockResolvedValue(false) const store = useCloudStore() const result = await store.init() expect(result).toBe(false) expect(store.authenticated).toBe(false) }) it('init skips login if already authenticated', async () => { mockedClient.login.mockResolvedValue(true) const store = useCloudStore() await store.init() await store.init() expect(mockedClient.login).toHaveBeenCalledOnce() }) it('navigate loads items and updates path', async () => { mockedClient.login.mockResolvedValue(true) mockedClient.listDirectory.mockResolvedValue(mockItems) const store = useCloudStore() store.authenticated = true await store.navigate('/photos') expect(store.items).toEqual(mockItems) expect(store.currentPath).toBe('/photos') expect(store.loading).toBe(false) expect(store.error).toBeNull() }) it('navigate authenticates if not authenticated', async () => { mockedClient.login.mockResolvedValue(true) mockedClient.listDirectory.mockResolvedValue(mockItems) const store = useCloudStore() await store.navigate('/') expect(mockedClient.login).toHaveBeenCalled() expect(store.items).toEqual(mockItems) }) it('navigate sets error on auth failure', async () => { mockedClient.login.mockResolvedValue(false) const store = useCloudStore() await store.navigate('/') expect(store.error).toBe('Failed to authenticate with File Browser') expect(store.loading).toBe(false) }) it('navigate falls back to creating directory on list failure', async () => { const store = useCloudStore() store.authenticated = true // First listDirectory fails, then createFolder succeeds, then retry succeeds mockedClient.listDirectory .mockRejectedValueOnce(new Error('Not found')) .mockResolvedValueOnce([]) mockedClient.createFolder.mockResolvedValue(undefined) await store.navigate('/new-folder') expect(mockedClient.createFolder).toHaveBeenCalledWith('/', 'new-folder') expect(store.currentPath).toBe('/new-folder') }) it('navigate falls back to root when directory creation also fails', async () => { const store = useCloudStore() store.authenticated = true // Call 1: listDirectory('/deep/nested') rejects // Call 2: listDirectory('/') in the fallback catch resolves mockedClient.listDirectory .mockRejectedValueOnce(new Error('Not found')) .mockResolvedValueOnce(mockItems) mockedClient.createFolder.mockRejectedValueOnce(new Error('Create failed')) await store.navigate('/deep/nested') expect(store.currentPath).toBe('/') expect(store.items).toEqual(mockItems) }) it('navigate sets error when root listing fails', async () => { const store = useCloudStore() store.authenticated = true mockedClient.listDirectory.mockRejectedValueOnce(new Error('Server error')) await store.navigate('/') expect(store.error).toBe('Failed to list root directory') }) it('breadcrumbs computes from path', () => { const store = useCloudStore() store.currentPath = '/photos/vacation/2026' expect(store.breadcrumbs).toEqual([ { name: 'Home', path: '/' }, { name: 'photos', path: '/photos' }, { name: 'vacation', path: '/photos/vacation' }, { name: '2026', path: '/photos/vacation/2026' }, ]) }) it('breadcrumbs at root only shows Home', () => { const store = useCloudStore() expect(store.breadcrumbs).toEqual([{ name: 'Home', path: '/' }]) }) it('sortedItems puts directories first, sorted alphabetically', () => { const store = useCloudStore() store.items = [ { name: 'readme.md', path: '/readme.md', size: 256, modified: '2026-01-01', isDir: false, type: '', extension: 'md' }, { name: 'docs', path: '/docs', size: 0, modified: '2026-01-01', isDir: true, type: '', extension: '' }, { name: 'archive.zip', path: '/archive.zip', size: 4096, modified: '2026-01-01', isDir: false, type: '', extension: 'zip' }, { name: 'assets', path: '/assets', size: 0, modified: '2026-01-01', isDir: true, type: '', extension: '' }, ] const sorted = store.sortedItems expect(sorted.map((i) => i.name)).toEqual(['assets', 'docs', 'archive.zip', 'readme.md']) }) it('uploadFile uploads and refreshes', async () => { const store = useCloudStore() store.authenticated = true store.currentPath = '/uploads' mockedClient.upload.mockResolvedValue(undefined) mockedClient.listDirectory.mockResolvedValue([]) const file = new File(['test'], 'test.txt') await store.uploadFile(file) expect(mockedClient.upload).toHaveBeenCalledWith('/uploads', file) expect(mockedClient.listDirectory).toHaveBeenCalled() }) it('deleteItem deletes and refreshes', async () => { const store = useCloudStore() store.authenticated = true store.currentPath = '/' mockedClient.deleteItem.mockResolvedValue(undefined) mockedClient.listDirectory.mockResolvedValue([]) await store.deleteItem('/old-file.txt') expect(mockedClient.deleteItem).toHaveBeenCalledWith('/old-file.txt') expect(mockedClient.listDirectory).toHaveBeenCalled() }) it('downloadUrl delegates to filebrowser client', () => { mockedClient.downloadUrl.mockReturnValue('http://localhost/api/raw/file.txt?auth=token') const store = useCloudStore() const url = store.downloadUrl('/file.txt') expect(url).toBe('http://localhost/api/raw/file.txt?auth=token') expect(mockedClient.downloadUrl).toHaveBeenCalledWith('/file.txt') }) it('reset clears all state', () => { const store = useCloudStore() store.currentPath = '/deep/path' store.items = mockItems store.loading = true store.error = 'something' store.reset() expect(store.currentPath).toBe('/') expect(store.items).toEqual([]) expect(store.loading).toBe(false) expect(store.error).toBeNull() }) })