@@ -250,4 +250,269 @@ describe('Query extensions', () => {
250
250
expect ( fs . unlink ) . toHaveBeenCalledWith ( 'im-a-invalid-path' )
251
251
} )
252
252
} )
253
+
254
+ describe ( 'upsert' , ( ) => {
255
+ it ( 'will remove old files and save new ones on upsert, if it exists [UPDATE]' , async ( ) => {
256
+ const ogDumbo = await prismaClient . dumbo . create ( {
257
+ data : {
258
+ firstUpload : '/tmp/oldFirst.txt' ,
259
+ secondUpload : '/tmp/oldSecond.txt' ,
260
+ } ,
261
+ } )
262
+
263
+ const updatedDumbo = await prismaClient . dumbo . upsert ( {
264
+ update : {
265
+ firstUpload : '/tmp/newFirst.txt' ,
266
+ } ,
267
+ create : {
268
+ // won't be used
269
+ firstUpload : 'x' ,
270
+ secondUpload : 'x' ,
271
+ } ,
272
+ where : {
273
+ id : ogDumbo . id ,
274
+ } ,
275
+ } )
276
+
277
+ expect ( updatedDumbo . firstUpload ) . toBe ( '/tmp/newFirst.txt' )
278
+ expect ( updatedDumbo . secondUpload ) . toBe ( '/tmp/oldSecond.txt' )
279
+ expect ( fs . unlink ) . toHaveBeenCalledOnce ( )
280
+ expect ( fs . unlink ) . toHaveBeenCalledWith ( '/tmp/oldFirst.txt' )
281
+ } )
282
+
283
+ it ( 'will create a new record (findOrCreate)' , async ( ) => {
284
+ const newDumbo = await prismaClient . dumbo . upsert ( {
285
+ create : {
286
+ firstUpload : '/tmp/first.txt' ,
287
+ secondUpload : '/bazinga/second.txt' ,
288
+ } ,
289
+ update : { } ,
290
+ where : {
291
+ id : 444444444 ,
292
+ } ,
293
+ } )
294
+
295
+ expect ( newDumbo . firstUpload ) . toBe ( '/tmp/first.txt' )
296
+ expect ( newDumbo . secondUpload ) . toBe ( '/bazinga/second.txt' )
297
+ } )
298
+
299
+ it ( 'will remove processed files if upsert CREATION fails (findOrCreate)' , async ( ) => {
300
+ // This is essentially findOrCreate, because update is empty
301
+ try {
302
+ await prismaClient . dumbo . upsert ( {
303
+ create : {
304
+ firstUpload : '/tmp/first.txt' ,
305
+ secondUpload : '/bazinga/second.txt' ,
306
+ // @ts -expect-error Checking the error here
307
+ id : 'this-is-the-incorrect-type' ,
308
+ } ,
309
+ } )
310
+ } catch {
311
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 1 , '/tmp/first.txt' )
312
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 2 , '/bazinga/second.txt' )
313
+ }
314
+
315
+ expect . assertions ( 2 )
316
+ } )
317
+
318
+ it ( 'will remove processed files if upsert UPDATE fails' , async ( ) => {
319
+ // Bit of a contrived case... why would you ever have different values for update and create...
320
+
321
+ const ogDumbo = await prismaClient . dumbo . create ( {
322
+ data : {
323
+ firstUpload : '/tmp/oldFirst.txt' ,
324
+ secondUpload : '/tmp/oldSecond.txt' ,
325
+ } ,
326
+ } )
327
+
328
+ try {
329
+ await prismaClient . dumbo . upsert ( {
330
+ where : {
331
+ id : ogDumbo . id ,
332
+ } ,
333
+ update : {
334
+ firstUpload : '/tmp/newFirst.txt' ,
335
+ secondUpload : '/tmp/newSecond.txt' ,
336
+ // @ts -expect-error Intentionally causing an error
337
+ id : 'this-should-cause-an-error' ,
338
+ } ,
339
+ create : {
340
+ firstUpload : '/tmp/createFirst.txt' ,
341
+ secondUpload : '/tmp/createSecond.txt' ,
342
+ } ,
343
+ } )
344
+ } catch ( error ) {
345
+ expect ( fs . unlink ) . toHaveBeenCalledTimes ( 2 )
346
+ expect ( fs . unlink ) . not . toHaveBeenCalledWith ( '/tmp/createFirst.txt' )
347
+ expect ( fs . unlink ) . not . toHaveBeenCalledWith ( '/tmp/createSecond.txt' )
348
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 1 , '/tmp/newFirst.txt' )
349
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 2 , '/tmp/newSecond.txt' )
350
+ expect ( error ) . toBeDefined ( )
351
+ }
352
+
353
+ // Verify the original files weren't deleted
354
+ const unchangedDumbo = await prismaClient . dumbo . findUnique ( {
355
+ where : { id : ogDumbo . id } ,
356
+ } )
357
+ expect ( unchangedDumbo ?. firstUpload ) . toBe ( '/tmp/oldFirst.txt' )
358
+ expect ( unchangedDumbo ?. secondUpload ) . toBe ( '/tmp/oldSecond.txt' )
359
+
360
+ expect . assertions ( 8 )
361
+ } )
362
+ } )
363
+
364
+ describe ( 'createMany' , ( ) => {
365
+ it ( 'createMany will remove files if all the create fails' , async ( ) => {
366
+ try {
367
+ await prismaClient . dumbo . createMany ( {
368
+ data : [
369
+ {
370
+ firstUpload : '/one/first.txt' ,
371
+ secondUpload : '/one/second.txt' ,
372
+ // @ts -expect-error Intentional
373
+ id : 'break' ,
374
+ } ,
375
+ {
376
+ firstUpload : '/two/first.txt' ,
377
+ secondUpload : '/two/second.txt' ,
378
+ // @ts -expect-error Intentional
379
+ id : 'break2' ,
380
+ } ,
381
+ ] ,
382
+ } )
383
+ } catch {
384
+ expect ( fs . unlink ) . toHaveBeenCalledTimes ( 4 )
385
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 1 , '/one/first.txt' )
386
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 2 , '/one/second.txt' )
387
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 3 , '/two/first.txt' )
388
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 4 , '/two/second.txt' )
389
+ }
390
+
391
+ expect . assertions ( 5 )
392
+ } )
393
+
394
+ it ( 'createMany will remove all files, even if one of them errors' , async ( ) => {
395
+ try {
396
+ await prismaClient . dumbo . createMany ( {
397
+ data : [
398
+ // This one is correct, but createMany fails together
399
+ // so all the files should be removed!
400
+ {
401
+ firstUpload : '/one/first.txt' ,
402
+ secondUpload : '/one/second.txt' ,
403
+ id : 9158125 ,
404
+ } ,
405
+ {
406
+ firstUpload : '/two/first.txt' ,
407
+ secondUpload : '/two/second.txt' ,
408
+ // @ts -expect-error Intentional
409
+ id : 'break2' ,
410
+ } ,
411
+ ] ,
412
+ } )
413
+ } catch {
414
+ // This one doesn't actually get created!
415
+ expect (
416
+ prismaClient . dumbo . findUnique ( { where : { id : 9158125 } } ) ,
417
+ ) . resolves . toBeNull ( )
418
+
419
+ expect ( fs . unlink ) . toHaveBeenCalledTimes ( 4 )
420
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 1 , '/one/first.txt' )
421
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 2 , '/one/second.txt' )
422
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 3 , '/two/first.txt' )
423
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 4 , '/two/second.txt' )
424
+ }
425
+
426
+ expect . assertions ( 6 )
427
+ } )
428
+ } )
429
+
430
+ describe ( 'updateMany' , ( ) => {
431
+ it ( 'will remove old files and save new ones on update, if they exist' , async ( ) => {
432
+ const ogDumbo1 = await prismaClient . dumbo . create ( {
433
+ data : {
434
+ firstUpload : '/FINDME/oldFirst1.txt' ,
435
+ secondUpload : '/FINDME/oldSecond1.txt' ,
436
+ } ,
437
+ } )
438
+
439
+ const ogDumbo2 = await prismaClient . dumbo . create ( {
440
+ data : {
441
+ firstUpload : '/FINDME/oldFirst2.txt' ,
442
+ secondUpload : '/FINDME/oldSecond2.txt' ,
443
+ } ,
444
+ } )
445
+
446
+ const updatedDumbos = await prismaClient . dumbo . updateMany ( {
447
+ data : {
448
+ firstUpload : '/REPLACED/newFirst.txt' ,
449
+ secondUpload : '/REPLACED/newSecond.txt' ,
450
+ } ,
451
+ where : {
452
+ firstUpload : {
453
+ contains : 'FINDME' ,
454
+ } ,
455
+ } ,
456
+ } )
457
+
458
+ expect ( updatedDumbos . count ) . toBe ( 2 )
459
+
460
+ const updatedDumbo1 = await prismaClient . dumbo . findFirstOrThrow ( {
461
+ where : {
462
+ id : ogDumbo1 . id ,
463
+ } ,
464
+ } )
465
+
466
+ const updatedDumbo2 = await prismaClient . dumbo . findFirstOrThrow ( {
467
+ where : {
468
+ id : ogDumbo2 . id ,
469
+ } ,
470
+ } )
471
+
472
+ // Still performs the update
473
+ expect ( updatedDumbo1 . firstUpload ) . toBe ( '/REPLACED/newFirst.txt' )
474
+ expect ( updatedDumbo1 . secondUpload ) . toBe ( '/REPLACED/newSecond.txt' )
475
+ expect ( updatedDumbo2 . firstUpload ) . toBe ( '/REPLACED/newFirst.txt' )
476
+ expect ( updatedDumbo2 . secondUpload ) . toBe ( '/REPLACED/newSecond.txt' )
477
+
478
+ // Then deletes the old files
479
+ expect ( fs . unlink ) . toHaveBeenCalledTimes ( 4 )
480
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 1 , '/FINDME/oldFirst1.txt' )
481
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 2 , '/FINDME/oldSecond1.txt' )
482
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 3 , '/FINDME/oldFirst2.txt' )
483
+ expect ( fs . unlink ) . toHaveBeenNthCalledWith ( 4 , '/FINDME/oldSecond2.txt' )
484
+ } )
485
+
486
+ it ( 'will __not__ remove files if the update fails' , async ( ) => {
487
+ const ogDumbo1 = await prismaClient . dumbo . create ( {
488
+ data : {
489
+ firstUpload : '/tmp/oldFirst1.txt' ,
490
+ secondUpload : '/tmp/oldSecond1.txt' ,
491
+ } ,
492
+ } )
493
+
494
+ const ogDumbo2 = await prismaClient . dumbo . create ( {
495
+ data : {
496
+ firstUpload : '/tmp/oldFirst2.txt' ,
497
+ secondUpload : '/tmp/oldSecond2.txt' ,
498
+ } ,
499
+ } )
500
+
501
+ const failedUpdatePromise = prismaClient . dumbo . updateMany ( {
502
+ data : {
503
+ // @ts -expect-error Intentional
504
+ id : 'this-is-the-incorrect-type' ,
505
+ } ,
506
+ where : {
507
+ OR : [ { id : ogDumbo1 . id } , { id : ogDumbo2 . id } ] ,
508
+ } ,
509
+ } )
510
+
511
+ // Id is invalid, so the update should fail
512
+ await expect ( failedUpdatePromise ) . rejects . toThrowError ( )
513
+
514
+ // The old files should NOT be deleted
515
+ expect ( fs . unlink ) . not . toHaveBeenCalled ( )
516
+ } )
517
+ } )
253
518
} )
0 commit comments