commit a18946380db569e8fcf7b138ee2d6b827e5f0e09
parent 2865cd9dc7cfa8b2d5023c48e2436df57ff8789d
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sat, 20 Apr 2024 09:31:40 -0700
Merge pull request #10 from ashermorgan/e2e-tests
Add end to end tests
Diffstat:
5 files changed, 988 insertions(+), 636 deletions(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
@@ -11,13 +11,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+ cache: 'pip'
- name: Install dependencies
run: pip install -r requirements.txt
- - name: Run tests with unittest
- run: python -m unittest
+ - name: Install playwright browsers
+ run: playwright install
+
+ - name: Run tests with pytest
+ run: python -m pytest
diff --git a/requirements.txt b/requirements.txt
@@ -2,3 +2,7 @@ flask
python-dotenv
python-pptx
requests
+pytest
+pytest-mock
+pytest-playwright
+pytest-xprocess
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_e2e.py b/tests/test_e2e.py
@@ -0,0 +1,339 @@
+import os
+import pytest
+from playwright.sync_api import Page, expect
+from xprocess import ProcessStarter
+
+@pytest.fixture(autouse=True, scope='session')
+def api(xprocess):
+ port = '5003'
+
+ class Starter(ProcessStarter):
+ pattern = '.*Running.*'
+ timeout = 10
+ args = ['python', '-m', 'flask', '--app', '../../../../mock_api.py',
+ 'run', '--port', port]
+
+ # Start API
+ xprocess.ensure('api', Starter)
+
+ yield f'http://localhost:{port}'
+
+ # Stop API
+ xprocess.getinfo('api').terminate()
+
+@pytest.fixture(autouse=True, scope='session')
+def server(xprocess, api):
+ port = '5002'
+
+ class Starter(ProcessStarter):
+ pattern = '.*Running.*'
+ timeout = 10
+ args = ['python', '-m', 'flask', '--app', '../../../../songs2slides',
+ 'run', '--port', port]
+ env = os.environ | { 'API_URL': api + '/{title}/{artist}/' }
+
+ # Start server
+ xprocess.ensure('server', Starter)
+
+ yield f'http://localhost:{port}'
+
+ # Stop server
+ xprocess.getinfo('server').terminate()
+
+@pytest.fixture(autouse=True, scope='session')
+def base_url(server):
+ return server
+
+def test_basic(page: Page):
+ # Start on homepage
+ page.goto('/')
+
+ # Click 'Create a Slideshow'
+ page.get_by_role('link', name='Create a Slideshow').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-1/')
+
+ # Fill in song information
+ page.get_by_placeholder('Song title').last.fill('Song 1')
+ page.get_by_placeholder('Song artist').last.fill('aRtIsT A')
+ page.get_by_role('button', name='Add Song').click()
+ page.get_by_placeholder('Song title').last.fill('Song 5')
+ page.get_by_placeholder('Song artist').last.fill('')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-2/')
+
+ # Assert missing song message is correct
+ expect(page.get_by_text('Lyrics must be entered manually for 1 song.')).to_be_visible()
+
+ # Assert songs are loaded
+ expect(page.get_by_text('Song 1 (Artist A)')).to_be_visible()
+ expect(page.get_by_text('Song 1 (Artist A) lyrics not found')).to_be_hidden()
+ expect(page.get_by_text('Song 5 lyrics not found')).to_be_visible()
+
+ # Assert song lyrics are loaded (Song 1 lyrics still collapsed)
+ expect(page.get_by_role('textbox')).to_have_count(1)
+ expect(page.get_by_role('textbox').first).to_have_value('')
+
+ # Uncollapse Song 1
+ page.get_by_text('Song 1 (Artist A)').click()
+
+ # Assert song lyrics are loaded (Song 1 lyrics uncollapsed)
+ expect(page.get_by_role('textbox')).to_have_count(2)
+ expect(page.get_by_role('textbox').first).to_have_value('Lyrics to song 1\nby artist A')
+ expect(page.get_by_role('textbox').last).to_have_value('')
+
+ # Fill in missing lyrics
+ page.get_by_role('textbox').last.fill('custom song 5 lyrics')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-3/')
+
+ # Fill in slideshow settings
+ page.get_by_role('checkbox', name='Include a title slide before each song').uncheck()
+
+ # Click create
+ page.get_by_role('button', name='Create').click()
+ expect(page).to_have_url('http://localhost:5002/slides/')
+
+ # Assert slide content is correct
+ expect(page.locator('css=section.present')).to_have_text('LYRICS TO SONG 1\nBY ARTIST A')
+ page.keyboard.press('ArrowRight')
+ expect(page.locator('css=section.present')).to_have_text('')
+ page.keyboard.press('ArrowRight')
+ expect(page.locator('css=section.present')).to_have_text('CUSTOM SONG 5 LYRICS')
+ page.keyboard.press('ArrowRight')
+ expect(page.locator('css=section.present')).to_have_text('CUSTOM SONG 5 LYRICS')
+
+def test_pptx(page: Page):
+ # Start on homepage
+ page.goto('/')
+
+ # Click 'Create a Slideshow'
+ page.get_by_role('link', name='Create a Slideshow').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-1/')
+
+ # Fill in song information
+ page.get_by_placeholder('Song title').last.fill('Song 1')
+ page.get_by_placeholder('Song artist').last.fill('aRtIsT A')
+ page.get_by_role('button', name='Add Song').click()
+ page.get_by_placeholder('Song title').last.fill('Song 5')
+ page.get_by_placeholder('Song artist').last.fill('')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-2/')
+
+ # Fill in missing lyrics
+ page.get_by_role('textbox').last.fill('custom song 5 lyrics')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-3/')
+
+ # Fill in slideshow settings
+ page.get_by_role('checkbox', name='Include a title slide before each song').uncheck()
+ page.get_by_role('radio', name='PowerPoint Download').check()
+
+ # Click create
+ with page.expect_download() as download:
+ page.get_by_role('button', name='Create').click()
+
+ # Assert PowerPoint was downloaded
+ assert download.value.suggested_filename == 'slides.pptx'
+
+ # Assert redirected to post download page
+ expect(page).to_have_url('http://localhost:5002/post-download/')
+
+def test_localStorage(page: Page):
+ # Start on homepage
+ page.goto('/')
+
+ # Click 'Create a Slideshow'
+ page.get_by_role('link', name='Create a Slideshow').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-1/')
+
+ # Fill in song information
+ page.get_by_placeholder('Song title').last.fill('Song 1')
+ page.get_by_placeholder('Song artist').last.fill('aRtIsT A')
+ page.get_by_role('button', name='Add Song').click()
+ page.get_by_placeholder('Song title').last.fill('Song 5')
+ page.get_by_placeholder('Song artist').last.fill('')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-2/')
+
+ # Fill in missing lyrics
+ page.get_by_role('textbox').last.fill('custom song 5 lyrics')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-3/')
+
+ # Assert slideshow settings have default values
+ expect(page.get_by_role('checkbox', name='Include a title slide before each song')).to_be_checked()
+ expect(page.get_by_role('checkbox', name='Include a blank slide between each song')).to_be_checked()
+ expect(page.get_by_role('radio', name='Web View')).to_be_checked()
+
+ # Fill in slideshow settings
+ page.get_by_role('checkbox', name='Include a title slide before each song').uncheck()
+ page.get_by_role('radio', name='PowerPoint Download').check()
+
+ # Click create
+ page.get_by_role('button', name='Create').click()
+ expect(page).to_have_url('http://localhost:5002/post-download/')
+
+ # Return to homepage
+ page.goto('/')
+
+ # Click 'Create a Slideshow'
+ page.get_by_role('link', name='Create a Slideshow').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-1/')
+
+ # Fill in song information
+ page.get_by_placeholder('Song title').last.fill('Song 1')
+ page.get_by_placeholder('Song artist').last.fill('aRtIsT A')
+ page.get_by_role('button', name='Add Song').click()
+ page.get_by_placeholder('Song title').last.fill('Song 5')
+ page.get_by_placeholder('Song artist').last.fill('')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-2/')
+
+ # Fill in missing lyrics
+ page.get_by_role('textbox').last.fill('custom song 5 lyrics')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-3/')
+
+ # Assert slideshow settings have custom values
+ expect(page.get_by_role('checkbox', name='Include a title slide before each song')).to_be_checked(checked = False)
+ expect(page.get_by_role('checkbox', name='Include a blank slide between each song')).to_be_checked()
+
+def test_back(page: Page):
+ # Start on homepage
+ page.goto('/')
+
+ # Click 'Create a Slideshow'
+ page.get_by_role('link', name='Create a Slideshow').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-1/')
+
+ # Fill in bad song information
+ page.get_by_placeholder('Song title').last.fill('Song 11')
+ page.get_by_placeholder('Song artist').last.fill('aRtIsT Aa')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-2/')
+
+ # Assert songs are loaded
+ expect(page.get_by_text('Song 11 (Artist Aa) lyrics not found')).to_be_visible()
+ expect(page.get_by_text('Song 5 lyrics not found')).to_be_hidden()
+
+ # Assert song lyrics are loaded
+ expect(page.get_by_role('textbox')).to_have_count(1)
+ expect(page.get_by_role('textbox').first).to_have_value('')
+
+ # Click Back
+ page.get_by_role('button', name='Back').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-1/')
+
+ # Assert bad song information is still present
+ expect(page.get_by_placeholder('Song title')).to_have_count(1)
+ expect(page.get_by_placeholder('Song title').first).to_have_value('Song 11')
+ expect(page.get_by_placeholder('Song artist')).to_have_count(1)
+ expect(page.get_by_placeholder('Song artist').last).to_have_value('aRtIsT Aa')
+
+ # Fill in correct song information
+ page.get_by_placeholder('Song title').last.fill('Song 1')
+ page.get_by_placeholder('Song artist').last.fill('aRtIsT A')
+ page.get_by_role('button', name='Add Song').click()
+ page.get_by_placeholder('Song title').last.fill('Song 5')
+ page.get_by_placeholder('Song artist').last.fill('')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-2/')
+
+ # Assert songs are loaded
+ expect(page.get_by_text('Song 1 (Artist A)')).to_be_visible()
+ expect(page.get_by_text('Song 1 (Artist A) lyrics not found')).to_be_hidden()
+ expect(page.get_by_text('Song 5 lyrics not found')).to_be_visible()
+
+ # Uncollapse Song 1
+ page.get_by_text('Song 1 (Artist A)').click()
+
+ # Assert songs lyrics are loaded
+ expect(page.get_by_role('textbox')).to_have_count(2)
+ expect(page.get_by_role('textbox').first).to_have_value('Lyrics to song 1\nby artist A')
+ expect(page.get_by_role('textbox').last).to_have_value('')
+
+ # Fill in bad missing lyrics
+ page.get_by_role('textbox').last.fill('custom song 5 lyrics (bad)')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-3/')
+
+ # Click Back
+ page.get_by_role('button', name='Back').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-2/')
+
+ # Uncollapse Song 1
+ page.get_by_text('Song 1 (Artist A)').click()
+
+ # Assert bad song lyrics are still loaded
+ expect(page.get_by_role('textbox')).to_have_count(2)
+ expect(page.get_by_role('textbox').first).to_have_value('Lyrics to song 1\nby artist A')
+ expect(page.get_by_role('textbox').last).to_have_value('custom song 5 lyrics (bad)')
+
+ # Fill in correct missing lyrics
+ page.get_by_role('textbox').last.fill('custom song 5 lyrics')
+
+ # Click Next
+ page.get_by_role('button', name='Next').click()
+ expect(page).to_have_url('http://localhost:5002/create/step-3/')
+
+ # Fill in bad slideshow settings
+ page.get_by_role('checkbox', name='Include a blank slide between each song').uncheck()
+
+ # Click create
+ page.get_by_role('button', name='Create').click()
+ expect(page).to_have_url('http://localhost:5002/slides/')
+
+ # Assert slide content is correct
+ expect(page.locator('css=section.present')).to_have_text('SONG 1')
+ page.keyboard.press('ArrowRight')
+ expect(page.locator('css=section.present')).to_have_text('LYRICS TO SONG 1\nBY ARTIST A')
+ page.keyboard.press('ArrowRight')
+ expect(page.locator('css=section.present')).to_have_text('SONG 5')
+ page.keyboard.press('ArrowRight')
+ expect(page.locator('css=section.present')).to_have_text('CUSTOM SONG 5 LYRICS')
+ page.keyboard.press('ArrowRight')
+ expect(page.locator('css=section.present')).to_have_text('CUSTOM SONG 5 LYRICS')
+
+ # Click back
+ page.go_back()
+ expect(page).to_have_url('http://localhost:5002/create/step-3/')
+
+ # Assert bad slideshow settings still loaded
+ expect(page.get_by_role('checkbox', name='Include a blank slide between each song')).to_be_checked(checked = False)
+ expect(page.get_by_role('checkbox', name='Include a title slide before each song')).to_be_checked()
+
+ # Fill in correct slideshow settings
+ page.get_by_role('checkbox', name='Include a title slide before each song').uncheck()
+
+ # Click create
+ page.get_by_role('button', name='Create').click()
+ expect(page).to_have_url('http://localhost:5002/slides/')
+
+ # Assert slide content is correct
+ expect(page.locator('css=section.present')).to_have_text('LYRICS TO SONG 1\nBY ARTIST A')
+ page.keyboard.press('ArrowRight')
+ expect(page.locator('css=section.present')).to_have_text('CUSTOM SONG 5 LYRICS')
+ page.keyboard.press('ArrowRight')
+ expect(page.locator('css=section.present')).to_have_text('CUSTOM SONG 5 LYRICS')
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)