Skip to content

Commit 25c613c

Browse files
committed
fix(UploadController): tests typedoc and error types
Updates the test cases to test on the expanded upload result types. Additionally typedoc documentation was updated to document the error and the controller more accurately.
1 parent 21375d3 commit 25c613c

File tree

4 files changed

+231
-25
lines changed

4 files changed

+231
-25
lines changed

src/controllers/UploadController.ts

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
NoFilesUploadedError,
1515
PartialUploadError,
1616
UploadFailedError,
17+
SingleUploadFailedError,
1718
} from "../lib/uploads/errors.js";
1819

1920
// Type definitions and guards at module scope
@@ -80,31 +81,37 @@ export class UploadController extends Controller {
8081
* });
8182
* ```
8283
*
83-
* Success Response (201):
84+
* Full Success Response (201):
8485
* ```json
8586
* {
8687
* "success": true,
8788
* "message": "Upload successful",
89+
* "uploadStatus": "all",
8890
* "data": {
8991
* "results": [
90-
* { "cid": "Qm...", "fileName": "example.txt" }
92+
* { "cid": "Qm...", "fileName": "example1.txt" },
93+
* { "cid": "Qm...", "fileName": "example2.txt" }
9194
* ],
9295
* "failed": []
9396
* }
9497
* }
9598
* ```
9699
*
97-
* Partial Success Response (207):
100+
* Multi-Status Response (207):
98101
* ```json
99102
* {
100103
* "success": false,
101104
* "message": "Some uploads failed",
105+
* "uploadStatus": "some",
102106
* "data": {
103107
* "results": [
104108
* { "cid": "Qm...", "fileName": "success.txt" }
105109
* ],
106110
* "failed": [
107-
* { "fileName": "failed.txt", "error": "Upload failed" }
111+
* {
112+
* "fileName": "failed.txt",
113+
* "error": "File exceeds size limit"
114+
* }
108115
* ]
109116
* }
110117
* }
@@ -115,6 +122,7 @@ export class UploadController extends Controller {
115122
* {
116123
* "success": false,
117124
* "message": "No files uploaded",
125+
* "uploadStatus": "none",
118126
* "errors": {
119127
* "upload": "No files uploaded"
120128
* }
@@ -164,8 +172,7 @@ export class UploadController extends Controller {
164172
fileName: file.originalname,
165173
};
166174
} catch (error) {
167-
throw new UploadFailedError(
168-
`Failed to upload file`,
175+
throw new SingleUploadFailedError(
169176
file.originalname,
170177
(error as Error).message,
171178
);
@@ -178,7 +185,7 @@ export class UploadController extends Controller {
178185
.map((result) => result.value);
179186

180187
const failed = uploadResults.filter(isFailedUpload).map((result) => {
181-
const error = result.reason as UploadFailedError;
188+
const error = result.reason as SingleUploadFailedError;
182189
return {
183190
fileName: error.fileName,
184191
error: error.errorDetail,
@@ -188,18 +195,14 @@ export class UploadController extends Controller {
188195
if (failed.length > 0) {
189196
const data = {
190197
results: successful,
191-
failed: failed.map((f) => ({
192-
fileName: f.fileName,
193-
error: f.error,
194-
})),
198+
failed: failed,
195199
};
196200

197201
if (failed.length === uploadResults.length) {
198-
throw new UploadFailedError(
199-
"All uploads failed",
200-
"multiple files",
201-
"None of the files could be uploaded",
202-
);
202+
throw new UploadFailedError("All uploads failed", {
203+
results: [],
204+
failed: failed, // Preserve all failed upload details
205+
});
203206
}
204207

205208
throw new PartialUploadError("Some uploads failed", data);
@@ -209,6 +212,7 @@ export class UploadController extends Controller {
209212
return {
210213
success: true,
211214
message: "Upload successful",
215+
uploadStatus: "all",
212216
data: {
213217
results: successful,
214218
failed: [],
@@ -220,16 +224,51 @@ export class UploadController extends Controller {
220224
return {
221225
success: false,
222226
message: error.message,
223-
...(error.errors && { errors: error.errors }),
224-
...(error instanceof PartialUploadError && { data: error.results }),
227+
uploadStatus: error instanceof PartialUploadError ? "some" : "none",
228+
errors: error.errors,
229+
data:
230+
error instanceof PartialUploadError ||
231+
error instanceof UploadFailedError
232+
? error.results
233+
: {
234+
results: [],
235+
failed: [
236+
{
237+
fileName:
238+
error instanceof SingleUploadFailedError
239+
? error.fileName
240+
: "unknown",
241+
error:
242+
error instanceof SingleUploadFailedError
243+
? error.errorDetail
244+
: error.message,
245+
},
246+
],
247+
},
225248
};
226249
}
227250

228-
throw new UploadFailedError(
229-
"Upload failed",
230-
"unknown file",
231-
(error as Error).message,
251+
const uploadError = new UploadFailedError(
252+
`Upload failed: ${(error as Error).message}`,
253+
{
254+
results: [],
255+
failed: [
256+
{
257+
fileName: "unknown",
258+
error: (error as Error).message,
259+
},
260+
],
261+
},
232262
);
263+
264+
this.setStatus(uploadError.code);
265+
return {
266+
success: false,
267+
message: uploadError.message,
268+
uploadStatus: "none",
269+
errors: uploadError.errors,
270+
data: uploadError.results,
271+
};
233272
}
234273
}
235274
}

src/lib/uploads/errors.ts

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,61 @@
11
import { UploadResponse } from "../../types/api.js";
22

3+
/**
4+
* Base class for file upload errors
5+
* @class FileUploadError
6+
* @extends Error
7+
*
8+
* @example
9+
* ```typescript
10+
* throw new FileUploadError(500, "Upload failed");
11+
* ```
12+
*
13+
* Response:
14+
* ```json
15+
* {
16+
* "success": false,
17+
* "message": "Upload failed",
18+
* "uploadStatus": "none"
19+
* }
20+
* ```
21+
*/
322
export class FileUploadError extends Error {
423
code: number;
524
public errors: UploadResponse["errors"];
625

26+
/**
27+
* @param code - HTTP status code
28+
* @param message - Error message
29+
*/
730
constructor(code: number, message: string) {
831
super(message);
932
this.name = "FileUploadError";
1033
this.code = code;
1134
}
1235
}
1336

37+
/**
38+
* Error thrown when no files are provided in the upload request
39+
* @class NoFilesUploadedError
40+
* @extends FileUploadError
41+
*
42+
* @example
43+
* ```typescript
44+
* throw new NoFilesUploadedError();
45+
* ```
46+
*
47+
* Response:
48+
* ```json
49+
* {
50+
* "success": false,
51+
* "message": "No files uploaded",
52+
* "uploadStatus": "none",
53+
* "errors": {
54+
* "upload": "No files uploaded"
55+
* }
56+
* }
57+
* ```
58+
*/
1459
export class NoFilesUploadedError extends FileUploadError {
1560
constructor() {
1661
super(400, "No files uploaded");
@@ -19,6 +64,36 @@ export class NoFilesUploadedError extends FileUploadError {
1964
}
2065
}
2166

67+
/**
68+
* Error thrown when some files uploaded successfully but others failed
69+
* @class PartialUploadError
70+
* @extends FileUploadError
71+
*
72+
* @example
73+
* ```typescript
74+
* throw new PartialUploadError("Some uploads failed", {
75+
* results: [{ cid: "Qm...", fileName: "success.txt" }],
76+
* failed: [{ fileName: "failed.txt", error: "Upload failed" }]
77+
* });
78+
* ```
79+
*
80+
* Response:
81+
* ```json
82+
* {
83+
* "success": false,
84+
* "message": "Some uploads failed",
85+
* "uploadStatus": "some",
86+
* "data": {
87+
* "results": [
88+
* { "cid": "Qm...", "fileName": "success.txt" }
89+
* ],
90+
* "failed": [
91+
* { "fileName": "failed.txt", "error": "Upload failed" }
92+
* ]
93+
* }
94+
* }
95+
* ```
96+
*/
2297
export class PartialUploadError extends FileUploadError {
2398
constructor(
2499
message: string,
@@ -29,11 +104,80 @@ export class PartialUploadError extends FileUploadError {
29104
}
30105
}
31106

32-
export class UploadFailedError extends FileUploadError {
107+
/**
108+
* Error thrown when a single file upload fails
109+
* @class SingleUploadFailedError
110+
* @extends FileUploadError
111+
*
112+
* @example
113+
* ```typescript
114+
* throw new SingleUploadFailedError("example.txt", "File too large");
115+
* ```
116+
*
117+
* Response:
118+
* ```json
119+
* {
120+
* "success": false,
121+
* "message": "Failed to upload example.txt",
122+
* "uploadStatus": "none",
123+
* "data": {
124+
* "results": [],
125+
* "failed": [
126+
* { "fileName": "example.txt", "error": "File too large" }
127+
* ]
128+
* }
129+
* }
130+
* ```
131+
*/
132+
export class SingleUploadFailedError extends FileUploadError {
33133
constructor(
34-
message: string,
35134
public fileName: string,
36135
public errorDetail: string,
136+
) {
137+
super(422, `Failed to upload ${fileName}`);
138+
this.name = "SingleUploadFailedError";
139+
}
140+
}
141+
142+
/**
143+
* Error thrown when the upload service is unavailable or all uploads fail due to service issues
144+
* @class UploadFailedError
145+
* @extends FileUploadError
146+
*
147+
* @example
148+
* ```typescript
149+
* throw new UploadFailedError("Upload service unavailable", {
150+
* results: [],
151+
* failed: [
152+
* { fileName: "file1.txt", error: "IPFS service unavailable" },
153+
* { fileName: "file2.txt", error: "Storage service not responding" }
154+
* ]
155+
* });
156+
* ```
157+
*
158+
* Response:
159+
* ```json
160+
* {
161+
* "success": false,
162+
* "message": "Upload service unavailable",
163+
* "uploadStatus": "none",
164+
* "errors": {
165+
* "upload": "Upload service unavailable"
166+
* },
167+
* "data": {
168+
* "results": [],
169+
* "failed": [
170+
* { "fileName": "file1.txt", "error": "IPFS service unavailable" },
171+
* { "fileName": "file2.txt", "error": "Storage service not responding" }
172+
* ]
173+
* }
174+
* }
175+
* ```
176+
*/
177+
export class UploadFailedError extends FileUploadError {
178+
constructor(
179+
message: string,
180+
public results: UploadResponse["data"],
37181
) {
38182
super(500, message);
39183
this.name = "UploadFailedError";

src/types/api.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ export interface ValidationResponse extends BaseResponse {
2626
// Storage-related interfaces
2727
export interface StorageResponse extends DataResponse<{ cid: string }> {}
2828

29-
export interface UploadResponse extends BaseResponse {
29+
export type UploadStatus = "all" | "some" | "none";
30+
31+
export interface UploadResponse {
32+
success: boolean;
33+
message: string;
34+
uploadStatus: UploadStatus;
3035
data?: {
3136
results: Array<{
3237
cid: string;
@@ -37,6 +42,7 @@ export interface UploadResponse extends BaseResponse {
3742
error: string;
3843
}>;
3944
};
45+
errors?: Record<string, string>;
4046
}
4147

4248
// Data-related interfaces

0 commit comments

Comments
 (0)