@@ -1153,6 +1153,220 @@ func TestHandler_HandleFindOrCreateDocument_ExistingDoc_NoQuotaRecord(t *testing
11531153 assert .False (t , recorder .recordCalled , "quota record should NOT be called for existing doc" )
11541154}
11551155
1156+ // ============================================================================
1157+ // TESTS - HandleDeleteMyDocument
1158+ // ============================================================================
1159+
1160+ // mockAdminService implements the adminService interface for tests.
1161+ type mockAdminService struct {
1162+ getDocFunc func (ctx context.Context , docID string ) (* models.Document , error )
1163+ deleteDocFunc func (ctx context.Context , docID string ) error
1164+ }
1165+
1166+ func (m * mockAdminService ) GetDocument (ctx context.Context , docID string ) (* models.Document , error ) {
1167+ if m .getDocFunc != nil {
1168+ return m .getDocFunc (ctx , docID )
1169+ }
1170+ return testDoc , nil
1171+ }
1172+
1173+ func (m * mockAdminService ) DeleteDocument (ctx context.Context , docID string ) error {
1174+ if m .deleteDocFunc != nil {
1175+ return m .deleteDocFunc (ctx , docID )
1176+ }
1177+ return nil
1178+ }
1179+
1180+ func (m * mockAdminService ) UpdateDocumentMetadata (_ context.Context , _ string , _ models.DocumentInput , _ string ) (* models.Document , error ) {
1181+ return testDoc , nil
1182+ }
1183+
1184+ func (m * mockAdminService ) ListExpectedSignersWithStatus (_ context.Context , _ string ) ([]* models.ExpectedSignerWithStatus , error ) {
1185+ return nil , nil
1186+ }
1187+
1188+ func (m * mockAdminService ) GetSignerStats (_ context.Context , _ string ) (* models.DocCompletionStats , error ) {
1189+ return & models.DocCompletionStats {}, nil
1190+ }
1191+
1192+ func (m * mockAdminService ) AddExpectedSigners (_ context.Context , _ string , _ []models.ContactInfo , _ string ) error {
1193+ return nil
1194+ }
1195+
1196+ func (m * mockAdminService ) RemoveExpectedSigner (_ context.Context , _ , _ string ) error {
1197+ return nil
1198+ }
1199+
1200+ // mockFileDeleter tracks Delete calls for test assertions.
1201+ type mockFileDeleter struct {
1202+ deletedKeys []string
1203+ deleteErr error
1204+ }
1205+
1206+ func (m * mockFileDeleter ) Delete (_ context.Context , key string ) error {
1207+ m .deletedKeys = append (m .deletedKeys , key )
1208+ return m .deleteErr
1209+ }
1210+
1211+ func makeDeleteRequest (docID string , user * models.User ) * http.Request {
1212+ req := httptest .NewRequest (http .MethodDelete , "/api/v1/users/me/documents/" + docID , nil )
1213+ rctx := chi .NewRouteContext ()
1214+ rctx .URLParams .Add ("docId" , docID )
1215+ ctx := context .WithValue (req .Context (), chi .RouteCtxKey , rctx )
1216+ if user != nil {
1217+ ctx = addUserToContext (ctx , user )
1218+ }
1219+ return req .WithContext (ctx )
1220+ }
1221+
1222+ func TestHandler_HandleDeleteMyDocument_DeletesStoredFile (t * testing.T ) {
1223+ t .Parallel ()
1224+
1225+ storedDoc := & models.Document {
1226+ DocID : "doc-with-file" ,
1227+ Title : "Doc With File" ,
1228+ CreatedBy : testUser .Email ,
1229+ StorageKey : "tenants/abc/doc-with-file.pdf" ,
1230+ StorageProvider : "s3" ,
1231+ }
1232+ deleter := & mockFileDeleter {}
1233+
1234+ handler := & Handler {
1235+ authorizer : newMockAuthorizer ([]string {}, false ),
1236+ storageProvider : deleter ,
1237+ adminService : & mockAdminService {
1238+ getDocFunc : func (_ context.Context , _ string ) (* models.Document , error ) {
1239+ return storedDoc , nil
1240+ },
1241+ },
1242+ }
1243+
1244+ rec := httptest .NewRecorder ()
1245+ handler .HandleDeleteMyDocument (rec , makeDeleteRequest (storedDoc .DocID , testUser ))
1246+
1247+ assert .Equal (t , http .StatusOK , rec .Code )
1248+ require .Len (t , deleter .deletedKeys , 1 )
1249+ assert .Equal (t , storedDoc .StorageKey , deleter .deletedKeys [0 ])
1250+ }
1251+
1252+ func TestHandler_HandleDeleteMyDocument_NoFileNoStorageCall (t * testing.T ) {
1253+ t .Parallel ()
1254+
1255+ docWithoutFile := & models.Document {
1256+ DocID : "doc-no-file" ,
1257+ Title : "URL-only doc" ,
1258+ CreatedBy : testUser .Email ,
1259+ URL : "https://example.com/doc.pdf" ,
1260+ // StorageKey intentionally empty
1261+ }
1262+ deleter := & mockFileDeleter {}
1263+
1264+ handler := & Handler {
1265+ authorizer : newMockAuthorizer ([]string {}, false ),
1266+ storageProvider : deleter ,
1267+ adminService : & mockAdminService {
1268+ getDocFunc : func (_ context.Context , _ string ) (* models.Document , error ) {
1269+ return docWithoutFile , nil
1270+ },
1271+ },
1272+ }
1273+
1274+ rec := httptest .NewRecorder ()
1275+ handler .HandleDeleteMyDocument (rec , makeDeleteRequest (docWithoutFile .DocID , testUser ))
1276+
1277+ assert .Equal (t , http .StatusOK , rec .Code )
1278+ assert .Empty (t , deleter .deletedKeys , "Delete must not be called when doc has no storage key" )
1279+ }
1280+
1281+ func TestHandler_HandleDeleteMyDocument_NoStorageProvider (t * testing.T ) {
1282+ t .Parallel ()
1283+
1284+ storedDoc := & models.Document {
1285+ DocID : "doc-with-file" ,
1286+ Title : "Doc With File" ,
1287+ CreatedBy : testUser .Email ,
1288+ StorageKey : "tenants/abc/doc-with-file.pdf" ,
1289+ StorageProvider : "s3" ,
1290+ }
1291+
1292+ // No storage provider set — should succeed without panic
1293+ handler := & Handler {
1294+ authorizer : newMockAuthorizer ([]string {}, false ),
1295+ adminService : & mockAdminService {
1296+ getDocFunc : func (_ context.Context , _ string ) (* models.Document , error ) {
1297+ return storedDoc , nil
1298+ },
1299+ },
1300+ }
1301+
1302+ rec := httptest .NewRecorder ()
1303+ handler .HandleDeleteMyDocument (rec , makeDeleteRequest (storedDoc .DocID , testUser ))
1304+
1305+ assert .Equal (t , http .StatusOK , rec .Code )
1306+ }
1307+
1308+ func TestHandler_HandleDeleteMyDocument_StorageErrorDoesNotFail (t * testing.T ) {
1309+ t .Parallel ()
1310+
1311+ storedDoc := & models.Document {
1312+ DocID : "doc-with-file" ,
1313+ Title : "Doc With File" ,
1314+ CreatedBy : testUser .Email ,
1315+ StorageKey : "tenants/abc/doc-with-file.pdf" ,
1316+ StorageProvider : "s3" ,
1317+ }
1318+ deleter := & mockFileDeleter {deleteErr : fmt .Errorf ("s3 unavailable" )}
1319+
1320+ handler := & Handler {
1321+ authorizer : newMockAuthorizer ([]string {}, false ),
1322+ storageProvider : deleter ,
1323+ adminService : & mockAdminService {
1324+ getDocFunc : func (_ context.Context , _ string ) (* models.Document , error ) {
1325+ return storedDoc , nil
1326+ },
1327+ },
1328+ }
1329+
1330+ rec := httptest .NewRecorder ()
1331+ handler .HandleDeleteMyDocument (rec , makeDeleteRequest (storedDoc .DocID , testUser ))
1332+
1333+ // Storage error is best-effort: request must still succeed
1334+ assert .Equal (t , http .StatusOK , rec .Code )
1335+ assert .Len (t , deleter .deletedKeys , 1 , "Delete must still have been attempted" )
1336+ }
1337+
1338+ func TestHandler_HandleDeleteMyDocument_DBErrorSkipsStorageDelete (t * testing.T ) {
1339+ t .Parallel ()
1340+
1341+ storedDoc := & models.Document {
1342+ DocID : "doc-with-file" ,
1343+ Title : "Doc With File" ,
1344+ CreatedBy : testUser .Email ,
1345+ StorageKey : "tenants/abc/doc-with-file.pdf" ,
1346+ StorageProvider : "s3" ,
1347+ }
1348+ deleter := & mockFileDeleter {}
1349+
1350+ handler := & Handler {
1351+ authorizer : newMockAuthorizer ([]string {}, false ),
1352+ storageProvider : deleter ,
1353+ adminService : & mockAdminService {
1354+ getDocFunc : func (_ context.Context , _ string ) (* models.Document , error ) {
1355+ return storedDoc , nil
1356+ },
1357+ deleteDocFunc : func (_ context.Context , _ string ) error {
1358+ return fmt .Errorf ("db error" )
1359+ },
1360+ },
1361+ }
1362+
1363+ rec := httptest .NewRecorder ()
1364+ handler .HandleDeleteMyDocument (rec , makeDeleteRequest (storedDoc .DocID , testUser ))
1365+
1366+ assert .Equal (t , http .StatusInternalServerError , rec .Code )
1367+ assert .Empty (t , deleter .deletedKeys , "Storage Delete must not be called when DB delete fails" )
1368+ }
1369+
11561370func Benchmark_detectReferenceType (b * testing.B ) {
11571371 refs := []string {
11581372 "https://example.com/doc.pdf" ,
0 commit comments