@@ -12,10 +12,10 @@ import {
1212 ProductStatus ,
1313} from "@medusajs/framework/utils"
1414import {
15- ProductImage ,
1615 Product ,
1716 ProductCategory ,
1817 ProductCollection ,
18+ ProductImage ,
1919 ProductType ,
2020} from "@models"
2121
@@ -180,6 +180,22 @@ moduleIntegrationTestRunner<IProductModuleService>({
180180 productTwo = res [ 1 ]
181181 } )
182182
183+ it ( "should update multiple products" , async ( ) => {
184+ await service . upsertProducts ( [
185+ { id : productOne . id , title : "updated title 1" } ,
186+ { id : productTwo . id , title : "updated title 2" } ,
187+ ] )
188+
189+ const products = await service . listProducts (
190+ { id : [ productOne . id , productTwo . id ] } ,
191+ { relations : [ "*" ] }
192+ )
193+
194+ expect ( products ) . toHaveLength ( 2 )
195+ expect ( products [ 0 ] . title ) . toEqual ( "updated title 1" )
196+ expect ( products [ 1 ] . title ) . toEqual ( "updated title 2" )
197+ } )
198+
183199 it ( "should update a product and upsert relations that are not created yet" , async ( ) => {
184200 const tags = await service . createProductTags ( [ { value : "tag-1" } ] )
185201 const data = buildProductAndRelationsData ( {
@@ -400,9 +416,7 @@ moduleIntegrationTestRunner<IProductModuleService>({
400416 options : { size : "x" , color : "red" } , // update options
401417 } ,
402418 {
403- id : existingVariant2 . id ,
404- title : "new variant 2" ,
405- options : { size : "l" , color : "green" } , // just preserve old one
419+ id : existingVariant2 . id , // just preserve old one
406420 } ,
407421 {
408422 product_id : product . id ,
@@ -722,30 +736,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
722736 expect ( error ) . toEqual ( `Product with id: does-not-exist was not found` )
723737 } )
724738
725- it ( "should throw because variant doesn't have all options set" , async ( ) => {
726- const error = await service
727- . createProducts ( [
728- {
729- title : "Product with variants and options" ,
730- options : [
731- { title : "opt1" , values : [ "1" , "2" ] } ,
732- { title : "opt2" , values : [ "3" , "4" ] } ,
733- ] ,
734- variants : [
735- {
736- title : "missing option" ,
737- options : { opt1 : "1" } ,
738- } ,
739- ] ,
740- } ,
741- ] )
742- . catch ( ( e ) => e )
743-
744- expect ( error . message ) . toEqual (
745- `Product "Product with variants and options" has variants with missing options: [missing option]`
746- )
747- } )
748-
749739 it ( "should update, create and delete variants" , async ( ) => {
750740 const updateData = {
751741 id : productTwo . id ,
@@ -849,6 +839,148 @@ moduleIntegrationTestRunner<IProductModuleService>({
849839 ] )
850840 )
851841 } )
842+
843+ it ( "should simultaneously update options and variants" , async ( ) => {
844+ const updateData = {
845+ id : productTwo . id ,
846+ options : [ { title : "material" , values : [ "cotton" , "silk" ] } ] ,
847+ variants : [ { title : "variant 1" , options : { material : "cotton" } } ] ,
848+ }
849+
850+ await service . upsertProducts ( [ updateData ] )
851+
852+ const product = await service . retrieveProduct ( productTwo . id , {
853+ relations : [ "*" ] ,
854+ } )
855+
856+ expect ( product . options ) . toHaveLength ( 1 )
857+ expect ( product . options [ 0 ] . title ) . toEqual ( "material" )
858+ expect ( product . options [ 0 ] . values ) . toEqual (
859+ expect . arrayContaining ( [
860+ expect . objectContaining ( {
861+ value : "cotton" ,
862+ } ) ,
863+ expect . objectContaining ( {
864+ value : "silk" ,
865+ } ) ,
866+ ] )
867+ )
868+
869+ expect ( product . variants ) . toHaveLength ( 1 )
870+ expect ( product . variants [ 0 ] . options ) . toEqual (
871+ expect . arrayContaining ( [
872+ expect . objectContaining ( {
873+ value : "cotton" ,
874+ } ) ,
875+ ] )
876+ )
877+ } )
878+
879+ it ( "should throw an error when some tag id does not exist" , async ( ) => {
880+ const error = await service
881+ . updateProducts ( productOne . id , {
882+ tag_ids : [ "does-not-exist" ] ,
883+ } )
884+ . catch ( ( e ) => e )
885+
886+ expect ( error ?. message ) . toEqual (
887+ `You tried to set relationship product_tag_id: does-not-exist, but such entity does not exist`
888+ )
889+ } )
890+
891+ it ( "should throw an error when some category id does not exist" , async ( ) => {
892+ const error = await service
893+ . updateProducts ( productOne . id , {
894+ category_ids : [ "does-not-exist" ] ,
895+ } )
896+ . catch ( ( e ) => e )
897+
898+ expect ( error ?. message ) . toEqual (
899+ `You tried to set relationship product_category_id: does-not-exist, but such entity does not exist`
900+ )
901+ } )
902+
903+ it ( "should throw an error when collection id does not exist" , async ( ) => {
904+ const error = await service
905+ . updateProducts ( productOne . id , {
906+ collection_id : "does-not-exist" ,
907+ } )
908+ . catch ( ( e ) => e )
909+
910+ expect ( error ?. message ) . toEqual (
911+ `You tried to set relationship collection_id: does-not-exist, but such entity does not exist`
912+ )
913+ } )
914+
915+ it ( "should throw an error when type id does not exist" , async ( ) => {
916+ const error = await service
917+ . updateProducts ( productOne . id , {
918+ type_id : "does-not-exist" ,
919+ } )
920+ . catch ( ( e ) => e )
921+
922+ expect ( error ?. message ) . toEqual (
923+ `You tried to set relationship type_id: does-not-exist, but such entity does not exist`
924+ )
925+ } )
926+
927+ it ( "should throw if two variants have the same options combination" , async ( ) => {
928+ const error = await service
929+ . updateProducts ( productTwo . id , {
930+ variants : [
931+ {
932+ title : "variant 1" ,
933+ options : { size : "small" , color : "blue" } ,
934+ } ,
935+ {
936+ title : "variant 2" ,
937+ options : { size : "small" , color : "blue" } ,
938+ } ,
939+ ] ,
940+ } )
941+ . catch ( ( e ) => e )
942+
943+ expect ( error ?. message ) . toEqual (
944+ `Variant "variant 1" has same combination of option values as "variant 2".`
945+ )
946+ } )
947+
948+ it ( "should throw if a variant doesn't have all options set" , async ( ) => {
949+ const error = await service
950+ . updateProducts ( productTwo . id , {
951+ variants : [
952+ {
953+ title : "variant 1" ,
954+ options : { size : "small" } ,
955+ } ,
956+ ] ,
957+ } )
958+ . catch ( ( e ) => e )
959+
960+ expect ( error ?. message ) . toEqual (
961+ `Product has 2 option values but there were 1 provided option values for the variant: variant 1.`
962+ )
963+ } )
964+
965+ it ( "should throw if a variant uses a non-existing option" , async ( ) => {
966+ const error = await service
967+ . updateProducts ( productTwo . id , {
968+ variants : [
969+ {
970+ title : "variant 1" ,
971+ options : {
972+ size : "small" ,
973+ non_existing_option : "non_existing_value" ,
974+ } ,
975+ } ,
976+ ] ,
977+ } )
978+ . catch ( ( e ) => e )
979+
980+ expect ( error ?. message ) . toEqual (
981+ `Option value non_existing_value does not exist for option non_existing_option`
982+ )
983+ } )
852984 } )
853985
854986 describe ( "create" , function ( ) {
@@ -963,6 +1095,30 @@ moduleIntegrationTestRunner<IProductModuleService>({
9631095 }
9641096 )
9651097 } )
1098+
1099+ it ( "should throw because variant doesn't have all options set" , async ( ) => {
1100+ const error = await service
1101+ . createProducts ( [
1102+ {
1103+ title : "Product with variants and options" ,
1104+ options : [
1105+ { title : "opt1" , values : [ "1" , "2" ] } ,
1106+ { title : "opt2" , values : [ "3" , "4" ] } ,
1107+ ] ,
1108+ variants : [
1109+ {
1110+ title : "missing option" ,
1111+ options : { opt1 : "1" } ,
1112+ } ,
1113+ ] ,
1114+ } ,
1115+ ] )
1116+ . catch ( ( e ) => e )
1117+
1118+ expect ( error . message ) . toEqual (
1119+ `Product "Product with variants and options" has variants with missing options: [missing option]`
1120+ )
1121+ } )
9661122 } )
9671123
9681124 describe ( "softDelete" , function ( ) {
@@ -1408,6 +1564,28 @@ moduleIntegrationTestRunner<IProductModuleService>({
14081564 ] )
14091565 } )
14101566
1567+ it ( "should delete images if empty array is passed on update" , async ( ) => {
1568+ const images = [
1569+ { url : "image-1" } ,
1570+ { url : "image-2" } ,
1571+ { url : "image-3" } ,
1572+ ]
1573+
1574+ const [ product ] = await service . createProducts ( [
1575+ buildProductAndRelationsData ( { images } ) ,
1576+ ] )
1577+
1578+ await service . updateProducts ( product . id , {
1579+ images : [ ] ,
1580+ } )
1581+
1582+ const productAfterUpdate = await service . retrieveProduct ( product . id , {
1583+ relations : [ "*" ] ,
1584+ } )
1585+
1586+ expect ( productAfterUpdate . images ) . toHaveLength ( 0 )
1587+ } )
1588+
14111589 it ( "should retrieve images in the correct order consistently" , async ( ) => {
14121590 const images = Array . from ( { length : 1000 } , ( _ , i ) => ( {
14131591 url : `image-${ i + 1 } ` ,
0 commit comments