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