blob: 031d343aa7c0a5c1459c0d62ffaec44df2cc16d5 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import { describe, it, expect, beforeEach, afterEach, jest } from "bun:test";
import { MinioUtils } from "./../lib/minio";
import { Result } from "./../lib/enums";
import * as minio from "minio";
import * as utils from "./../lib/utils";
// Mock the minio module
let minioClientMock: any;
// Create a mock ReadableStream
class MockReadableStream {
private position: number = 0;
constructor(public content: string) {}
async *[Symbol.asyncIterator]() {
yield this.content;
}
get size() {
return this.content.length;
}
getReader() {
return new MockReadableStreamReader(this);
}
}
class MockReadableStreamReader {
private position: number = 0;
constructor(private stream: MockReadableStream) {}
async read() {
if (this.position < this.stream.size) {
const chunk = this.stream.content[this.position];
this.position++;
return { value: chunk, done: false };
} else {
return { done: true };
}
}
releaseLock() {
// Implement as needed for your tests
}
}
beforeEach(() => {
// Create a mock object for minioClient methods
minioClientMock = {
listObjectsV2: jest.fn().mockReturnValue({
[Symbol.asyncIterator]: async function* () {
yield { name: "test-object-1" };
yield { name: "test-object-2" };
},
}),
removeObjects: jest.fn(),
putObject: jest.fn(),
removeObject: jest.fn(),
statObject: jest.fn(),
};
// Use jest.spyOn to mock the minio.Client constructor
jest.spyOn(minio, 'Client').mockImplementation(() => minioClientMock);
// Mock process.stdout.write
jest.spyOn(process.stdout, 'write').mockImplementation(() => {});
// Mock the toBuffer function
jest.spyOn(utils, 'toBuffer').mockImplementation(async (stream: ReadableStream) => {
return new Buffer('file-content');
});
});
afterEach(() => {
// Restore all mocks after each test
jest.restoreAllMocks();
});
describe("minio.ts", () => {
let minioUtils: MinioUtils;
const programOptions = {
"--verbose": false,
};
const bucketName = "test-bucket";
beforeEach(() => {
// Initialize MinioUtils with the mocked minio.Client
minioUtils = new MinioUtils({}, programOptions, bucketName);
});
it("should clean the bucket successfully", async () => {
// Mock removeObjects to resolve without error
minioClientMock.removeObjects.mockResolvedValue(undefined);
await minioUtils.cleanBucket();
// Check if listObjectsV2 was called correctly
expect(minioClientMock.listObjectsV2).toHaveBeenCalledWith(bucketName, "", true);
// Check if removeObjects was called with correct object names
expect(minioClientMock.removeObjects).toHaveBeenCalledWith(bucketName, [
"test-object-1",
"test-object-2",
]);
});
it("should handle cleanBucket with no objects in the bucket", async () => {
// Mock listObjectsV2 to return an empty iterable
minioClientMock.listObjectsV2.mockReturnValue({
[Symbol.asyncIterator]: async function* () {},
});
await minioUtils.cleanBucket();
// Ensure removeObjects is not called since there are no objects
expect(minioClientMock.removeObjects).not.toHaveBeenCalled();
});
it("should upload content successfully", async () => {
// Mock the Bun.file
const fileContent = "file-content";
const mockBuffer = Buffer.from(fileContent);
const mockFile = {
stream: () => new MockReadableStream(fileContent),
size: fileContent.length,
type: "text/plain",
};
global.Bun.file = jest.fn().mockReturnValue(mockFile);
minioClientMock.putObject.mockResolvedValue(undefined);
const result = await minioUtils.uploadContent("file.txt", "fileAddr");
expect(result).toBe(Result.COMPLETED);
expect(minioClientMock.putObject).toHaveBeenCalledWith(
bucketName,
"fileAddr",
mockBuffer,
mockFile.size,
{ "Content-Type": "text/plain" }
);
});
it("should handle uploadContent failure and retry", async () => {
const fileContent = "file-content";
const mockBuffer = Buffer.from(fileContent);
const mockFile = {
stream: () => new MockReadableStream(fileContent),
size: fileContent.length,
type: "text/plain",
};
global.Bun.file = jest.fn().mockReturnValue(mockFile);
// Mock putObject to throw an error simulating ConnectionClosed
minioClientMock.putObject.mockRejectedValueOnce({ code: "ConnectionClosed" });
minioClientMock.putObject.mockResolvedValueOnce(undefined);
const result = await minioUtils.uploadContent("file.txt", "fileAddr");
expect(result).toBe(Result.COMPLETED);
expect(minioClientMock.putObject).toHaveBeenCalledTimes(2);
});
it("should delete content successfully", async () => {
// Mock existsContent to return Result.EXISTS
jest.spyOn(minioUtils, "existsContent").mockResolvedValue(Result.EXISTS);
// Mock removeObject to resolve without error
minioClientMock.removeObject.mockResolvedValue(undefined);
const result = await minioUtils.deleteContent("fileAddr");
expect(result).toBe(Result.COMPLETED);
expect(minioClientMock.removeObject).toHaveBeenCalledWith(bucketName, "fileAddr", {
forceDelete: true,
});
});
it("should skip deleting non-existent content", async () => {
// Mock existsContent to return Result.DOESNT_EXISTS
jest.spyOn(minioUtils, "existsContent").mockResolvedValue(Result.DOESNT_EXISTS);
const result = await minioUtils.deleteContent("fileAddr");
expect(result).toBe(Result.SKIPPED);
expect(minioClientMock.removeObject).not.toHaveBeenCalled();
});
});