Skip to content

Commit 5e371fd

Browse files
authored
Merge pull request #6 from vcon-dev/fix-external-dialog-adds-to-body
Add tests for Dialog's external and inline data handling; fix externa…
2 parents d592eae + b9fc87f commit 5e371fd

File tree

4 files changed

+135
-182
lines changed

4 files changed

+135
-182
lines changed

.github/workflows/python-package-conda.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ jobs:
3131
- name: Test with pytest
3232
run: |
3333
conda install pytest
34+
conda install pytest-mock
3435
pytest

poetry.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/vcon/dialog.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ def add_external_data(self, url: str, filename: str, mimetype: str) -> None:
172172
"""
173173
response = requests.get(url)
174174
if response.status_code == 200:
175-
self.body = response.text
176175
self.mimetype = response.headers["Content-Type"]
177176
else:
178177
raise Exception(f"Failed to fetch external data: {response.status_code}")
@@ -190,7 +189,7 @@ def add_external_data(self, url: str, filename: str, mimetype: str) -> None:
190189
# Calculate the SHA-256 hash of the body as the signature
191190
self.alg = "sha256"
192191
self.encoding = "base64url"
193-
self.signature = base64.urlsafe_b64encode(hashlib.sha256(self.body.encode()).digest()).decode()
192+
self.signature = base64.urlsafe_b64encode(hashlib.sha256(response.text.encode()).digest()).decode()
194193

195194
def add_inline_data(self, body: str, filename: str, mimetype: str) -> None:
196195
"""

tests/test_dialog.py

Lines changed: 132 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
import pytest
2-
import unittest
3-
from unittest.mock import Mock
4-
from datetime import datetime
52
from src.vcon.dialog import Dialog
6-
from src.vcon.party import PartyHistory
7-
import json
83
import hashlib
94
import base64
105

116
class TestDialog:
12-
137
# Initialization of Dialog object with all parameters
148
def test_initialization_with_all_parameters(self):
159
from datetime import datetime
@@ -88,43 +82,6 @@ def test_initialization_with_missing_optional_parameters(self):
8882
assert dialog.originator is None
8983
assert dialog.mimetype is None
9084

91-
def test_initialization_with_missing_optional_parameters(self):
92-
# Given
93-
from datetime import datetime
94-
from src.vcon.dialog import Dialog
95-
96-
dialog = Dialog(
97-
type="audio",
98-
start=datetime.now(),
99-
duration=None,
100-
parties=[1, 2],
101-
originator=None,
102-
mimetype=None,
103-
filename=None,
104-
body=None,
105-
encoding=None,
106-
url=None,
107-
alg=None,
108-
signature=None,
109-
disposition=None,
110-
party_history=None,
111-
transferee=None,
112-
transferor=None,
113-
transfer_target=None,
114-
original=None,
115-
consultation=None,
116-
target_dialog=None,
117-
campaign=None,
118-
interaction=None,
119-
skill=None
120-
)
121-
122-
# When & Then
123-
assert dialog.type == "audio"
124-
assert dialog.duration is None
125-
assert dialog.originator is None
126-
assert dialog.mimetype is None
127-
12885
def test_initialization_with_default_optional_parameters(self):
12986
# Given
13087
from datetime import datetime
@@ -176,141 +133,6 @@ def test_retrieve_dialog_mimetype_when_set(self):
176133
# When & Then
177134
assert dialog.mimetype == "video/mp4"
178135

179-
def test_initialization_with_missing_optional_parameters(self):
180-
# Given
181-
from datetime import datetime
182-
from src.vcon.dialog import Dialog
183-
184-
dialog = Dialog(
185-
type="audio",
186-
start=datetime.now(),
187-
duration=None,
188-
parties=[1, 2],
189-
originator=None,
190-
mimetype=None,
191-
filename=None,
192-
body=None,
193-
encoding=None,
194-
url=None,
195-
alg=None,
196-
signature=None,
197-
disposition=None,
198-
party_history=None,
199-
transferee=None,
200-
transferor=None,
201-
transfer_target=None,
202-
original=None,
203-
consultation=None,
204-
target_dialog=None,
205-
campaign=None,
206-
interaction=None,
207-
skill=None
208-
)
209-
210-
# When
211-
# Then
212-
assert dialog.type == "audio"
213-
assert dialog.duration is None
214-
assert dialog.originator is None
215-
assert dialog.mimetype is None
216-
217-
def test_initialization_with_default_optional_parameters(self):
218-
# Given
219-
from datetime import datetime
220-
from src.vcon.dialog import Dialog
221-
222-
dialog = Dialog(
223-
type="video",
224-
start=datetime.now(),
225-
duration=0.0,
226-
parties=[1],
227-
originator=1
228-
)
229-
230-
# When
231-
# Then
232-
assert dialog.duration == 0.0
233-
assert dialog.parties == [1]
234-
assert dialog.originator == 1
235-
236-
# Initialization of Dialog object with all parameters
237-
def test_initialization_with_all_parameters(self):
238-
from datetime import datetime
239-
from src.vcon.dialog import Dialog
240-
from src.vcon.party import PartyHistory
241-
242-
# Given
243-
party_history = [PartyHistory(1, "join", datetime.now())]
244-
dialog = Dialog(
245-
type="text",
246-
start=datetime.now(),
247-
duration=120.0,
248-
parties=[1, 2],
249-
originator=1,
250-
mimetype="text/plain",
251-
filename="example.txt",
252-
body="Hello, World!",
253-
encoding="utf-8",
254-
url="http://example.com",
255-
alg="sha256",
256-
signature="signature",
257-
disposition="inline",
258-
party_history=party_history,
259-
transferee=2,
260-
transferor=1,
261-
transfer_target=3,
262-
original=1,
263-
consultation=2,
264-
target_dialog=3,
265-
campaign="campaign1",
266-
interaction="interaction1",
267-
skill="skill1"
268-
)
269-
270-
# When & Then
271-
assert dialog.type == "text"
272-
assert dialog.duration == 120.0
273-
assert dialog.parties == [1, 2]
274-
assert dialog.party_history == party_history
275-
276-
# Initialization with missing optional parameters
277-
def test_initialization_with_missing_optional_parameters(self):
278-
from datetime import datetime
279-
from src.vcon.dialog import Dialog
280-
281-
# Given
282-
dialog = Dialog(
283-
type="audio",
284-
start=datetime.now(),
285-
duration=None,
286-
parties=[1, 2],
287-
originator=None,
288-
mimetype=None,
289-
filename=None,
290-
body=None,
291-
encoding=None,
292-
url=None,
293-
alg=None,
294-
signature=None,
295-
disposition=None,
296-
party_history=None,
297-
transferee=None,
298-
transferor=None,
299-
transfer_target=None,
300-
original=None,
301-
consultation=None,
302-
target_dialog=None,
303-
campaign=None,
304-
interaction=None,
305-
skill=None
306-
)
307-
308-
# When & Then
309-
assert dialog.type == "audio"
310-
assert dialog.duration is None
311-
assert dialog.originator is None
312-
assert dialog.mimetype is None
313-
314136
# Conversion of Dialog object to dictionary
315137
def test_conversion_to_dict(self):
316138
# Given
@@ -355,4 +177,135 @@ def test_conversion_to_dict(self):
355177
assert dialog_dict["parties"] == [1, 2]
356178
assert dialog_dict["party_history"] == [{"party": 1, "event": "join", "time": party_time}]
357179

358-
180+
# Successfully fetches external data from a valid URL
181+
def test_fetch_external_data_success(self, mocker):
182+
# Arrange
183+
dialog = Dialog(type="text", start="2023-06-01T10:00:00Z", parties=[0])
184+
url = "http://example.com/data"
185+
filename = "data.txt"
186+
mimetype = "text/plain"
187+
response_mock = mocker.Mock()
188+
response_mock.status_code = 200
189+
response_mock.headers = {"Content-Type": "text/plain"}
190+
response_mock.text = "sample data"
191+
mocker.patch("requests.get", return_value=response_mock)
192+
193+
# Act
194+
dialog.add_external_data(url, filename, mimetype)
195+
196+
# Assert
197+
assert dialog.mimetype == "text/plain"
198+
assert dialog.filename == filename
199+
assert dialog.alg == "sha256"
200+
assert dialog.encoding == "base64url"
201+
expected_signature = base64.urlsafe_b64encode(hashlib.sha256("sample data".encode()).digest()).decode()
202+
assert dialog.signature == expected_signature
203+
assert dialog.body is None
204+
205+
# URL returns a non-200 status code
206+
def test_fetch_external_data_failure(self, mocker):
207+
# Arrange
208+
dialog = Dialog(type="text", start="2023-06-01T10:00:00Z", parties=[0])
209+
url = "http://example.com/data"
210+
filename = "data.txt"
211+
mimetype = "text/plain"
212+
response_mock = mocker.Mock()
213+
response_mock.status_code = 404
214+
mocker.patch("requests.get", return_value=response_mock)
215+
216+
# Act & Assert
217+
with pytest.raises(Exception) as excinfo:
218+
dialog.add_external_data(url, filename, mimetype)
219+
220+
assert str(excinfo.value) == "Failed to fetch external data: 404"
221+
222+
# Correctly sets the mimetype from the response headers
223+
def test_correctly_sets_mimetype(self, mocker):
224+
# Setup
225+
dialog = Dialog(type="text", start="2023-06-01T10:00:00Z", parties=[0])
226+
url = "http://example.com/data"
227+
filename = "example_data.txt"
228+
mimetype = "text/plain"
229+
response_mock = mocker.Mock()
230+
response_mock.status_code = 200
231+
response_mock.headers = {"Content-Type": mimetype}
232+
response_mock.text = "dummy data"
233+
mocker.patch('requests.get', return_value=response_mock)
234+
235+
# Invoke
236+
dialog.add_external_data(url, filename, None)
237+
238+
# Assert
239+
assert dialog.mimetype == mimetype
240+
241+
# Overrides the filename if provided
242+
def test_overrides_filename_if_provided(self, mocker):
243+
# Setup
244+
dialog = Dialog(type="text", start="2023-06-01T10:00:00Z", parties=[0])
245+
url = "http://example.com/data"
246+
filename = "example_data.txt"
247+
new_filename = "new_data.txt"
248+
mimetype = "text/plain"
249+
response_mock = mocker.Mock()
250+
response_mock.status_code = 200
251+
response_mock.headers = {"Content-Type": mimetype}
252+
response_mock.text = "dummy data"
253+
mocker.patch('requests.get', return_value=response_mock)
254+
255+
# Invoke
256+
dialog.add_external_data(url, filename, None)
257+
dialog.add_external_data(url, new_filename, None)
258+
259+
# Assert
260+
assert dialog.filename == new_filename
261+
262+
# Correctly sets body, filename, and mimetype attributes
263+
def test_correctly_sets_attributes(self):
264+
dialog = Dialog(type="text", start="2023-06-01T10:00:00Z", parties=[0])
265+
body = "sample body"
266+
filename = "sample.txt"
267+
mimetype = "text/plain"
268+
269+
dialog.add_inline_data(body, filename, mimetype)
270+
271+
assert dialog.body == body
272+
assert dialog.filename == filename
273+
assert dialog.mimetype == mimetype
274+
275+
# Handles empty string for body
276+
def test_handles_empty_body(self):
277+
dialog = Dialog(type="text", start="2023-06-01T10:00:00Z", parties=[0])
278+
body = ""
279+
filename = "empty.txt"
280+
mimetype = "text/plain"
281+
282+
dialog.add_inline_data(body, filename, mimetype)
283+
284+
assert dialog.body == body
285+
assert dialog.filename == filename
286+
assert dialog.mimetype == mimetype
287+
assert dialog.signature == base64.urlsafe_b64encode(
288+
hashlib.sha256(body.encode()).digest()).decode()
289+
290+
# Generates a valid SHA-256 hash signature for the body
291+
def test_valid_sha256_signature(self):
292+
# Initialize the dialog object
293+
dialog = Dialog(type="text", start="2023-06-01T10:00:00Z", parties=[0])
294+
295+
# Add inline data
296+
dialog.add_inline_data("example_body", "example_filename", "text/plain")
297+
298+
# Check if the SHA-256 hash signature is valid
299+
expected_signature = base64.urlsafe_b64encode(hashlib.sha256("example_body".encode()).digest()).decode()
300+
assert dialog.signature == expected_signature
301+
302+
# Sets the encoding to "base64url"
303+
def test_encoding_base64url(self):
304+
# Initialize the dialog object
305+
dialog = Dialog(type="text", start="2023-06-01T10:00:00Z", parties=[0])
306+
307+
# Add inline data
308+
dialog.add_inline_data("example_body", "example_filename", "text/plain")
309+
310+
# Check if the encoding is set to "base64url"
311+
assert dialog.encoding == "base64url"

0 commit comments

Comments
 (0)