From 612d542c062b8232b24f93c891381806dd41427e Mon Sep 17 00:00:00 2001 From: curo1305 Date: Fri, 29 May 2026 08:05:59 +0200 Subject: [PATCH] feat(05-07): cloud connections Pinia store + API client functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- frontend/package.json | 3 +- frontend/src/api/client.js | 26 ++++++++ .../stores/__tests__/cloudConnections.test.js | 59 +++++++++++++++++++ frontend/src/stores/cloudConnections.js | 39 ++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 frontend/src/stores/__tests__/cloudConnections.test.js create mode 100644 frontend/src/stores/cloudConnections.js diff --git a/frontend/package.json b/frontend/package.json index 6ce0a35..1bc493c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,8 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest run" }, "dependencies": { "pinia": "^2.1.0", diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index da51261..aa5783f 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -364,3 +364,29 @@ export function adminListAuditLog({ start, end, user_id, event_type, page = 1, p export function getDocumentContentUrl(docId) { 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 }), + }) +} diff --git a/frontend/src/stores/__tests__/cloudConnections.test.js b/frontend/src/stores/__tests__/cloudConnections.test.js new file mode 100644 index 0000000..94ba165 --- /dev/null +++ b/frontend/src/stores/__tests__/cloudConnections.test.js @@ -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) + }) +}) diff --git a/frontend/src/stores/cloudConnections.js b/frontend/src/stores/cloudConnections.js new file mode 100644 index 0000000..fb39a41 --- /dev/null +++ b/frontend/src/stores/cloudConnections.js @@ -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 } +})