diff --git a/.gitignore b/.gitignore
index e57cb7e..7fb500b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -136,4 +136,6 @@ buildNumber.properties
# End of https://www.toptal.com/developers/gitignore/api/intellij+all,java,maven
-infra/.env
\ No newline at end of file
+infra/.env
+
+TestApplication.java
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f74089..81aa44c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## v1.1.0 - [Supports v0.29.2](https://github.com/supabase/storage-api/releases/tag/v0.29.2)
+- Added support for file size limit on bucket leve.
+- Added utility class file size for parsing file sizes with `B, KB, MB, GB` in them
+- Added support for allowed mime types on bucket leve, so you can now restrict uploads to buckets for a certain file type.
+
## v1.0.1 - [Supports v0.28.2](https://github.com/supabase/storage-api/releases/tag/v0.28.2)
- Added new `FileTransformOptions`.
- Updated [javadocs](https://supabase-community.github.io/storage-java)
diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml
index 6c7a37c..b00f7cc 100644
--- a/infra/docker-compose.yml
+++ b/infra/docker-compose.yml
@@ -10,8 +10,8 @@ services:
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
KONG_PLUGINS: request-transformer,cors,key-auth,http-log
ports:
- - "8000:8000/tcp"
- - "8443:8443/tcp"
+ - 8000:8000/tcp
+ - 8443:8443/tcp
rest:
image: postgrest/postgrest:latest
ports:
@@ -38,14 +38,17 @@ services:
ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.ReNhHIoXIOa-8tL1DO3e26mJmOTnYuvdgobwIYGzrLQ
SERVICE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.FhK1kZdHmWdCIEZELt0QDCw6FIlCS8rVmp4RzaeI2LM
PROJECT_REF: bjwdssmqcnupljrqypxz # can be any random string
- REGION: eu-west-1 # region where your bucket is located
POSTGREST_URL: http://rest:3000
- GLOBAL_S3_BUCKET: java-supa-storage-testing # name of s3 bucket where you want to store objects
PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
- DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
- PGOPTIONS: "-c search_path=storage"
+
+ REGION: eu-west-1 # region where your bucket is located
+ GLOBAL_S3_BUCKET: java-supa-storage-testing # name of s3 bucket where you want to store objects
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
+
+ DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
+ PGOPTIONS: "-c search_path=storage"
+
FILE_SIZE_LIMIT: 52428800
STORAGE_BACKEND: file
FILE_STORAGE_BACKEND_PATH: /tmp/storage
@@ -54,13 +57,12 @@ services:
volumes:
- assets-volume:/tmp/storage
healthcheck:
- test: [ 'CMD-SHELL', 'curl -f -LI http://localhost:5000/status' ]
-
+ test: ['CMD-SHELL', 'curl -f -LI http://localhost:5000/status']
db:
build:
context: ./postgres
ports:
- - "5432:5432"
+ - 5432:5432
command:
- postgres
- -c
@@ -79,9 +81,9 @@ services:
imgproxy:
image: darthsim/imgproxy
ports:
- - "50020:8080"
+ - 50020:8080
volumes:
- - assets-volume:/tmp/storage
+ - assets-volume:/tmp/storage
environment:
- IMGPROXY_LOCAL_FILESYSTEM_ROOT=/
- IMGPROXY_USE_ETAG=true
diff --git a/infra/postgres/dummy-data.sql b/infra/postgres/dummy-data.sql
index 5d5feba..690ed45 100644
--- a/infra/postgres/dummy-data.sql
+++ b/infra/postgres/dummy-data.sql
@@ -51,4 +51,6 @@ CREATE POLICY authenticated_folder ON storage.objects for all USING (bucket_id='
-- allow CRUD access to a folder in bucket2 to its owners
CREATE POLICY crud_owner_only ON storage.objects for all USING (bucket_id='bucket2' and (storage.foldername(name))[1] = 'only_owner' and owner = auth.uid());
-- allow CRUD access to bucket4
-CREATE POLICY open_all_update ON storage.objects for all WITH CHECK (bucket_id='bucket4');
\ No newline at end of file
+CREATE POLICY open_all_update ON storage.objects for all WITH CHECK (bucket_id='bucket4');
+
+CREATE POLICY crud_my_bucket ON storage.objects for all USING (bucket_id='my-private-bucket' and auth.uid()::text = '317eadce-631a-4429-a0bb-f19a7a517b4a');
\ No newline at end of file
diff --git a/infra/postgres/storage-schema.sql b/infra/postgres/storage-schema.sql
index 3db47ee..dd49a20 100644
--- a/infra/postgres/storage-schema.sql
+++ b/infra/postgres/storage-schema.sql
@@ -37,68 +37,68 @@ CREATE INDEX name_prefix_search ON storage.objects(name text_pattern_ops);
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
CREATE OR REPLACE FUNCTION storage.foldername(name text)
- RETURNS text[]
- LANGUAGE plpgsql
+ RETURNS text[]
+ LANGUAGE plpgsql
AS $function$
DECLARE
-_parts text[];
+ _parts text[];
BEGIN
-select string_to_array(name, '/') into _parts;
-return _parts[1:array_length(_parts,1)-1];
+ select string_to_array(name, '/') into _parts;
+ return _parts[1:array_length(_parts,1)-1];
END
$function$;
CREATE OR REPLACE FUNCTION storage.filename(name text)
- RETURNS text
- LANGUAGE plpgsql
+ RETURNS text
+ LANGUAGE plpgsql
AS $function$
DECLARE
-_parts text[];
+ _parts text[];
BEGIN
-select string_to_array(name, '/') into _parts;
-return _parts[array_length(_parts,1)];
+ select string_to_array(name, '/') into _parts;
+ return _parts[array_length(_parts,1)];
END
$function$;
CREATE OR REPLACE FUNCTION storage.extension(name text)
- RETURNS text
- LANGUAGE plpgsql
+ RETURNS text
+ LANGUAGE plpgsql
AS $function$
DECLARE
-_parts text[];
-_filename text;
+ _parts text[];
+ _filename text;
BEGIN
-select string_to_array(name, '/') into _parts;
-select _parts[array_length(_parts,1)] into _filename;
-return split_part(_filename, '.', 2);
+ select string_to_array(name, '/') into _parts;
+ select _parts[array_length(_parts,1)] into _filename;
+ return split_part(_filename, '.', 2);
END
$function$;
CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0)
- RETURNS TABLE (
- name text,
- id uuid,
- updated_at TIMESTAMPTZ,
- created_at TIMESTAMPTZ,
- last_accessed_at TIMESTAMPTZ,
- metadata jsonb
- )
- LANGUAGE plpgsql
+ RETURNS TABLE (
+ name text,
+ id uuid,
+ updated_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ,
+ last_accessed_at TIMESTAMPTZ,
+ metadata jsonb
+ )
+ LANGUAGE plpgsql
AS $function$
BEGIN
-return query
- with files_folders as (
- select ((string_to_array(objects.name, '/'))[levels]) as folder
- from objects
- where objects.name ilike prefix || '%'
- and bucket_id = bucketname
- GROUP by folder
- limit limits
- offset offsets
- )
-select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
- left join objects
- on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname;
+ return query
+ with files_folders as (
+ select ((string_to_array(objects.name, '/'))[levels]) as folder
+ from objects
+ where objects.name ilike prefix || '%'
+ and bucket_id = bucketname
+ GROUP by folder
+ limit limits
+ offset offsets
+ )
+ select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
+ left join objects
+ on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname;
END
$function$;
diff --git a/infra/storage/Dockerfile b/infra/storage/Dockerfile
index 7ffd6d7..8da2f90 100644
--- a/infra/storage/Dockerfile
+++ b/infra/storage/Dockerfile
@@ -1,3 +1,3 @@
-FROM supabase/storage-api:v0.28.0
+FROM supabase/storage-api:v0.29.0
RUN apk add curl --no-cache
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index db79a56..6274619 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.supabase
storage-java
- 1.0.1
+ 1.1.0
Storage Java
An async client library for the Supabase Storage API in Java
diff --git a/src/main/java/io/supabase/StorageClient.java b/src/main/java/io/supabase/StorageClient.java
index ef2a408..e382cc3 100644
--- a/src/main/java/io/supabase/StorageClient.java
+++ b/src/main/java/io/supabase/StorageClient.java
@@ -15,7 +15,7 @@ public StorageClient(String apiKey, String url) {
this(url, new HashMap<>() {{
put("Authorization", "Bearer " + apiKey);
}});
- // Validate URL and throw if not a valid url.
+ //TODO: Validate URL and throw if not a valid url.
}
private StorageClient(String url, Map headers) {
diff --git a/src/main/java/io/supabase/api/StorageBucketAPI.java b/src/main/java/io/supabase/api/StorageBucketAPI.java
index fea1312..e958f63 100644
--- a/src/main/java/io/supabase/api/StorageBucketAPI.java
+++ b/src/main/java/io/supabase/api/StorageBucketAPI.java
@@ -1,11 +1,13 @@
package io.supabase.api;
+import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import io.supabase.data.bucket.Bucket;
import io.supabase.data.bucket.BucketCreateOptions;
import io.supabase.data.bucket.BucketUpdateOptions;
import io.supabase.data.bucket.CreateBucketResponse;
+import io.supabase.utils.FileSize;
import io.supabase.utils.MessageResponse;
import io.supabase.utils.RestUtils;
@@ -37,7 +39,7 @@ public StorageBucketAPI(String url, Map headers) {
*/
@Override
public CompletableFuture createBucket(String bucketId) {
- return createBucket(bucketId, new BucketCreateOptions(false));
+ return createBucket(bucketId, new BucketCreateOptions(false, new FileSize(0), null));
}
/**
@@ -50,9 +52,15 @@ public CompletableFuture createBucket(String bucketId) {
@Override
public CompletableFuture createBucket(String bucketId, BucketCreateOptions options) {
JsonObject body = new JsonObject();
+ JsonArray allowedMimeTypes = new JsonArray();
+ for (String mimeType : options.getAllowedMimeTypes()) {
+ allowedMimeTypes.add(mimeType);
+ }
body.addProperty("name", bucketId);
body.addProperty("id", bucketId);
body.addProperty("public", options.isPublic());
+ body.addProperty("file_size_limit", options.getFileSizeLimit().getFileSizeAsB());
+ body.add("allowed_mime_types", allowedMimeTypes);
return RestUtils.post(new TypeToken() {
}, headers, url + "bucket", body);
}
diff --git a/src/main/java/io/supabase/data/bucket/Bucket.java b/src/main/java/io/supabase/data/bucket/Bucket.java
index 1678b63..74d9928 100644
--- a/src/main/java/io/supabase/data/bucket/Bucket.java
+++ b/src/main/java/io/supabase/data/bucket/Bucket.java
@@ -1,49 +1,28 @@
package io.supabase.data.bucket;
+import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;
-
-public class Bucket {
- private final String id;
- private final String name;
- private final String owner;
- @SerializedName("public")
- private final boolean isBucketPublic;
- @SerializedName("created_at")
- private final String createdAt;
- @SerializedName("updated_at")
- private final String updatedAt;
-
- public Bucket(String id, String name, String owner, boolean isBucketPublic, String createdAt, String updatedAt) {
- this.id = id;
- this.name = name;
- this.owner = owner;
- this.isBucketPublic = isBucketPublic;
- this.createdAt = createdAt;
- this.updatedAt = updatedAt;
- }
-
- public String getId() {
- return id;
- }
-
- public String getName() {
- return name;
- }
-
- public String getOwner() {
- return owner;
- }
-
- public boolean isBucketPublic() {
- return isBucketPublic;
- }
-
- public String getCreatedAt() {
- return createdAt;
- }
-
- public String getUpdatedAt() {
- return updatedAt;
+import io.supabase.utils.FileSize;
+
+import java.util.List;
+
+public record Bucket(String id, String name, String owner,
+ @SerializedName("public") boolean isBucketPublic,
+ @SerializedName("file_size_limit") @JsonAdapter(FileSize.class) FileSize fileSizeLimit,
+ @SerializedName("allowed_mime_types") List allowedMimeTypes,
+ @SerializedName("created_at") String createdAt,
+ @SerializedName("updated_at") String updatedAt) {
+
+ @Override
+ public String toString() {
+ return "Bucket{" +
+ "id='" + id + '\'' +
+ ", name='" + name + '\'' +
+ ", owner='" + owner + '\'' +
+ ", isBucketPublic=" + isBucketPublic +
+ ", createdAt='" + createdAt + '\'' +
+ ", updatedAt='" + updatedAt + '\'' +
+ '}';
}
}
diff --git a/src/main/java/io/supabase/data/bucket/BucketCreateOptions.java b/src/main/java/io/supabase/data/bucket/BucketCreateOptions.java
index 58b41d8..98b114e 100644
--- a/src/main/java/io/supabase/data/bucket/BucketCreateOptions.java
+++ b/src/main/java/io/supabase/data/bucket/BucketCreateOptions.java
@@ -1,8 +1,11 @@
package io.supabase.data.bucket;
-public class BucketCreateOptions extends BucketOptions {
+import io.supabase.utils.FileSize;
+
+import java.util.List;
- public BucketCreateOptions(boolean isPublic) {
- super(isPublic);
+public class BucketCreateOptions extends BucketOptions {
+ public BucketCreateOptions(boolean isPublic, FileSize fileSizeLimit, List allowedMimeTypes) {
+ super(isPublic, fileSizeLimit, allowedMimeTypes == null ? List.of() : allowedMimeTypes);
}
}
diff --git a/src/main/java/io/supabase/data/bucket/BucketOptions.java b/src/main/java/io/supabase/data/bucket/BucketOptions.java
index 2991c18..00ded5c 100644
--- a/src/main/java/io/supabase/data/bucket/BucketOptions.java
+++ b/src/main/java/io/supabase/data/bucket/BucketOptions.java
@@ -1,13 +1,29 @@
package io.supabase.data.bucket;
+import io.supabase.utils.FileSize;
+
+import java.util.List;
+
public class BucketOptions {
private final boolean isPublic;
+ private final FileSize fileSizeLimit;
+ private final List allowedMimeTypes;
- public BucketOptions(boolean isPublic) {
+ public BucketOptions(boolean isPublic, FileSize fileSizeLimit, List allowedMimeTypes) {
this.isPublic = isPublic;
+ this.fileSizeLimit = fileSizeLimit;
+ this.allowedMimeTypes = List.copyOf(allowedMimeTypes);
}
public boolean isPublic() {
return isPublic;
}
+
+ public FileSize getFileSizeLimit() {
+ return fileSizeLimit;
+ }
+
+ public List getAllowedMimeTypes() {
+ return List.copyOf(allowedMimeTypes);
+ }
}
diff --git a/src/main/java/io/supabase/data/bucket/BucketUpdateOptions.java b/src/main/java/io/supabase/data/bucket/BucketUpdateOptions.java
index 4b4f4c5..3d72365 100644
--- a/src/main/java/io/supabase/data/bucket/BucketUpdateOptions.java
+++ b/src/main/java/io/supabase/data/bucket/BucketUpdateOptions.java
@@ -1,7 +1,11 @@
package io.supabase.data.bucket;
+import io.supabase.utils.FileSize;
+
+import java.util.List;
+
public class BucketUpdateOptions extends BucketOptions {
- public BucketUpdateOptions(boolean isPublic) {
- super(isPublic);
+ public BucketUpdateOptions(boolean isPublic, FileSize fileSizeLimit, List allowedMimeTypes) {
+ super(isPublic, fileSizeLimit, allowedMimeTypes == null ? List.of() : allowedMimeTypes);
}
}
diff --git a/src/main/java/io/supabase/data/bucket/CreateBucketResponse.java b/src/main/java/io/supabase/data/bucket/CreateBucketResponse.java
index 7c5c3b1..1c2b680 100644
--- a/src/main/java/io/supabase/data/bucket/CreateBucketResponse.java
+++ b/src/main/java/io/supabase/data/bucket/CreateBucketResponse.java
@@ -10,4 +10,11 @@ public CreateBucketResponse(String name) {
public String getName() {
return name;
}
+
+ @Override
+ public String toString() {
+ return "CreateBucketResponse{" +
+ "name='" + name + '\'' +
+ '}';
+ }
}
diff --git a/src/main/java/io/supabase/utils/FileSize.java b/src/main/java/io/supabase/utils/FileSize.java
new file mode 100644
index 0000000..1202450
--- /dev/null
+++ b/src/main/java/io/supabase/utils/FileSize.java
@@ -0,0 +1,71 @@
+package io.supabase.utils;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+import java.lang.reflect.Type;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class FileSize implements JsonDeserializer {
+ private long fileSize;
+
+ public FileSize(String fileSizeWithUnit) {
+ Matcher matcher = Pattern.compile("(\\d+)(B|KB|MB|GB)", Pattern.CASE_INSENSITIVE).matcher(fileSizeWithUnit);
+ if (!matcher.matches()) throw new IllegalArgumentException("Invalid file size format");
+ int value = Integer.parseInt(matcher.group(1));
+ String unit = matcher.group(2).toUpperCase();
+
+ switch (unit) {
+ case "B" -> fileSize = value;
+ case "KB" -> fileSize = value * 1000L;
+ case "MB" -> fileSize = value * 1000L * 1000L;
+ case "GB" -> fileSize = value * 1000L * 1000L * 1000L;
+ }
+ }
+
+ public FileSize(long fileSize) {
+ this.fileSize = fileSize;
+ }
+
+ public String getFileSizeAsB() {
+ return fileSize + "B";
+ }
+
+ public String getFileSizeAsKB() {
+ return (fileSize / 1000L) + "KB";
+ }
+
+ public String getFileSizeAsMB() {
+ return (fileSize / 1000L / 1000L) + "MB";
+ }
+
+ public String getFileSizeAsGB() {
+ return (fileSize / 1000L / 1000L / 1000L) + "GB";
+ }
+
+ public long getFileSize() {
+ return fileSize;
+ }
+
+ @Override
+ public FileSize deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
+ return new FileSize(jsonElement.getAsLong());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ FileSize fileSize1 = (FileSize) o;
+ return fileSize == fileSize1.fileSize;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fileSize);
+ }
+}
diff --git a/src/test/java/io/supabase/api/StorageBucketAPITest.java b/src/test/java/io/supabase/api/StorageBucketAPITest.java
index 44789af..0bba90d 100644
--- a/src/test/java/io/supabase/api/StorageBucketAPITest.java
+++ b/src/test/java/io/supabase/api/StorageBucketAPITest.java
@@ -7,6 +7,7 @@
import io.supabase.data.bucket.BucketUpdateOptions;
import io.supabase.data.bucket.CreateBucketResponse;
import io.supabase.errors.StorageException;
+import io.supabase.utils.FileSize;
import io.supabase.utils.MessageResponse;
import org.junit.jupiter.api.*;
@@ -60,24 +61,30 @@ public void getBucketThrowsIfNotFound() {
@Test
@Order(1)
- public void createBucket() throws ExecutionException, InterruptedException {
+ public void createBucketSuccessfully() throws ExecutionException, InterruptedException {
CreateBucketResponse response = client.createBucket(newBucketName).get();
assertEquals(response.getName(), newBucketName);
+
}
@Test
- public void createPublicBucket() throws ExecutionException, InterruptedException {
+ public void createPublicBucketSuccessfully() throws ExecutionException, InterruptedException {
String bucketName = "my-new-public-bucket" + LocalDateTime.now();
- client.createBucket(bucketName, new BucketCreateOptions(true)).get();
+ FileSize fileSizeLimit = new FileSize(0);
+ List mimeTypes = List.of("image/png", "image/jpeg");
+ client.createBucket(bucketName, new BucketCreateOptions(true, fileSizeLimit, mimeTypes)).get();
Bucket actual = client.getBucket(bucketName).get();
assertTrue(actual.isBucketPublic());
+ assertFalse(actual.allowedMimeTypes().isEmpty());
+ assertTrue(actual.allowedMimeTypes().containsAll(mimeTypes));
+ Assertions.assertEquals(fileSizeLimit.getFileSize(), actual.fileSizeLimit().getFileSize());
}
@Test
@Order(2)
- public void updateBucket() throws ExecutionException, InterruptedException {
- MessageResponse updateResponse = client.updateBucket(newBucketName, new BucketUpdateOptions(true)).get();
+ public void updateBucketSuccessfully() throws ExecutionException, InterruptedException {
+ MessageResponse updateResponse = client.updateBucket(newBucketName, new BucketUpdateOptions(true, new FileSize(0), null)).get();
expect(updateResponse).toMatchSnapshot();
Bucket bucket = client.getBucket(newBucketName).get();
assertTrue(bucket.isBucketPublic());
diff --git a/src/test/java/io/supabase/api/StorageFileAPITest.java b/src/test/java/io/supabase/api/StorageFileAPITest.java
index 0948fca..c1836f4 100644
--- a/src/test/java/io/supabase/api/StorageFileAPITest.java
+++ b/src/test/java/io/supabase/api/StorageFileAPITest.java
@@ -3,6 +3,7 @@
import io.supabase.StorageClient;
import io.supabase.data.bucket.BucketCreateOptions;
import io.supabase.data.file.*;
+import io.supabase.utils.FileSize;
import io.supabase.utils.MessageResponse;
import io.supabase.utils.Utilities;
import org.junit.jupiter.api.*;
@@ -26,7 +27,7 @@ public class StorageFileAPITest {
private static String newBucket(boolean isPublic) throws ExecutionException, InterruptedException {
String bucketName = "bucket-" + LocalDateTime.now();
- client.createBucket(bucketName, new BucketCreateOptions(isPublic)).get();
+ client.createBucket(bucketName, new BucketCreateOptions(isPublic, new FileSize(0), null)).get();
return bucketName;
}
diff --git a/src/test/java/io/supabase/utils/FileSizeTest.java b/src/test/java/io/supabase/utils/FileSizeTest.java
new file mode 100644
index 0000000..25c97d9
--- /dev/null
+++ b/src/test/java/io/supabase/utils/FileSizeTest.java
@@ -0,0 +1,21 @@
+package io.supabase.utils;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class FileSizeTest {
+
+ @Test
+ public void fileSizeParsesCorrectly() {
+ FileSize fileSizeInKb = new FileSize("1000B");
+ FileSize fileSizeInGB = new FileSize("20GB");
+
+ assertEquals("1KB", fileSizeInKb.getFileSizeAsKB());
+ assertEquals("0MB", fileSizeInKb.getFileSizeAsMB());
+
+ assertEquals("20000MB", fileSizeInGB.getFileSizeAsMB());
+ assertEquals("20000000KB", fileSizeInGB.getFileSizeAsKB());
+ assertEquals("20000000000B", fileSizeInGB.getFileSizeAsB());
+ }
+}