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