Skip to content

Commit e3ecd97

Browse files
committed
Bump version to 0.3.8
1 parent c9d4509 commit e3ecd97

File tree

4 files changed

+91
-23
lines changed

4 files changed

+91
-23
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "vcon"
3-
version = "0.3.7"
3+
version = "0.3.8"
44
description = "The vCon library"
55
authors = ["Thomas McCarthy-Howe <ghostofbasho@gmail.com>"]
66
license = "MIT"

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name="vcon",
8-
version="0.3.6",
8+
version="0.3.8",
99
author="Thomas McCarthy-Howe",
1010
author_email="ghostofbasho@gmail.com",
1111
description="A package for working with vCon containers",
@@ -19,4 +19,4 @@
1919
"Operating System :: OS Independent",
2020
],
2121
python_requires=">=3.6",
22-
)
22+
)

src/vcon/dialog.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,15 @@ def add_external_data(self, url: str, filename: str, mimetype: str) -> None:
156156
else:
157157
raise Exception(f"Failed to fetch external data: {response.status_code}")
158158

159-
# Overide the filename if provided, otherwise use the filename from the URL
159+
# Override the filename if provided, otherwise use the filename from the URL
160160
if filename:
161161
self.filename = filename
162162
else:
163-
self.filename = url.split("/")[-1]
163+
# Extract filename from URL, removing any query parameters
164+
url_path = url.split("?")[0]
165+
self.filename = url_path.split("/")[-1]
164166

165-
# Overide the mimetype if provided, otherwise use the mimetype from the URL
167+
# Override the mimetype if provided, otherwise use the mimetype from the URL
166168
if mimetype:
167169
self.mimetype = mimetype
168170

@@ -282,30 +284,33 @@ def to_inline_data(self) -> None:
282284
:return: None
283285
:rtype: None
284286
"""
287+
# Ensure filename attribute exists
288+
if not hasattr(self, "filename"):
289+
self.filename = None
290+
285291
# Read the contents from the URL
286292
response = requests.get(self.url)
287293
if response.status_code == 200:
288-
self.body = response.text
289-
self.mimetype = response.headers["Content-Type"]
294+
# For binary content, use response.content instead of response.text
295+
raw_content = response.content
296+
# Base64url encode the body
297+
self.body = base64.urlsafe_b64encode(raw_content).decode()
298+
self.mimetype = response.headers.get("Content-Type")
290299
else:
291300
raise Exception(f"Failed to fetch external data: {response.status_code}")
292301

293-
# Calculate the SHA-256 hash of the body as the signature
302+
# Calculate the SHA-256 hash of the original binary content
294303
self.alg = "sha256"
295304
self.encoding = "base64url"
296305
self.signature = base64.urlsafe_b64encode(
297-
hashlib.sha256(self.body.encode()).digest()
306+
hashlib.sha256(raw_content).digest()
298307
).decode()
299308

300-
# Overide the filename if provided, otherwise use the filename from the URL
301-
if self.filename:
302-
self.filename = self.filename
303-
else:
304-
self.filename = self.url.split("/")[-1]
305-
306-
# Overide the mimetype if provided, otherwise use the mimetype from the URL
307-
if self.mimetype:
308-
self.mimetype = self.mimetype
309+
# Set the filename if not already provided
310+
if not self.filename:
311+
# Extract filename from URL, removing any query parameters
312+
url_path = self.url.split("?")[0]
313+
self.filename = url_path.split("/")[-1]
309314

310-
# Add the body to the dialog
311-
self.add_inline_data(self.body, self.filename, self.mimetype)
315+
# Remove the url since this is now inline data
316+
delattr(self, "url")

tests/test_dialog.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from src.vcon.dialog import Dialog
33
import hashlib
44
import base64
5+
import requests
6+
from unittest.mock import Mock, patch
7+
from datetime import datetime
58

69

710
class TestDialog:
@@ -346,7 +349,7 @@ def test_handles_invalid_datetime_string(self):
346349
Dialog(type="text", start="invalid-datetime", parties=[1, 2, 3])
347350

348351
# Converts start time to ISO 8601 string if provided as datetime
349-
def test_convert_start_time_to_iso_string(self):
352+
def test_convert_datetime_to_iso_string(self):
350353
from datetime import datetime
351354
from src.vcon.dialog import Dialog
352355
from unittest.mock import patch
@@ -365,7 +368,7 @@ def test_convert_start_time_to_iso_string(self):
365368
assert dialog.start == "2022-09-15T10:30:00"
366369

367370
# Converts start time to ISO 8601 string if provided as string
368-
def test_convert_start_time_to_iso_string(self):
371+
def test_convert_string_to_iso_string(self):
369372
from datetime import datetime
370373
from src.vcon.dialog import Dialog
371374
from unittest.mock import patch
@@ -379,3 +382,63 @@ def test_convert_start_time_to_iso_string(self):
379382
dialog = Dialog(type="text", start=start_time, parties=[1, 2, 3])
380383

381384
assert dialog.start == expected_iso_time
385+
386+
def test_to_inline_data_binary(self):
387+
# Create some fake binary audio data
388+
fake_binary_data = (
389+
b"\x52\x49\x46\x46\x24\x08\x00\x00\x57\x41\x56\x45" # WAV header snippet
390+
)
391+
392+
# Mock the requests.get response
393+
mock_response = Mock()
394+
mock_response.status_code = 200
395+
mock_response.content = fake_binary_data
396+
mock_response.headers = {"Content-Type": "audio/x-wav"}
397+
398+
# Create a dialog with external data
399+
dialog = Dialog(
400+
type="audio",
401+
start=datetime.now(),
402+
parties=[1, 2],
403+
url="http://example.com/audio.wav",
404+
)
405+
406+
# Mock the requests.get call
407+
with patch("requests.get", return_value=mock_response):
408+
dialog.to_inline_data()
409+
410+
# Verify the conversion was successful
411+
assert not hasattr(dialog, "url") # URL should be removed
412+
assert dialog.mimetype == "audio/x-wav"
413+
assert dialog.filename == "audio.wav"
414+
assert dialog.encoding == "base64url"
415+
assert dialog.alg == "sha256"
416+
417+
# Decode the base64url body and verify it matches original content
418+
decoded_body = base64.urlsafe_b64decode(dialog.body.encode())
419+
assert decoded_body == fake_binary_data
420+
421+
# Verify the signature matches the content
422+
expected_signature = base64.urlsafe_b64encode(
423+
hashlib.sha256(fake_binary_data).digest()
424+
).decode()
425+
assert dialog.signature == expected_signature
426+
427+
def test_to_inline_data_failed_request(self):
428+
# Create a dialog with external data
429+
dialog = Dialog(
430+
type="audio",
431+
start=datetime.now(),
432+
parties=[1, 2],
433+
url="http://example.com/audio.wav",
434+
)
435+
436+
# Mock a failed request
437+
mock_response = Mock()
438+
mock_response.status_code = 404
439+
440+
# Verify that the conversion raises an exception
441+
with patch("requests.get", return_value=mock_response):
442+
with pytest.raises(Exception) as exc_info:
443+
dialog.to_inline_data()
444+
assert "Failed to fetch external data: 404" in str(exc_info.value)

0 commit comments

Comments
 (0)