Files
Dorod-Sky/skyvern-ts/client/tests/unit/file/file.test.ts
2025-12-19 12:16:02 -08:00

499 lines
18 KiB
TypeScript

import fs from "fs";
import { join } from "path";
import { Readable } from "stream";
import { toBinaryUploadRequest, type Uploadable } from "../../../src/core/file/index";
describe("toBinaryUploadRequest", () => {
const TEST_FILE_PATH = join(__dirname, "..", "test-file.txt");
beforeEach(() => {
vi.clearAllMocks();
});
describe("Buffer input", () => {
it("should handle Buffer with all metadata", async () => {
const buffer = Buffer.from("test data");
const input: Uploadable.WithMetadata = {
data: buffer,
filename: "test.txt",
contentType: "text/plain",
contentLength: 42,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(buffer);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="test.txt"',
"Content-Type": "text/plain",
"Content-Length": "42",
});
});
it("should handle Buffer without metadata", async () => {
const buffer = Buffer.from("test data");
const input: Uploadable.WithMetadata = {
data: buffer,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(buffer);
expect(result.headers).toEqual({
"Content-Length": "9", // buffer.length
});
});
it("should handle Buffer passed directly", async () => {
const buffer = Buffer.from("test data");
const result = await toBinaryUploadRequest(buffer);
expect(result.body).toBe(buffer);
expect(result.headers).toEqual({
"Content-Length": "9", // buffer.length
});
});
});
describe("ArrayBuffer input", () => {
it("should handle ArrayBuffer with metadata", async () => {
const arrayBuffer = new ArrayBuffer(10);
const input: Uploadable.WithMetadata = {
data: arrayBuffer,
filename: "data.bin",
contentType: "application/octet-stream",
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(arrayBuffer);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="data.bin"',
"Content-Type": "application/octet-stream",
"Content-Length": "10", // arrayBuffer.byteLength
});
});
it("should handle ArrayBuffer passed directly", async () => {
const arrayBuffer = new ArrayBuffer(10);
const result = await toBinaryUploadRequest(arrayBuffer);
expect(result.body).toBe(arrayBuffer);
expect(result.headers).toEqual({
"Content-Length": "10", // arrayBuffer.byteLength
});
});
});
describe("Uint8Array input", () => {
it("should handle Uint8Array with metadata", async () => {
const uint8Array = new Uint8Array([1, 2, 3, 4, 5]);
const input: Uploadable.WithMetadata = {
data: uint8Array,
filename: "bytes.bin",
contentType: "application/octet-stream",
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(uint8Array);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="bytes.bin"',
"Content-Type": "application/octet-stream",
"Content-Length": "5", // uint8Array.byteLength
});
});
it("should handle Uint8Array passed directly", async () => {
const uint8Array = new Uint8Array([1, 2, 3, 4, 5]);
const result = await toBinaryUploadRequest(uint8Array);
expect(result.body).toBe(uint8Array);
expect(result.headers).toEqual({
"Content-Length": "5", // uint8Array.byteLength
});
});
});
describe("Blob input", () => {
it("should handle Blob with metadata", async () => {
const blob = new Blob(["test content"], { type: "text/plain" });
const input: Uploadable.WithMetadata = {
data: blob,
filename: "override.txt",
contentType: "text/html", // Override blob's type
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(blob);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="override.txt"',
"Content-Type": "text/html", // Should use provided contentType
"Content-Length": "12", // blob.size
});
});
it("should handle Blob with intrinsic type", async () => {
const blob = new Blob(["test content"], { type: "application/json" });
const input: Uploadable.WithMetadata = {
data: blob,
filename: "data.json",
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(blob);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="data.json"',
"Content-Type": "application/json", // Should use blob's type
"Content-Length": "12", // blob.size
});
});
it("should handle Blob passed directly", async () => {
const blob = new Blob(["test content"], { type: "text/plain" });
const result = await toBinaryUploadRequest(blob);
expect(result.body).toBe(blob);
expect(result.headers).toEqual({
"Content-Type": "text/plain", // Should use blob's type
"Content-Length": "12", // blob.size
});
});
});
describe("File input", () => {
it("should handle File with metadata", async () => {
const file = new File(["file content"], "original.txt", { type: "text/plain" });
const input: Uploadable.WithMetadata = {
data: file,
filename: "renamed.txt",
contentType: "text/html", // Override file's type
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(file);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="renamed.txt"',
"Content-Type": "text/html", // Should use provided contentType
"Content-Length": "12", // file.size
});
});
it("should handle File with intrinsic properties", async () => {
const file = new File(["file content"], "test.json", { type: "application/json" });
const input: Uploadable.WithMetadata = {
data: file,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(file);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="test.json"', // Should use file's name
"Content-Type": "application/json", // Should use file's type
"Content-Length": "12", // file.size
});
});
it("should handle File passed directly", async () => {
const file = new File(["file content"], "direct.txt", { type: "text/plain" });
const result = await toBinaryUploadRequest(file);
expect(result.body).toBe(file);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="direct.txt"',
"Content-Type": "text/plain",
"Content-Length": "12", // file.size
});
});
});
describe("ReadableStream input", () => {
it("should handle ReadableStream with metadata", async () => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("stream data"));
controller.close();
},
});
const input: Uploadable.WithMetadata = {
data: stream,
filename: "stream.txt",
contentType: "text/plain",
contentLength: 100,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(stream);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="stream.txt"',
"Content-Type": "text/plain",
"Content-Length": "100", // Should use provided contentLength
});
});
it("should handle ReadableStream without size", async () => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("stream data"));
controller.close();
},
});
const input: Uploadable.WithMetadata = {
data: stream,
filename: "stream.txt",
contentType: "text/plain",
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(stream);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="stream.txt"',
"Content-Type": "text/plain",
// No Content-Length header since it cannot be determined from ReadableStream
});
});
it("should handle ReadableStream passed directly", async () => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("stream data"));
controller.close();
},
});
const result = await toBinaryUploadRequest(stream);
expect(result.body).toBe(stream);
expect(result.headers).toEqual({
// No headers since no metadata provided and cannot be determined
});
});
});
describe("Node.js Readable stream input", () => {
it("should handle Readable stream with metadata", async () => {
const readable = new Readable({
read() {
this.push("readable data");
this.push(null);
},
});
const input: Uploadable.WithMetadata = {
data: readable,
filename: "readable.txt",
contentType: "text/plain",
contentLength: 50,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(readable);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="readable.txt"',
"Content-Type": "text/plain",
"Content-Length": "50", // Should use provided contentLength
});
});
it("should handle Readable stream without size", async () => {
const readable = new Readable({
read() {
this.push("readable data");
this.push(null);
},
});
const input: Uploadable.WithMetadata = {
data: readable,
filename: "readable.txt",
contentType: "text/plain",
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(readable);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="readable.txt"',
"Content-Type": "text/plain",
// No Content-Length header since it cannot be determined from Readable
});
});
it("should handle Readable stream passed directly", async () => {
const readable = new Readable({
read() {
this.push("readable data");
this.push(null);
},
});
const result = await toBinaryUploadRequest(readable);
expect(result.body).toBe(readable);
expect(result.headers).toEqual({
// No headers since no metadata provided and cannot be determined
});
});
});
describe("File path input (FromPath type)", () => {
it("should handle file path with all metadata", async () => {
const input: Uploadable.FromPath = {
path: TEST_FILE_PATH,
filename: "custom.txt",
contentType: "text/html",
contentLength: 42,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBeInstanceOf(fs.ReadStream);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="custom.txt"',
"Content-Type": "text/html",
"Content-Length": "42", // Should use provided contentLength
});
});
it("should handle file path with minimal metadata", async () => {
const input: Uploadable.FromPath = {
path: TEST_FILE_PATH,
contentType: "text/plain",
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBeInstanceOf(fs.ReadStream);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="test-file.txt"', // Should extract from path
"Content-Type": "text/plain",
"Content-Length": "21", // Should determine from file system (test file is 21 bytes)
});
});
it("should handle file path with no metadata", async () => {
const input: Uploadable.FromPath = {
path: TEST_FILE_PATH,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBeInstanceOf(fs.ReadStream);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="test-file.txt"', // Should extract from path
"Content-Length": "21", // Should determine from file system (test file is 21 bytes)
});
});
});
describe("ArrayBufferView input", () => {
it("should handle ArrayBufferView with metadata", async () => {
const arrayBuffer = new ArrayBuffer(10);
const arrayBufferView = new Int8Array(arrayBuffer);
const input: Uploadable.WithMetadata = {
data: arrayBufferView,
filename: "view.bin",
contentType: "application/octet-stream",
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(arrayBufferView);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="view.bin"',
"Content-Type": "application/octet-stream",
"Content-Length": "10", // arrayBufferView.byteLength
});
});
it("should handle ArrayBufferView passed directly", async () => {
const arrayBuffer = new ArrayBuffer(10);
const arrayBufferView = new Int8Array(arrayBuffer);
const result = await toBinaryUploadRequest(arrayBufferView);
expect(result.body).toBe(arrayBufferView);
expect(result.headers).toEqual({
"Content-Length": "10", // arrayBufferView.byteLength
});
});
});
describe("Edge cases", () => {
it("should handle empty headers when no metadata is available", async () => {
const buffer = Buffer.from("");
const input: Uploadable.WithMetadata = {
data: buffer,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(buffer);
expect(result.headers).toEqual({
"Content-Length": "0",
});
});
it("should handle zero contentLength", async () => {
const buffer = Buffer.from("test");
const input: Uploadable.WithMetadata = {
data: buffer,
contentLength: 0,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(buffer);
expect(result.headers).toEqual({
"Content-Length": "0", // Should use provided 0
});
});
it("should handle null filename", async () => {
const buffer = Buffer.from("test");
const input: Uploadable.WithMetadata = {
data: buffer,
filename: undefined,
contentType: "text/plain",
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(buffer);
expect(result.headers).toEqual({
"Content-Type": "text/plain",
"Content-Length": "4",
// No Content-Disposition since filename is undefined
});
});
it("should handle null contentType", async () => {
const buffer = Buffer.from("test");
const input: Uploadable.WithMetadata = {
data: buffer,
filename: "test.txt",
contentType: undefined,
};
const result = await toBinaryUploadRequest(input);
expect(result.body).toBe(buffer);
expect(result.headers).toEqual({
"Content-Disposition": 'attachment; filename="test.txt"',
"Content-Length": "4",
// No Content-Type since contentType is undefined
});
});
});
});