Share persistent alerts, distinguish partial persistence from validation errors, preserve model identity on retries, and apply the store's deletion eligibility before clearing credentials. Document toast, field-error, alert, and Storybook ownership.
130 lines
4.3 KiB
TypeScript
130 lines
4.3 KiB
TypeScript
import { expect, test } from 'bun:test'
|
|
|
|
import { effectScope, ref } from 'vue'
|
|
|
|
import { useStorageSettings } from '@/app/integrations/storage/settings/use'
|
|
import type { CredentialManager } from '@/app/settings/credentials/types'
|
|
|
|
function fixture() {
|
|
const preferences = ref<Record<string, string>>({
|
|
endpoint: 'https://old.example.com',
|
|
bucket: 'designs',
|
|
region: 'auto'
|
|
})
|
|
const writes: string[] = []
|
|
const tests: Record<string, string>[] = []
|
|
const manager: CredentialManager = {
|
|
backend: 'memory',
|
|
availability: async () => 'available',
|
|
status: async () => 'configured',
|
|
set: async (reference) => {
|
|
writes.push(`set:${reference.field}`)
|
|
},
|
|
clear: async (reference) => {
|
|
writes.push(`clear:${reference.field}`)
|
|
}
|
|
}
|
|
return {
|
|
preferences,
|
|
writes,
|
|
tests,
|
|
services: {
|
|
readPreferences: () => ({ ...preferences.value }),
|
|
writePreference: (_provider: string, field: string, value: string) => {
|
|
preferences.value = { ...preferences.value, [field]: value }
|
|
writes.push(`preference:${field}`)
|
|
},
|
|
statuses: async () => ({
|
|
'access-key-id': 'configured' as const,
|
|
'secret-access-key': 'configured' as const
|
|
}),
|
|
manager,
|
|
test: async (_provider: string, values: Record<string, string>) => {
|
|
tests.push(values)
|
|
return { ok: true, message: 'Connected' }
|
|
},
|
|
resume: async () => undefined
|
|
}
|
|
}
|
|
}
|
|
|
|
test('testing storage drafts and cancelling never persist settings', async () => {
|
|
const f = fixture()
|
|
const scope = effectScope()
|
|
const credentials = ref<Record<string, string>>({})
|
|
try {
|
|
const state = scope.run(() => useStorageSettings(credentials, f.services))
|
|
if (!state) throw new Error('Missing scope')
|
|
state.preferenceDrafts.value.endpoint = 'https://draft.example.com'
|
|
credentials.value['secret-access-key'] = 'test-only-draft'
|
|
expect(state.dirty.value).toBe(true)
|
|
expect(await state.testConnection()).toEqual({ ok: true, message: 'Connected' })
|
|
expect(f.tests[0]?.endpoint).toBe('https://draft.example.com')
|
|
expect(f.writes).toEqual([])
|
|
expect(f.preferences.value.endpoint).toBe('https://old.example.com')
|
|
state.cancel()
|
|
expect(state.dirty.value).toBe(false)
|
|
expect(credentials.value).toEqual({})
|
|
expect(state.preferenceDrafts.value.endpoint).toBe('https://old.example.com')
|
|
} finally {
|
|
scope.stop()
|
|
}
|
|
})
|
|
|
|
test('credential clearing is staged until Save', async () => {
|
|
const f = fixture()
|
|
const scope = effectScope()
|
|
try {
|
|
const state = scope.run(() => useStorageSettings(ref({}), f.services))
|
|
if (!state) throw new Error('Missing scope')
|
|
state.clearCredential('secret-access-key')
|
|
expect(f.writes).toEqual([])
|
|
expect(state.dirty.value).toBe(true)
|
|
expect(await state.save()).toBe('saved')
|
|
expect(f.writes).toContain('clear:secret-access-key')
|
|
expect(state.dirty.value).toBe(false)
|
|
} finally {
|
|
scope.stop()
|
|
}
|
|
})
|
|
|
|
test('failure before any storage write is not reported as a partial save', async () => {
|
|
const f = fixture()
|
|
f.services.writePreference = () => {
|
|
throw new Error('Preferences unavailable')
|
|
}
|
|
const scope = effectScope()
|
|
try {
|
|
const state = scope.run(() => useStorageSettings(ref({}), f.services))
|
|
if (!state) throw new Error('Missing scope')
|
|
expect(await state.save()).toBe('failed')
|
|
expect(state.saveResult.value).toBe('failed')
|
|
expect(f.writes).toEqual([])
|
|
} finally {
|
|
scope.stop()
|
|
}
|
|
})
|
|
|
|
test('failed storage saves retain replacement drafts and expose the failure', async () => {
|
|
const f = fixture()
|
|
f.services.manager.set = async () => {
|
|
throw new Error('Credential store locked')
|
|
}
|
|
const scope = effectScope()
|
|
const credentials = ref<Record<string, string>>({})
|
|
try {
|
|
const state = scope.run(() => useStorageSettings(credentials, f.services))
|
|
if (!state) throw new Error('Missing scope')
|
|
credentials.value['secret-access-key'] = 'test-only-replacement'
|
|
expect(await state.save()).toBe('partial')
|
|
expect(state.saveResult.value).toBe('partial')
|
|
expect(state.error.value).toBe('Credential store locked')
|
|
expect(state.busy.value).toBe(false)
|
|
expect(credentials.value['secret-access-key']).toBe('test-only-replacement')
|
|
scope.stop()
|
|
expect(credentials.value).toEqual({})
|
|
} finally {
|
|
scope.stop()
|
|
}
|
|
})
|