2309e9f43a
Five-service streaming platform: auth, catalogue, streaming, ingest, thumbnailer. Includes React frontend served by nginx, NATS JetStream event bus, aiobotocore async S3, PyAV video metadata + thumbnail extraction, service-to-service JWT auth, and a full unit + e2e test suite.
106 lines
2.6 KiB
Python
106 lines
2.6 KiB
Python
import io
|
|
|
|
import boto3
|
|
import pytest
|
|
from moto import mock_aws
|
|
|
|
from streamstack.core.s3 import S3SeekableFile, open_s3_buffered
|
|
|
|
BUCKET = "test-bucket"
|
|
KEY = "test/video.mp4"
|
|
CONTENT = b"Hello World! This is seekable S3 content for testing purposes."
|
|
|
|
|
|
@pytest.fixture
|
|
def s3_object():
|
|
with mock_aws():
|
|
s3 = boto3.client("s3", region_name="us-east-1")
|
|
s3.create_bucket(Bucket=BUCKET)
|
|
s3.put_object(Bucket=BUCKET, Key=KEY, Body=CONTENT)
|
|
yield s3
|
|
|
|
|
|
def test_readable_and_seekable(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
assert f.readable() is True
|
|
assert f.seekable() is True
|
|
|
|
|
|
def test_size(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
assert f.size == len(CONTENT)
|
|
|
|
|
|
def test_initial_position(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
assert f.tell() == 0
|
|
|
|
|
|
def test_read_from_start(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
data = f.read(5)
|
|
assert data == CONTENT[:5]
|
|
assert f.tell() == 5
|
|
|
|
|
|
def test_seek_set(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
pos = f.seek(7)
|
|
assert pos == 7
|
|
assert f.tell() == 7
|
|
data = f.read(5)
|
|
assert data == CONTENT[7:12]
|
|
|
|
|
|
def test_seek_cur(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
f.read(5)
|
|
f.seek(2, io.SEEK_CUR)
|
|
assert f.tell() == 7
|
|
|
|
|
|
def test_seek_end(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
pos = f.seek(-5, io.SEEK_END)
|
|
assert pos == len(CONTENT) - 5
|
|
data = f.read()
|
|
assert data == CONTENT[-5:]
|
|
|
|
|
|
def test_seek_beyond_end_clamps(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
pos = f.seek(len(CONTENT) + 100)
|
|
assert pos == len(CONTENT)
|
|
|
|
|
|
def test_read_at_eof_returns_empty(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
f.seek(0, io.SEEK_END)
|
|
buf = bytearray(10)
|
|
n = f.readinto(buf)
|
|
assert n == 0
|
|
|
|
|
|
def test_full_read(s3_object):
|
|
f = S3SeekableFile(BUCKET, KEY, s3_client=s3_object)
|
|
data = f.read()
|
|
assert data == CONTENT
|
|
|
|
|
|
def test_open_s3_buffered(s3_object):
|
|
with mock_aws():
|
|
s3 = boto3.client("s3", region_name="us-east-1")
|
|
s3.create_bucket(Bucket=BUCKET)
|
|
s3.put_object(Bucket=BUCKET, Key=KEY, Body=CONTENT)
|
|
with patch_s3_client(s3):
|
|
buf = open_s3_buffered(BUCKET, KEY)
|
|
assert isinstance(buf, io.BufferedReader)
|
|
data = buf.read(5)
|
|
assert data == CONTENT[:5]
|
|
|
|
|
|
def patch_s3_client(client):
|
|
from unittest.mock import patch
|
|
|
|
return patch("streamstack.core.s3.get_s3_client", return_value=client)
|