233 lines
7.6 KiB
Python
233 lines
7.6 KiB
Python
"""
|
|
Test suite for S3 CloudStorage integration.
|
|
|
|
Tests verify file upload, cleanup, URL generation, and error handling.
|
|
"""
|
|
|
|
import asyncio
|
|
import importlib
|
|
import os
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
import sglang.multimodal_gen.runtime.entrypoints.openai.storage as storage_mod
|
|
from sglang.multimodal_gen.runtime.entrypoints.openai.storage import CloudStorage
|
|
|
|
|
|
def _create_temp_file(tmp_path, name="test.png", content=b"\x89PNG\r\n\x1a\nfake"):
|
|
"""Create a temporary test file."""
|
|
p = tmp_path / name
|
|
p.write_bytes(content)
|
|
return str(p)
|
|
|
|
|
|
# UNIT TESTS
|
|
|
|
|
|
def test_upload_file_success(tmp_path):
|
|
"""Test successful upload with correct URL generation."""
|
|
file_path = _create_temp_file(tmp_path, "image.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "my-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://s3.example.com"
|
|
storage_mod.cloud_storage.region_name = None
|
|
|
|
called = {}
|
|
|
|
def fake_upload(local_path, bucket, key, ExtraArgs=None):
|
|
called["local_path"] = local_path
|
|
called["bucket"] = bucket
|
|
called["key"] = key
|
|
called["extra"] = ExtraArgs
|
|
|
|
storage_mod.cloud_storage.client = SimpleNamespace(upload_file=fake_upload)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_file(file_path, "image.png"))
|
|
|
|
assert url == "https://s3.example.com/my-bucket/image.png"
|
|
assert called["local_path"] == file_path
|
|
assert called["bucket"] == "my-bucket"
|
|
assert called["key"] == "image.png"
|
|
assert called["extra"]["ContentType"] == "image/png"
|
|
|
|
|
|
def test_upload_and_cleanup(tmp_path):
|
|
"""Test that local file is deleted after successful upload."""
|
|
file_path = _create_temp_file(tmp_path, "cleanup.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "my-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://s3.example.com"
|
|
storage_mod.cloud_storage.client = SimpleNamespace(
|
|
upload_file=lambda *args, **kwargs: None
|
|
)
|
|
|
|
assert os.path.exists(file_path)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_and_cleanup(file_path))
|
|
|
|
assert url == "https://s3.example.com/my-bucket/cleanup.png"
|
|
assert not os.path.exists(file_path)
|
|
|
|
|
|
def test_upload_failure_preserves_file(tmp_path):
|
|
"""Test that file is preserved when upload fails."""
|
|
file_path = _create_temp_file(tmp_path, "preserve.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "my-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://s3.example.com"
|
|
|
|
def fake_upload_raises(*args, **kwargs):
|
|
raise RuntimeError("simulated failure")
|
|
|
|
storage_mod.cloud_storage.client = SimpleNamespace(upload_file=fake_upload_raises)
|
|
|
|
result = asyncio.run(storage_mod.cloud_storage.upload_and_cleanup(file_path))
|
|
|
|
assert result is None
|
|
assert os.path.exists(file_path)
|
|
|
|
|
|
def test_disabled_storage_returns_none(tmp_path):
|
|
"""Test that disabled storage returns None."""
|
|
file_path = _create_temp_file(tmp_path, "test.png")
|
|
|
|
prev_enabled = storage_mod.cloud_storage.enabled
|
|
storage_mod.cloud_storage.enabled = False
|
|
|
|
try:
|
|
result = asyncio.run(
|
|
storage_mod.cloud_storage.upload_file(file_path, "test.png")
|
|
)
|
|
assert result is None
|
|
finally:
|
|
storage_mod.cloud_storage.enabled = prev_enabled
|
|
|
|
|
|
def test_aws_url_with_region(tmp_path):
|
|
"""Test AWS S3 URL generation with specific region."""
|
|
file_path = _create_temp_file(tmp_path, "aws.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "aws-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = None
|
|
storage_mod.cloud_storage.region_name = "us-west-2"
|
|
storage_mod.cloud_storage.client = SimpleNamespace(
|
|
upload_file=lambda *args, **kwargs: None
|
|
)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_file(file_path, "aws.png"))
|
|
|
|
assert url == "https://aws-bucket.s3.us-west-2.amazonaws.com/aws.png"
|
|
|
|
|
|
def test_aws_url_default_region(tmp_path):
|
|
"""Test AWS S3 URL defaults to us-east-1 when region not specified."""
|
|
file_path = _create_temp_file(tmp_path, "default.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "default-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = None
|
|
storage_mod.cloud_storage.region_name = None
|
|
storage_mod.cloud_storage.client = SimpleNamespace(
|
|
upload_file=lambda *args, **kwargs: None
|
|
)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_file(file_path, "default.png"))
|
|
|
|
assert url == "https://default-bucket.s3.us-east-1.amazonaws.com/default.png"
|
|
|
|
|
|
def test_custom_endpoint_url(tmp_path):
|
|
"""Test URL generation with custom endpoint (MinIO/OSS/COS)."""
|
|
file_path = _create_temp_file(tmp_path, "custom.png")
|
|
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "custom-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://minio.example.com/"
|
|
storage_mod.cloud_storage.region_name = None
|
|
storage_mod.cloud_storage.client = SimpleNamespace(
|
|
upload_file=lambda *args, **kwargs: None
|
|
)
|
|
|
|
url = asyncio.run(storage_mod.cloud_storage.upload_file(file_path, "custom.png"))
|
|
|
|
# Verify trailing slash is stripped
|
|
assert url == "https://minio.example.com/custom-bucket/custom.png"
|
|
|
|
|
|
def test_content_type_detection(tmp_path):
|
|
"""Test Content-Type header for different file extensions."""
|
|
storage_mod.cloud_storage.enabled = True
|
|
storage_mod.cloud_storage.bucket_name = "test-bucket"
|
|
storage_mod.cloud_storage.endpoint_url = "https://s3.test"
|
|
|
|
test_cases = [
|
|
("image.png", "image/png"),
|
|
("image.jpg", "image/jpeg"),
|
|
("image.jpeg", "image/jpeg"),
|
|
("image.webp", "image/webp"),
|
|
("video.mp4", "video/mp4"),
|
|
("file.bin", "application/octet-stream"),
|
|
]
|
|
|
|
for filename, expected_type in test_cases:
|
|
called = {}
|
|
|
|
def fake_upload(local_path, bucket, key, ExtraArgs=None):
|
|
called["content_type"] = ExtraArgs.get("ContentType")
|
|
|
|
storage_mod.cloud_storage.client = SimpleNamespace(upload_file=fake_upload)
|
|
|
|
file_path = _create_temp_file(tmp_path, filename)
|
|
asyncio.run(storage_mod.cloud_storage.upload_file(file_path, filename))
|
|
|
|
assert called["content_type"] == expected_type
|
|
|
|
|
|
# requires moto and boto3
|
|
has_moto = (
|
|
importlib.util.find_spec("moto") is not None
|
|
and importlib.util.find_spec("boto3") is not None
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(not has_moto, reason="moto/boto3 not installed")
|
|
def test_integration_with_moto(tmp_path):
|
|
"""Integration test using moto to mock real S3 service."""
|
|
import boto3
|
|
from moto import mock_aws
|
|
|
|
os.environ["SGLANG_CLOUD_STORAGE_TYPE"] = "s3"
|
|
os.environ["SGLANG_S3_BUCKET_NAME"] = "integration-test"
|
|
os.environ["SGLANG_S3_REGION_NAME"] = "us-east-1"
|
|
|
|
with mock_aws():
|
|
s3 = boto3.client("s3", region_name="us-east-1")
|
|
s3.create_bucket(Bucket="integration-test")
|
|
|
|
storage = CloudStorage()
|
|
assert storage.is_enabled()
|
|
|
|
file_path = _create_temp_file(tmp_path, "integration.png", b"test_data")
|
|
|
|
url = asyncio.run(storage.upload_and_cleanup(file_path))
|
|
|
|
assert url is not None
|
|
assert "integration-test" in url
|
|
assert "integration.png" in url
|
|
assert not os.path.exists(file_path)
|
|
|
|
obj = s3.get_object(Bucket="integration-test", Key="integration.png")
|
|
assert obj["Body"].read() == b"test_data"
|
|
|
|
for key in [
|
|
"SGLANG_CLOUD_STORAGE_TYPE",
|
|
"SGLANG_S3_BUCKET_NAME",
|
|
"SGLANG_S3_REGION_NAME",
|
|
]:
|
|
os.environ.pop(key, None)
|