commit 29fe8b1b30203b8046648f560f9a3ff4dc9d7306
parent 2865cd9dc7cfa8b2d5023c48e2436df57ff8789d
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sun, 14 Apr 2024 11:34:47 -0700
Migrate from unittest to pytest
Diffstat:
4 files changed, 639 insertions(+), 634 deletions(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
@@ -19,5 +19,5 @@ jobs:
- name: Install dependencies
run: pip install -r requirements.txt
- - name: Run tests with unittest
- run: python -m unittest
+ - name: Run tests with pytest
+ run: python -m pytest
diff --git a/requirements.txt b/requirements.txt
@@ -2,3 +2,5 @@ flask
python-dotenv
python-pptx
requests
+pytest
+pytest-mock
diff --git a/tests/test_core.py b/tests/test_core.py
@@ -1,392 +1,390 @@
-import unittest
-from unittest.mock import patch, call
+import pytest
from songs2slides import core
-class TestCore(unittest.TestCase):
- def test_filter_lyrics_inline(self):
- # Declare raw lyrics and expected cleaned lyrics
- lyrics = 'A[remove]B\nC(remove)D'
- expected = 'AB\nCD'
-
- # Clean lyrics
- result = core.filter_lyrics(lyrics)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_filter_lyrics_whole_lines(self):
- # Declare raw lyrics and expected cleaned lyrics
- lyrics = 'A\n[remove]\nB\n(remove)\nC'
- expected = 'A\nB\nC'
-
- # Clean lyrics
- result = core.filter_lyrics(lyrics)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_filter_lyrics_multiple_lines(self):
- # Declare raw lyrics and expected cleaned lyrics
- lyrics = 'A\n[re\nmove]\nB\n(re\nmove)\nC'
- expected = 'A\nB\nC'
-
- # Clean lyrics
- result = core.filter_lyrics(lyrics)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_filter_lyrics_blank_lines(self):
- # Declare raw lyrics and expected cleaned lyrics
- lyrics = 'A\n[remove]\n\n(remove)\nB'
- expected = 'A\n\nB'
-
- # Clean lyrics
- result = core.filter_lyrics(lyrics)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_filter_lyrics_all(self):
- # Declare raw lyrics and expected cleaned lyrics
- lyrics = 'A[remove]B\n[remove]\n\nC(remove)D\n(re\nmove)'
- expected = 'AB\n\nCD'
-
- # Clean lyrics
- result = core.filter_lyrics(lyrics)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_filter_lyrics_empty_string(self):
- # Clean lyrics
- result = core.filter_lyrics('')
-
- # Assert slides are correct
- self.assertEqual(result, '')
-
- def test_get_song_data_success(self):
- with patch('songs2slides.core.os.getenv') as mocked_env, \
- patch('songs2slides.core.requests.get') as mocked_get, \
- patch('songs2slides.core.filter_lyrics') as mocked_clean:
-
- # Mock os.getenv, requests.get, and core.filter_lyrics
- mocked_env.side_effect = [
- 'api://lyrics/{artist}/{title}',
- 'Bearer secrettoken'
- ]
- mocked_get.return_value.json.return_value = {
- 'lyrics': 'raw',
- 'title': 'Foo',
- 'artist': 'Bar',
- }
- mocked_get.return_value.status_code = 200
- mocked_clean.return_value = 'clean'
-
- # Get song data
- song_data = core.get_song_data('foo', 'bar')
-
- # Assert mocked methods were used correctly
- mocked_env.assert_has_calls([
- call('API_URL'),
- call('API_AUTH', None)
- ])
- mocked_get.assert_called_with('api://lyrics/bar/foo', headers={
- 'Authorization': 'Bearer secrettoken'
- })
- mocked_clean.assert_called_with('raw')
-
- # Assert song data is correct
- self.assertEqual(song_data.title, 'Foo')
- self.assertEqual(song_data.artist, 'Bar')
- self.assertEqual(song_data.lyrics, 'clean')
-
- def test_get_song_data_no_auth_header(self):
- with patch('songs2slides.core.os.getenv') as mocked_env, \
- patch('songs2slides.core.requests.get') as mocked_get, \
- patch('songs2slides.core.filter_lyrics') as mocked_clean:
-
- # Mock os.getenv, requests.get, and core.filter_lyrics
- mocked_env.side_effect = [
- 'api://lyrics/{artist}/{title}',
- None,
- ]
- mocked_get.return_value.json.return_value = {
- 'lyrics': 'raw',
- 'title': 'Foo',
- 'artist': 'Bar',
- }
- mocked_get.return_value.status_code = 200
- mocked_clean.return_value = 'clean'
-
- # Get song data
- song_data = core.get_song_data('foo', 'bar')
-
- # Assert mocked methods were used correctly
- mocked_env.assert_has_calls([
- call('API_URL'),
- call('API_AUTH', None)
- ])
- mocked_get.assert_called_with('api://lyrics/bar/foo', headers={})
- mocked_clean.assert_called_with('raw')
-
- # Assert song data is correct
- self.assertEqual(song_data.title, 'Foo')
- self.assertEqual(song_data.artist, 'Bar')
- self.assertEqual(song_data.lyrics, 'clean')
-
- def test_get_song_data_no_url(self):
- with patch('songs2slides.core.os.getenv') as mocked_env, \
- patch('songs2slides.core.requests.get') as mocked_get:
-
- # Mock os.getenv and requests.get
- mocked_env.return_value = None
- mocked_get.return_value.text = b'{}'
- mocked_get.return_value.status_code = 200
-
- # Try to get song data
- with self.assertRaises(Exception):
- song_data = core.get_song_data('foo', 'bar')
-
- # Assert request was not called
- mocked_get.assert_not_called()
-
- def test_get_song_data_not_found(self):
- with patch('songs2slides.core.os.getenv') as mocked_env, \
- patch('songs2slides.core.requests.get') as mocked_get:
-
- # Mock os.getenv and requests.get
- mocked_env.side_effect = [
- 'api://lyrics/{artist}/{title}',
- 'Bearer secrettoken'
- ]
- mocked_get.return_value.text = b'{}'
- mocked_get.return_value.status_code = 200
-
- # Try to get song data
- with self.assertRaises(Exception):
- song_data = core.get_song_data('foo', 'bar')
-
- # Assert request was called
- mocked_get.assert_called_with('api://lyrics/bar/foo', headers={
- 'Authorization': 'Bearer secrettoken'
- })
-
- def test_parse_song_lyrics_basic(self):
- # Declare song data and expected slides
- lyrics = 'A\nB\nC\nD\nE\nF\n\nG\nH'
- expected = ['A\nB\nC\nD', 'E\nF', 'G\nH']
-
- # Get slide content
- result = core.parse_song_lyrics(lyrics, 4)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_parse_song_lyrics_3_lines_per_slide(self):
- # Declare song data and expected slides
- lyrics = 'A\nB\nC\nD\nE\nF\n\nG\nH'
- expected = ['A\nB\nC', 'D\nE\nF', 'G\nH']
-
- # Get slide content
- result = core.parse_song_lyrics(lyrics, 3)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_parse_song_lyrics_empty_string(self):
- # Declare song data and expected slides
- lyrics = ''
- expected = []
-
- # Get slide content
- result = core.parse_song_lyrics(lyrics, 4)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_parse_song_lyrics_one_line(self):
- # Declare song data and expected slides
- lyrics = 'A'
- expected = ['A']
-
- # Get slide content
- result = core.parse_song_lyrics(lyrics, 4)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_parse_song_lyrics_one_slide(self):
- # Declare song data and expected slides
- lyrics = 'A\nB\nC\nD'
- expected = ['A\nB\nC\nD']
+def test_filter_lyrics_inline():
+ # Declare raw lyrics and expected cleaned lyrics
+ lyrics = 'A[remove]B\nC(remove)D'
+ expected = 'AB\nCD'
+
+ # Clean lyrics
+ result = core.filter_lyrics(lyrics)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_filter_lyrics_whole_lines():
+ # Declare raw lyrics and expected cleaned lyrics
+ lyrics = 'A\n[remove]\nB\n(remove)\nC'
+ expected = 'A\nB\nC'
+
+ # Clean lyrics
+ result = core.filter_lyrics(lyrics)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_filter_lyrics_multiple_lines():
+ # Declare raw lyrics and expected cleaned lyrics
+ lyrics = 'A\n[re\nmove]\nB\n(re\nmove)\nC'
+ expected = 'A\nB\nC'
+
+ # Clean lyrics
+ result = core.filter_lyrics(lyrics)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_filter_lyrics_blank_lines():
+ # Declare raw lyrics and expected cleaned lyrics
+ lyrics = 'A\n[remove]\n\n(remove)\nB'
+ expected = 'A\n\nB'
+
+ # Clean lyrics
+ result = core.filter_lyrics(lyrics)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_filter_lyrics_all():
+ # Declare raw lyrics and expected cleaned lyrics
+ lyrics = 'A[remove]B\n[remove]\n\nC(remove)D\n(re\nmove)'
+ expected = 'AB\n\nCD'
+
+ # Clean lyrics
+ result = core.filter_lyrics(lyrics)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_filter_lyrics_empty_string():
+ # Clean lyrics
+ result = core.filter_lyrics('')
+
+ # Assert slides are correct
+ assert result == ''
+
+def test_get_song_data_success(mocker):
+ # Mock os.getenv, requests.get, and filter_lyrics
+ mocker.patch('songs2slides.core.os.getenv')
+ mocker.patch('songs2slides.core.requests.get')
+ mocker.patch('songs2slides.core.filter_lyrics')
+ core.os.getenv.side_effect = [
+ 'api://lyrics/{artist}/{title}',
+ 'Bearer secrettoken'
+ ]
+ core.requests.get.return_value.json.return_value = {
+ 'lyrics': 'raw',
+ 'title': 'Foo',
+ 'artist': 'Bar',
+ }
+ core.requests.get.return_value.status_code = 200
+ core.filter_lyrics.return_value = 'clean'
+
+ # Get song data
+ song_data = core.get_song_data('foo', 'bar')
+
+ # Assert mocked methods were used correctly
+ core.os.getenv.assert_has_calls([
+ mocker.call('API_URL'),
+ mocker.call('API_AUTH', None)
+ ])
+ core.requests.get.assert_called_with('api://lyrics/bar/foo', headers={
+ 'Authorization': 'Bearer secrettoken'
+ })
+ core.filter_lyrics.assert_called_with('raw')
+
+ # Assert song data is correct
+ assert song_data.title == 'Foo'
+ assert song_data.artist == 'Bar'
+ assert song_data.lyrics == 'clean'
+
+def test_get_song_data_no_auth_header(mocker):
+ # Mock os.getenv, requests.get, and filter_lyrics
+ mocker.patch('songs2slides.core.os.getenv')
+ mocker.patch('songs2slides.core.requests.get')
+ mocker.patch('songs2slides.core.filter_lyrics')
+ core.os.getenv.side_effect = [
+ 'api://lyrics/{artist}/{title}',
+ None,
+ ]
+ core.requests.get.return_value.json.return_value = {
+ 'lyrics': 'raw',
+ 'title': 'Foo',
+ 'artist': 'Bar',
+ }
+ core.requests.get.return_value.status_code = 200
+ core.filter_lyrics.return_value = 'clean'
+
+ # Get song data
+ song_data = core.get_song_data('foo', 'bar')
+
+ # Assert mocked methods were used correctly
+ core.os.getenv.assert_has_calls([
+ mocker.call('API_URL'),
+ mocker.call('API_AUTH', None)
+ ])
+ core.requests.get.assert_called_with('api://lyrics/bar/foo', headers={})
+ core.filter_lyrics.assert_called_with('raw')
+
+ # Assert song data is correct
+ assert song_data.title == 'Foo'
+ assert song_data.artist == 'Bar'
+ assert song_data.lyrics == 'clean'
+
+def test_get_song_data_no_url(mocker):
+ # Mock os.getenv and requests.get
+ mocker.patch('songs2slides.core.os.getenv')
+ mocker.patch('songs2slides.core.requests.get')
+ core.os.getenv.return_value = None
+ core.requests.get.return_value.text = b'{}'
+ core.requests.get.return_value.status_code = 200
+
+ # Try to get song data
+ with pytest.raises(Exception):
+ song_data = core.get_song_data('foo', 'bar')
+
+ # Assert request was not called
+ core.requests.get.assert_not_called()
+
+def test_get_song_data_not_found(mocker):
+ # Mock os.getenv and requests.get
+ mocker.patch('songs2slides.core.os.getenv')
+ mocker.patch('songs2slides.core.requests.get')
+ core.os.getenv.side_effect = [
+ 'api://lyrics/{artist}/{title}',
+ 'Bearer secrettoken'
+ ]
+ core.requests.get.return_value.text = b'{}'
+ core.requests.get.return_value.status_code = 200
+
+ # Try to get song data
+ with pytest.raises(Exception):
+ song_data = core.get_song_data('foo', 'bar')
+
+ # Assert request was called
+ core.requests.get.assert_called_with('api://lyrics/bar/foo', headers={
+ 'Authorization': 'Bearer secrettoken'
+ })
+
+def test_parse_song_lyrics_basic():
+ # Declare song data and expected slides
+ lyrics = 'A\nB\nC\nD\nE\nF\n\nG\nH'
+ expected = ['A\nB\nC\nD', 'E\nF', 'G\nH']
+
+ # Get slide content
+ result = core.parse_song_lyrics(lyrics, 4)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_parse_song_lyrics_3_lines_per_slide():
+ # Declare song data and expected slides
+ lyrics = 'A\nB\nC\nD\nE\nF\n\nG\nH'
+ expected = ['A\nB\nC', 'D\nE\nF', 'G\nH']
+
+ # Get slide content
+ result = core.parse_song_lyrics(lyrics, 3)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_parse_song_lyrics_empty_string():
+ # Declare song data and expected slides
+ lyrics = ''
+ expected = []
+
+ # Get slide content
+ result = core.parse_song_lyrics(lyrics, 4)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_parse_song_lyrics_one_line():
+ # Declare song data and expected slides
+ lyrics = 'A'
+ expected = ['A']
+
+ # Get slide content
+ result = core.parse_song_lyrics(lyrics, 4)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_parse_song_lyrics_one_slide():
+ # Declare song data and expected slides
+ lyrics = 'A\nB\nC\nD'
+ expected = ['A\nB\nC\nD']
+
+ # Get slide content
+ result = core.parse_song_lyrics(lyrics, 4)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_parse_song_lyrics_tripple_newlines():
+ # Declare song data and expected slides
+ lyrics = 'A\nB\n\n\nC\nD'
+ expected = ['A\nB', '', 'C\nD']
+
+ # Get slide content
+ result = core.parse_song_lyrics(lyrics, 4)
+
+ # Assert slides are correct
+ assert result == expected
+
+def test_parse_song_lyrics_extra_whitespace():
+ # Declare song data and expected slides
+ lyrics = ' A\n B \nC D\nE '
+ expected = ['A\nB\nC D\nE']
- # Get slide content
- result = core.parse_song_lyrics(lyrics, 4)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
+ # Get slide content
+ result = core.parse_song_lyrics(lyrics, 4)
+
+ # Assert slides are correct
+ assert result == expected
- def test_parse_song_lyrics_tripple_newlines(self):
- # Declare song data and expected slides
- lyrics = 'A\nB\n\n\nC\nD'
- expected = ['A\nB', '', 'C\nD']
+def test_parse_song_lyrics_extra_newlines():
+ # Declare song data and expected slides
+ lyrics = '\n\n\nA\n\n\n\n\nB\n\n\n'
+ expected = ['A', '', 'B']
+
+ # Get slide content
+ result = core.parse_song_lyrics(lyrics, 4)
- # Get slide content
- result = core.parse_song_lyrics(lyrics, 4)
+ # Assert slides are correct
+ assert result == expected
- # Assert slides are correct
- self.assertEqual(result, expected)
+def test_assemble_slides_calls_parse_song_lyrics(mocker):
+ # Mock parse_song_lyrics
+ mocker.patch('songs2slides.core.parse_song_lyrics')
+ core.parse_song_lyrics.side_effect = [['aaa'], ['b1', 'b2']]
- def test_parse_song_lyrics_extra_whitespace(self):
- # Declare song data and expected slides
- lyrics = ' A\n B \nC D\nE '
- expected = ['A\nB\nC D\nE']
-
- # Get slide content
- result = core.parse_song_lyrics(lyrics, 4)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_parse_song_lyrics_extra_newlines(self):
- # Declare song data and expected slides
- lyrics = '\n\n\nA\n\n\n\n\nB\n\n\n'
- expected = ['A', '', 'B']
-
- # Get slide content
- result = core.parse_song_lyrics(lyrics, 4)
-
- # Assert slides are correct
- self.assertEqual(result, expected)
-
- def test_assemble_slides_calls_parse_song_lyrics(self):
- with patch('songs2slides.core.parse_song_lyrics') as mocked_parse:
-
- # Mock parse_song_lyrics
- mocked_parse.side_effect = [['aaa'], ['b1', 'b2']]
-
- # Declare song data and expected slides
- songs = [
- core.SongData('T1', 'A1', 'l1'),
- core.SongData('T2', 'A3', 'l2'),
- ]
- expected = ['T1', 'aaa', '', 'T2', 'b1', 'b2']
-
- # Get slides
- slides = core.assemble_slides(songs)
-
- # Assert slides are correct
- self.assertEqual(slides, expected)
-
- # Assert parse_song_lyrics called
- mocked_parse.assert_has_calls([call('L1', 4), call('L2', 4)])
-
- def test_assemble_slides_default(self):
- # Declare song data and expected slides
- songs = [
- core.SongData('t1', 'a1', 'l1\nl2\nl3\nl4\nl5'),
- core.SongData('T2', 'A3', 'L6\nL7\n\nL8\n\n\nL9'),
- ]
- expected = [
- 'T1', 'L1\nL2\nL3\nL4', 'L5', '',
- 'T2', 'L6\nL7', 'L8', '', 'L9',
- ]
-
- # Get slides
- slides = core.assemble_slides(songs)
-
- # Assert slides are correct
- self.assertEqual(slides, expected)
-
- def test_assemble_slides_custom_lines_per_slide(self):
- with patch('songs2slides.core.parse_song_lyrics') as mocked_parse:
-
- # Mock parse_song_lyrics
- mocked_parse.side_effect = [['aaa'], ['b1', 'b2']]
-
- # Declare song data and expected slides
- songs = [
- core.SongData('T1', 'A1', 'l1'),
- core.SongData('T2', 'A3', 'l2'),
- ]
- expected = ['T1', 'aaa', '', 'T2', 'b1', 'b2']
-
- # Get slides
- slides = core.assemble_slides(songs, lines_per_slide = 3)
-
- # Assert slides are correct
- self.assertEqual(slides, expected)
-
- # Assert parse_song_lyrics called correctly
- mocked_parse.assert_has_calls([call('L1', 3), call('L2', 3)])
-
- def test_assemble_slides_no_title_slides(self):
- # Declare song data and expected slides
- songs = [
- core.SongData('t1', 'a1', 'l1\nl2\nl3\nl4\nl5'),
- core.SongData('T2', 'A3', 'L6\nL7\n\nL8\n\n\nL9'),
- ]
- expected = [
- 'L1\nL2\nL3\nL4', 'L5', '',
- 'L6\nL7', 'L8', '', 'L9',
- ]
-
- # Get slides
- slides = core.assemble_slides(songs, title_slides = False)
-
- # Assert slides are correct
- self.assertEqual(slides, expected)
-
- def test_assemble_slides_no_blank_slides(self):
- # Declare song data and expected slides
- songs = [
- core.SongData('t1', 'a1', 'l1\nl2\nl3\nl4\nl5'),
- core.SongData('T2', 'A3', 'L6\nL7\n\nL8\n\n\nL9'),
- ]
- expected = [
- 'T1', 'L1\nL2\nL3\nL4', 'L5',
- 'T2', 'L6\nL7', 'L8', '', 'L9',
- ]
-
- # Get slides
- slides = core.assemble_slides(songs, blank_slides = False)
-
- # Assert slides are correct
- self.assertEqual(slides, expected)
-
- def test_assemble_slides_no_extra_slides(self):
- # Declare song data and expected slides
- songs = [
- core.SongData('t1', 'a1', 'l1\nl2\nl3\nl4\nl5'),
- core.SongData('T2', 'A3', 'L6\nL7\n\nL8\n\n\nL9'),
+ # Declare song data and expected slides
+ songs = [
+ core.SongData('T1', 'A1', 'l1'),
+ core.SongData('T2', 'A3', 'l2'),
]
- expected = [
- 'L1\nL2\nL3\nL4', 'L5',
- 'L6\nL7', 'L8', '', 'L9',
+ expected = ['T1', 'aaa', '', 'T2', 'b1', 'b2']
+
+ # Get slides
+ slides = core.assemble_slides(songs)
+
+ # Assert slides are correct
+ assert slides == expected
+
+ # Assert parse_song_lyrics called
+ core.parse_song_lyrics.assert_has_calls([
+ mocker.call('L1', 4), mocker.call('L2', 4)
+ ])
+
+def test_assemble_slides_default():
+ # Declare song data and expected slides
+ songs = [
+ core.SongData('t1', 'a1', 'l1\nl2\nl3\nl4\nl5'),
+ core.SongData('T2', 'A3', 'L6\nL7\n\nL8\n\n\nL9'),
+ ]
+ expected = [
+ 'T1', 'L1\nL2\nL3\nL4', 'L5', '',
+ 'T2', 'L6\nL7', 'L8', '', 'L9',
+ ]
+
+ # Get slides
+ slides = core.assemble_slides(songs)
+
+ # Assert slides are correct
+ assert slides == expected
+
+def test_assemble_slides_custom_lines_per_slide(mocker):
+ # Mock parse_song_lyrics
+ mocker.patch('songs2slides.core.parse_song_lyrics')
+ core.parse_song_lyrics.side_effect = [['aaa'], ['b1', 'b2']]
+
+ # Declare song data and expected slides
+ songs = [
+ core.SongData('T1', 'A1', 'l1'),
+ core.SongData('T2', 'A3', 'l2'),
]
-
- # Get slides
- slides = core.assemble_slides(songs, title_slides = False, blank_slides = False)
-
- # Assert slides are correct
- self.assertEqual(slides, expected)
-
- def test_assemble_slides_no_songs(self):
- # Declare expected slides
- expected = []
-
- # Get slides
- slides = core.assemble_slides([])
-
- # Assert slides are correct
- self.assertEqual(slides, expected)
-
- def test_create_pptx(self):
- with patch('songs2slides.core.pptx.presentation.Presentation.save') as mocked_save:
- # Create PowerPoint
- core.create_pptx(['A', 'B\nC', 'D'], 'test.pptx')
-
- # Assert PowerPoint was saved
- mocked_save.assert_called_with('test.pptx')
+ expected = ['T1', 'aaa', '', 'T2', 'b1', 'b2']
+
+ # Get slides
+ slides = core.assemble_slides(songs, lines_per_slide = 3)
+
+ # Assert slides are correct
+ assert slides == expected
+
+ # Assert parse_song_lyrics called correctly
+ core.parse_song_lyrics.assert_has_calls([
+ mocker.call('L1', 3), mocker.call('L2', 3)
+ ])
+
+def test_assemble_slides_no_title_slides():
+ # Declare song data and expected slides
+ songs = [
+ core.SongData('t1', 'a1', 'l1\nl2\nl3\nl4\nl5'),
+ core.SongData('T2', 'A3', 'L6\nL7\n\nL8\n\n\nL9'),
+ ]
+ expected = [
+ 'L1\nL2\nL3\nL4', 'L5', '',
+ 'L6\nL7', 'L8', '', 'L9',
+ ]
+
+ # Get slides
+ slides = core.assemble_slides(songs, title_slides = False)
+
+ # Assert slides are correct
+ assert slides == expected
+
+def test_assemble_slides_no_blank_slides():
+ # Declare song data and expected slides
+ songs = [
+ core.SongData('t1', 'a1', 'l1\nl2\nl3\nl4\nl5'),
+ core.SongData('T2', 'A3', 'L6\nL7\n\nL8\n\n\nL9'),
+ ]
+ expected = [
+ 'T1', 'L1\nL2\nL3\nL4', 'L5',
+ 'T2', 'L6\nL7', 'L8', '', 'L9',
+ ]
+
+ # Get slides
+ slides = core.assemble_slides(songs, blank_slides = False)
+
+ # Assert slides are correct
+ assert slides == expected
+
+def test_assemble_slides_no_extra_slides():
+ # Declare song data and expected slides
+ songs = [
+ core.SongData('t1', 'a1', 'l1\nl2\nl3\nl4\nl5'),
+ core.SongData('T2', 'A3', 'L6\nL7\n\nL8\n\n\nL9'),
+ ]
+ expected = [
+ 'L1\nL2\nL3\nL4', 'L5',
+ 'L6\nL7', 'L8', '', 'L9',
+ ]
+
+ # Get slides
+ slides = core.assemble_slides(songs, title_slides = False, blank_slides = False)
+
+ # Assert slides are correct
+ assert slides == expected
+
+def test_assemble_slides_no_songs():
+ # Declare expected slides
+ expected = []
+
+ # Get slides
+ slides = core.assemble_slides([])
+
+ # Assert slides are correct
+ assert slides == expected
+
+def test_create_pptx(mocker):
+ # Mock Presentation.save
+ mocker.patch('songs2slides.core.pptx.presentation.Presentation.save')
+
+ # Create PowerPoint
+ core.create_pptx(['A', 'B\nC', 'D'], 'test.pptx')
+
+ # Assert PowerPoint was saved
+ core.pptx.presentation.Presentation.save.assert_called_with('test.pptx')
diff --git a/tests/test_routes.py b/tests/test_routes.py
@@ -1,250 +1,255 @@
-import unittest
-from unittest.mock import patch, call
-
-from songs2slides import create_app, core
-
-class TestRoutes(unittest.TestCase):
- def setUp(self):
- self.app = create_app()
- self.client = self.app.test_client()
-
- def test_get_lyrics_basic(self):
- with patch('songs2slides.core.get_song_data') as mocked_get, \
- patch('songs2slides.core.parse_song_lyrics') as mocked_parse, \
- patch('songs2slides.routes.render_template') as mocked_render:
-
- # Mock get_song_data and parse_song_lyrics
- songs = [
- core.SongData('T1', 'A1', 'L1'),
- core.SongData('T2', 'A2', 'L2'),
- ]
- mocked_get.side_effect = songs
- mocked_parse.side_effect = ['L1', 'L2']
-
- # Send request
- self.client.post('/create/step-2/', data={
- 'title-1': 'T1',
- 'artist-1': 'A1',
- 'title-2': 'T2',
- 'artist-2': 'A2',
- })
-
- # Assert mocks called correctly
- mocked_get.assert_has_calls([call('T1', 'A1'), call('T2', 'A2')])
- mocked_parse.assert_has_calls([call('L1', 4), call('L2', 4)])
- mocked_render.assert_called_with('create-step-2.html', songs=songs, missing=0)
-
- def test_get_lyrics_one_error(self):
- with patch('songs2slides.core.get_song_data') as mocked_get, \
- patch('songs2slides.core.parse_song_lyrics') as mocked_parse, \
- patch('songs2slides.routes.render_template') as mocked_render:
-
- # Mock get_song_data and parse_song_lyrics
- songs = [
- core.SongData('T1', 'A1', None),
- core.SongData('T2', 'A2', 'L2'),
- ]
- mocked_get.side_effect = [Exception(), songs[1]]
- mocked_parse.side_effect = ['L1', 'L2']
-
- # Send request
- self.client.post('/create/step-2/', data={
- 'title-1': 'T1',
- 'artist-1': 'A1',
- 'title-2': 'T2',
- 'artist-2': 'A2',
- })
-
- # Assert mocks called correctly
- mocked_get.assert_has_calls([call('T1', 'A1'), call('T2', 'A2')])
- mocked_parse.assert_has_calls([call('L2', 4)])
- mocked_render.assert_called_with('create-step-2.html', songs=songs, missing=1)
-
- def test_get_lyrics_missing_artist(self):
- with patch('songs2slides.core.get_song_data') as mocked_get:
-
- # Send request
- res = self.client.post('/create/step-2/', data={
- 'title-1': 'T1',
- 'title-2': 'T2',
- 'artist-2': 'A2',
- })
-
- # Assert mocks not called
- mocked_get.assert_not_called()
-
- # Assert response has 400 status code
- self.assertEqual(res.status_code, 400)
-
- def test_update_lyrics(self):
- with patch('songs2slides.routes.render_template') as mocked_render:
-
- # Send request
- self.client.post('/create/step-3/', data={
- 'title-1': 'T1',
- 'artist-1': 'A1',
- 'lyrics-1': 'L1',
- 'title-2': 'T2',
- 'artist-2': 'A2',
- 'lyrics-2': 'L2',
- 'output-type': 'html',
- 'title-slides': 'on',
- })
-
- # Assert render_template called correctly
- mocked_render.assert_called_with('create-step-3.html', songs=[
- core.SongData('T1', 'A1', 'L1'),
- core.SongData('T2', 'A2', 'L2'),
- ])
-
- def test_create_slides_basic(self):
- with patch('songs2slides.core.assemble_slides') as mocked_assemble, \
- patch('songs2slides.core.create_pptx') as mocked_create, \
- patch('songs2slides.routes.send_file') as mocked_send:
-
- # Send request
- self.client.post('/slides/', data={
- 'title-1': 'T1',
- 'artist-1': 'A1',
- 'lyrics-1': 'L1',
- 'title-2': 'T2',
- 'artist-2': 'A2',
- 'lyrics-2': 'L2',
- 'output-type': 'pptx',
- 'title-slides': 'on',
- 'blank-slides': 'on',
- })
-
- # Assert mocks called correctly
- mocked_assemble.assert_called_with([
- core.SongData('T1', 'A1', 'L1'),
- core.SongData('T2', 'A2', 'L2'),
- ],
- lines_per_slide = None,
- title_slides = True,
- blank_slides = True,
- )
- file = mocked_create.call_args.args[1]
- mocked_send.assert_called_with(file, as_attachment=True,
- download_name='slides.pptx')
-
- def test_create_slides_mising_artist(self):
- with patch('songs2slides.core.assemble_slides') as mocked_assemble:
-
- # Send request
- res = self.client.post('/slides/', data={
- 'title-1': 'T1',
- 'artist-1': 'A1',
- 'lyrics-1': 'L1',
- 'title-2': 'T2',
- 'lyrics-2': 'L2',
- 'output-type': 'pptx',
- 'title-slides': 'on',
- 'blank-slides': 'on',
- })
-
- # Assert response has 400 status code
- self.assertEqual(res.status_code, 400)
-
- # Assert assemble_slides not called
- mocked_assemble.assert_not_called()
-
- def test_create_slides_html_slides(self):
- with patch('songs2slides.core.assemble_slides') as mocked_assemble, \
- patch('songs2slides.core.create_pptx') as mocked_create, \
- patch('songs2slides.routes.render_template') as mocked_render:
-
- # Mock assemble_slides
- slides = ['T1', 'L1\nL2', 'L3', 'T2', 'L4']
- mocked_assemble.return_value = slides
-
- # Send request
- self.client.post('/slides/', data={
- 'title-1': 'T1',
- 'artist-1': 'A1',
- 'lyrics-1': 'L1',
- 'title-2': 'T2',
- 'artist-2': 'A2',
- 'lyrics-2': 'L2',
- 'output-type': 'html',
- 'title-slides': 'on',
- 'blank-slides': 'on',
- })
-
- # Assert mocks called correctly
- mocked_assemble.assert_called_with([
- core.SongData('T1', 'A1', 'L1'),
- core.SongData('T2', 'A2', 'L2'),
- ],
- lines_per_slide = None,
- title_slides = True,
- blank_slides = True,
- )
- mocked_create.assert_not_called()
- mocked_render.assert_called_with('slides.html', slides=slides)
-
- def test_create_slides_no_title_slides(self):
- with patch('songs2slides.core.assemble_slides') as mocked_assemble, \
- patch('songs2slides.core.create_pptx') as mocked_create, \
- patch('songs2slides.routes.render_template') as mocked_render:
-
- # Mock assemble_slides
- slides = ['T1', 'L1\nL2', 'L3', 'T2', 'L4']
- mocked_assemble.return_value = slides
-
- # Send request
- self.client.post('/slides/', data={
- 'title-1': 'T1',
- 'artist-1': 'A1',
- 'lyrics-1': 'L1',
- 'title-2': 'T2',
- 'artist-2': 'A2',
- 'lyrics-2': 'L2',
- 'output-type': 'html',
- 'blank-slides': 'on',
- })
-
- # Assert mocks called correctly
- mocked_assemble.assert_called_with([
- core.SongData('T1', 'A1', 'L1'),
- core.SongData('T2', 'A2', 'L2'),
- ],
- lines_per_slide = None,
- title_slides = False,
- blank_slides = True,
- )
- mocked_create.assert_not_called()
- mocked_render.assert_called_with('slides.html', slides=slides)
-
- def test_create_slides_no_blank_slides(self):
- with patch('songs2slides.core.assemble_slides') as mocked_assemble, \
- patch('songs2slides.core.create_pptx') as mocked_create, \
- patch('songs2slides.routes.render_template') as mocked_render:
-
- # Mock assemble_slides
- slides = ['T1', 'L1\nL2', 'L3', 'T2', 'L4']
- mocked_assemble.return_value = slides
-
- # Send request
- self.client.post('/slides/', data={
- 'title-1': 'T1',
- 'artist-1': 'A1',
- 'lyrics-1': 'L1',
- 'title-2': 'T2',
- 'artist-2': 'A2',
- 'lyrics-2': 'L2',
- 'output-type': 'html',
- 'title-slides': 'on',
- })
-
- # Assert mocks called correctly
- mocked_assemble.assert_called_with([
- core.SongData('T1', 'A1', 'L1'),
- core.SongData('T2', 'A2', 'L2'),
- ],
- lines_per_slide = None,
- title_slides = True,
- blank_slides = False,
- )
- mocked_create.assert_not_called()
- mocked_render.assert_called_with('slides.html', slides=slides)
+import pytest
+
+from songs2slides import create_app, core, routes
+
+@pytest.fixture(autouse=True)
+def client():
+ app = create_app()
+ return app.test_client()
+
+def test_get_lyrics_basic(client, mocker):
+ # Mock get_song_data, parse_song_lyrics, and render_template
+ mocker.patch('songs2slides.core.get_song_data')
+ mocker.patch('songs2slides.core.parse_song_lyrics')
+ mocker.patch('songs2slides.routes.render_template')
+ songs = [
+ core.SongData('T1', 'A1', 'L1'),
+ core.SongData('T2', 'A2', 'L2'),
+ ]
+ core.get_song_data.side_effect = songs
+ core.parse_song_lyrics.side_effect = ['L1', 'L2']
+
+ # Send request
+ client.post('/create/step-2/', data={
+ 'title-1': 'T1',
+ 'artist-1': 'A1',
+ 'title-2': 'T2',
+ 'artist-2': 'A2',
+ })
+
+ # Assert mocks called correctly
+ core.get_song_data.assert_has_calls([
+ mocker.call('T1', 'A1'), mocker.call('T2', 'A2')
+ ])
+ core.parse_song_lyrics.assert_has_calls([
+ mocker.call('L1', 4), mocker.call('L2', 4)
+ ])
+ routes.render_template.assert_called_with('create-step-2.html',
+ songs=songs, missing=0)
+
+def test_get_lyrics_one_error(client, mocker):
+ # Mock get_song_data, parse_song_lyrics, and render_template
+ mocker.patch('songs2slides.core.get_song_data')
+ mocker.patch('songs2slides.core.parse_song_lyrics')
+ mocker.patch('songs2slides.routes.render_template')
+ songs = [
+ core.SongData('T1', 'A1', None),
+ core.SongData('T2', 'A2', 'L2'),
+ ]
+ core.get_song_data.side_effect = [Exception(), songs[1]]
+ core.parse_song_lyrics.side_effect = ['L1', 'L2']
+
+ # Send request
+ client.post('/create/step-2/', data={
+ 'title-1': 'T1',
+ 'artist-1': 'A1',
+ 'title-2': 'T2',
+ 'artist-2': 'A2',
+ })
+
+ # Assert mocks called correctly
+ core.get_song_data.assert_has_calls([
+ mocker.call('T1', 'A1'), mocker.call('T2', 'A2')
+ ])
+ core.parse_song_lyrics.assert_has_calls([mocker.call('L2', 4)])
+ routes.render_template.assert_called_with('create-step-2.html', songs=songs, missing=1)
+
+def test_get_lyrics_missing_artist(client, mocker):
+ # Mock get_song_data
+ mocker.patch('songs2slides.core.get_song_data')
+
+ # Send request
+ res = client.post('/create/step-2/', data={
+ 'title-1': 'T1',
+ 'title-2': 'T2',
+ 'artist-2': 'A2',
+ })
+
+ # Assert mocks not called
+ core.get_song_data.assert_not_called()
+
+ # Assert response has 400 status code
+ assert res.status_code == 400
+
+def test_update_lyrics(client, mocker):
+ # Mock render_template
+ mocker.patch('songs2slides.routes.render_template')
+
+ # Send request
+ client.post('/create/step-3/', data={
+ 'title-1': 'T1',
+ 'artist-1': 'A1',
+ 'lyrics-1': 'L1',
+ 'title-2': 'T2',
+ 'artist-2': 'A2',
+ 'lyrics-2': 'L2',
+ 'output-type': 'html',
+ 'title-slides': 'on',
+ })
+
+ # Assert render_template called correctly
+ routes.render_template.assert_called_with('create-step-3.html', songs=[
+ core.SongData('T1', 'A1', 'L1'),
+ core.SongData('T2', 'A2', 'L2'),
+ ])
+
+def test_create_slides_basic(client, mocker):
+ # Mock assemble_slides, create_pptx, and send_file
+ mocker.patch('songs2slides.core.assemble_slides')
+ mocker.patch('songs2slides.core.create_pptx')
+ mocker.patch('songs2slides.routes.send_file')
+
+ # Send request
+ client.post('/slides/', data={
+ 'title-1': 'T1',
+ 'artist-1': 'A1',
+ 'lyrics-1': 'L1',
+ 'title-2': 'T2',
+ 'artist-2': 'A2',
+ 'lyrics-2': 'L2',
+ 'output-type': 'pptx',
+ 'title-slides': 'on',
+ 'blank-slides': 'on',
+ })
+
+ # Assert mocks called correctly
+ core.assemble_slides.assert_called_with([
+ core.SongData('T1', 'A1', 'L1'),
+ core.SongData('T2', 'A2', 'L2'),
+ ],
+ lines_per_slide = None,
+ title_slides = True,
+ blank_slides = True,
+ )
+ file = core.create_pptx.call_args.args[1]
+ routes.send_file.assert_called_with(file, as_attachment=True,
+ download_name='slides.pptx')
+
+def test_create_slides_mising_artist(client, mocker):
+ # Mock assemble_slides
+ mocker.patch('songs2slides.core.assemble_slides')
+
+ # Send request
+ res = client.post('/slides/', data={
+ 'title-1': 'T1',
+ 'artist-1': 'A1',
+ 'lyrics-1': 'L1',
+ 'title-2': 'T2',
+ 'lyrics-2': 'L2',
+ 'output-type': 'pptx',
+ 'title-slides': 'on',
+ 'blank-slides': 'on',
+ })
+
+ # Assert response has 400 status code
+ assert res.status_code == 400
+
+ # Assert assemble_slides not called
+ core.assemble_slides.assert_not_called()
+
+def test_create_slides_html_slides(client, mocker):
+ # Mock assemble_slides, create_pptx, render_template
+ mocker.patch('songs2slides.core.assemble_slides')
+ mocker.patch('songs2slides.core.create_pptx')
+ mocker.patch('songs2slides.routes.render_template')
+ slides = ['T1', 'L1\nL2', 'L3', 'T2', 'L4']
+ core.assemble_slides.return_value = slides
+
+ # Send request
+ client.post('/slides/', data={
+ 'title-1': 'T1',
+ 'artist-1': 'A1',
+ 'lyrics-1': 'L1',
+ 'title-2': 'T2',
+ 'artist-2': 'A2',
+ 'lyrics-2': 'L2',
+ 'output-type': 'html',
+ 'title-slides': 'on',
+ 'blank-slides': 'on',
+ })
+
+ # Assert mocks called correctly
+ core.assemble_slides.assert_called_with([
+ core.SongData('T1', 'A1', 'L1'),
+ core.SongData('T2', 'A2', 'L2'),
+ ],
+ lines_per_slide = None,
+ title_slides = True,
+ blank_slides = True,
+ )
+ core.create_pptx.assert_not_called()
+ routes.render_template.assert_called_with('slides.html', slides=slides)
+
+def test_create_slides_no_title_slides(client, mocker):
+ # Mock assemble_slides, create_pptx, render_template
+ mocker.patch('songs2slides.core.assemble_slides')
+ mocker.patch('songs2slides.core.create_pptx')
+ mocker.patch('songs2slides.routes.render_template')
+ slides = ['T1', 'L1\nL2', 'L3', 'T2', 'L4']
+ core.assemble_slides.return_value = slides
+
+ # Send request
+ client.post('/slides/', data={
+ 'title-1': 'T1',
+ 'artist-1': 'A1',
+ 'lyrics-1': 'L1',
+ 'title-2': 'T2',
+ 'artist-2': 'A2',
+ 'lyrics-2': 'L2',
+ 'output-type': 'html',
+ 'blank-slides': 'on',
+ })
+
+ # Assert mocks called correctly
+ core.assemble_slides.assert_called_with([
+ core.SongData('T1', 'A1', 'L1'),
+ core.SongData('T2', 'A2', 'L2'),
+ ],
+ lines_per_slide = None,
+ title_slides = False,
+ blank_slides = True,
+ )
+ core.create_pptx.assert_not_called()
+ routes.render_template.assert_called_with('slides.html', slides=slides)
+
+def test_create_slides_no_blank_slides(client, mocker):
+ # Mock assemble_slides, create_pptx, render_template
+ mocker.patch('songs2slides.core.assemble_slides')
+ mocker.patch('songs2slides.core.create_pptx')
+ mocker.patch('songs2slides.routes.render_template')
+ slides = ['T1', 'L1\nL2', 'L3', 'T2', 'L4']
+ core.assemble_slides.return_value = slides
+
+ # Send request
+ client.post('/slides/', data={
+ 'title-1': 'T1',
+ 'artist-1': 'A1',
+ 'lyrics-1': 'L1',
+ 'title-2': 'T2',
+ 'artist-2': 'A2',
+ 'lyrics-2': 'L2',
+ 'output-type': 'html',
+ 'title-slides': 'on',
+ })
+
+ # Assert mocks called correctly
+ core.assemble_slides.assert_called_with([
+ core.SongData('T1', 'A1', 'L1'),
+ core.SongData('T2', 'A2', 'L2'),
+ ],
+ lines_per_slide = None,
+ title_slides = True,
+ blank_slides = False,
+ )
+ core.create_pptx.assert_not_called()
+ routes.render_template.assert_called_with('slides.html', slides=slides)