Initial commit — StreamStack v1
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.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import subprocess
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from streamstack.core.auth import create_access_token, create_refresh_token, decode_token
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def rsa_key_pair(tmp_path_factory):
|
||||
tmp = tmp_path_factory.mktemp("keys")
|
||||
priv = tmp / "private.pem"
|
||||
pub = tmp / "public.pem"
|
||||
subprocess.run(
|
||||
["openssl", "genrsa", "-out", str(priv), "2048"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
subprocess.run(
|
||||
["openssl", "rsa", "-in", str(priv), "-pubout", "-out", str(pub)],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
return str(priv), str(pub)
|
||||
|
||||
|
||||
def test_access_token_roundtrip(rsa_key_pair):
|
||||
priv, pub = rsa_key_pair
|
||||
with patch("streamstack.core.auth.settings") as mock_settings:
|
||||
mock_settings.jwt_private_key_path = priv
|
||||
mock_settings.jwt_public_key_path = pub
|
||||
mock_settings.jwt_algorithm = "RS256"
|
||||
mock_settings.jwt_expire_minutes = 30
|
||||
token = create_access_token("user-123", "test@example.com", ["viewer"])
|
||||
with patch("streamstack.core.auth.settings") as mock_settings:
|
||||
mock_settings.jwt_public_key_path = pub
|
||||
mock_settings.jwt_algorithm = "RS256"
|
||||
payload = decode_token(token)
|
||||
assert payload["sub"] == "user-123"
|
||||
assert payload["email"] == "test@example.com"
|
||||
assert payload["roles"] == ["viewer"]
|
||||
assert "jti" in payload
|
||||
assert "exp" in payload
|
||||
|
||||
|
||||
def test_refresh_token_has_type(rsa_key_pair):
|
||||
priv, pub = rsa_key_pair
|
||||
with patch("streamstack.core.auth.settings") as mock_settings:
|
||||
mock_settings.jwt_private_key_path = priv
|
||||
mock_settings.jwt_public_key_path = pub
|
||||
mock_settings.jwt_algorithm = "RS256"
|
||||
mock_settings.jwt_expire_minutes = 30
|
||||
mock_settings.jwt_refresh_expire_days = 7
|
||||
token = create_refresh_token("user-123")
|
||||
with patch("streamstack.core.auth.settings") as mock_settings:
|
||||
mock_settings.jwt_public_key_path = pub
|
||||
mock_settings.jwt_algorithm = "RS256"
|
||||
payload = decode_token(token)
|
||||
assert payload["type"] == "refresh"
|
||||
assert payload["sub"] == "user-123"
|
||||
@@ -0,0 +1,105 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user