Skip to content
Draft
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/http-specs"
---

Add Spector tests for File type with various content types (body and multipart)
146 changes: 146 additions & 0 deletions packages/http-specs/spec-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -2050,6 +2050,68 @@ Content-Type: image/jpg
--abcde12345--
```

### Payload_MultiPart_FormData_File_uploadFileArray

- Endpoint: `post /multipart/form-data/file/file-array`

Test multiple File instances in multipart form data.
Expected request:

```
POST /multipart/form-data/file/file-array HTTP/1.1
Content-Type: multipart/form-data; boundary=abcde12345

--abcde12345
Content-Disposition: form-data; name="files"; filename="image1.png"
Content-Type: image/png

{…file content of image.png…}
--abcde12345
Content-Disposition: form-data; name="files"; filename="image2.png"
Content-Type: image/png

{…file content of image.png…}
--abcde12345--
```

### Payload_MultiPart_FormData_File_uploadFileRequiredFilename

- Endpoint: `post /multipart/form-data/file/required-filename`

Test File type in multipart form data with required filename.
Expected request:

```
POST /multipart/form-data/file/required-filename HTTP/1.1
Content-Type: multipart/form-data; boundary=abcde12345

--abcde12345
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png

{…file content of image.png…}
--abcde12345--
```

### Payload_MultiPart_FormData_File_uploadFileSpecificContentType

- Endpoint: `post /multipart/form-data/file/specific-content-type`

Test File type in multipart form data with specific content type.
Expected request:

```
POST /multipart/form-data/file/specific-content-type HTTP/1.1
Content-Type: multipart/form-data; boundary=abcde12345

--abcde12345
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png

{…file content of image.png…}
--abcde12345--
```

### Payload_MultiPart_FormData_fileArrayAndBasic

- Endpoint: `post /multipart/form-data/complex-parts`
Expand Down Expand Up @@ -5210,6 +5272,90 @@ Expect to send a known value. Mock api expect to receive 'Monday'

Expect to handle an unknown value. Mock api expect to receive 'Weekend'

### Type_File_Body_downloadFileDefaultContentType

- Endpoint: `get /type/file/body/response/default-content-type`

Test File type as response body with unspecified content type.
The File type accepts any content type. For testing, server will return image/png.
Expected response:

- Content-Type header: image/png
- Body: binary content matching packages/http-specs/assets/image.png

### Type_File_Body_downloadFileJsonContentType

- Endpoint: `get /type/file/body/response/json-content-type`

Test File type as response body with JSON content type.
Expected response:

- Content-Type header: application/json
- Body: JSON content with file data

### Type_File_Body_downloadFileMultipleContentTypes

- Endpoint: `get /type/file/body/response/multiple-content-types`

Test File type as response body with multiple allowed content types.
Service will return image/png.
Expected response:

- Content-Type header: image/png
- Body: binary content matching packages/http-specs/assets/image.png

### Type_File_Body_downloadFileSpecificContentType

- Endpoint: `get /type/file/body/response/specific-content-type`

Test File type as response body with specific content type.
Expected response:

- Content-Type header: image/png
- Body: binary content matching packages/http-specs/assets/image.png

### Type_File_Body_uploadFileDefaultContentType

- Endpoint: `post /type/file/body/request/default-content-type`

Test File type as request body with unspecified content type.
The File type accepts any content type. For testing, sender will use image/png.
Expected request:

- Content-Type header: image/png
- Body: binary content matching packages/http-specs/assets/image.png

### Type_File_Body_uploadFileJsonContentType

- Endpoint: `post /type/file/body/request/json-content-type`

Test File type as request body with JSON content type.
Expected request:

- Content-Type header: application/json
- Body: JSON content with file data

### Type_File_Body_uploadFileMultipleContentTypes

- Endpoint: `post /type/file/body/request/multiple-content-types`

Test File type as request body with multiple allowed content types (image/png or image/jpeg).
Client should send image/png.
Expected request:

- Content-Type header: image/png
- Body: binary content matching packages/http-specs/assets/image.png

### Type_File_Body_uploadFileSpecificContentType

- Endpoint: `post /type/file/body/request/specific-content-type`

Test File type as request body with specific content type.
Expected request:

- Content-Type header: image/png
- Body: binary content matching packages/http-specs/assets/image.png

### Type_Model_Empty_getEmpty

- Endpoint: `get /type/model/empty/alone`
Expand Down
87 changes: 87 additions & 0 deletions packages/http-specs/specs/payload/multipart/main.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -608,4 +608,91 @@ namespace FormData {
): NoContentResponse;
}
}

@route("/file")
namespace File {
model FileWithRequiredFilename extends TypeSpec.Http.File<"image/png"> {
filename: string;
}

@scenario
@scenarioDoc("""
Test File type in multipart form data with specific content type.
Expected request:
```
POST /multipart/form-data/file/specific-content-type HTTP/1.1
Content-Type: multipart/form-data; boundary=abcde12345

--abcde12345
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png

{…file content of image.png…}
--abcde12345--
```
""")
@post
@route("/specific-content-type")
op uploadFileSpecificContentType(
@header contentType: "multipart/form-data",
@multipartBody body: {
file: HttpPart<TypeSpec.Http.File<"image/png">>;
},
): NoContentResponse;

@scenario
@scenarioDoc("""
Test File type in multipart form data with required filename.
Expected request:
```
POST /multipart/form-data/file/required-filename HTTP/1.1
Content-Type: multipart/form-data; boundary=abcde12345

--abcde12345
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png

{…file content of image.png…}
--abcde12345--
```
""")
@post
@route("/required-filename")
op uploadFileRequiredFilename(
@header contentType: "multipart/form-data",
@multipartBody body: {
file: HttpPart<FileWithRequiredFilename>;
},
): NoContentResponse;

@scenario
@scenarioDoc("""
Test multiple File instances in multipart form data.
Expected request:
```
POST /multipart/form-data/file/file-array HTTP/1.1
Content-Type: multipart/form-data; boundary=abcde12345

--abcde12345
Content-Disposition: form-data; name="files"; filename="image1.png"
Content-Type: image/png

{…file content of image.png…}
--abcde12345
Content-Disposition: form-data; name="files"; filename="image2.png"
Content-Type: image/png

{…file content of image.png…}
--abcde12345--
```
""")
@post
@route("/file-array")
op uploadFileArray(
@header contentType: "multipart/form-data",
@multipartBody body: {
files: HttpPart<TypeSpec.Http.File<"image/png">>[];
},
): NoContentResponse;
}
}
89 changes: 89 additions & 0 deletions packages/http-specs/specs/payload/multipart/mockapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,92 @@ Scenarios.Payload_MultiPart_FormData_HttpParts_NonString_float = passOnSuccess({
handler: (req: MockRequest) => createHandler(req, [checkFloat]),
kind: "MockApiDefinition",
});

// Helper function to check file in multipart for File type tests
function checkMultipartFile(
req: MockRequest,
file: Record<string, any>,
expectedContent: Buffer,
expectedContentType: string,
fieldName: string = "file",
expectedFileName?: string,
) {
req.expect.deepEqual(file.fieldname, fieldName);
req.expect.deepEqual(file.mimetype, expectedContentType);
req.expect.deepEqual(file.buffer, expectedContent);
if (expectedFileName) {
req.expect.deepEqual(file.originalname, expectedFileName);
}
}

// Multipart File type tests
Scenarios.Payload_MultiPart_FormData_File_uploadFileSpecificContentType = passOnSuccess({
uri: "/multipart/form-data/file/specific-content-type",
method: "post",
request: {
headers: {
"content-type": "multipart/form-data",
},
},
response: {
status: 204,
},
handler(req: MockRequest) {
if (req.files instanceof Array && req.files.length === 1) {
const file = req.files[0];
checkMultipartFile(req, file, pngFile, "image/png", "file", "image.png");
return { status: 204 };
} else {
throw new ValidationError("Expected exactly one file", "1 file", req.files);
}
},
kind: "MockApiDefinition",
});

Scenarios.Payload_MultiPart_FormData_File_uploadFileRequiredFilename = passOnSuccess({
uri: "/multipart/form-data/file/required-filename",
method: "post",
request: {
headers: {
"content-type": "multipart/form-data",
},
},
response: {
status: 204,
},
handler(req: MockRequest) {
if (req.files instanceof Array && req.files.length === 1) {
const file = req.files[0];
checkMultipartFile(req, file, pngFile, "image/png", "file", "image.png");
return { status: 204 };
} else {
throw new ValidationError("Expected exactly one file", "1 file", req.files);
}
},
kind: "MockApiDefinition",
});

Scenarios.Payload_MultiPart_FormData_File_uploadFileArray = passOnSuccess({
uri: "/multipart/form-data/file/file-array",
method: "post",
request: {
headers: {
"content-type": "multipart/form-data",
},
},
response: {
status: 204,
},
handler(req: MockRequest) {
if (req.files instanceof Array && req.files.length === 2) {
for (const file of req.files) {
req.expect.deepEqual(file.fieldname, "files");
checkMultipartFile(req, file, pngFile, "image/png", "files");
}
return { status: 204 };
} else {
throw new ValidationError("Expected exactly two files", "2 files", req.files);
}
},
kind: "MockApiDefinition",
});
Loading
Loading