@@ -115,7 +115,7 @@ afterEach(async () => {
115115describe ( 'top-level upload commands' , ( ) => {
116116 test ( 'upload prepares storage, executes funding, uploads, and maps copy results' , async ( ) => {
117117 const filePath = await tempFile ( 'upload.txt' , 'data' )
118- const contexts = [ { id : 'ctx-primary' } ]
118+ const contexts = [ { id : 'ctx-primary' } , { id : 'ctx-secondary' } ]
119119 const execute = mock ( async ( ) => ( { hash : '0xprepare' } ) )
120120
121121 synapseStorage . createContexts . mockImplementation ( async ( ) => contexts )
@@ -125,8 +125,8 @@ describe('top-level upload commands', () => {
125125 synapseStorage . upload . mockImplementation ( async ( ) => ( {
126126 pieceCid : cid ( 'baga-upload' ) ,
127127 size : 4 ,
128- requestedCopies : 3 ,
129- complete : false ,
128+ requestedCopies : 2 ,
129+ complete : true ,
130130 copies : [
131131 {
132132 dataSetId : 42n ,
@@ -136,12 +136,20 @@ describe('top-level upload commands', () => {
136136 isNewDataSet : true ,
137137 role : 'primary' ,
138138 } ,
139+ {
140+ dataSetId : 43n ,
141+ retrievalUrl : 'https://backup.example/piece/baga-upload' ,
142+ pieceId : 8n ,
143+ providerId : 79n ,
144+ isNewDataSet : false ,
145+ role : 'secondary' ,
146+ } ,
139147 ] ,
140148 failedAttempts : [
141149 {
142150 providerId : 78n ,
143151 role : 'secondary' ,
144- error : 'temporarily unavailable ' ,
152+ error : 'replaced after transient failure ' ,
145153 explicit : false ,
146154 } ,
147155 ] ,
@@ -150,7 +158,7 @@ describe('top-level upload commands', () => {
150158 const result = await uploadCommand . run (
151159 commandContext ( {
152160 args : { path : filePath } ,
153- options : { copies : 3 , withCDN : true } ,
161+ options : { copies : 2 , withCDN : true } ,
154162 } )
155163 )
156164
@@ -159,7 +167,7 @@ describe('top-level upload commands', () => {
159167 { client : fakeWalletClient , source : 'foc-cli' } ,
160168 ] )
161169 expect ( synapseStorage . createContexts ) . toHaveBeenCalledWith ( {
162- copies : 3 ,
170+ copies : 2 ,
163171 withCDN : true ,
164172 } )
165173 expect ( synapseStorage . prepare ) . toHaveBeenCalledWith ( {
@@ -171,13 +179,13 @@ describe('top-level upload commands', () => {
171179 contexts,
172180 withCDN : true ,
173181 } )
174- expect ( result . status ) . toBe ( 'partially_uploaded ' )
182+ expect ( result . status ) . toBe ( 'uploaded ' )
175183 expect ( result . result ) . toEqual ( {
176184 pieceCid : 'baga-upload' ,
177185 pieceScannerUrl : 'https://pdp.vxb.ai/calibration/piece/baga-upload' ,
178186 size : 4 ,
179- requestedCopies : 3 ,
180- complete : false ,
187+ requestedCopies : 2 ,
188+ complete : true ,
181189 copyResults : [
182190 {
183191 dataSetId : '42' ,
@@ -188,6 +196,80 @@ describe('top-level upload commands', () => {
188196 isNewDataSet : true ,
189197 providerRole : 'primary' ,
190198 } ,
199+ {
200+ dataSetId : '43' ,
201+ datasetScannerUrl : 'https://pdp.vxb.ai/calibration/dataset/43' ,
202+ url : 'https://backup.example/piece/baga-upload' ,
203+ pieceId : '8' ,
204+ providerId : '79' ,
205+ isNewDataSet : false ,
206+ providerRole : 'secondary' ,
207+ } ,
208+ ] ,
209+ copyFailures : [
210+ {
211+ providerId : '78' ,
212+ role : 'secondary' ,
213+ error : 'replaced after transient failure' ,
214+ explicit : false ,
215+ } ,
216+ ] ,
217+ } )
218+ } )
219+
220+ test ( 'upload reports partial status when Synapse commits fewer copies than requested' , async ( ) => {
221+ const filePath = await tempFile ( 'partial.txt' , 'data' )
222+ const contexts = [
223+ { id : 'ctx-primary' } ,
224+ { id : 'ctx-secondary-a' } ,
225+ { id : 'ctx-secondary-b' } ,
226+ ]
227+
228+ synapseStorage . createContexts . mockImplementation ( async ( ) => contexts )
229+ synapseStorage . upload . mockImplementation ( async ( ) => ( {
230+ pieceCid : cid ( 'baga-partial' ) ,
231+ size : 4 ,
232+ requestedCopies : 3 ,
233+ complete : false ,
234+ copies : [
235+ {
236+ dataSetId : 42n ,
237+ retrievalUrl : 'https://provider.example/piece/baga-partial' ,
238+ pieceId : 7n ,
239+ providerId : 77n ,
240+ isNewDataSet : true ,
241+ role : 'primary' ,
242+ } ,
243+ ] ,
244+ failedAttempts : [
245+ {
246+ providerId : 78n ,
247+ role : 'secondary' ,
248+ error : 'temporarily unavailable' ,
249+ explicit : false ,
250+ } ,
251+ ] ,
252+ } ) )
253+
254+ const result = await uploadCommand . run (
255+ commandContext ( {
256+ args : { path : filePath } ,
257+ options : { copies : 3 } ,
258+ } )
259+ )
260+
261+ expect ( result . status ) . toBe ( 'partially_uploaded' )
262+ expect ( result . result ) . toMatchObject ( {
263+ pieceCid : 'baga-partial' ,
264+ requestedCopies : 3 ,
265+ complete : false ,
266+ copyResults : [
267+ {
268+ dataSetId : '42' ,
269+ pieceId : '7' ,
270+ providerId : '77' ,
271+ providerRole : 'primary' ,
272+ } ,
191273 ] ,
192274 copyFailures : [
193275 {
0 commit comments