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