feat(05-07): cloud connections Pinia store + API client functions

- Create useCloudConnectionsStore with connections/loading/error refs
- fetchConnections, disconnect(id), disconnectAll() actions
- Append listCloudConnections, disconnectCloud, connectWebDav, updateDefaultStorage to api/client.js
- Add vitest test script to package.json
- 4 unit tests passing (W4 — CLAUDE.md)
This commit is contained in:
curo1305
2026-05-29 08:05:59 +02:00
parent c44e861271
commit 612d542c06
4 changed files with 126 additions and 1 deletions
+2 -1
View File
@@ -5,7 +5,8 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview",
"test": "vitest run"
}, },
"dependencies": { "dependencies": {
"pinia": "^2.1.0", "pinia": "^2.1.0",
+26
View File
@@ -364,3 +364,29 @@ export function adminListAuditLog({ start, end, user_id, event_type, page = 1, p
export function getDocumentContentUrl(docId) { export function getDocumentContentUrl(docId) {
return `/api/documents/${docId}/content` return `/api/documents/${docId}/content`
} }
// ── Cloud Storage ─────────────────────────────────────────────────────────────
export function listCloudConnections() {
return request('/api/cloud/connections')
}
export function disconnectCloud(id) {
return request(`/api/cloud/connections/${id}`, { method: 'DELETE' })
}
export function connectWebDav(provider, serverUrl, username, password) {
return request('/api/cloud/connections/webdav', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ provider, server_url: serverUrl, username, password }),
})
}
export function updateDefaultStorage(backend) {
return request('/api/users/me/default-storage', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ backend }),
})
}
@@ -0,0 +1,59 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
// Mock api/client.js — no real HTTP calls in unit tests (CLAUDE.md W4)
vi.mock('../../api/client.js', () => ({
listCloudConnections: vi.fn(),
disconnectCloud: vi.fn(),
connectWebDav: vi.fn(),
updateDefaultStorage: vi.fn(),
}))
import { useCloudConnectionsStore } from '../cloudConnections.js'
import * as api from '../../api/client.js'
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
})
describe('useCloudConnectionsStore', () => {
it('fetchConnections sets connections from API response', async () => {
api.listCloudConnections.mockResolvedValue({
items: [{ id: '1', provider: 'google_drive', status: 'ACTIVE' }],
})
const store = useCloudConnectionsStore()
await store.fetchConnections()
expect(store.connections).toHaveLength(1)
expect(store.connections[0].provider).toBe('google_drive')
expect(store.loading).toBe(false)
})
it('fetchConnections sets error on API failure', async () => {
api.listCloudConnections.mockRejectedValue(new Error('Network error'))
const store = useCloudConnectionsStore()
await store.fetchConnections()
expect(store.error).toBeTruthy()
expect(store.connections).toHaveLength(0)
})
it('disconnect removes connection from state after API call', async () => {
api.disconnectCloud.mockResolvedValue(null)
const store = useCloudConnectionsStore()
store.connections = [{ id: 'conn-1', provider: 'google_drive', status: 'ACTIVE' }]
await store.disconnect('conn-1')
expect(store.connections).toHaveLength(0)
expect(api.disconnectCloud).toHaveBeenCalledWith('conn-1')
})
it('disconnectAll clears all connections', async () => {
api.disconnectCloud.mockResolvedValue(null)
const store = useCloudConnectionsStore()
store.connections = [
{ id: 'a', provider: 'google_drive', status: 'ACTIVE' },
{ id: 'b', provider: 'onedrive', status: 'ACTIVE' },
]
await store.disconnectAll()
expect(store.connections).toHaveLength(0)
})
})
+39
View File
@@ -0,0 +1,39 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import * as api from '../api/client.js'
export const useCloudConnectionsStore = defineStore('cloudConnections', () => {
const connections = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchConnections() {
loading.value = true
error.value = null
try {
const data = await api.listCloudConnections()
connections.value = data.items ?? []
} catch (e) {
error.value = e.message || 'Failed to load cloud connections'
} finally {
loading.value = false
}
}
async function disconnect(id) {
try {
await api.disconnectCloud(id)
connections.value = connections.value.filter(c => c.id !== id)
} catch (e) {
throw e
}
}
async function disconnectAll() {
const ids = connections.value.map(c => c.id)
for (const id of ids) await disconnect(id)
connections.value = []
}
return { connections, loading, error, fetchConnections, disconnect, disconnectAll }
})