summaryrefslogtreecommitdiff
path: root/tests/test_gui_duplicate_prevention.py
blob: 52932e7f7557cf631f927002d1e672746baba530 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
"""
test_gui_duplicate_prevention.py

Test suite for GUI duplicate prevention features.

Tests cover:
- Tools menu action creation
- Duplicate prevention toggle
- Settings persistence in GUI
- Status messages
"""

from unittest.mock import MagicMock, patch

import pytest

# Mock Qt before importing GUI modules
with patch("tidal_dl_ng.gui.QtWidgets"), patch("tidal_dl_ng.gui.QtGui"), patch("tidal_dl_ng.gui.QtCore"):
    pass


class TestToolsMenuIntegration:
    """Test Tools menu integration for duplicate prevention."""

    @patch("tidal_dl_ng.gui.QtWidgets.QMenuBar")
    @patch("tidal_dl_ng.gui.QtGui.QAction")
    def test_tools_menu_created_if_not_exists(self, mock_action, mock_menubar):
        """Test that Tools menu is created if it doesn't exist."""
        # This test verifies the logic in _init_menu_actions
        # We're testing the structure, not the actual Qt implementation

        mock_menubar_instance = MagicMock()
        mock_menubar_instance.actions.return_value = []  # No existing Tools menu
        mock_menubar_instance.addMenu = MagicMock()

        # Simulate the logic from _init_menu_actions
        tools_menu = None
        for action in mock_menubar_instance.actions():
            if action.text() == "Tools":
                tools_menu = action.menu()
                break

        if not tools_menu:
            tools_menu = mock_menubar_instance.addMenu("Tools")

        # Verify addMenu was called
        assert mock_menubar_instance.addMenu.called

    @patch("tidal_dl_ng.gui.QtWidgets.QMenuBar")
    @patch("tidal_dl_ng.gui.QtGui.QAction")
    def test_tools_menu_found_if_exists(self, mock_action, mock_menubar):
        """Test that existing Tools menu is found and reused."""
        mock_menubar_instance = MagicMock()

        # Create mock existing Tools menu
        mock_tools_action = MagicMock()
        mock_tools_action.text.return_value = "Tools"
        mock_tools_menu = MagicMock()
        mock_tools_action.menu.return_value = mock_tools_menu

        mock_menubar_instance.actions.return_value = [mock_tools_action]
        mock_menubar_instance.addMenu = MagicMock()

        # Simulate the logic
        tools_menu = None
        for action in mock_menubar_instance.actions():
            if action.text() == "Tools":
                tools_menu = action.menu()
                break

        # Should have found existing menu
        assert tools_menu is mock_tools_menu
        # Should NOT have called addMenu
        assert not mock_menubar_instance.addMenu.called


class TestDuplicatePreventionAction:
    """Test the duplicate prevention menu action."""

    def test_action_is_checkable(self):
        """Test that the duplicate prevention action is checkable."""
        # This verifies the action configuration
        # In the actual code: self.a_toggle_duplicate_prevention.setCheckable(True)

        mock_action = MagicMock()
        mock_action.setCheckable = MagicMock()

        # Simulate action creation
        mock_action.setCheckable(True)

        mock_action.setCheckable.assert_called_once_with(True)

    def test_action_initial_state_from_settings(self):
        """Test that action initial state reflects settings."""
        mock_history_service = MagicMock()
        mock_history_service.get_settings.return_value = {"preventDuplicates": True}

        # Simulate getting initial state
        is_preventing = mock_history_service.get_settings().get("preventDuplicates", True)

        assert is_preventing is True

    def test_action_connected_to_handler(self):
        """Test that action triggered signal connects to handler."""
        mock_action = MagicMock()
        mock_handler = MagicMock()

        # Simulate connection
        mock_action.triggered.connect(mock_handler)

        mock_action.triggered.connect.assert_called_once_with(mock_handler)


class TestToggleDuplicatePreventionHandler:
    """Test the on_toggle_duplicate_prevention handler method."""

    def test_handler_updates_settings_when_enabled(self):
        """Test that handler updates settings to True when enabled."""
        mock_history_service = MagicMock()

        # Simulate the handler logic
        enabled = True
        mock_history_service.update_settings(preventDuplicates=enabled)

        mock_history_service.update_settings.assert_called_once_with(preventDuplicates=True)

    def test_handler_updates_settings_when_disabled(self):
        """Test that handler updates settings to False when disabled."""
        mock_history_service = MagicMock()

        # Simulate the handler logic
        enabled = False
        mock_history_service.update_settings(preventDuplicates=enabled)

        mock_history_service.update_settings.assert_called_once_with(preventDuplicates=False)

    def test_handler_emits_status_message_enabled(self):
        """Test that handler emits status message when enabled."""

        # Simulate the handler logic
        enabled = True
        status_msg = "enabled" if enabled else "disabled"

        assert status_msg == "enabled"

    def test_handler_emits_status_message_disabled(self):
        """Test that handler emits status message when disabled."""
        enabled = False
        status_msg = "enabled" if enabled else "disabled"

        assert status_msg == "disabled"

    def test_handler_logs_change(self):
        """Test that handler logs the settings change."""
        from unittest.mock import MagicMock

        mock_logger = MagicMock()

        # Simulate handler logging
        enabled = True
        status_msg = "enabled" if enabled else "disabled"
        mock_logger.info(f"Duplicate download prevention {status_msg}")

        mock_logger.info.assert_called_once()
        call_args = str(mock_logger.info.call_args)
        assert "enabled" in call_args


class TestSettingsPersistence:
    """Test that GUI settings changes persist."""

    def test_toggle_persists_to_json(self):
        """Test that toggling the option persists to the JSON file."""
        mock_history_service = MagicMock()

        # Simulate toggle
        mock_history_service.update_settings(preventDuplicates=False)

        # Verify update_settings was called (which triggers save)
        mock_history_service.update_settings.assert_called_with(preventDuplicates=False)

    def test_settings_restored_on_gui_restart(self):
        """Test that settings are restored when GUI restarts."""
        mock_history_service = MagicMock()
        mock_history_service.get_settings.return_value = {"preventDuplicates": False}

        # Simulate GUI init reading settings
        is_preventing = mock_history_service.get_settings().get("preventDuplicates", True)

        assert is_preventing is False


class TestStatusBarMessages:
    """Test status bar message display."""

    def test_status_message_format_enabled(self):
        """Test status message format when enabled."""
        from tidal_dl_ng.model.gui_data import StatusbarMessage

        enabled = True
        status_msg = "enabled" if enabled else "disabled"
        message = StatusbarMessage(message=f"Duplicate prevention {status_msg}.", timeout=2500)

        assert "Duplicate prevention enabled" in message.message
        assert message.timeout == 2500

    def test_status_message_format_disabled(self):
        """Test status message format when disabled."""
        from tidal_dl_ng.model.gui_data import StatusbarMessage

        enabled = False
        status_msg = "enabled" if enabled else "disabled"
        message = StatusbarMessage(message=f"Duplicate prevention {status_msg}.", timeout=2500)

        assert "Duplicate prevention disabled" in message.message
        assert message.timeout == 2500


class TestViewHistoryAction:
    """Test the View Download History action."""

    def test_view_history_action_exists(self):
        """Test that View History action is created."""
        mock_action = MagicMock()
        mock_handler = MagicMock()

        # Simulate action creation
        mock_action.setText("View Download History...")
        mock_action.triggered.connect(mock_handler)

        assert "View Download History" in str(mock_action.setText.call_args)

    def test_view_history_opens_dialog(self):
        """Test that View History action opens the dialog."""
        mock_dialog_class = MagicMock()
        mock_history_service = MagicMock()

        # Simulate dialog opening
        mock_dialog_class(history_service=mock_history_service, parent=None)

        mock_dialog_class.assert_called_once()


class TestMenuSeparator:
    """Test that separator is added between menu items."""

    def test_separator_added_between_items(self):
        """Test that a separator is added between View History and Prevent Duplicates."""
        mock_menu = MagicMock()

        # Simulate adding items
        mock_menu.addAction(MagicMock())  # View History
        mock_menu.addSeparator()
        mock_menu.addAction(MagicMock())  # Prevent Duplicates

        # Verify separator was called
        mock_menu.addSeparator.assert_called_once()


class TestIntegrationWithDownloadQueue:
    """Test integration with download queue."""

    def test_prevention_affects_queue_downloads(self):
        """Test that prevention setting affects downloads from queue."""
        mock_history_service = MagicMock()
        mock_history_service.should_skip_download.return_value = True

        # Simulate queue download check
        track_id = "123456"
        should_skip = mock_history_service.should_skip_download(track_id)

        assert should_skip is True

    def test_prevention_disabled_allows_queue_downloads(self):
        """Test that disabling prevention allows queue downloads."""
        mock_history_service = MagicMock()
        mock_history_service.should_skip_download.return_value = False

        # Simulate queue download check
        track_id = "123456"
        should_skip = mock_history_service.should_skip_download(track_id)

        assert should_skip is False


class TestActionStateSync:
    """Test that action state stays synced with settings."""

    def test_action_checked_when_prevention_enabled(self):
        """Test that action appears checked when prevention is enabled."""
        mock_action = MagicMock()
        mock_history_service = MagicMock()
        mock_history_service.get_settings.return_value = {"preventDuplicates": True}

        # Simulate setting initial state
        is_preventing = mock_history_service.get_settings().get("preventDuplicates", True)
        mock_action.setChecked(is_preventing)

        mock_action.setChecked.assert_called_once_with(True)

    def test_action_unchecked_when_prevention_disabled(self):
        """Test that action appears unchecked when prevention is disabled."""
        mock_action = MagicMock()
        mock_history_service = MagicMock()
        mock_history_service.get_settings.return_value = {"preventDuplicates": False}

        # Simulate setting initial state
        is_preventing = mock_history_service.get_settings().get("preventDuplicates", True)
        mock_action.setChecked(is_preventing)

        mock_action.setChecked.assert_called_once_with(False)


class TestErrorHandling:
    """Test error handling in GUI actions."""

    def test_handler_continues_on_settings_error(self):
        """Test that handler continues gracefully if settings update fails."""
        mock_history_service = MagicMock()
        mock_history_service.update_settings.side_effect = Exception("Settings error")

        # Simulate handler with try/except (if implemented)
        try:
            mock_history_service.update_settings(preventDuplicates=True)
            success = True
        except Exception:
            success = False

        # In current implementation, error would propagate
        # This test documents current behavior
        assert success is False


if __name__ == "__main__":
    pytest.main([__file__, "-v"])