Skip to content

Commit 1944352

Browse files
refactor(vue3): migrate OpenSSL root certificate settings to script setup ts
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 51ec304 commit 1944352

2 files changed

Lines changed: 446 additions & 166 deletions

File tree

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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 RootCertificateOpenSsl from '../../../views/Settings/RootCertificateOpenSsl.vue'
10+
11+
const axiosGetMock = vi.fn()
12+
const axiosPostMock = vi.fn()
13+
const showErrorMock = vi.fn()
14+
const loadStateMock = vi.fn()
15+
const subscribeMock = vi.fn()
16+
const unsubscribeMock = vi.fn()
17+
const checkSetupMock = vi.fn()
18+
19+
vi.mock('@nextcloud/axios', () => ({
20+
default: {
21+
get: (...args: unknown[]) => axiosGetMock(...args),
22+
post: (...args: unknown[]) => axiosPostMock(...args),
23+
},
24+
}))
25+
26+
vi.mock('@nextcloud/dialogs', () => ({
27+
showError: (...args: unknown[]) => showErrorMock(...args),
28+
}))
29+
30+
vi.mock('@nextcloud/event-bus', () => ({
31+
subscribe: (...args: unknown[]) => subscribeMock(...args),
32+
unsubscribe: (...args: unknown[]) => unsubscribeMock(...args),
33+
}))
34+
35+
vi.mock('@nextcloud/initial-state', () => ({
36+
loadState: (...args: unknown[]) => loadStateMock(...args),
37+
}))
38+
39+
vi.mock('@nextcloud/router', () => ({
40+
generateOcsUrl: vi.fn((path: string) => path),
41+
}))
42+
43+
vi.mock('@nextcloud/l10n', () => ({
44+
t: vi.fn((_app: string, text: string, vars?: Record<string, string>) => {
45+
if (!vars) {
46+
return text
47+
}
48+
return text.replace(/{(\w+)}/g, (_match, key) => String(vars[key]))
49+
}),
50+
translate: vi.fn((_app: string, text: string) => text),
51+
translatePlural: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
52+
n: vi.fn((_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)),
53+
getLanguage: vi.fn(() => 'en'),
54+
getLocale: vi.fn(() => 'en'),
55+
isRTL: vi.fn(() => false),
56+
}))
57+
58+
vi.mock('../../../helpers/certification', () => ({
59+
selectCustonOption: vi.fn(() => ({
60+
unwrap: () => ({ label: 'Country' }),
61+
})),
62+
}))
63+
64+
vi.mock('../../../logger.js', () => ({
65+
default: {
66+
debug: vi.fn(),
67+
},
68+
}))
69+
70+
vi.mock('../../../store/configureCheck.js', () => ({
71+
useConfigureCheckStore: vi.fn(() => ({
72+
items: [{ resource: 'openssl-configure', status: 'success' }],
73+
isConfigureOk: vi.fn(() => true),
74+
checkSetup: (...args: unknown[]) => checkSetupMock(...args),
75+
})),
76+
}))
77+
78+
describe('RootCertificateOpenSsl.vue', () => {
79+
beforeEach(() => {
80+
vi.clearAllMocks()
81+
loadStateMock.mockImplementation((_app: string, _key: string, fallback: unknown) => fallback)
82+
axiosGetMock.mockResolvedValue({
83+
data: {
84+
ocs: {
85+
data: {
86+
generated: false,
87+
rootCert: {
88+
commonName: '',
89+
names: [],
90+
},
91+
configPath: '',
92+
},
93+
},
94+
},
95+
})
96+
axiosPostMock.mockResolvedValue({
97+
data: {
98+
ocs: {
99+
data: {
100+
data: {
101+
generated: true,
102+
rootCert: {
103+
commonName: 'LibreSign Root',
104+
names: [{ id: 'C', value: 'BR' }],
105+
},
106+
configPath: '/tmp/root.cnf',
107+
},
108+
},
109+
},
110+
},
111+
})
112+
})
113+
114+
function createWrapper() {
115+
return mount(RootCertificateOpenSsl, {
116+
global: {
117+
stubs: {
118+
NcSettingsSection: { template: '<section><slot /></section>' },
119+
NcDialog: { template: '<div><slot /><slot name="actions" /></div>' },
120+
NcButton: { template: '<button @click="$emit(\'click\')"><slot /></button>' },
121+
NcTextField: { template: '<input />' },
122+
NcCheckboxRadioSwitch: { template: '<div><slot /></div>' },
123+
CertificateCustonOptions: { template: '<div />' },
124+
CertificatePolicy: { template: '<div />' },
125+
},
126+
},
127+
})
128+
}
129+
130+
it('loads the OpenSSL engine state and root certificate on mount', async () => {
131+
loadStateMock.mockImplementation((_app: string, key: string, fallback: unknown) => {
132+
if (key === 'certificate_engine') return 'openssl'
133+
return fallback
134+
})
135+
136+
const wrapper = createWrapper()
137+
await flushPromises()
138+
139+
expect(wrapper.vm.isThisEngine).toBe(true)
140+
expect(axiosGetMock).toHaveBeenCalledWith('/apps/libresign/api/v1/admin/certificate')
141+
expect(wrapper.vm.description).toBe('To generate new signatures, you must first generate the root certificate.')
142+
expect(wrapper.vm.submitLabel).toBe('Generate root certificate')
143+
})
144+
145+
it('requires a valid certificate policy only when the toggle is enabled', async () => {
146+
const wrapper = createWrapper()
147+
await flushPromises()
148+
149+
wrapper.vm.formDisabled = false
150+
wrapper.vm.toggleCertificatePolicy = false
151+
expect(wrapper.vm.canSave).toBe(true)
152+
153+
wrapper.vm.toggleCertificatePolicy = true
154+
wrapper.vm.certificatePolicyValid = false
155+
expect(wrapper.vm.canSave).toBe(false)
156+
157+
wrapper.vm.certificatePolicyValid = true
158+
expect(wrapper.vm.canSave).toBe(true)
159+
})
160+
161+
it('resets the form when clearing a generated certificate', async () => {
162+
const wrapper = createWrapper()
163+
await flushPromises()
164+
165+
wrapper.vm.certificate = {
166+
rootCert: {
167+
commonName: 'LibreSign Root',
168+
names: [{ id: 'C', value: 'BR' }],
169+
},
170+
configPath: '/tmp/root.cnf',
171+
}
172+
wrapper.vm.customData = true
173+
wrapper.vm.formDisabled = true
174+
wrapper.vm.modal = true
175+
176+
wrapper.vm.clearAndShowForm()
177+
178+
expect(wrapper.vm.certificate.rootCert.commonName).toBe('')
179+
expect(wrapper.vm.certificate.rootCert.names).toEqual([])
180+
expect(wrapper.vm.certificate.configPath).toBe('')
181+
expect(wrapper.vm.customData).toBe(false)
182+
expect(wrapper.vm.formDisabled).toBe(false)
183+
expect(wrapper.vm.modal).toBe(false)
184+
})
185+
186+
it('updates visibility and reloads data when the certificate engine changes', async () => {
187+
const wrapper = createWrapper()
188+
await flushPromises()
189+
axiosGetMock.mockClear()
190+
191+
wrapper.vm.changeEngine('cfssl')
192+
await flushPromises()
193+
expect(wrapper.vm.isThisEngine).toBe(false)
194+
expect(axiosGetMock).not.toHaveBeenCalled()
195+
196+
wrapper.vm.changeEngine('openssl')
197+
await flushPromises()
198+
199+
expect(wrapper.vm.isThisEngine).toBe(true)
200+
expect(axiosGetMock).toHaveBeenCalledWith('/apps/libresign/api/v1/admin/certificate')
201+
})
202+
203+
it('generates the certificate and refreshes setup checks on success', async () => {
204+
const wrapper = createWrapper()
205+
await flushPromises()
206+
wrapper.vm.certificate.rootCert.commonName = 'LibreSign Root'
207+
208+
await wrapper.vm.generateCertificate()
209+
await flushPromises()
210+
211+
expect(axiosPostMock).toHaveBeenCalledWith(
212+
'/apps/libresign/api/v1/admin/certificate/openssl',
213+
expect.objectContaining({
214+
rootCert: expect.objectContaining({ commonName: 'LibreSign Root' }),
215+
}),
216+
)
217+
expect(wrapper.vm.certificate.generated).toBe(true)
218+
expect(wrapper.vm.submitLabel).toBe('Generated certificate!')
219+
expect(checkSetupMock).toHaveBeenCalledTimes(1)
220+
})
221+
222+
it('shows a user-facing error when generation fails', async () => {
223+
axiosPostMock.mockRejectedValue({
224+
response: {
225+
data: {
226+
ocs: {
227+
data: {
228+
message: 'OpenSSL error',
229+
},
230+
},
231+
},
232+
},
233+
})
234+
235+
const wrapper = createWrapper()
236+
await flushPromises()
237+
238+
await wrapper.vm.generateCertificate()
239+
await flushPromises()
240+
241+
expect(showErrorMock).toHaveBeenCalledWith('Could not generate certificate.\nOpenSSL error')
242+
expect(wrapper.vm.submitLabel).toBe('Generate root certificate')
243+
})
244+
})

0 commit comments

Comments
 (0)