tests/TestGuiCookie.py
Rama master · réplica de lectura del repositorio original.
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (c) 2008-2026 Andrew Ziem.
#
# This work is licensed under the terms of the GNU GPL, version 3 or
# later. See the COPYING file in the top-level directory.
"""
Test case for module GuiCookie
"""
# These tests reach into internals on purpose.
# pylint: disable=protected-access
import json
import os
import types
import unittest
from unittest import mock
from tests import common
import bleachbit
from bleachbit import General, IS_WINDOWS
from bleachbit.Cookie import COOKIE_KEEP_LIST_FILENAME
from bleachbit.GtkShim import is_gtk_available
HAVE_GTK = is_gtk_available()
if HAVE_GTK:
from bleachbit.GtkShim import Gtk
from bleachbit.GuiCookie import CookieManagerPane
@unittest.skipUnless(HAVE_GTK, 'requires GTK+ module and a display environment')
class GuiCookieTestCase(common.BleachbitTestCase):
"""Test case for CookieManagerPane"""
def _fake_pane(self, keep_list_path, domains):
"""A minimal stand-in for CookieManagerPane, avoiding a real Gtk widget."""
return types.SimpleNamespace(
keep_list_path=keep_list_path,
_iter_selected_domains=lambda: iter(domains))
def test_save_changes_refuses_symlink(self):
"""save_changes() must not write the keep list through a symlink"""
filename = self.write_file(
'cookie_keep_list_target.json', text='["keepme.example"]')
pane = self._fake_pane(filename, ['evil.example'])
with mock.patch('bleachbit.FileUtilities.os.path.islink',
side_effect=lambda p: p == filename):
# pylint: disable-next=possibly-used-before-assignment
result = CookieManagerPane.save_changes(pane)
self.assertFalse(result)
with open(filename, encoding='utf-8') as f:
self.assertEqual(json.load(f), ['keepme.example'])
@common.also_with_sudo
def test_save_changes_still_works(self):
"""save_changes() still writes the sorted keep list normally"""
filename = os.path.join(self.tempdir, 'cookie_keep_list.json')
pane = self._fake_pane(filename, ['b.example', 'a.example'])
result = CookieManagerPane.save_changes(pane)
self.assertTrue(result)
with open(filename, encoding='utf-8') as f:
self.assertEqual(json.load(f), ['a.example', 'b.example'])
self.assertEqual(pane.saved_domains, {'a.example', 'b.example'})
if not IS_WINDOWS:
self.assertEqual(os.stat(filename).st_uid,
General.get_real_uid())
@common.skipIfWindows
def test_save_changes_sudo_chown(self):
"""Creating the keep list in sudo mode chowns it to the user."""
filename = os.path.join(self.mkdtemp(), 'cookie_keep_list.json')
pane = self._fake_pane(filename, ['a.example'])
with mock.patch('bleachbit.General.sudo_mode', return_value=True), \
mock.patch('bleachbit.General.chownself') as mock_chown:
self.assertTrue(CookieManagerPane.save_changes(pane))
mock_chown.assert_called_once_with(filename)
mock_chown.reset_mock()
# Overwriting an existing file should not chown again
self.assertTrue(CookieManagerPane.save_changes(pane))
mock_chown.assert_not_called()
def test_activate_link_stops_default_handler(self):
"""The 'Learn more' link must not also be opened by GTK"""
with mock.patch('bleachbit.GuiCookie.threading.Thread'):
pane = CookieManagerPane()
# pylint: disable-next=possibly-used-before-assignment
window = Gtk.Window()
window.add(pane)
self.addCleanup(window.destroy)
url = 'https://docs.bleachbit.org/'
with mock.patch('bleachbit.GuiCookie.open_url') as open_url:
handled = pane.on_activate_link(None, url)
self.assertTrue(handled)
open_url.assert_called_once_with(
url, parent_window=window, prompt=True)
@unittest.skipIf(common.have_root(), 'root users do not get a confirmation')
def test_activate_link_confirmation_parent(self):
"""The real confirmation dialog must be transient for Preferences."""
with mock.patch('bleachbit.GuiCookie.threading.Thread'):
pane = CookieManagerPane()
window = Gtk.Window()
window.add(pane)
self.addCleanup(window.destroy)
dialog_properties = []
def cancel_dialog(dialog):
dialog_properties.append(
(dialog.get_transient_for(), dialog.get_modal()))
return Gtk.ResponseType.CANCEL
with mock.patch.object(Gtk.Dialog, 'run', cancel_dialog), \
mock.patch('bleachbit.GuiBasic.Gtk.show_uri_on_window') as show_uri:
handled = pane.on_activate_link(
None, 'https://docs.bleachbit.org/')
self.assertTrue(handled)
self.assertEqual(dialog_properties, [(window, True)])
show_uri.assert_not_called()
def test_unreadable_keep_list(self):
"""A corrupt keep list leaves the page usable, and a save replaces it"""
keep_path = os.path.join(
bleachbit.options_dir, COOKIE_KEEP_LIST_FILENAME)
os.makedirs(bleachbit.options_dir, exist_ok=True)
self.addCleanup(lambda: os.path.exists(
keep_path) and os.remove(keep_path))
self.write_file(keep_path, text='["a.example",]')
with mock.patch('bleachbit.GuiCookie.threading.Thread'), \
self.assertLogs('bleachbit.GuiCookie', level='ERROR'):
pane = CookieManagerPane()
self.assertEqual(pane.saved_domains, set())
pane._finish_populate(['b.example'])
pane.on_select_all_clicked(None)
with open(keep_path, encoding='utf-8') as f:
self.assertEqual(json.load(f), ['b.example'])
pane.destroy()
def test_columns_do_not_sort_filter_model(self):
"""A sortable column needs a sortable model, which a filter is not"""
with mock.patch('bleachbit.GuiCookie.threading.Thread'):
pane = CookieManagerPane()
# pylint: disable-next=possibly-used-before-assignment
if not isinstance(pane.treeview.get_model(), Gtk.TreeSortable):
for column in pane.treeview.get_columns():
self.assertEqual(column.get_sort_column_id(), -1)
pane.destroy()
def test_discovery_failure_finishes_loading(self):
"""Any error during cookie discovery still ends the loading state"""
class SyncThread:
def __init__(self, target=None, daemon=None):
self.target = target
self.daemon = daemon
def start(self):
self.target()
with mock.patch('bleachbit.GuiCookie.threading.Thread', SyncThread), \
mock.patch('bleachbit.GuiCookie.list_unique_cookies',
side_effect=ModuleNotFoundError('sqlite3')), \
mock.patch('bleachbit.GuiCookie.GLib.idle_add') as idle_add, \
self.assertLogs('bleachbit.GuiCookie', level='ERROR'):
pane = CookieManagerPane()
idle_add.assert_called_once_with(pane._finish_populate, [])
pane.destroy()
def test_saved_padded_host_stays_checked(self):
"""Whitespace in a saved domain must not create a duplicate row."""
keep_path = os.path.join(
bleachbit.options_dir, COOKIE_KEEP_LIST_FILENAME)
self.write_file(keep_path, text=json.dumps([' \t.Google.COM\n']))
self.addCleanup(lambda: os.path.exists(
keep_path) and os.remove(keep_path))
with mock.patch('bleachbit.GuiCookie.threading.Thread'):
pane = CookieManagerPane()
self.addCleanup(pane.destroy)
pane._finish_populate(['.google.com', 'accounts.google.com'])
self.assertEqual([tuple(row) for row in pane.cookie_store],
[(False, 'accounts.google.com'), (True, 'google.com')])
def test_saved_dotted_host_stays_checked(self):
"""A saved '.example.com' shows as one checked row after reopening"""
keep_path = os.path.join(
bleachbit.options_dir, COOKIE_KEEP_LIST_FILENAME)
os.makedirs(bleachbit.options_dir, exist_ok=True)
self.addCleanup(lambda: os.path.exists(
keep_path) and os.remove(keep_path))
self.write_file(keep_path, text='[".google.com"]')
with mock.patch('bleachbit.GuiCookie.threading.Thread'):
pane = CookieManagerPane()
pane._finish_populate(['.google.com', 'accounts.google.com'])
self.assertEqual([tuple(row) for row in pane.cookie_store],
[(False, 'accounts.google.com'), (True, 'google.com')])
pane.destroy()