import { assert, assertEquals, assertInstanceOf, assertRejects } from "./deps-tests.ts";import { S3Client, S3Errors } from "./mod.ts";
const config = { endPoint: "localhost", port: 9000, useSSL: false, region: "dev-region", accessKey: "AKIA_DEV", secretKey: "secretkey", bucket: "dev-bucket", pathStyle: true,};const client = new S3Client(config);
Deno.test({ name: "the API client can be used without authentication (this also tests SSL and pathStyle: false)", fn: async () => { const publicClient = new S3Client({ endPoint: "s3.amazonaws.com", port: 443, useSSL: true, region: "us-east-1", bucket: "amazon-pqa", pathStyle: false, }); const response = await publicClient.getObject("readme.txt").then((r) => r.text()); const expected = await fetch("https://amazon-pqa.s3.amazonaws.com/readme.txt").then((r) => r.text()); assertEquals(response, expected); },});
Deno.test({ name: "error parsing", fn: async () => { const unauthorizedClient = new S3Client({ ...config, secretKey: "invalid key" }); const err = await assertRejects( () => unauthorizedClient.putObject("test.txt", "This is the contents of the file."), ); assertInstanceOf(err, S3Errors.ServerError); assertEquals(err.statusCode, 403); assertEquals(err.code, "SignatureDoesNotMatch"); assertEquals( err.message, "The request signature we calculated does not match the signature you provided. Check your key and signing method.", ); assertEquals(err.bucketName, config.bucket); assertEquals(err.region, config.region); },});
Deno.test({ name: "putObject() can upload a small file", fn: async () => { const response = await client.putObject("test.txt", "This is the contents of the file."); assertEquals(response.etag, "f6b64dbfb5d44e98363ff586e08f7fe6"); },});
Deno.test({ name: "putObject() can set metadata", fn: async () => { const key = "test-with-metadata.txt"; const metadata = { "Content-Type": "text/plain", "Cache-Control": "public, max-age=456789, immutable", "x-amz-meta-custom-header": "This is a custom value", }; await client.putObject(key, "This is the contents of the file.", { metadata }); const stat = await client.statObject(key); assertEquals(stat.key, key); assertEquals(stat.metadata, metadata); },});
Deno.test({ name: "putObject() can stream a large file upload", fn: async () => { const dataStream = ReadableStream.from(async function* () { for (let i = 0; i < 32; i++) { yield new Uint8Array(1024 * 1024).fill(i % 256); } }());
const key = "test-32m.dat"; const metadata = { "Content-Type": "test/streaming", "x-amz-meta-custom-header": "This is a custom value!" }; const response = await client.putObject(key, dataStream, { partSize: 5 * 1024 * 1024, metadata }); assertEquals(response.etag, "ca6d977b6e7dc87ab5c5892e124c7277-7"); const stat = await client.statObject(key); assertEquals(stat.metadata, metadata); },});
Deno.test({ name: "exists() can check if an object exists", fn: async () => { const result1 = await client.exists("definitely-does-not-exist.foobar"); assertEquals(result1, false); await client.putObject("this-will-exist.now", "contents"); const result2 = await client.exists("this-will-exist.now"); assertEquals(result2, true); },});
Deno.test({ name: "deleteObject() can delete an object", fn: async () => { const key = "object-for-deletion-tests.txt"; await client.putObject(key, "contents"); assertEquals(await client.exists(key), true); await client.deleteObject(key); assertEquals(await client.exists(key), false); },});
Deno.test({ name: "statObject() can get an object's status", fn: async () => { const key = "test-stat.txt"; const metadata = { "Content-Type": "test/fake-data", "Cache-Control": "public, max-age=456789, immutable", "x-amz-meta-custom-header": "This is a custom value!", }; const contents = "This is the contents of the file. 🎈"; await client.putObject(key, contents, { metadata }); const stat = await client.statObject(key); assertEquals(stat.type, "Object"); assertEquals(stat.key, key); assertInstanceOf(stat.lastModified, Date); assertEquals(stat.lastModified.getFullYear(), new Date().getFullYear()); assertEquals(stat.size, new TextEncoder().encode(contents).length); assertEquals(stat.versionId, null); assertEquals(stat.metadata, metadata); },});
Deno.test({ name: "getObject() can download a small file", fn: async () => { const contents = "This is the contents of the file. 👻"; await client.putObject("test-get.txt", contents); const response = await client.getObject("test-get.txt"); assertEquals(await response.text(), contents); },});
Deno.test({ name: "getPartialObject() can download a partial file", fn: async () => { await client.putObject("test-get2.txt", "This is the contents of the file. 👻"); const response = await client.getPartialObject("test-get2.txt", { offset: 12, length: 8 }); assertEquals(await response.text(), "contents"); },});
for ( const path of [ "simple.txt", "файл/gemütlich.txt", "path with spaces.txt", "yes&no.dat", "foo(bar)", "1+1=2", "~backup<crazy>.foo", ]) { Deno.test({ name: `get/put/list with unicode or special characters in URLs: ${path}`, fn: async () => { const prefix = `filenames-test-${(Math.random() + 1).toString(36).substring(7)}/`; const contents = `This is the contents of the file called '${path}'.`; await client.putObject(prefix + path, contents); const response = await client.getObject(prefix + path); assertEquals(await response.text(), contents); const keys = await Array.fromAsync(client.listObjects({ prefix }), (entry) => entry.key); assertEquals(keys, [prefix + path]); }, });}
Deno.test({ name: "presignedGetObject() can create a pre-signed URL to download a file.", fn: async () => { const contents = "This is the contents of the file. 👻"; await client.putObject("test-presigned.cstm", contents); const presignedUrl = await client.presignedGetObject("test-presigned.cstm", { responseParams: { "response-content-type": "custom/content-type" }, }); const response = await fetch(presignedUrl); assertEquals(await response.text(), contents); assertEquals(await response.headers.get("Content-Type"), "custom/content-type"); },});
Deno.test({ name: "listObjects() can return an empty list when no keys match the prefix", fn: async () => { const response = client.listObjects({ prefix: "NO MATCH" }); assertEquals(await response.next(), { done: true, value: undefined }); },});
Deno.test({ name: "listObjects() can return a flat list of objects under a certain prefix", fn: async () => { const prefix = "list-objects-test-1/"; await client.putObject(`not-under-that-prefix.txt`, "file Zero"); await client.putObject(`${prefix}file-a.txt`, "file A"); await client.putObject(`${prefix}file-b.txt`, "file B"); await client.putObject(`${prefix}subpath/file-c.txt`, "file C"); await client.putObject(`${prefix}subpath/file-d.txt`, "file D"); const response = client.listObjects({ prefix }); const results = await Array.fromAsync(response); assertEquals(results.length, 4); assertEquals(results[0].key, "list-objects-test-1/file-a.txt"); assertEquals(results[0].etag, "31d97c4d04593b21b399ace73b061c34"); assertEquals(results[0].size, 6); assertEquals(results[0].type, "Object"); assertEquals(results[0].lastModified instanceof Date, true); assertEquals(results[0].lastModified.getFullYear(), new Date().getFullYear()); assertEquals(results[0].lastModified.getMonth(), new Date().getMonth());
assertEquals(results[1].key, "list-objects-test-1/file-b.txt"); assertEquals(results[1].etag, "1651d570b74339e94cace90cde7d3147"); assertEquals(results[2].key, "list-objects-test-1/subpath/file-c.txt"); assertEquals(results[3].key, "list-objects-test-1/subpath/file-d.txt"); },});
Deno.test({ name: "listObjects() can return a flat list of objects, spanning multiple pages", fn: async () => { const prefix = "list-objects-test-2/"; const putPromises = []; for (let i = 0; i < 30; i++) { putPromises.push(client.putObject(`${prefix}file-${i < 10 ? "0" : ""}${i}.txt`, `file ${i} contents`)); } await Promise.all(putPromises); const response = client.listObjects({ prefix, pageSize: 10 }); const results = await Array.fromAsync(response); assertEquals(results.length, 30); assertEquals(results[0].key, `${prefix}file-00.txt`); assertEquals(results[29].key, `${prefix}file-29.txt`);
const limitedResponse = client.listObjects({ prefix, pageSize: 10, maxResults: 25 }); const limitedResults = await Array.fromAsync(limitedResponse); assertEquals(limitedResults.length, 25); },});
Deno.test({ name: "listObjectsGrouped() can group results using a delimiter", fn: async () => { const prefix = "list-objects-test-3/"; await client.putObject(`${prefix}file-a.txt`, "file A"); await client.putObject(`${prefix}file-b.txt`, "file B"); await client.putObject(`${prefix}subpath-1/file-1-a.txt`, "file 1A"); await client.putObject(`${prefix}subpath-1/file-1-b.txt`, "file 1B"); await client.putObject(`${prefix}subpath-2/file-2-a.txt`, "file 1A"); await client.putObject(`${prefix}subpath-2/file-2-b.txt`, "file 1B"); await client.putObject(`${prefix}x-file.txt`, "file X");
const response = client.listObjectsGrouped({ prefix, delimiter: "/", pageSize: 3 }); const results = await Array.fromAsync(response); assertEquals(results.length, 5); assert(results[0].type === "Object"); assertEquals(results[0].key, `${prefix}file-a.txt`); assert(results[1].type === "Object"); assertEquals(results[1].key, `${prefix}file-b.txt`); assert(results[2].type === "CommonPrefix"); assertEquals(results[2].prefix, `${prefix}subpath-1/`); assert(results[3].type === "CommonPrefix"); assertEquals(results[3].prefix, `${prefix}subpath-2/`); assert(results[4].type === "Object"); assertEquals(results[4].key, `${prefix}x-file.txt`); },});
Deno.test({ name: "copyObject() can copy a file", fn: async () => { const contents = "This is the contents of the copy test file. 👻"; const sourceKey = "test-copy-source.txt"; const destKey = "test-copy-dest.txt";
const uploadResult = await client.putObject(sourceKey, contents); await client.deleteObject(destKey); assertEquals(await client.exists(destKey), false);
const response = await client.copyObject({ sourceKey }, destKey); assertEquals(uploadResult.etag, response.etag); assertEquals(uploadResult.versionId, response.copySourceVersionId); assertInstanceOf(response.lastModified, Date);
const downloadResult = await client.getObject(destKey); assertEquals(await downloadResult.text(), contents); },});
Deno.test({ name: "copyObject() gives an appropriate error if the source file doesn't exist.", fn: async () => { const sourceKey = "non-existent-source"; const err = await assertRejects( () => client.copyObject({ sourceKey }, "any-dest.txt"), ); assertInstanceOf(err, S3Errors.ServerError); assertEquals(err.code, "NoSuchKey"); assertEquals(err.statusCode, 404); assertEquals(err.key, sourceKey); assertEquals(err.message, "The specified key does not exist."); },});
Deno.test({ name: "bucketExists() can check if a bucket exists", fn: async () => { const testBucketName = "test-bucket"; let exists = await client.bucketExists(testBucketName); assertEquals(exists, false);
await client.makeBucket(testBucketName); exists = await client.bucketExists(testBucketName); assertEquals(exists, true);
await client.removeBucket(testBucketName); exists = await client.bucketExists(testBucketName); assertEquals(exists, false); },});