@@ -1107,5 +1107,88 @@ describe('files store - critical business rules', () => {
11071107 expect ( store . selectedFileId ) . toBe ( 12 )
11081108 expect ( store . ordered ) . toContain ( 12 )
11091109 } )
1110+
1111+ /**
1112+ * Regression: POST /request-signature missing "file" parameter.
1113+ *
1114+ * When init.ts uploads a file and the WebDAV PROPFIND response is missing
1115+ * the Nextcloud-specific fileid (because getDefaultPropfind() was not used),
1116+ * AppFilesTab.update() receives fileInfo.id = '' and creates a temp object
1117+ * { id: -0 = 0, nodeId: '' }. addFile() silently rejects this (both falsy),
1118+ * selectedFileId stays 0, and getFile() returns the shared emptyFile reference.
1119+ * The signer gets pushed into emptyFile.signers (mutation of shared state),
1120+ * and saveOrUpdateSignatureRequest sends POST with signers but no "file".
1121+ *
1122+ * Correct path (after fix): init.ts uses getDefaultPropfind() → fileid is
1123+ * populated → AppFilesTab creates a properly keyed temp file with a valid
1124+ * negative id and a valid nodeId → saveOrUpdateSignatureRequest includes
1125+ * file: { nodeId } in the request body.
1126+ */
1127+ describe ( 'RULE: file reference in request-signature payload (init.ts upload flow)' , ( ) => {
1128+ it ( 'includes file.nodeId when file has a temporary negative id' , async ( ) => {
1129+ const store = useFilesStore ( )
1130+ // Simulate the state AppFilesTab creates when the LibreSign API has not
1131+ // yet indexed the file (fallback path in AppFilesTab.update):
1132+ // id is -nodeId (temporary), nodeId is the real Nextcloud file id.
1133+ const nodeId = 12345
1134+ const tempId = - nodeId
1135+ store . files [ tempId ] = {
1136+ id : tempId ,
1137+ nodeId,
1138+ name : 'test.pdf' ,
1139+ signers : [ { email : 'signer@example.com' , identify : 'signer@example.com' } ] ,
1140+ signatureFlow : 'parallel' ,
1141+ }
1142+ store . selectedFileId = tempId
1143+
1144+ axiosMock . mockResolvedValue ( {
1145+ data : { ocs : { data : { id : nodeId , nodeId, signatureFlow : 'parallel' , signers : [ ] } } } ,
1146+ } )
1147+
1148+ await store . saveOrUpdateSignatureRequest ( { } )
1149+
1150+ const config = axiosMock . mock . calls [ 0 ] [ 0 ]
1151+ expect ( config . data . file ) . toEqual ( { nodeId } )
1152+ } )
1153+
1154+ it ( 'includes file.fileId when file has a real positive LibreSign id' , async ( ) => {
1155+ const store = useFilesStore ( )
1156+ // Simulate the state when LibreSign already knows about the file
1157+ // (returned by getAllFiles after the /api/v1/file POST in init.ts).
1158+ store . files [ 7 ] = {
1159+ id : 7 ,
1160+ nodeId : 12345 ,
1161+ name : 'test.pdf' ,
1162+ signers : [ { email : 'signer@example.com' , identify : 'signer@example.com' } ] ,
1163+ signatureFlow : 'parallel' ,
1164+ }
1165+ store . selectedFileId = 7
1166+
1167+ axiosMock . mockResolvedValue ( {
1168+ data : { ocs : { data : { id : 7 , nodeId : 12345 , signatureFlow : 'parallel' , signers : [ ] } } } ,
1169+ } )
1170+
1171+ await store . saveOrUpdateSignatureRequest ( { } )
1172+
1173+ const config = axiosMock . mock . calls [ 0 ] [ 0 ]
1174+ expect ( config . data . file ) . toEqual ( { fileId : 7 } )
1175+ } )
1176+
1177+ it ( 'does NOT mutate the shared emptyFile when no file is selected' , ( ) => {
1178+ const store = useFilesStore ( )
1179+ store . selectedFileId = 0 // nothing selected
1180+
1181+ const signer = { email : 'a@example.com' , identify : 'a@example.com' }
1182+ // If emptyFile were mutated, the signerUpdate below would persist
1183+ // across store instances and corrupt subsequent tests.
1184+ store . signerUpdate ( signer )
1185+
1186+ // Create a fresh store — it must start with empty signers
1187+ const store2 = useFilesStore ( )
1188+ store2 . selectedFileId = 0
1189+ const file2 = store2 . getFile ( )
1190+ expect ( file2 . signers ) . toHaveLength ( 0 )
1191+ } )
1192+ } )
11101193 } )
11111194} )
0 commit comments