Skip to content

Commit 1bf7870

Browse files
refactor(vue3): migrate TSA settings to script setup ts
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 085678b commit 1bf7870

2 files changed

Lines changed: 485 additions & 248 deletions

File tree

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 LibreSign contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { flushPromises, mount } from '@vue/test-utils'
8+
9+
import TSA from '../../../views/Settings/TSA.vue'
10+
11+
const loadStateMock = vi.fn()
12+
const confirmPasswordMock = vi.fn()
13+
const axiosPostMock = vi.fn()
14+
const axiosDeleteMock = vi.fn()
15+
16+
vi.mock('@nextcloud/initial-state', () => ({
17+
loadState: (...args: unknown[]) => loadStateMock(...args),
18+
}))
19+
20+
vi.mock('@nextcloud/password-confirmation', () => ({
21+
confirmPassword: (...args: unknown[]) => confirmPasswordMock(...args),
22+
}))
23+
24+
vi.mock('@nextcloud/router', () => ({
25+
generateOcsUrl: vi.fn((path: string) => path),
26+
}))
27+
28+
vi.mock('@nextcloud/axios', () => ({
29+
default: {
30+
post: (...args: unknown[]) => axiosPostMock(...args),
31+
delete: (...args: unknown[]) => axiosDeleteMock(...args),
32+
},
33+
}))
34+
35+
vi.mock('@nextcloud/l10n', () => ({
36+
t: vi.fn((_app: string, text: string, vars?: Record<string, string>) => {
37+
if (!vars) {
38+
return text
39+
}
40+
return text.replace(/{(\w+)}/g, (_match, key) => String(vars[key]))
41+
}),
42+
translate: vi.fn((_app: string, text: string) => text),
43+
translatePlural: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
44+
n: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
45+
getLanguage: vi.fn(() => 'en'),
46+
getLocale: vi.fn(() => 'en'),
47+
isRTL: vi.fn(() => false),
48+
}))
49+
50+
describe('TSA.vue', () => {
51+
beforeEach(() => {
52+
vi.clearAllMocks()
53+
vi.useFakeTimers()
54+
loadStateMock.mockImplementation((_app: string, _key: string, fallback: unknown) => fallback)
55+
confirmPasswordMock.mockResolvedValue(undefined)
56+
axiosPostMock.mockResolvedValue({ data: { ocs: { data: {} } } })
57+
axiosDeleteMock.mockResolvedValue({ data: { ocs: { data: {} } } })
58+
})
59+
60+
function createWrapper() {
61+
return mount(TSA, {
62+
global: {
63+
stubs: {
64+
NcSettingsSection: { template: '<section><slot /></section>' },
65+
NcCheckboxRadioSwitch: { template: '<div><slot /></div>' },
66+
NcTextField: { template: '<input />' },
67+
NcPasswordField: { template: '<input />' },
68+
NcSelect: { template: '<div />' },
69+
},
70+
},
71+
})
72+
}
73+
74+
it('loads the saved TSA configuration from initial state', () => {
75+
loadStateMock.mockImplementation((_app: string, key: string, fallback: unknown) => {
76+
if (key === 'tsa_url') return 'https://tsa.example.test'
77+
if (key === 'tsa_policy_oid') return '1.2.3.4'
78+
if (key === 'tsa_auth_type') return 'basic'
79+
if (key === 'tsa_username') return 'admin'
80+
if (key === 'tsa_password') return 'secret'
81+
return fallback
82+
})
83+
84+
const wrapper = createWrapper()
85+
86+
expect(wrapper.vm.enabled).toBe(true)
87+
expect(wrapper.vm.tsa_url).toBe('https://tsa.example.test')
88+
expect(wrapper.vm.tsa_policy_oid).toBe('1.2.3.4')
89+
expect(wrapper.vm.tsa_auth_type).toBe('basic')
90+
expect(wrapper.vm.tsa_username).toBe('admin')
91+
expect(wrapper.vm.tsa_password).toBe('secret')
92+
expect(wrapper.vm.selectedAuthType).toEqual({ id: 'basic', label: 'Username / Password' })
93+
})
94+
95+
it('clears credentials when authentication is switched back to none', async () => {
96+
const wrapper = createWrapper()
97+
wrapper.vm.tsa_auth_type = wrapper.vm.AUTH_TYPES.BASIC
98+
wrapper.vm.tsa_username = 'admin'
99+
wrapper.vm.tsa_password = 'secret'
100+
101+
wrapper.vm.selectedAuthType = { id: 'none', label: 'Without authentication' }
102+
await vi.advanceTimersByTimeAsync(wrapper.vm.DEBOUNCE_DELAY)
103+
await flushPromises()
104+
105+
expect(wrapper.vm.tsa_auth_type).toBe('none')
106+
expect(wrapper.vm.tsa_username).toBe('')
107+
expect(wrapper.vm.tsa_password).toBe('')
108+
expect(confirmPasswordMock).toHaveBeenCalledTimes(1)
109+
expect(axiosPostMock).toHaveBeenCalledWith('/apps/libresign/api/v1/admin/tsa', expect.objectContaining({
110+
tsa_auth_type: 'none',
111+
tsa_username: '',
112+
tsa_password: '',
113+
}))
114+
})
115+
116+
it('validates TSA URL and policy OID fields', () => {
117+
const wrapper = createWrapper()
118+
119+
wrapper.vm.validateField('tsa_url', 'invalid-url')
120+
expect(wrapper.vm.errors.tsa_url).toBe('Invalid URL')
121+
122+
wrapper.vm.validateField('tsa_policy_oid', 'abc.def')
123+
expect(wrapper.vm.errors.tsa_policy_oid).toContain('Invalid OID format')
124+
})
125+
126+
it('applies the default TSA URL when enabling an empty configuration', async () => {
127+
const wrapper = createWrapper()
128+
wrapper.vm.enabled = true
129+
wrapper.vm.tsa_url = ''
130+
131+
await wrapper.vm.toggleTsa()
132+
133+
expect(wrapper.vm.tsa_url).toBe(wrapper.vm.DEFAULT_TSA_URL)
134+
expect(confirmPasswordMock).toHaveBeenCalledTimes(1)
135+
expect(axiosPostMock).toHaveBeenCalledWith('/apps/libresign/api/v1/admin/tsa', expect.objectContaining({
136+
tsa_url: wrapper.vm.DEFAULT_TSA_URL,
137+
}))
138+
})
139+
140+
it('clears the persisted configuration when disabling TSA', async () => {
141+
const wrapper = createWrapper()
142+
wrapper.vm.enabled = false
143+
144+
await wrapper.vm.toggleTsa()
145+
146+
expect(confirmPasswordMock).toHaveBeenCalledTimes(1)
147+
expect(axiosDeleteMock).toHaveBeenCalledWith('/apps/libresign/api/v1/admin/tsa')
148+
})
149+
150+
it('maps backend validation errors to the affected fields', () => {
151+
const wrapper = createWrapper()
152+
153+
wrapper.vm.handleSaveError({
154+
response: {
155+
status: 400,
156+
data: {
157+
ocs: {
158+
data: {
159+
message: 'Username and password are required for basic authentication',
160+
},
161+
},
162+
},
163+
},
164+
})
165+
166+
expect(wrapper.vm.errors.tsa_username).toBe('Name is mandatory')
167+
expect(wrapper.vm.errors.tsa_password).toBe('Password is mandatory')
168+
})
169+
170+
it('persists the TSA configuration through the admin endpoint', async () => {
171+
const wrapper = createWrapper()
172+
wrapper.vm.tsa_url = 'https://tsa.example.test'
173+
wrapper.vm.tsa_policy_oid = '1.2.3.4'
174+
wrapper.vm.tsa_auth_type = 'basic'
175+
wrapper.vm.tsa_username = 'admin'
176+
wrapper.vm.tsa_password = 'secret'
177+
178+
await wrapper.vm.saveTsaConfig()
179+
await flushPromises()
180+
181+
expect(confirmPasswordMock).toHaveBeenCalledTimes(1)
182+
expect(axiosPostMock).toHaveBeenCalledWith('/apps/libresign/api/v1/admin/tsa', {
183+
tsa_url: 'https://tsa.example.test',
184+
tsa_policy_oid: '1.2.3.4',
185+
tsa_auth_type: 'basic',
186+
tsa_username: 'admin',
187+
tsa_password: 'secret',
188+
})
189+
expect(wrapper.vm.loading).toBe(false)
190+
})
191+
192+
it('clears the TSA configuration through the delete endpoint', async () => {
193+
const wrapper = createWrapper()
194+
wrapper.vm.tsa_url = 'https://tsa.example.test'
195+
wrapper.vm.tsa_policy_oid = '1.2.3.4'
196+
wrapper.vm.tsa_auth_type = 'basic'
197+
wrapper.vm.tsa_username = 'admin'
198+
wrapper.vm.tsa_password = 'secret'
199+
200+
await wrapper.vm.clearTsaConfig()
201+
202+
expect(confirmPasswordMock).toHaveBeenCalledTimes(1)
203+
expect(axiosDeleteMock).toHaveBeenCalledWith('/apps/libresign/api/v1/admin/tsa')
204+
expect(wrapper.vm.tsa_url).toBe('')
205+
expect(wrapper.vm.tsa_policy_oid).toBe('')
206+
expect(wrapper.vm.tsa_auth_type).toBe('none')
207+
expect(wrapper.vm.tsa_username).toBe('')
208+
expect(wrapper.vm.tsa_password).toBe('')
209+
})
210+
})

0 commit comments

Comments
 (0)